魔改车钥匙实现远程控车:(3)通过蓝牙与手机通信并持久化保存参数设置

news/2024/11/16 22:34:51/

前言

在前两篇文章中,我们已经完成了控制与距离感应,建议先看完前两篇文章再来看这篇文章,不然你会看的一头雾水的。

在今天这篇文章中,我们需要解决的是将某些参数设置持久化储存在 ESP32 的储存器中,并且在重新上电运行时实时读取保存的参数。

而这些参数应该由手机通过蓝牙与 ESP32 通信来设置。

正如前面说过的,为了安全性,所以这里的与手机通讯会用回经典蓝牙。

实现过程

持久化存储

EEPROM

Arduino 自带一个持久化储存的方案: EEPROM

EEPROM (Electrically Erasable Programmable Read-Only Memory),电可擦可编程只读存储器–一种掉电后数据不丢失的存储芯片。

EEPROM 使用也比较简单,首先导入头文件 #include <EEPROM.h>

写入数据:

EEPROM.write(addr, val);

读取数据:

EEPROM.read(address);

可以看到,EEPROM 读取和写入都是直接按地址写/读字节,对于我们的需求:储存参数设置。十分的不方便,还需要我们自行处理保存地址和参数的关系。非常麻烦,因此我们放弃这个方案。

好在 ESP32 支持一种键值对的持久化储存方法:Preferences

Preferences

Preferences 的键值对储存方法简直就是为了储存参数量身定制的。

先来简单的看一下怎么使用:

首先依然是导入头文件:

#include <Preferences.h>

然后定义一个 Preferences 对象:

Preferences prefs;

创建命名空间:

prefs.begin("setting");

写入:

prefs.putXXX("key", value);

读取:

prefs.getInt("key", defalutValue)

注意:上面是伪代码,运行不了的

因为 Preferences 储存的是键值对,所以不管是写入还是读取,都需要一个 key,且 key 只能是 string。

而写入的数据可以是所有 c++ 支持的数据类型,例如想要写入 int 则将XXX 替换为 int:

prefs.putInt("key", int_value);

需要注意的是,如果写入同一个命名空间的键(key)重复的话会覆盖(更新)已有键的值为新值。

读取数据时同理,如果需要读取 int 数据则为:

prefs.getInt("key", defalutIntValue)

第二个参数为默认值,如果没有查找到该键对应的值则返回这个默认值。

另外,prefs.end(); 用于关闭命名空间。

更改参数

确定了方案后,我们来更改之前写的代码。

这里以 BLE 扫描持续为例,之前我们是直接硬编码了扫描持续时间为 5 s。

现在我们改为通过读取 Preferences 中的数据确定扫描持续时间:

....
#define KEY_SCAN_TIME "SCAN_TIME"
....
void start_scan() {isScanedDevice = 0;BLEDevice::init("");BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());pBLEScan->setActiveScan(true); BLEScanResults foundDevices = pBLEScan->start(prefs.getInt(KEY_SCAN_TIME, 5));  // 扫描持续时间实时从 Preferences 读取if (isScanedDevice == 0) {Serial.println("Power Off by ble not found!");lock_car();}
}
...
复制代码

而更新参数只需要在接受到新值时 prefs.putInt(KEY_SCAN_TIME, newValue); 即可。

与手机通信更新设置参数

上一节中我们已经说了如何持久化储存参数设置,但是光是能储存显然是不够的,我们还需要支持通过手机与 ESP32 通信更新参数。

在之前的代码中,我们已经实现了通过经典蓝牙让手机和 ESP32 交互数据。

但是有一个问题,ESP32 接收数据时是使用流的方式一个字节一个字节的接收的。

这意味着我们可能需要定义一个通信协议用来传输更新参数。

这里我们就简单定义如下:

第一个字节固定为 FF 表示起始标记;

第二个字节为需要设置的参数码,例如这里我把 01 定义为设置扫描持续时间;

第三个字节表示将该参数值设置为多少,例如这里表示更新扫描持续时间为 5;

第四个字节固定为 FF 表示结束标记。

