明珠的个人博客

是谁告诉你,你是赤裸的?

0%

物联网之请求JSON信息

ESP8266客户端请求JSON信息

由于易于解析且量级很轻,JSON成为了常用的物联网信息传输格式之一。本次内容如下:

  1. 使用ESP8266来建立物联网服务器,该服务器可以向客户端发送JSON格式响应信息从而实现物联网信息通讯。
  2. 使用ESP8266来通过网络向物联网服务器请求JSON信息
  3. 使用ESP8266来通过ArduinoJson库解析JSON信息

在接下来的讲解中,我们将需要两块ESP8266-NodeMCU开发板。其中一块作为服务器,另一块作为客户端。如下图所示,客户端将会向服务器发送请求信息。服务器端在接收到客户端请求后,会将JSON信息加入服务器响应信息中发送给客户端。

示例一 ESP8266客户端请求单一JSON数据信息

本示例分为两部分,一部分为服务器程序,另一部分为客户端程序。

服务器端程序

服务器端程序主要功能:

  1. 实时读取A0、 D1、D2以及D3引脚的读数。
  2. 当有客户端请求时,通过响应信息将引脚读数和测试数据信息发送给客户端。
    信息发送格式为json格式。以下为该json信息的示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    "info": {
    "name": "taichimaker",
    "url": "www.taichi-maker.com",
    "email": "taichimaker@163.com"
    },
    "digital_pin": {
    "d1": "1",
    "d2": "0",
    "d3": "1"
    },
    "analog_pin": {
    "a0": "500"
    }
    }
    以上JSON信息包含有三个数据,第一个数据”info”对应的值是一个包含有三个数据的对象。这三个数据值都是字符串格式。他们在整个程序运行中是保持不变的。第二个数据”digital_pin”所对应的值是一个含有三个数据的对象,这三个数据是ESP8266开发板的D1、D2、D3引脚的实时电平状态。其中D3引脚的状态正是NodeMCU开发板上按键的引脚状态。通过按下该按键,可以改变D3引脚电平状态。第三个数据”analog_pin”对应的值是一个含有一个数据的对象。该数据是ESP8266的模拟输入引脚实时读数。换句话说, “digital_pin”和”analog_pin”所对应的数据值都是ESP8266引脚的实时状态,这些信息是会改变的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**********************************************************************
项目名称/Project : 零基础入门学用物联网
程序名称/Program name : cgj_server_1
团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)
作者/Author : CYNO朔
日期/Date(YYYYMMDD) : 2020305
程序目的/Purpose :
本实例用于演示esp8266的json数据通讯。
操作测试本程序需要使用两台8266开发板。其中一台为服务器端,一台为客户端。
本程序为服务器程序,功能如下:

1. 实时读取A0、 D1、D2以及D3引脚的读数。
2. 当有客户端请求信息时,将会通过http响应将引脚读数等信息发送给客户端。
信息发送格式为json格式。
3. 本程序使用了wifi.config对开发板的IP进行了配置。
-----------------------------------------------------------------------
修订历史/Revision History
日期/Date 作者/Author 参考号/Ref 修订说明/Revision Description
20200511 CYNO朔 001 1 移除handleNotFound使教程代码更加精简
2 改请求路径为update
***********************************************************************/
#include <ESP8266WiFi.h> // 本程序使用 ESP8266WiFi库
#include <ESP8266WiFiMulti.h> // ESP8266WiFiMulti库
#include <ESP8266WebServer.h> // ESP8266WebServer库

#define buttonPin D3 // 按钮引脚D3

ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象,对象名称是'wifiMulti'

ESP8266WebServer esp8266_server(80);// 建立网络服务器对象,该对象用于响应HTTP请求。监听端口(80)

IPAddress local_IP(192, 168, 0, 123); // 设置ESP8266-NodeMCU联网后的IP
IPAddress gateway(192, 168, 0, 1); // 设置网关IP(通常网关IP是WiFI路由IP)
IPAddress subnet(255, 255, 255, 0); // 设置子网掩码
IPAddress dns(192,168,0,1); // 设置局域网DNS的IP(通常局域网DNS的IP是WiFI路由IP)

void setup(){
Serial.begin(9600); // 启动串口通讯
Serial.println("");

// 将引脚设置为输入上拉模式
pinMode(D1, INPUT_PULLUP);
pinMode(D2, INPUT_PULLUP);
pinMode(buttonPin, INPUT_PULLUP); // NodeMCU开发板按键连接在D3引脚上

// 设置开发板网络环境
if (!WiFi.config(local_IP, gateway, subnet)) {
Serial.println("Failed to Config ESP8266 IP");
}

//通过addAp函数存储 WiFi名称 WiFi密码
wifiMulti.addAP("taichi-maker1", "12345678"); // 这三条语句通过调用函数addAP来记录3个不同的WiFi网络信息。
wifiMulti.addAP("taichi-maker2", "87654321"); // 这3个WiFi网络名称分别是taichi-maker, taichi-maker2, taichi-maker3。
wifiMulti.addAP("taichi-maker3", "13572468"); // 这3个网络的密码分别是123456789,87654321,13572468。
// 此处WiFi信息只是示例,请在使用时将需要连接的WiFi信息填入相应位置。
// 另外这里只存储了3个WiFi信息,您可以存储更多的WiFi信息在此处。

int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 此处的wifiMulti.run()是重点。通过wifiMulti.run(),NodeMCU将会在当前
delay(1000); // 环境中搜索addAP函数所存储的WiFi。如果搜到多个存储的WiFi那么NodeMCU
Serial.print(i++); Serial.print(' '); // 将会连接信号最强的那一个WiFi信号。
} // 一旦连接WiFI成功,wifiMulti.run()将会返回“WL_CONNECTED”。这也是
// 此处while循环判断是否跳出循环的条件。
// WiFi连接成功后将通过串口监视器输出连接成功信息
Serial.println('\n'); // WiFi连接成功后
Serial.print("Connected to "); // NodeMCU将通过串口监视器输出。
Serial.println(WiFi.SSID()); // 连接的WiFI名称
Serial.print("IP address:\t"); // 以及
Serial.println(WiFi.localIP()); // NodeMCU的IP地址

esp8266_server.on("/", handleRoot);
esp8266_server.begin();

Serial.println("HTTP esp8266_server started");// 告知用户ESP8266网络服务功能已经启动
}

void loop(){
// 处理http服务器访问
esp8266_server.handleClient();
}

void handleRoot() { //处理网站目录“/”的访问请求
esp8266_server.send(200, "application/json", rootJson());
}

// 实时获取ESP8266开发板引脚信息并且建立JSON信息
// 以便ESP8266服务器通过响应信息发送给客户端
String rootJson(){

String jsonCode = "{\"info\": {\"name\": \"taichimaker\",\"url\": \"www.taichi-maker.com\",\"email\": \"taichimaker@163.com\"},\"digital_pin\": {\"d1\": \"";
jsonCode += String(digitalRead(D1));
jsonCode += "\",\"d2\": \"";
jsonCode += String(digitalRead(D2));
jsonCode += "\",\"d3\": \"";
jsonCode += String(digitalRead(D3));
jsonCode += "\"},\"analog_pin\": {\"a0\": \"";
jsonCode += String(analogRead(A0));
jsonCode += "\"}}";

Serial.print("jsonCode: ");Serial.println(jsonCode);

return jsonCode;
}

以上程序的重点是函数rootJson。该函数作用是实时获取ESP8266开发板引脚信息并且建立JSON信息。该信息将会通过服务器响应信息发送给请求这一信息的客户端。此方法相对简单,但是缺点是不够灵活。假如需要修改响应的JSON信息,那么就要重新构建JSON字符串。另外,假如响应JSON信息比较复杂,那么构建这个字符串的工作还是很麻烦的。

