ESP8266 ArduinoIDE 物联网web客户端开发

news/2024/11/27 20:36:35/

一、使用 esp8266 实现 HTTP 客户端协议

在 arduinoIDE 中,并没有专门的 HTTP 协议客户端库。但是我们可以用 TCP 协议来自动手动实现。

1.1 HTTP 请求报文简介

所谓请求报文,即是基于 TCP/IP 协议发送的一串规范字符,这串规范字符描述了当前请求的具体细节。

一个 HTTP请求报文至少要包括请求行头部字段,其中包括了:

请求方法(method):是一个动词,如 GET/POST,表示对资源的操作;

请求目标(URI):通常是一个 URI,标记了请求方法要操作的资源;

版本号(Version):表示报文使用的 HTTP 协议版本。

 这三个部分通常使用空格(space)来分隔,最后要用 CRLF 换行表示结束。

GET /update HTTP/1.1

在这个请求行里,“GET”是请求方法,“/update”是请求目标,“HTTP/1.1”是版本号,把这三部分连起来,意思就是“服务器你好,我想获取网站 /update 目录下内容,我用的协议版本号是 1.1,请不要用 1.0 或者 2.0 回复我。”

请求行之后便是头部字段了,请求行或状态行再加上头部字段集合就构成了 HTTP 报文里完整的请求头响应头

在这里我只介绍 Host 字段,它属于请求字段,只能出现在请求头里,它同时也是唯一一个 HTTP/1.1 规范里要求必须出现的字段,也就是说,如果请求头里没有 Host,那这就是一个错误的报文。

Host 字段告诉服务器这个请求应该由哪个主机来处理,当一台计算机上托管了多个虚拟主机的时候,服务器端就需要用 Host 字段来选择,有点像是一个简单的“路由重定向”。

1.2 esp8266 要发送的报文

在这篇案例中我们让 esp8266 发送一个最简单的报文。

GET /update HTTP/1.1
Host: 192.168.0.123

这个报文使用了 GET 请求,使用HTTP1.1协议 访问了 http://192.168.0.123/update 这个连接,其作用和把他粘贴到浏览器是一样的。不过浏览器会在头部字段添加一些本机信息,这些对本章内容并不重要,我们不需要添加这些。

 

我们只需要把这些字符串按 TCP 协议发送出去,服务端即会返回给我们想要的数据。

需要注意的是,HTTP 协议是一问一答的,在服务端返回数据后即可断开这个 TCP 连接,客户端或者服务端主动断开都可以。想再次和服务端使用 HTTP 协议通讯只需要再次启动一个 TCP 连接即可。 

1.2 HTTP 请求案例

本案例实现一个对 http://192.168.0.123/update 进行请求,服务器返回值控制 esp8266 LED 灯亮的实例。

#include <ESP8266WiFi.h>const char* host = "192.168.0.102";   // 网络服务器IP
const int httpPort = 8888;              // http端口80const char* ssid = "home";
const char* password = "123456";void setup(){Serial.begin(9600);          Serial.println("");pinMode(LED_BUILTIN, OUTPUT);digitalWrite(LED_BUILTIN, HIGH);//设置ESP8266工作模式为无线终端模式WiFi.mode(WIFI_STA);//开始连接wifiWiFi.begin(ssid, password);int i = 0;  while (WiFi.status() != 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(){wifiClientRequest();  delay(3000);
}void wifiClientRequest(){WiFiClient client;          // 建立WiFiClient对象bool buttonState;     // 储存服务器按钮状态变量  Serial.print("Connecting to "); Serial.print(host);// 连接服务器if (client.connect(host, httpPort)){Serial.println(" Success!");// 建立客户端请求信息String httpRequest =  String("GET /update") + " HTTP/1.1\r\n" +"Host: " + host + "\r\n";// 发送客户端请求Serial.println("Sending request: ");Serial.print(httpRequest);  client.print(httpRequest);// 获取服务器响应信息中的按钮状态信息while (client.connected() || client.available()){if(client.find("buttonState:")){      buttonState = client.parseInt(); Serial.print("buttonState: " ); Serial.println(buttonState); }}} else{Serial.println(" failed!");} Serial.println("===============");client.stop();    // 停止客户端  // 根据服务器按键状态点亮或熄灭LEDbuttonState == 0 ? digitalWrite(LED_BUILTIN, LOW) : digitalWrite(LED_BUILTIN, HIGH);
}

 如果要使用这个案例,我们使用工具创建一个 TCP Server:

 

在同一局域网下,esp8266 填入正确的电脑 IP。 连接后向 esp8266 发送响应报文,通过这种形式模拟一个 web 服务器:

HTTP/1.0 200 OK 
Content-Type: text/plain
Content-Length: 14buttonState: 0

 

