跳转到内容

JSON消息构建

本节介绍使用ArduinoJson库在ESP32上创建和解析JSON消息。通过本节学习,你将能够:

  • 安装和配置ArduinoJson库
  • 为MQTT发布构建JSON对象
  • 解析来自Node-RED或其他MQTT客户端的JSON消息
  • 使用ArduinoJson Assistant工具生成正确的代码
  • 已完成 ESP32 基础编程环境配置
  • 掌握基础草图架构

JSON(JavaScript对象表示法)是物联网通信的标准数据格式,因为:

  • 人类可读:易于检查和调试
  • 自描述:字段名说明数据内容
  • 语言无关:适用于ESP32、Node-RED、Python、JavaScript等
  • 结构灵活:轻松添加或删除字段而不破坏解析器
  • 广泛支持:每个MQTT客户端库都处理JSON

示例JSON负载

{
"device": "ESP32",
"temperature": 25.5,
"humidity": 65.2,
"lux": 312,
"rssi": -67
}

ArduinoJson是最流行的Arduino/ESP32 JSON库。关键特性:

  • 反序列化:将JSON字符串解析为程序可访问的对象
  • 序列化:从程序变量构建JSON字符串
  • 内存高效:通过 StaticJsonDocumentDynamicJsonDocument 进行可配置分配
  • Assistant工具:基于Web的代码生成器,位于 arduinojson.org/v7/assistant/

ArduinoJson v7(当前版本)

描述
JsonDocumentJSON文档存储的基类
StaticJsonDocument<N>固定大小分配(栈)——大小已知时使用
DynamicJsonDocument<N>堆分配——用于可变大小
JsonObject命名键值对
JsonArray值的有序列表

PlatformIOplatformio.ini):

lib_deps =
bblanchon/ArduinoJson @ ^7.0

Arduino IDE:库管理器 → 搜索”ArduinoJson” → 安装

ArduinoJson Assistant 为你的特定JSON结构生成正确的代码:

  1. 访问 https://arduinojson.org/v7/assistant/
  2. 选择你的开发板:ESP32
  3. 选择操作:Deserialize(接收)或 Serialize(发送)
  4. 粘贴示例JSON文档
  5. Assistant生成所需的确切代码
  6. 将代码复制到你的草图中

这消除了手动内存计算和语法猜测。

第三步:序列化JSON(ESP32 → MQTT发布)

Section titled “第三步:序列化JSON(ESP32 → MQTT发布)”

构建要通过MQTT发布的JSON负载:

#include <ArduinoJson.h>
void sendMQTTValues() {
// 计算所需文档大小
// 使用Assistant获取精确值:https://arduinojson.org/v7/assistant/
StaticJsonDocument<200> doc;
// 填充JSON文档
doc["device"] = "ESP32";
doc["temperature"] = 25.5;
doc["humidity"] = 65;
doc["lux"] = 312;
doc["online"] = true;
// 序列化到字符串缓冲区
char buffer[256];
size_t len = serializeJson(doc, buffer);
// 通过MQTT发布
if (mqttClient.publish("esp32/data", buffer, len)) {
Serial.print("Published JSON: ");
Serial.println(buffer);
}
}

使用真实的传感器数据

void sendSensorData(float temperature, float humidity, int lux) {
StaticJsonDocument<200> doc;
doc["device"] = "sensor-01";
doc["temperature"] = temperature;
doc["humidity"] = humidity;
doc["lux"] = lux;
doc["rssi"] = WiFi.RSSI();
doc["timestamp"] = millis() / 1000; // 运行秒数
char buffer[256];
size_t len = serializeJson(doc, buffer);
mqttClient.publish("esp32/data", buffer, len);
}

嵌套JSON对象

void sendComplexData() {
StaticJsonDocument<300> doc;
// 设备信息
JsonObject device = doc.createNestedObject("device");
device["id"] = "ESP32-001";
device["firmware"] = "2.1.0";
// 传感器读数
JsonObject sensors = doc.createNestedObject("sensors");
sensors["temperature"] = 25.5;
sensors["humidity"] = 65.2;
// 值数组
JsonArray history = doc.createNestedArray("history");
history.add(25.1);
history.add(25.3);
history.add(25.5);
char buffer[512];
serializeJsonPretty(doc, Serial); // 格式化打印用于调试
size_t len = serializeJson(doc, buffer);
mqttClient.publish("esp32/data", buffer, len);
}

输出:

{
"device": {
"id": "ESP32-001",
"firmware": "2.1.0"
},
"sensors": {
"temperature": 25.5,
"humidity": 65.2
},
"history": [25.1, 25.3, 25.5]
}

第四步:反序列化JSON(MQTT接收 → ESP32)

Section titled “第四步:反序列化JSON(MQTT接收 → ESP32)”

解析从Node-RED接收的JSON消息:

void mqttCallback(char* topic, byte* payload, unsigned int length) {
// 将负载转换为字符串
char json[length + 1];
memcpy(json, payload, length);
json[length] = '\0';
Serial.print("Received JSON: ");
Serial.println(json);
// 解析JSON
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial.print("JSON parse failed: ");
Serial.println(error.c_str());
return;
}
// 提取值
const char* command = doc["command"];
int value = doc["value"];
bool enabled = doc["enabled"];
Serial.print("Command: ");
Serial.println(command ? command : "null");
Serial.print("Value: ");
Serial.println(value);
Serial.print("Enabled: ");
Serial.println(enabled ? "true" : "false");
// 根据解析的数据执行操作
if (strcmp(command, "LED_ON") == 0) {
digitalWrite(2, HIGH);
} else if (strcmp(command, "LED_OFF") == 0) {
digitalWrite(2, LOW);
} else if (strcmp(command, "SET_THRESHOLD") == 0) {
temperatureThreshold = value;
}
}
// 传入的JSON:{"channels": [1, 3, 5], "mode": "auto"}
void parseArrayExample(char* json) {
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, json);
if (error) return;
// 访问数组
JsonArray channels = doc["channels"];
const char* mode = doc["mode"];
Serial.print("Mode: ");
Serial.println(mode);
Serial.print("Active channels: ");
for (int channel : channels) {
Serial.print(channel);
Serial.print(" ");
// 激活每个通道
}
Serial.println();
}

最常见的ArduinoJson陷阱是错误的内存分配:

// 太小——会静默溢出
StaticJsonDocument<64> doc; // 对大多数负载来说太小
doc["temperature"] = 25.5; // 数据可能损坏
doc["humidity"] = 65.2; // 溢出!
// 正确——使用Assistant计算
StaticJsonDocument<200> doc; // 典型传感器负载的正确大小
// 对于未知大小,使用DynamicJsonDocument
DynamicJsonDocument doc(2048); // 堆分配,2048字节

内存估算

  • 每个键:~(键长度 + 1)字节
  • 每个字符串值:~(字符串长度 + 1)字节
  • 每个数字:8字节
  • 每个布尔值:2字节
  • JSON开销:~ 30-40字节

使用 ArduinoJson Assistant 精确计算。

  • 通过MQTT发布的JSON负载是有效的JSON(用JSON验证器验证)
  • Node-RED能解析发布的JSON负载
  • ESP32正确解析来自Node-RED的JSON命令
  • 数组和嵌套对象处理正常工作
  • 内存分配足以容纳最大的预期负载

原因:缓冲区大小不足以容纳JSON文档。

解决方案

  1. 增加 StaticJsonDocument<N> 的大小
  2. 增加 char buffer[N] 的大小(必须大于文档)
  3. 使用 serializeJson(doc, buffer, sizeof(buffer)) 防止溢出

原因JsonDocument 对于传入的JSON太小。

解决方案

  1. 使用Assistant计算正确的大小
  2. 对于可变大小负载,切换到 DynamicJsonDocument
  3. 增加文档大小

编译错误:“JsonDocument is too large for stack”

Section titled “编译错误:“JsonDocument is too large for stack””

原因StaticJsonDocument 在栈上分配;非常大的文档超过栈限制。

解决方案

  1. 切换到 DynamicJsonDocument(堆分配)
  2. 通过简化JSON结构减小文档大小
  3. 通过 build_flags = -DCONFIG_ARDUINO_LOOP_STACK_SIZE=16384 增加ESP32栈大小
  • 始终检查反序列化错误if (deserializeJson(doc, json)) 捕获格式错误的JSON
  • 使用Assistant:它消除了内存分配的猜测
  • 已知大小优先使用 StaticJsonDocument:更快且避免堆碎片
  • 开发期间使用 serializeJsonPretty():可读输出有助于调试
  • 在关键路径中避免动态字段名:为键创建字符串比文字慢
  • 对复杂结构使用 JsonObjectJsonArray:它们是类型安全且高效的
  1. ArduinoJson处理JSON序列化(ESP32 → MQTT)和反序列化(MQTT → ESP32)
  2. ArduinoJson Assistant Web工具为任何JSON结构生成正确的代码
  3. 内存管理至关重要:使用正确的 JsonDocument 大小
  4. 解析传入消息时始终检查 DeserializationError
  5. 完全支持复杂的嵌套对象和数组
  6. JSON是本课程中ESP32与Node-RED通信的标准数据格式