接下来我们在以上程序的基础上做一下修改。使用ArduinoJson官网的在线工具来自动生成代码,搭建我们想要的JSON信息并应用到程序的rootJson函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**********************************************************************
项目名称/Project : 零基础入门学用物联网
程序名称/Program name : cgj_server_1_serialize
团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)
作者/Author : CYNO朔
日期/Date(YYYYMMDD) : 2020517
程序目的/Purpose :
本实例用于演示esp8266的JSON数据通讯。
操作测试本程序需要使用两台8266开发板。其中一台为服务器端,一台为客户端。
本程序为服务器程序,功能如下:

1. 实时读取A0、 D1、D2以及D3引脚的读数。
2. 当有客户端请求信息时,将会通过http响应将引脚读数等信息发送给客户端。
信息发送格式为JSON格式。
3. 使用ArduinoJson库的Serialize方式建立响应JSON信息
-----------------------------------------------------------------------
修订历史/Revision History
日期/Date 作者/Author 参考号/Ref 修订说明/Revision Description
***********************************************************************/
#include <ESP8266WiFi.h> // 本程序使用 ESP8266WiFi库
#include <ESP8266WiFiMulti.h> // ESP8266WiFiMulti库
#include <ESP8266WebServer.h> // ESP8266WebServer库
#include <ArduinoJson.h> // ArduinoJson库

#define buttonPin D3 // 按钮引脚D3

ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象,对象名称是'wifiMulti'

ESP8266WebServer esp8266_server(80);// 建立网络服务器对象,该对象用于响应HTTP请求。监听端口(80)

IPAddress local_IP(192, 168, 0, 123); // 设置ESP8266-NodeMCU联网后的IP
IPAddress gateway(192, 168, 0, 1); // 设置网关IP(通常网关IP是WiFI路由IP)
IPAddress subnet(255, 255, 255, 0); // 设置子网掩码
IPAddress dns(192,168,0,1); // 设置局域网DNS的IP(通常局域网DNS的IP是WiFI路由IP)

void setup(){
Serial.begin(9600); // 启动串口通讯
Serial.println("");

// 将引脚设置为输入上拉模式
pinMode(D1, INPUT_PULLUP);
pinMode(D2, INPUT_PULLUP);
pinMode(buttonPin, INPUT_PULLUP); // NodeMCU开发板按键连接在D3引脚上

// 设置开发板网络环境
if (!WiFi.config(local_IP, gateway, subnet)) {
Serial.println("Failed to Config ESP8266 IP");
}

//通过addAp函数存储 WiFi名称 WiFi密码
wifiMulti.addAP("taichi-maker1", "12345678"); // 这三条语句通过调用函数addAP来记录3个不同的WiFi网络信息。
wifiMulti.addAP("taichi-maker2", "87654321"); // 这3个WiFi网络名称分别是taichi-maker, taichi-maker2, taichi-maker3。
wifiMulti.addAP("taichi-maker3", "13572468"); // 这3个网络的密码分别是123456789,87654321,13572468。
// 此处WiFi信息只是示例,请在使用时将需要连接的WiFi信息填入相应位置。
// 另外这里只存储了3个WiFi信息,您可以存储更多的WiFi信息在此处。

int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 此处的wifiMulti.run()是重点。通过wifiMulti.run(),NodeMCU将会在当前
delay(1000); // 环境中搜索addAP函数所存储的WiFi。如果搜到多个存储的WiFi那么NodeMCU
Serial.print(i++); Serial.print(' '); // 将会连接信号最强的那一个WiFi信号。
} // 一旦连接WiFI成功,wifiMulti.run()将会返回“WL_CONNECTED”。这也是
// 此处while循环判断是否跳出循环的条件。
// WiFi连接成功后将通过串口监视器输出连接成功信息
Serial.println('\n'); // WiFi连接成功后
Serial.print("Connected to "); // NodeMCU将通过串口监视器输出。
Serial.println(WiFi.SSID()); // 连接的WiFI名称
Serial.print("IP address:\t"); // 以及
Serial.println(WiFi.localIP()); // NodeMCU的IP地址

esp8266_server.begin();
esp8266_server.on("/", handleRoot);

Serial.println("HTTP esp8266_server started");// 告知用户ESP8266网络服务功能已经启动
}

void loop(){
// 处理http服务器访问
esp8266_server.handleClient();
}

void handleRoot() { //处理网站目录“/”的访问请求
esp8266_server.send(200, "application/json", rootJson());
}

// 实时获取ESP8266开发板引脚信息并且建立JSON信息
// 以便ESP8266服务器通过响应信息发送给客户端
String rootJson(){
// 开始ArduinoJson Assistant的serialize代码
const size_t capacity = JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(3)+140;
DynamicJsonDocument doc(capacity);

JsonObject info = doc.createNestedObject("info");
info["name"] = "taichimaker";
info["url"] = "www.taichi-maker.com";
info["email"] = "taichimaker@163.com";

JsonObject digital_pin = doc.createNestedObject("digital_pin");
digital_pin["d1"] = String(digitalRead(D1));
digital_pin["d2"] = String(digitalRead(D2));
digital_pin["d3"] = String(digitalRead(D3));

JsonObject analog_pin = doc.createNestedObject("analog_pin");
analog_pin["a0"] = String(analogRead(A0));
// 结束assistant的serialize代码

String jsonCode;
serializeJson(doc, jsonCode);
Serial.print("Root Json Code: ");Serial.println(jsonCode);

return jsonCode;
}

客户端程序

客户端程序的主要功能:

  1. 向服务器端请求json数据信息
  2. 解析服务器端响应的json信息内容。
  3. 将解析后的数据信息显示于串口监视器
  4. 利用服务器端D3引脚(按键引脚)读数来控制客户端开发板上LED的点亮和熄灭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**********************************************************************
项目名称/Project : 零基础入门学用物联网
程序名称/Program name : cgj_client_1
团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)
作者/Author : CYNO朔
日期/Date(YYYYMMDD) : 20200228
程序目的/Purpose :
本实例用于演示esp8266的json数据通讯。
操作测试本程序需要使用两台8266开发板。其中一台为服务器端,一台为客户端。
本程序为客户端程序,功能如下:

1. 通过http协议向服务器端请求json数据信息
2. 解析服务器端响应的json信息内容。
3. 将解析后的数据信息显示于串口监视器
4. 利用服务器端D3引脚(按键引脚)读数来控制客户端开发板上LED的点亮和熄灭
-----------------------------------------------------------------------
修订历史/Revision History
日期/Date 作者/Author 参考号/Ref 修订说明/Revision Description
20200302 CYNO朔 001 添加arduinojson解析错误识别
20200511 CYNO朔 002 改请求路径为update
20200511 CYNO朔 003 parse过程使用函数完成
***********************************************************************/
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象

const char* host = "192.168.0.123"; // 将要连接的服务器地址
const int httpPort = 80; // 将要连接的服务器端口

void setup(){
Serial.begin(9600);
Serial.println("");

// 设置开发板LED引脚
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);

wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1"); // 将需要连接的一系列WiFi ID和密码输入这里
wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); // ESP8266-NodeMCU再启动后会扫描当前网络
wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); // 环境查找是否有这里列出的WiFi ID。如果有
Serial.println("Connecting ...");

int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 尝试进行wifi连接。
delay(1000);
Serial.print(i++); Serial.print(' ');
}

// WiFi连接成功后将通过串口监视器输出连接成功信息
Serial.println("");
Serial.print("Connected to ");
Serial.println(WiFi.SSID()); // WiFi名称
Serial.print("IP address:\t");
Serial.println(WiFi.localIP()); // IP
}

void loop(){
httpRequest();

delay(3000);
}