发送后点击关闭连接,这样才是一个完整的 http 请求。之后 esp8266 的 LED 灯即会点亮。

 

 我们在代码中的逻辑是:如果服务器不端口连接,就会一直接受信息。这符合 HTTP 的协议。

    // 获取服务器响应信息中的按钮状态信息while (client.connected() || client.available()){if(client.find("buttonState:")){      buttonState = client.parseInt(); Serial.print("buttonState: " ); Serial.println(buttonState); }

二、esp8266 解析JSON

2.1 JSON 概述

JSON数据以“名”“值”对呈现。数据“名”“值”由冒号分隔。JSON数据的书写格式是:

"info": {"name" : "taichi-maker","website" : "www.taichi-maker.com"
}

 当然也可以有 JSON 数组

"info": [{"name" : "taichi-maker","website" : "www.taichi-maker.com"},{"year": 2020,"month": 12,"day": 30}
]

2.2 单一对象JSON解析

#include <ArduinoJson.h>void setup() {Serial.begin(9600);Serial.println("");// 重点2:即将解析的json文件String json = "{\"name\":\"taichi-maker\",\"number\":1}";// 重点1:DynamicJsonDocument对象const size_t capacity = json.length()*2;DynamicJsonDocument doc(capacity);// 重点3:反序列化数据deserializeJson(doc, json);// 重点4:获取解析后的数据信息String nameStr = doc["name"].as<String>();int numberInt = doc["number"].as<int>();// 通过串口监视器输出解析后的数据信息Serial.print("nameStr = ");Serial.println(nameStr);Serial.print("numberInt = ");Serial.println(numberInt);
}void loop() {}

关于 JsonDocument 缓冲区大小下边的连接有更详细的讲解。How to determine the capacity of the JsonDocument? | ArduinoJson 6

不过我在参考其他例子中,发现有很多人直接把字符串长度 *2 

  // 重点1:DynamicJsonDocument对象const size_t capacity = json.length()*2;DynamicJsonDocument doc(capacity);

 这样也许会造成一些内存浪费,但他是极其方便的。我推荐使用这种办法。

2.3 解析网络 JSON 心知天气

 在心知天气中,我们看到获得天气的接口是这样的,先用 postman 来试试~

 可见成功返回了 json 对象

{"results": [{"location": {"id": "WX4FBX****KE4F","name": "Beijing","country": "CN","path": "Beijing,Beijing,China","timezone": "Asia/Shanghai","timezone_offset": "+08:00"},"now": {"text": "Sunny","code": "0","temperature": "-1"},"last_update": "2023-01-22T09:48:23+08:00"}]
}

 示例代码:

#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
const char* host = "api.seniverse.com";  // 网络服务器IP
const int httpPort = 80;                 // http端口80const char* ssid = "home";
const char* password = "123456";// 心知天气HTTP请求所需信息
String reqUserKey = "SG0vWTMFqCxPaCyyv";  // 私钥
String reqLocation = "Beijing";           // 城市
String reqUnit = "c";                     // 摄氏/华氏void setup() {Serial.begin(9600);Serial.println("");pinMode(LED_BUILTIN, OUTPUT);digitalWrite(LED_BUILTIN, HIGH);//设置ESP8266工作模式为无线终端模式WiFi.mode(WIFI_STA);//开始连接wifiWiFi.begin(ssid, password);int i = 0;while (WiFi.status() != WL_CONNECTED) {  // 尝试进行wifi连接。delay(1000);Serial.print(i++);Serial.print(' ');}
}void loop() {wifiClientRequest();delay(3000);
}void wifiClientRequest() {WiFiClient client;  // 建立WiFiClient对象// 建立心知天气API当前天气请求资源地址String reqRes = "/v3/weather/now.json?key=" + reqUserKey + +"&location=" + reqLocation + "&language=en&unit=" + reqUnit;String httpRequest = String("GET ") + reqRes + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n";Serial.println("");Serial.print("Connecting to ");Serial.print(host);// 尝试连接服务器if (client.connect(host, 80)) {Serial.println(" Success!");// 向服务器发送http请求信息client.print(httpRequest);Serial.println("Sending request: ");// 获取并显示服务器响应状态行// 获取并显示服务器响应状态行 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.");}//获得 json 正文String str = client.readString();Serial.println(str);DynamicJsonDocument doc(str.length()*2);deserializeJson(doc, a);JsonObject results_0 = doc["results"][0];JsonObject results_0_now = results_0["now"];const char* results_0_now_text = results_0_now["text"]; // "Sunny"const char* results_0_now_code = results_0_now["code"]; // "0"const char* results_0_now_temperature = results_0_now["temperature"]; // "32"const char* results_0_last_update = results_0["last_update"]; // "2020-06-02T14:40:00+08:00" // 通过串口监视器显示以上信息String results_0_now_text_str = results_0_now["text"].as<String>(); int results_0_now_code_int = results_0_now["code"].as<int>(); int results_0_now_temperature_int = results_0_now["temperature"].as<int>(); String results_0_last_update_str = results_0["last_update"].as<String>();   Serial.println(F("======Weahter Now======="));Serial.print(F("Weather Now: "));Serial.print(results_0_now_text_str);Serial.print(F(" "));Serial.println(results_0_now_code_int);Serial.print(F("Temperature: "));Serial.println(results_0_now_temperature_int);Serial.print(F("Last Update: "));Serial.println(results_0_last_update_str);Serial.println(F("========================"));}
}

其中返回来的值会有响应头和响应体两部分,按照http标准,他们会空一行。

范例如下:

所以在代码中我们用 \r\n\r\n 跳过这个响应头。之后获得的都是响应体 JSON 了。 

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

在这里先使用 string 对象保存 余下的 JSON,之后打印。

我们按照之前的说明,将 JSON 长度 *2 作为解析堆栈长度。 

  //获得 json 正文String str = client.readString();Serial.println(str);DynamicJsonDocument doc(str.length()*2);deserializeJson(doc, a);

重点就是这些,解析部分没什么好说的,仔细看看把代码跑跑就行。

运行结果:

 


http://www.ppmy.cn/news/18644.html

相关文章

趣味三角——第2章——弦

目录 2.1 三角学的雏形与和弦表的产生 2.2 解读残缺粘土板“Plimpton 322”上的三角学 “知识来自影子&#xff0c;影子来自 磬折形(The knowledge comes from the shadow, and the shadow comes from the gnomon)” ——摘自<<Chou-pei Suan-king>>(周髀(b)算经…

RSD高分卫星数据处理能力提升——日正射处理数千景高分数据集

李国春 通常认为&#xff0c;能够单日处理几百景高分辨率对地观测卫星数据的系统就已经是非常优秀的卫星数据处理系统了。RSD此次优化将其处理能力提升超过了一个数量级&#xff0c;达到了单日正射处理数千景高分辨率卫星数据集的水平。 不仅如此&#xff0c;RSD达到如此高的…

实战20:基于MSCNNopencv 的食堂人群密度检测(附完整项目代码)

项目概述:本项目从生活细节出发,将计算机视觉应用在饭堂人数检测上,结合软硬件设施:算法基于深度卷积神经网络模型,硬件基于树莓派 RaspberryPi3 Model B,终端为 Web APP 或者公众号平台,学生可以通过终端获取饭堂人数密度热力图。 本次实验硬件通过组员手中原有的树莓派…

java面向接口编程2023027

那就再进一步&#xff1a;面向接口编程 面向接口编程前面已经提到&#xff0c;接口体现的是一种规范和实现分离的设计哲学&#xff0c;充分利用接口可以极好地降低程序各模块之间的耦合&#xff0c;从而提高系统的可扩展性和可维护性。 基于这种原则&#xff0c;很多软件架构设…

基于ffmpeg的视频处理与MPEG的压缩试验(下载安装使用全流程)

基于ffmpeg的视频处理与MPEG的压缩试验ffmpeg介绍与基础知识对提取到的图像进行处理RGB并转化为YUV对YUV进行DCT变换对每个8*8的图像块进行进行量化操作ffmpeg介绍与基础知识 ffmpeg是视频和图像处理的工具包&#xff0c;它的下载网址是https://ffmpeg.org/download.html。页面…

一起自学SLAM算法:7.3 估计理论

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 不管是用贝叶斯网络还是因子图&#xff0c;一旦SLAM问题用概率图模型得到表示后&#xff0c;接下来就是利用可观测量&#xff08;和&#xff09;推理不可观测量&#xff08;和&#xff09;&#xff0c;也就是说S…

8种时间序列分类方法总结

对时间序列进行分类是应用机器和深度学习模型的常见任务之一。本篇文章将涵盖 8 种类型的时间序列分类方法。这包括从简单的基于距离或间隔的方法到使用深度神经网络的方法。这篇文章旨在作为所有时间序列分类算法的参考文章。 时间序列定义 在涵盖各种类型的时间序列 (TS) 分…

机器学习数据挖掘作业:基于BP神经网络、决策树、朴素贝叶斯网络的旧金山犯罪分类案例

研究内容 根据数据进行分类模型的构建 要求: 用python实现学习算法至少实现2-3种不同类型的学习算法(贝叶斯、神经网络、决策树等)要求比较和分析通过不同学习算法建立的模型的准确率数据自行查找合适的数据源,但不得少于1000条研究环境 系统环境: Windows 10 学生版 语言…