其实这个协议非常粗糙,目前有几个问题:

  1. 数据结束符可能会和设置的参数值冲突,如果参数值设置为 FF(255)就会被错误的处理为结束。这个也很好解决,只要在通信头紧跟一个字节用于表示所有通信数据长度即可,这样就可以不用设置结束符,也避免了冲突。但是这里就先不改了(因为我懒)。
  2. 没有对通信数据做校验,因为是无线通信,所以肯定会有数据丢失的情况发生,正常来说都应该加一个校验,例如 CRC 校验,但是这里先不加了(还是因为我懒)
  3. 设置参数最大值只能支持设置到 255 ,因为参数值只能使用一个字节,所以最大值只能是 255,解决方法同 1(但是我还是懒得改)

代码如下:

void loop()
{if (!isConnectDevice) {start_scan();  // 开始搜索 BLE 设备}if (confirmRequestPending){if (Serial.available()){int dat = Serial.read();if (dat == '1'){SerialBT.confirmReply(true);}else{SerialBT.confirmReply(false);}}}else{if (Serial.available()){SerialBT.write(Serial.read());}if (SerialBT.available()){int msg = SerialBT.read();Serial.write(msg);if (msg == 1) { get_start();Serial.println("rev 1");}else if (msg == 2) {cut_power();Serial.println("rev 2");}else if (msg == 3) {lock_car();Serial.println("rev 3");}else if (msg == 4) {luncher_car();Serial.println("rev 4");}else if (msg == 5) {shut_down_car();Serial.println("rev 5");}else if (msg == 6) {find_my_car();Serial.println("rev 6");}else if (msg == 7) {click_loop();Serial.println("rev 7");}else if (msg == 8) {click_lock();Serial.println("rev 8");}else if (msg == 9) {click_unlock();Serial.println("rev 9");}  // 上面这几个(1-10)都是之前写的手动控制else if (msg == 255) {  // 接受到设置参数的头字符Serial.println("rev 255");while (SerialBT.available()) {msg = SerialBT.read();if (setName == "null") {  // 设置参数类型为空,在这里读取设置参数类型if (msg == 1) {Serial.println("rev 1 in setting");setName = KEY_SCAN_TIME; // 间隔时间}else if (msg == 2) {Serial.println("rev 2 in setting");setName = KEY_RSSI; // rssi 阈值}else if (msg == 3) {Serial.println("rev 3 in setting");setName = KEY_IS_UNLOCK; // 是否触发解锁}else {  // 查找到未指定的参数类型,退出Serial.println("rev else in setting");Serial.printf("value=%d\n", msg);break;}}else if (msg == 255) { // 接收到请求设置参数结束符号Serial.println("rev 0 in setting");break;}            else {  // 这里是参数的具体数值,开始保存(参数值不能设置成 255 否则会和结束符号冲突)Serial.println("rev else in setting");Serial.printf("new value=%d\n", msg);prefs.putInt(setName, msg);setName = "null"; // 重置参数名}}}else if (msg == 10) {Serial.println("rev 10");read_status();}}}  
}
复制代码

运行效果如下:

上面图 2 是 手机端的截图。

图 3 是 ESP32 的串口日志。

手机端的截图它自动把十六进制数据转换成了 ACII 字符,所以,^J 表示 A;^A 表示 1;“乱码”表示的是 FF。

可以看到,我在手机端先发送了一个读取状态指令 A(10),其中扫描持续时间 scale_time 现在是 10 。

然后我发送了一个更新 01 (持续时间)参数为 01 的指令。

最后又发送了一个读取状态指令,可以看到持续时间已经成功被改为 1 了。

对了,读取状态的代码为:

void read_status() {int power_value = digitalRead(PIN_POWER);int loop_value = digitalRead(PIN_LOOP);int lock_value = digitalRead(PIN_LOCK);int unlock_value = digitalRead(PIN_UNLOCK);int scale_time = prefs.getInt(KEY_SCAN_TIME, 5);int rssi = prefs.getInt(KEY_RSSI, 100);int is_unlock = prefs.getInt(KEY_IS_UNLOCK, 0);char s[200];sprintf(s, "Pin Status: \n power %d, loop %d, lock %d, unlock %d\n Setting Value: \n scale_time %d, rssi %d, is_unlock %d", power_value, loop_value, lock_value, unlock_value, scale_time, rssi, is_unlock);SerialBT.print(s);
}
复制代码

总结

自此,ESP32 程序基本已经为完全完成了,下一步是编写一个专属的控制 APP,毕竟不可能每次更新参数都要手撸代码吧,多麻烦啊,写个傻瓜式 APP 一键设置多方便啊。

哈哈,终于可以干回我的老本行:安卓 了。


http://www.ppmy.cn/news/724852.html

相关文章

汽车PKE无钥匙入一键启动手机智能控车系统

汽车手机控制智能一键启动.智能无钥匙进入、一键启动、遥控启动、寻车功能、智能防盗系统,离车自锁、中控智能化&#xff0c;手机控车。可以无限距离手机APP界面操控。汽车无钥匙进入系统&#xff0c;简称PKE&#xff0c;该系统采用了RFID无线射频技术和车辆身份编码识别系统&a…

手机与汽车之间的控制方法

手机控车一键启动无钥匙进入 1.手机远程启动车辆&#xff1a;移动管家手机控车智能控车系统&#xff0c;在有手机信号的任何地方&#xff0c;使用车主加密绑定的手机发送指令到车辆&#xff0c;发动机自动启动并回复短信致车主手机提示车辆已经启动。&#xff08;车辆在启动状…

安卓手机控制小车(自己做成功了,有全套资料)

注:图片不知道怎么显示,可以去我空间的相册里查看图片。 小车分几部分组成:1、安卓手机控制软件:该软件可以通过蓝牙发送前进、后退、左转、右转指令(ASCII码)。2、蓝牙模块:通过与手机连接后,可以接收到手机发送的指令,采用HC_06模块。3、单片机最小系统:蓝牙模块将…

手机智能控制汽车系统作用详解

汽车手机启动&#xff0c;汽车远程启动是装置在智能汽车的一部分&#xff0c;是实现简远程遥控启动汽车的一个智能装置&#xff0c;同时也可以熄火。汽车手机启动可以在原车钥匙保留模式改装&#xff0c;也可以独立在汽车上改装。 目前很多汽车已经配有一键启动智能无钥匙进入…

uni-app获取节点的相关信息

获取单个节点&#xff1a; selectorQuery.select(selector) 在当前页面下选择第一个匹配选择器 selector 的节点&#xff0c;返回一个 NodesRef 对象实例&#xff0c;可以用于获取节点信息。 selector 说明&#xff1a; selector 类似于 CSS 的选择器&#xff0c;但仅支持下列…

Vue3依赖注入 provide/inject

官网&#xff1a;https://cn.vuejs.org/guide/components/provide-inject.html Prop 逐级透传问题 有一些多层级嵌套的组件&#xff0c;形成了一颗巨大的组件树&#xff0c;而某个深层的子组件需要一个较远的祖先组件中的部分数据。如果组件链 路非常长&#xff0c;可能会影响…

B与BL

b与bl指令的作用是什么&#xff1f; b与bl指令的作用&#xff1a;实现程序跳转&#xff0c;也就是调用子程序。 b与bl指令的区别是什么&#xff1f; b与bl指令的区别&#xff1a;b指令&#xff1a;简单的程序跳转&#xff0c;跳转到到目标标号处执行。 bl指令&#xff1a;带链…

ASBU(Aviation System Block Upgrade, 民航系统组件升级)的机场运行领域的模块B0-APTA介绍500字...

ASBU(Aviation System Block Upgrade)是一种民航系统组件升级方案&#xff0c;主要用于提高机场的运行效率和安全性。B0-APTA模块是ASBU的一部分&#xff0c;主要用于支持机场短期规划和调度。 B0-APTA模块包括以下功能&#xff1a; 机场规划与调度&#xff1a;支持机场的短期规…