// 向服务器请求信息并对信息进行解析
void httpRequest(){
WiFiClient client;

String httpRequest = String("GET /") + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n";

Serial.print("Connecting to "); Serial.print(host);

if (client.connect(host, 80)){
Serial.println(" Success!");

// 向服务器发送http请求信息
client.print(httpRequest);
Serial.println("Sending request: ");
Serial.println(httpRequest);

// 获取并显示服务器响应状态行
String status_response = client.readStringUntil('\n');
Serial.print("status_response: ");
Serial.println(status_response);

// 使用find跳过HTTP响应头
if (client.find("\r\n\r\n")) {
Serial.println("Found Header End. Start Parsing.");
}

parseInfo(client);
}
else {
Serial.println(" connection failed!");
}
//断开客户端与服务器连接工作
client.stop();
}

void parseInfo(WiFiClient client){
const size_t capacity = JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(3) + 140;
DynamicJsonDocument doc(capacity);

deserializeJson(doc, client);

JsonObject info = doc["info"];
const char* info_name = info["name"]; // "taichimaker"
const char* info_url = info["url"]; // "www.taichi-maker.com"
const char* info_email = info["email"]; // "taichimaker@163.com"

JsonObject digital_pin = doc["digital_pin"];
const char* digital_pin_d1 = digital_pin["d1"]; // "1"
const char* digital_pin_d2 = digital_pin["d2"]; // "0"
const char* digital_pin_d3 = digital_pin["d3"]; // "1"

const char* analog_pin_a0 = doc["analog_pin"]["a0"]; // "500"

String info_name_str = info["name"].as<String>();
bool d3_bool = digital_pin["d3"].as<int>();

Serial.print("info_name_str = ");Serial.println(info_name_str);
Serial.print("d3_bool = ");Serial.println(d3_bool);

d3_bool == 0 ? digitalWrite (LED_BUILTIN, LOW) : digitalWrite(LED_BUILTIN, HIGH);
}

示例程序客户端的串口监视器截图如下:
服务器端的按键按下时,客户端d3的BOOL值为0,且客户端的指示灯点亮,不按时为1且灯熄灭。

此时服务器端的串口监视器截图如下:

总结

所以整个流程就是:服务器端会发送JSON格式的信息,这个信息里面包含了我们想要的内容,因此我们建立了一个客户端,跟服务器端建立通信并解析它发送的JSON格式的信息。而解析JSON可以手动解析也可以使用辅助工具解析。

ESP8266客户端请求多种JSON数据信息

在以上示例程序中,服务器响应的信息形式只有一种,也就是将所有JSON信息全部响应给客户端。然而在实际开发物联网项目过程中,可能客户端只需要服务器JSON信息中的的某一个或某几个信息。这种情况下,如果服务器总是把所有信息都发送给客户端,这一操作会产生网络资源和系统运算资源的浪费。

接下来的示例程序中,客户端可以有选择性地向服务器请求信息内容。服务器端也会在接收到客户端请求后,根据客户端的需求来选择性的发送服务器信息。而这些信息同样使用JSON格式来传输。

服务器端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**********************************************************************
项目名称/Project : 零基础入门学用物联网
程序名称/Program name : cgj_server_2
团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)
作者/Author : CYNO朔
日期/Date(YYYYMMDD) : 2020311
程序目的/Purpose :
本实例用于演示esp8266的json数据通讯。
操作测试本程序需要使用两台8266开发板。其中一台为服务器端,一台为客户端。
本程序为服务器程序,功能如下:

