Author:teacherXue
一、什么是JSON
定义
JSON(JavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式。它基于 ECMAScript(European Computer ManufacturersAssociation, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。——来自于百度百科。
简单点说就是对象的字符串表达形式。学过高级开发语言的同学应该知道内存对象来自于类的定义,例如:
class Student
{
public String name;
public String sex;
public Integer age;
public Student(String name,String sex,Integer age){
this.name=name;
this.sex=sex;
this.age=age;
}
}
使用时需要创建实体对象,并赋予属性值:
Student st1=new Student(“张三丰”,”男”,155)
但程序在编译执行时,内存里面的t1不过是一对的01代码,如果你不知道Student的类的定义,就没办法解析出数据。那么不同应用之间的数据传递就出现了困难。
意义
那么怎么才能便于不同程序之间的沟通呢?答案就是纯文本,只要是计算机系统就有基本字符集,就能解析显示字符信息。于是我们协商好,用一个怎样的方式使用字符来表示对象就好了。比如下面:
{"student":{"name":"张三丰","sex":"男","age":155}}
这样看是不是更清晰:
于是当先很多跨应用的程序在数据传输时,都采取了对象字符化的形式。当然复杂的JSON数据还包括集合等元素,例如下面的一段物联网数据:
{
"protocol":"1.0",
"chip_id":"xm_4020C974",
"chip_type":"ESP8266-12E",
"product_line":"xust_19_teacher",
"private_ip":"192.168.137.99",
"public_ip":"",
"timestamp":"",
"sensor_list":[
{
"number":1,
"type":"amp_no",
"name":"厨房灯",
"data":{
"data_vals":[
{
"data_name":"state",
"data_val":false
}
]
}
},
{
"number":2,
"type":"lamp_ctl",
"name":"主卧灯",
"data":{
"data_vals":[
{
"data_name":"state",
"data_val":false
},
{
"data_name":"extent",
"data_val":0
}
]
}
},
{
"number":3,
"type":"th",
"name":"温湿度",
"data":{
"data_vals":[
{
"data_name":"t_val",
"data_val":25.89999962
},
{
"data_name":"h_val",
"data_val":26.89999962
}
]
}
}
]
}
二、MCU中构建JSON
思路
既然json是对象的字符表现形式,那么我们就需要对MCU芯片要传输的数据进行抽象,抽成对象后才方便进行json数据封装。大致抽象后的形式如下。
数据类:
基本数据1:值
基本数据2:值
传感器列表:
传感器1:
传感器数据1:值
传感器数据2:值
传感器2:
传感器数据1:值
传感器数据2:值
传感器3:
传感器数据1:值
传感器数据2:值
内存中的json可以抽象的理解为一个倒置的树状结构,我们可以把每个节点当成单独的数据项,每个数据项包含名字和值。创建好之后,我们就可以向挂圣诞树礼物一样,把它们依次挂在不同的节点之上。如下图:
ArduinoJson扩展库
内存节点树的样子和操作很形象,但你要最终拼接的json串可能如下图,常量和变量要拼接在一起,不出错很困难。
拿来主义,使用ArduinoJson扩展库可以帮我们将数据像节点一样进行创建、挂载、移除等操作。
1)创建项目Lot_mqtt_json_test_v1.0,配置串口波特率。
2)在扩展库里搜索ArduinoJson并安装到当前项目(选择6.X以上版本),有问题的请参阅之前章节。
3)沿用之前案例的外设,我们模拟如下业务
- 一个LED只能开关,模拟普通白炽灯。
- 一个LED旋钮调光,模拟调光灯。
- 一个旋钮,模拟调光灯光的物理控制。
- 一个按钮开关模拟白炽灯的开关。
- 一个温湿度传感器采集环境温湿度。
- 并且灯光的当前状态和亮度值信息都要进行封装,为后面的mqtt应用封装数据。
4)导入依赖库
#include <Arduino.h>
#include <JC_Button.h>//导入按钮扩展库
#include <WiFiManager.h> //wifi管理
#include <TaskScheduler.h> // 导入多任务库
#include <Adafruit_Sensor.h>//导入传感器库
#include <DHT.h>
// json对象处理类,6.x 和5.x不一样 ,这里用的是6.19
#include <ArduinoJson.h>
5) 声明业务所需GPIO引脚
#define LED_1 D5// 不可调光灯
#define LED_2 D6// 可调光灯
#define BUTTON_PIN D4 // 按钮引脚
#define DHTPIN D3 // DHT温湿度传感器引脚
#define analogInPin A0 // 模拟输入引脚A0
6)创建所需扩展库对象
#define DHTTYPE DHT22 // 声明DHT传感器类型
DHT dht(DHTPIN, DHTTYPE); // 创建DHT对象
Scheduler runner; // 任务调度器对象
Button myBtn(BUTTON_PIN); // 按钮对象
7)数据和参数的全局保存
// 温湿度值全局保存
float t = 0.0;
float h = 0.0;
// led灯光状态
bool led1state = false;
bool led2state = false;
// 旋钮值和灯光亮度的保存
unsigned int knobValue = 0;
unsigned int dutyCycle = 0;
8)定义两个字符缓冲,一个存放芯片hostname,一个存放封装的json数据,后者要大一点。
// 存放芯片ID的缓冲
char chipId[20];
// 消息发送缓冲,json最后字符串方式传输
char messageBuff[1024];
9)为温湿度采集、旋钮采集、封装json数据创建任务调度,它们不需要被连续不断的执行。
// 前置函数声明
void getTH();
void getKnob();
void getJson();//封装json的方法
Task t1(2000, TASK_FOREVER, &getTH); // 任务名称t1,间隔2秒一直执行.
Task t2(30, TASK_FOREVER, &getKnob); // 任务名称t2,间隔30毫秒,一直执行。
Task t3(3000, TASK_FOREVER, &getJson); // 任务名称t3,间隔3秒,一直执行。
10)Setup方法里,需要设置各个引脚的初始化状态,各个扩展库对象的初始化、配网操作、任务调度等。
void setup()
{
Serial.begin(115200);
pinMode(LED_1, OUTPUT);
pinMode(LED_2, OUTPUT);
// led1为关灯
digitalWrite(LED_1, LOW);
// led2使用pwm调光
analogWrite(LED_2, 0);
dht.begin(); // DHT传感器对象工作
myBtn.begin(); // button按钮运作
WiFiManager wm; // wifi管理对象,配网用
bool res;
// 拼接芯片的hostname
sprintf(chipId, "xm_%06X", ESP.getChipId);
res = wm.autoConnect(chipId, "12345678"); // 密码认证模式的AP
if (!res)
{
Serial.println("Failed to connect");
// ESP.restart();
}
else
{
// if you get here you have connected to the WiFi
WiFi.setHostname(chipId); // 从模式后设置设备名
Serial.println("connected...yeey :)");
}
// 初始化任务调度器,规划任务链
runner.init();
runner.addTask(t1);
runner.addTask(t2);
runner.addTask(t3);
t1.enable();
t2.enable();
t3.enable();
}
11)温湿度和旋钮控光的任务方法
// 获得温湿度
void getTH()
{
h = dht.readHumidity();
// Read temperature as Celsius (the default)
t = dht.readTemperature();
Serial.print(F("Humidity: "));
Serial.print(h);
Serial.print(F("% Temperature: "));
Serial.print(t);
Serial.println(F("°C "));
}
// 旋钮操作
void getKnob()
{
knobValue = analogRead(analogInPin);
dutyCycle = map(knobValue, 15, 1008, 0, 255);
}
12)封装json的方法,json结构按我这里的前后台解析协议所制定,该协议下MCU端设备数量任意,但需要标定设备编号,以便于后续接受控制指令。其中有公网IP和实时时间数据暂时空着,以后会讲解,代码注释很全。基本思路就是先创建根节点,再创建json节点对象或者json数组对象,再往json对象里封装设备的数据,json节点创建时已经决定了其挂载顺序。
// 获得json封装的数据
void getJson()
{
//------------封装json对象传递----------------------------
/* 申明一个大小为1K的DynamicJsonDocument对象JSON_Buffer,
用于存储反序列化后的(即由字符串转换成JSON格式的)JSON报文,
*/
// DynamicJsonDocument doc(2048);
// 创建json对象
StaticJsonDocument<1024> doc;
// 创建json根节点对象
JsonObject root = doc.to<JsonObject>();
// root节点下创建子节点并赋值
root["protocol"] = "1.0";
root["chip_id"] = chipId;
root["chip_type"] = "ESP8266-12E";
root["product_line"] = "xust_19_teacher";
root["private_ip"] = WiFi.localIP();
root["public_ip"] = "";
root["timestamp"] = "";
// 创建json对象集合,存放该mcu节点下的所有传感器列表
JsonArray sensors = root.createNestedArray("sensor_list");
// 集合节点,创建子节点对象
// 1.不可调光主灯
JsonObject s1 = sensors.createNestedObject();
s1["number"] = 1;
s1["type"] = "amp_no";
s1["name"] = "厨房灯";
JsonObject s1_data = s1.createNestedObject("data");
JsonArray s1_data_ls = s1_data.createNestedArray("data_vals");
JsonObject s1_data1 = s1_data_ls.createNestedObject();
s1_data1["data_name"] = "state";
s1_data1["data_val"] = led1state;
//---------------- --------------------------
// 集合节点,创建子节点对象
// 2.可调光主灯
JsonObject s2 = sensors.createNestedObject();
s2["number"] = 2;
s2["type"] = "lamp_ctl";
s2["name"] = "主卧灯";
JsonObject s2_data = s2.createNestedObject("data");
JsonArray s2_data_ls = s2_data.createNestedArray("data_vals");
JsonObject s2_data1 = s2_data_ls.createNestedObject();
s2_data1["data_name"] = "state";
s2_data1["data_val"] = led2state;
JsonObject s2_data2 = s2_data_ls.createNestedObject();
s2_data2["data_name"] = "extent";
s2_data2["data_val"] = dutyCycle;
//---------------- --------------------------
// 集合节点,创建子节点对象
// 3.温湿度传感器
JsonObject s3 = sensors.createNestedObject();
s3["number"] = 3;
s3["type"] = "th";
s3["name"] = "温湿度";
JsonObject s3_data = s3.createNestedObject("data");
JsonArray s3_data_ls = s3_data.createNestedArray("data_vals");
JsonObject s3_data1 = s3_data_ls.createNestedObject();
s3_data1["data_name"] = "t_val";
s3_data1["data_val"] = t;
JsonObject s3_data2 = s3_data_ls.createNestedObject();
s3_data2["data_name"] = "h_val";
s3_data2["data_val"] = h;
// 序列化json数据,就是将json对象转为字符串放在messageBuff
serializeJson(doc, messageBuff);
Serial.print("messageBuff:");
Serial.println(messageBuff);
}
13)loop 方法调度任务的执行,判断按钮的读取以及设置灯光的状态。
void loop()
{
runner.execute(); // 执行任务
//处理按钮事件
myBtn.read();
if (myBtn.wasPressed())
{
led1state = !led1state;
digitalWrite(LED_1, led1state);
}
analogWrite(LED_2, dutyCycle);//根据状态控灯
}
14)如果你想直接运行看结果,下面上全部代码:
#include <Arduino.h>
#include <JC_Button.h>
#include <WiFiManager.h>
// 导入多任务库
#include <TaskScheduler.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
// json对象处理类,6.x 和5.x不一样 ,这里用的是6.19
#include <ArduinoJson.h>
#define LED_1 D5
#define LED_2 D6
#define BUTTON_PIN D4
#define DHTPIN D3 // DHT温湿度传感器引脚
#define analogInPin A0 // 模拟输入引脚A0
#define DHTTYPE DHT22 // 声明DHT传感器类型
DHT dht(DHTPIN, DHTTYPE); // 创建DHT对象
Scheduler runner; // 任务调度器对象
Button myBtn(BUTTON_PIN); // 按钮对象
// 温湿度值全局保存
float t = 0.0;
float h = 0.0;
// led灯光状态
bool led1state = false;
bool led2state = false;
// 旋钮值和灯光亮度的保存
unsigned int knobValue = 0;
unsigned int dutyCycle = 0;
// 存放芯片ID的缓冲
char chipId[20];
// 消息发送缓冲,json最后字符串方式传输
char messageBuff[1024];
// 前置函数声明
void getTH();
void getKnob();
void getJson();
Task t1(2000, TASK_FOREVER, &getTH); // 任务名称t1,间隔2秒一直执行.
Task t2(30, TASK_FOREVER, &getKnob); // 任务名称t2,间隔30毫秒,一直执行。
Task t3(3000, TASK_FOREVER, &getJson); // 任务名称t3,间隔3秒,一直执行。
void setup()
{
Serial.begin(115200);
pinMode(LED_1, OUTPUT);
pinMode(LED_2, OUTPUT);
// led1为关灯
digitalWrite(LED_1, LOW);
// led2使用pwm调光
analogWrite(LED_2, 0);
dht.begin(); // DHT传感器对象工作
myBtn.begin(); // button按钮运作
WiFiManager wm; // wifi管理对象,配网用
bool res;
// 拼接芯片的hostname
sprintf(chipId, "xm_%06X", ESP.getChipId);
res = wm.autoConnect(chipId, "12345678"); // 密码认证模式的AP
if (!res)
{
Serial.println("Failed to connect");
// ESP.restart();
}
else
{
// if you get here you have connected to the WiFi
WiFi.setHostname(chipId); // 从模式后设置设备名
Serial.println("connected...yeey :)");
}
// 初始化任务调度器,规划任务链
runner.init();
runner.addTask(t1);
runner.addTask(t2);
runner.addTask(t3);
t1.enable();
t2.enable();
t3.enable();
}
void loop()
{
runner.execute(); // 执行任务
//处理按钮事件
myBtn.read();
if (myBtn.wasPressed())
{
led1state = !led1state;
digitalWrite(LED_1, led1state);
}
analogWrite(LED_2, dutyCycle);//根据状态控灯
}
// 获得温湿度
void getTH()
{
h = dht.readHumidity();
// Read temperature as Celsius (the default)
t = dht.readTemperature();
Serial.print(F("Humidity: "));
Serial.print(h);
Serial.print(F("% Temperature: "));
Serial.print(t);
Serial.println(F("°C "));
}
// 旋钮操作
void getKnob()
{
// 关灯时不做任何调整
// if (!led2state)
// {
// return;
// }
knobValue = analogRead(analogInPin);
dutyCycle = map(knobValue, 15, 1008, 0, 255);
}
// 获得json封装的数据
void getJson()
{
//------------封装json对象传递----------------------------
/* 申明一个大小为1K的DynamicJsonDocument对象JSON_Buffer,
用于存储反序列化后的(即由字符串转换成JSON格式的)JSON报文,
*/
// DynamicJsonDocument doc(2048);
// 创建json对象
StaticJsonDocument<1024> doc;
// 创建json根节点对象
JsonObject root = doc.to<JsonObject>();
// root节点下创建子节点并赋值
root["protocol"] = "1.0";
root["chip_id"] = chipId;
root["chip_type"] = "ESP8266-12E";
root["product_line"] = "xust_19_teacher";
root["private_ip"] = WiFi.localIP();
root["public_ip"] = "";
root["timestamp"] = "";
// 创建json对象集合,存放该mcu节点下的所有传感器列表
JsonArray sensors = root.createNestedArray("sensor_list");
// 集合节点,创建子节点对象
// 1.不可调光主灯
JsonObject s1 = sensors.createNestedObject();
s1["number"] = 1;
s1["type"] = "amp_no";
s1["name"] = "厨房灯";
JsonObject s1_data = s1.createNestedObject("data");
JsonArray s1_data_ls = s1_data.createNestedArray("data_vals");
JsonObject s1_data1 = s1_data_ls.createNestedObject();
s1_data1["data_name"] = "state";
s1_data1["data_val"] = led1state;
//---------------- --------------------------
// 集合节点,创建子节点对象
// 2.可调光主灯
JsonObject s2 = sensors.createNestedObject();
s2["number"] = 2;
s2["type"] = "lamp_ctl";
s2["name"] = "主卧灯";
JsonObject s2_data = s2.createNestedObject("data");
JsonArray s2_data_ls = s2_data.createNestedArray("data_vals");
JsonObject s2_data1 = s2_data_ls.createNestedObject();
s2_data1["data_name"] = "state";
s2_data1["data_val"] = led2state;
JsonObject s2_data2 = s2_data_ls.createNestedObject();
s2_data2["data_name"] = "extent";
s2_data2["data_val"] = dutyCycle;
//---------------- --------------------------
// 集合节点,创建子节点对象
// 3.温湿度传感器
JsonObject s3 = sensors.createNestedObject();
s3["number"] = 3;
s3["type"] = "th";
s3["name"] = "温湿度";
JsonObject s3_data = s3.createNestedObject("data");
JsonArray s3_data_ls = s3_data.createNestedArray("data_vals");
JsonObject s3_data1 = s3_data_ls.createNestedObject();
s3_data1["data_name"] = "t_val";
s3_data1["data_val"] = t;
JsonObject s3_data2 = s3_data_ls.createNestedObject();
s3_data2["data_name"] = "h_val";
s3_data2["data_val"] = h;
// 序列化json数据,就是将json对象转为字符串放在messageBuff
serializeJson(doc, messageBuff);
Serial.print("messageBuff:");
Serial.println(messageBuff);
}
15)烧录并运行代码,打开串口监视器的同时,转动旋钮以及触动开关,可以看到输出的json结果同步变化,后面我们将json数据发送出去,有相关应用来进行解析。
格式化结果: