12-ESP8266数据封装成JSON

Author:teacherXue

一、什么是JSON

  1. 定义

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的类的定义,就没办法解析出数据。那么不同应用之间的数据传递就出现了困难。

  1. 意义

那么怎么才能便于不同程序之间的沟通呢?答案就是纯文本,只要是计算机系统就有基本字符集,就能解析显示字符信息。于是我们协商好,用一个怎样的方式使用字符来表示对象就好了。比如下面:

{"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

  1. 思路

既然json是对象的字符表现形式,那么我们就需要对MCU芯片要传输的数据进行抽象,抽成对象后才方便进行json数据封装。大致抽象后的形式如下。

数据类:
    基本数据1:值
    基本数据2:值
    传感器列表:
        传感器1:
            传感器数据1:值
传感器数据2:值
传感器2:
            传感器数据1:值
传感器数据2:值

传感器3:
            传感器数据1:值
传感器数据2:值

内存中的json可以抽象的理解为一个倒置的树状结构,我们可以把每个节点当成单独的数据项,每个数据项包含名字和值。创建好之后,我们就可以向挂圣诞树礼物一样,把它们依次挂在不同的节点之上。如下图:

  1. 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数据发送出去,有相关应用来进行解析。

格式化结果:

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盐池虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值