1. 实时读取A0,D1,D2以及D3引脚的读数。
2. 当有客户端请求信息时,将会通过响应将引脚读数等信息发送给客户端。
3. 服务器在发送响应信息时,会根据客户端的需求而有选择性地发送响应信息。
4. 信息发送格式为json格式。建立json过程使用arduinojson serialize
-----------------------------------------------------------------------
修订历史/Revision History
日期/Date 作者/Author 参考号/Ref 修订说明/Revision Description
20200511 CYNO朔 001 移除handleNotFound以精简示例代码
***********************************************************************/
#include <ESP8266WiFi.h> // 本程序使用 ESP8266WiFi库
#include <ESP8266WiFiMulti.h> // ESP8266WiFiMulti库
#include <ESP8266WebServer.h> // ESP8266WebServer库
#include <ArduinoJson.h> // ArduinoJson库

#define buttonPin D3 // 按钮引脚D3

ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象,对象名称是'wifiMulti'

ESP8266WebServer esp8266_server(80);// 建立网络服务器对象,该对象用于响应HTTP请求。监听端口(80)

IPAddress local_IP(192, 168, 0, 123); // 设置ESP8266-NodeMCU联网后的IP
IPAddress gateway(192, 168, 0, 1); // 设置网关IP(通常网关IP是WiFI路由IP)
IPAddress subnet(255, 255, 255, 0); // 设置子网掩码
IPAddress dns(192,168,0,1); // 设置局域网DNS的IP(通常局域网DNS的IP是WiFI路由IP)

void setup(){
Serial.begin(9600); // 启动串口通讯
Serial.println("");

// 将引脚设置为输入上拉模式
pinMode(D1, INPUT_PULLUP);
pinMode(D2, INPUT_PULLUP);
pinMode(buttonPin, INPUT_PULLUP); // NodeMCU开发板按键连接在D3引脚上

// 设置开发板网络环境
if (!WiFi.config(local_IP, gateway, subnet)) {
Serial.println("Failed to Config ESP8266 IP");
}

//通过addAp函数存储 WiFi名称 WiFi密码
wifiMulti.addAP("taichi-maker1", "12345678"); // 这三条语句通过调用函数addAP来记录3个不同的WiFi网络信息。
wifiMulti.addAP("taichi-maker2", "87654321"); // 这3个WiFi网络名称分别是taichi-maker, taichi-maker2, taichi-maker3。
wifiMulti.addAP("taichi-maker3", "13572468"); // 这3个网络的密码分别是123456789,87654321,13572468。
// 此处WiFi信息只是示例,请在使用时将需要连接的WiFi信息填入相应位置。
// 另外这里只存储了3个WiFi信息,您可以存储更多的WiFi信息在此处。

int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 此处的wifiMulti.run()是重点。通过wifiMulti.run(),NodeMCU将会在当前
delay(1000); // 环境中搜索addAP函数所存储的WiFi。如果搜到多个存储的WiFi那么NodeMCU
Serial.print(i++); Serial.print(' '); // 将会连接信号最强的那一个WiFi信号。
} // 一旦连接WiFI成功,wifiMulti.run()将会返回“WL_CONNECTED”。这也是
// 此处while循环判断是否跳出循环的条件。
// WiFi连接成功后将通过串口监视器输出连接成功信息
Serial.println('\n'); // WiFi连接成功后
Serial.print("Connected to "); // NodeMCU将通过串口监视器输出。
Serial.println(WiFi.SSID()); // 连接的WiFI名称
Serial.print("IP address:\t"); // 以及
Serial.println(WiFi.localIP()); // NodeMCU的IP地址

// 重点一: 建立回调函数以满足客户端的不同请求
esp8266_server.begin();
esp8266_server.on("/", handleRoot);
esp8266_server.on("/info", handleInfo);
esp8266_server.on("/digital_pin", handleDigitalPin);

Serial.println("HTTP esp8266_server started");// 告知用户ESP8266网络服务功能已经启动
}

void loop(){
// 处理http服务器访问
esp8266_server.handleClient();
}

void handleRoot() { //处理网站目录“/”的访问请求
esp8266_server.send(200, "application/json", rootJson());
}

void handleInfo() { //处理网站目录“/info”的访问请求
esp8266_server.send(200, "application/json", infoJson());
}

