以下是针对ESP32通过蓝牙(BLE或经典蓝牙)接收JSON数据的完整协议设计方案和代码实现,重点解决数据分包、粘包处理和JSON解析问题。
协议设计方案
1. 数据帧格式
为可靠接收不定长的JSON数据,设计基于长度前缀 + 数据内容的协议:
[起始标志][数据长度][JSON数据][结束标志]
- 起始标志(1字节):固定为
0xAA ,标识数据帧开始。
- 数据长度(2字节):JSON数据的实际长度(大端序)。
- JSON数据(N字节):UTF-8编码的JSON字符串。
- 结束标志(1字节):固定为
0x55 ,标识数据帧结束。
2. 数据传输流程
发送端:
1. 计算JSON数据长度(N)。
2. 构造帧:[0xAA][高8位长度][低8位长度][JSON数据][0x55]
3. 通过蓝牙发送完整帧。
接收端:
1. 检测起始标志0xAA。
2. 读取长度字段,计算需接收的JSON数据长度。
3. 读取指定长度的JSON数据和结束标志0x55。
4. 校验结束标志,若正确则解析JSON。
3. 优点
- 明确标识数据边界,解决粘包问题。
- 长度校验避免缓冲区溢出。
- 支持动态长度JSON数据。
代码实现
1. 依赖库
- ArduinoJson:处理JSON解析。
- BluetoothSerial:ESP32经典蓝牙库。
2. 完整代码
#include <BluetoothSerial.h>
#include <ArduinoJson.h>
BluetoothSerial SerialBT;
#define BUFFER_SIZE 512 // 根据实际JSON长度调整
// 协议常量
#define START_MARKER 0xAA
#define END_MARKER 0x55
// 接收状态机
enum RecvState {
WAIT_START,
READ_LENGTH_HIGH,
READ_LENGTH_LOW,
READ_DATA,
CHECK_END
};
// 全局变量
uint8_t buffer[BUFFER_SIZE];
uint16_t data_length = 0;
uint16_t data_index = 0;
RecvState state = WAIT_START;
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32_JSON"); // 蓝牙设备名称
Serial.println("Bluetooth Started!");
}
void loop() {
while (SerialBT.available() > 0) {
uint8_t byte = SerialBT.read();
switch (state) {
case WAIT_START:
if (byte == START_MARKER) {
state = READ_LENGTH_HIGH;
}
break;
case READ_LENGTH_HIGH:
data_length = byte << 8;
state = READ_LENGTH_LOW;
break;
case READ_LENGTH_LOW:
data_length |= byte;
if (data_length > BUFFER_SIZE) {
// 数据过长,重置状态
resetState();
Serial.println("Error: Data too long!");
} else {
data_index = 0;
state = READ_DATA;
}
break;
case READ_DATA:
buffer[data_index++] = byte;
if (data_index >= data_length) {
state = CHECK_END;
}
break;
case CHECK_END:
if (byte == END_MARKER) {
// 完整帧接收完成,解析JSON
processJSON(buffer, data_length);
} else {
Serial.println("Error: End marker mismatch!");
}
resetState();
break;
}
}
}
// 解析JSON数据
void processJSON(uint8_t* data, uint16_t len) {
// 转换为字符串并添加终止符
char json_str[len + 1];
memcpy(json_str, data, len);
json_str[len] = '\0';
// 解析JSON
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, json_str);
if (error) {
Serial.print("JSON解析失败: ");
Serial.println(error.c_str());
return;
}
// 提取数据示例
if (doc.containsKey("sensor")) {
const char* sensor = doc["sensor"];
float value = doc["value"];
Serial.printf("传感器: %s, 值: %.2f\n", sensor, value);
}
}
// 重置接收状态
void resetState() {
state = WAIT_START;
data_length = 0;
data_index = 0;
}
关键点说明
1. 状态机设计
- 使用状态机(
WAIT_START → READ_LENGTH → READ_DATA → CHECK_END )确保按协议顺序处理数据。
- 解决蓝牙数据分包和粘包问题。
2. 缓冲区安全
- 检查
data_length 是否超过 BUFFER_SIZE ,防止溢出。
- 动态分配缓冲区(可根据项目需求改用静态分配)。
3. 错误处理
- 结束标志校验:检测帧是否完整。
- JSON解析失败时输出错误信息。
发送端示例(Python模拟)
# 安装依赖: pip install pybluez
import bluetooth
import struct
def send_json_over_bt(address, json_str):
sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
sock.connect((address, 1))
# 构造协议帧
start_marker = b'\xAA'
end_marker = b'\x55'
data = json_str.encode('utf-8')
length = struct.pack('>H', len(data)) # 大端序2字节
frame = start_marker + length + data + end_marker
sock.send(frame)
sock.close()
# 使用示例
send_json_over_bt("ESP32_JSON_MAC", '{"sensor":"temperature","value":25.5}')
优化建议
-
增加CRC校验:在帧尾添加2字节CRC16校验,提高数据可靠性。
-
超时机制:若长时间未收到结束标志,自动重置接收状态。
-
PSRAM支持:如果JSON数据较大,启用ESP32的PSRAM:
// 替换DynamicJsonDocument为PSRAM版本
DynamicJsonDocument doc(8192); // 8KB PSRAM
通过以上方案,可稳定实现ESP32通过蓝牙接收并解析JSON数据。实际项目中需根据数据量调整缓冲区大小和JSON文档容量。 |