void handleDigitalPin() { //处理网站目录“/digital_pin”的访问请求
esp8266_server.send(200, "application/json", digitalPinJson());
}

// 实时获取ESP8266开发板引脚信息并且建立JSON信息
// 以便ESP8266服务器通过响应信息发送给客户端
String rootJson(){
// 开始ArduinoJson Assistant的serialize代码
const size_t capacity = JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(3);
DynamicJsonDocument doc(capacity);

JsonObject info = doc.createNestedObject("info");
info["name"] = "taichimaker";
info["url"] = "www.taichi-maker.com";
info["email"] = "taichimaker@163.com";

JsonObject digital_pin = doc.createNestedObject("digital_pin");
digital_pin["d1"] = String(digitalRead(D1));
digital_pin["d2"] = String(digitalRead(D2));
digital_pin["d3"] = String(digitalRead(D3));

JsonObject analog_pin = doc.createNestedObject("analog_pin");
analog_pin["a0"] = String(analogRead(A0));
// 结束assistant的serialize代码

String jsonCode;
serializeJson(doc, jsonCode);
Serial.print("Root Json Code: ");Serial.println(jsonCode);

return jsonCode;
}

//建立infoJson信息
String infoJson(){

// 开始ArduinoJson Assistant的serialize代码
const size_t capacity = JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(3)+140;
DynamicJsonDocument doc(capacity);

JsonObject info = doc.createNestedObject("info");
info["name"] = "taichimaker";
info["url"] = "www.taichi-maker.com";
info["email"] = "taichimaker@163.com";
// 结束assistant的serialize代码

String jsonCode;
serializeJson(doc, jsonCode);
Serial.print("info Json Code: ");Serial.println(jsonCode);

return jsonCode;
}

//建立digitalPinJson信息
String digitalPinJson(){

// 开始ArduinoJson Assistant的serialize代码
const size_t capacity = JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(3)+140;
DynamicJsonDocument doc(capacity);

JsonObject digital_pin = doc.createNestedObject("digital_pin");
digital_pin["d1"] = String(digitalRead(D1));
digital_pin["d2"] = String(digitalRead(D2));
digital_pin["d3"] = String(digitalRead(D3));

String jsonCode;
serializeJson(doc, jsonCode);
Serial.print("Dpin json Code: ");Serial.println(jsonCode);

return jsonCode;
}

以上示例程序中建立了一系列回调函数handleRoot、handleInfo、handleDigitalPin。这些回调函数会针对客户端的请求来发送不同的JSON响应信息。这一点与之前的服务器端示例程序有所区别。另外注意,在建立JSON响应信息时,使用了ArduinoJson库的createNestedObject函数以及serializeJson函数来实现。

客户端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**********************************************************************
项目名称/Project : 零基础入门学用物联网
程序名称/Program name : cgj_client_2
团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)
作者/Author : CYNO朔
日期/Date(YYYYMMDD) : 20200228
程序目的/Purpose :
本实例用于演示esp8266的json数据通讯。
操作测试本程序需要使用两台8266开发板。其中一台为服务器端,一台为客户端。
本程序为客户端程序,功能如下:

1. 通过http协议向服务器端请求json数据信息
2. 解析服务器端响应的json信息内容。
3. 将解析后的数据信息显示于串口监视器
4. 检查服务器响应JSON否存在某指定key
-----------------------------------------------------------------------
修订历史/Revision History
日期/Date 作者/Author 参考号/Ref 修订说明/Revision Description
20200302 CYNO朔 001 添加arduinojson解析错误识别
20200511 CYNO朔 002 调整变量名称,调整httpRequest函数
使其更加精简
20200519 CYNO朔 003 根据client_1程序调整,使示例具有连贯性
***********************************************************************/
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象

const char* host = "192.168.0.123"; // 将要连接的服务器地址
const int httpPort = 80; // 将要连接的服务器端口

void setup(){
Serial.begin(9600);
Serial.println("");

// 设置开发板LED引脚
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);

wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1"); // 将需要连接的一系列WiFi ID和密码输入这里
wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); // ESP8266-NodeMCU再启动后会扫描当前网络
wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); // 环境查找是否有这里列出的WiFi ID。如果有
Serial.println("Connecting ...");

int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 尝试进行wifi连接。
delay(1000);
Serial.print(i++); Serial.print(' ');
}

// WiFi连接成功后将通过串口监视器输出连接成功信息
Serial.println("");
Serial.print("Connected to ");
Serial.println(WiFi.SSID()); // WiFi名称
Serial.print("IP address:\t");
Serial.println(WiFi.localIP()); // IP
}

void loop(){
httpRequest("/");
delay(2000);
httpRequest("/info");
delay(2000);
httpRequest("/digital_pin");
delay(2000);
}

// 向服务器请求信息并对信息进行解析
void httpRequest(String url){
WiFiClient client;

String httpRequest = String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n";

Serial.print("Connecting to "); Serial.print(host);

if (client.connect(host, 80)){
Serial.println(" Success!");

// 向服务器发送http请求信息
client.print(httpRequest);
Serial.println("Sending request: ");
Serial.println(httpRequest);

// 获取并显示服务器响应状态行
String status_response = client.readStringUntil('\n');
Serial.print("status_response: ");
Serial.println(status_response);

// 使用find跳过HTTP响应头
if (client.find("\r\n\r\n")) {
Serial.println("Found Header End. Start Parsing.");
}

parseInfo(client);
}
else {
Serial.println(" connection failed!");
}
//断开客户端与服务器连接工作
client.stop();
}

void parseInfo(WiFiClient client){
String info_name_str;
bool d3_bool;

const size_t capacity = JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(3) + 140;
DynamicJsonDocument doc(capacity);

deserializeJson(doc, client);

JsonObject info = doc["info"];
if(info){
Serial.println("Server Json has info: true");
const char* info_name = info["name"]; // "taichimaker"
const char* info_url = info["url"]; // "www.taichi-maker.com"
const char* info_email = info["email"]; // "taichimaker@163.com"
info_name_str = info["name"].as<String>();
Serial.print("info_name_str = ");Serial.println(info_name_str);
} else {
Serial.println("Server Json has info: false");
}

JsonObject digital_pin = doc["digital_pin"];
if (digital_pin){
Serial.println("Server Json has digital_pin: true");
const char* digital_pin_d1 = digital_pin["d1"]; // "1"
const char* digital_pin_d2 = digital_pin["d2"]; // "1"
const char* digital_pin_d3 = digital_pin["d3"]; // "1"
d3_bool = digital_pin["d3"].as<int>();
Serial.print("d3_bool = ");Serial.println(d3_bool);
} else {
Serial.println("Server Json has digital_pin: false");
}

const char* analog_pin_a0 = doc["analog_pin"]["a0"]; // "500"
if (analog_pin_a0){
Serial.println("Server Json has analog_pin_a0: true");
} else {
Serial.println("Server Json has analog_pin_a0: false");
}

d3_bool == 0 ? digitalWrite (LED_BUILTIN, LOW) : digitalWrite(LED_BUILTIN, HIGH);
}

以上,通过使用不同参数来调用以上示例程序中的httpRequest函数,从而实现客户端向服务器发送不同JSON请求。在解析服务器JSON响应信息时,使用逻辑判断语句来检查服务器响应JSON信息中是否包含有所需的信息内容。如果包含所需信息,则进一步操作来获取信息内容。否则,ESP8266将会通过串口监视器告诉用户,服务器的响应JSON中,没有指定的内容。

客户端程序串口监视器截图:

可以看到客户端开发板上d3的引脚状态是变动的,服务器端的按键按下后,客户端上d3的引脚状态就会变为0,松开后又会恢复原来状态。

服务器端程序串口监视器截图:
服务器端d3的引脚状态是1,按键按下时为0,松开后又恢复为1。