ESP8266 ArduinoIDE 物联网web客户端开发

news2024/9/25 17:20:09

一、使用 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端口80

const 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);

  //开始连接wifi
  WiFi.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();    // 停止客户端  
   
  // 根据服务器按键状态点亮或熄灭LED
  buttonState == 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: 14

buttonState: 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端口80

const 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);

  //开始连接wifi
  WiFi.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.coloradmin.cn/o/175795.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

liunx centos9安装nodejs并搭建vue 图文详解手把手教程

首先nodjs的官网找到liunx的安装包 https://nodejs.org/en/download/ 这里不推荐用源码安装&#xff0c;因为实在太慢&#xff0c;我安装时一下在不停安装连续15分钟都还在跑就是不知道什么原因 解压包 tar -xvf /root/node-v18.13.0-linux-x64.tar.xz设置全局 -s后面地址就是…

Android入门第59天-进入MVVM

什么是MVVM用“某大文豪亲”的话说&#xff1a;MVVM并不存在&#xff0c;只是xml里找控件找了太多了&#xff0c;自然而然就“找”出了一套共性。所以&#xff0c;MVVM只是包括了以下这些技术&#xff1a;DataBind&#xff1b;ViewModel双向绑定&#xff1b;Okhttp3retrofitrxj…

图解函数递归、数组详解

目录 一.修炼必备 二.图解递归的执行过程 三.数组 3.1 一维数组 3.2 二维数组 3.3 数组的共同问题 一.修炼必备 1.入门必备&#xff1a;VS2019社区版&#xff0c;下载地址&#xff1a;Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com) 2.趁手武…

视频文缩略图SDK:GleamTech VideoUltimate Crack

Video Reader and Thumbnailer for .NET Core 和 .网络框架 读取地球上的任何视频文件格式。逐帧读取视频文件。生成有意义的缩略图。 VideoUltimate是最快&#xff0c;最简单的.NET视频阅读器和缩略图器&#xff0c;可以读取任何视频文件格式 在地球上。它允许您逐帧读取视频…

C++ 一文解决 数据库ODB 安装/调试/使用的问题

引用&#xff1a; ODB Download (codesynthesis.com) Installing ODB on Linux/UNIX (codesynthesis.com) 缘起&#xff1a; 在开发过程中发现&#xff0c;现有的软件缺乏持久层&#xff08;Persistence Layer&#xff09;&#xff0c;即专注于实现数据持久化应用领域的某个…

广义零样本学习的转移增量

摘要&#xff1a;零样本学习&#xff08;ZSL&#xff09;是一种成功的从未知类中对对象进行分类的范例。然而&#xff0c;它在广义零样本学习&#xff08;GZSL&#xff09;设置中遭受严重的性能降级&#xff0c;即以识别来自可见类和不可见类的测试图像。在本文中&#xff0c;为…

C语言-qsort函数基本使用

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C/C】 先来看一下qsort函数的介绍&#xff1a; Compare 函数的返回值描述>0elem1将被排在elem2前面0elem1等于elem2<0elem1 将被…

LeetCode刷题模版:171-174、179

目录 简介171. Excel 表列序号172. 阶乘后的零173. 二叉搜索树迭代器174. 地下城游戏【未理解】179. 最大数结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,…

Redis学习【1】之Nosql概述

文章目录一 从技术发展探究使用Nosql的原因1.1 单机Mysql时代1.2 Memcached&#xff08;缓存&#xff09; MySQL 垂直拆分[读写分离]1.3 MySQL主从读写分离1.4 分表分库 水平拆分 Mysql 集群1.5 如今时代1.6 使用NoSQL的原因二 Nosql初识2.1 NoSQL的特点【解耦】三 NoSQL的四…

树状数组的原理和区间和

目录 一、前言 二、树状数组的原理 1、杂论 2、从二叉树到树状数组 3、神奇的 lowbit(x) 操作 4、tree[ ]数组&#xff1a;将一维信息转换为树形信息存储 5、基于 tree[ ] 的计算 6、tree[]的更新&#xff08;要加lowbit&#xff09; 三、树状数组的应用 1、单点修改…

流媒体方案之Nginx

1.Nginx可以作为流体服务器。2三种web服务器的比较3.推流端: FFmpeg使用RTMP协议向Nginx推流拉流端: •VLC播放器使用RTMP或HTTPFLV协议从Nginx拉流•浏览器使用HTTPFLV协议从Nginx拉流(安装flv.js)4.有两种方法&#xff1a;下载源码&#xff0c;手工编译使用Buildroot&#xf…

Redis分布式锁 | 黑马点评

目录 一、分布式锁概述 二、基于Redis的分布式锁 1、思路分析 2、初级版本 3、误删问题 4、改进分布式锁 5、原子性问题 6、使用Lua脚本解决原子性问题 7、setnx实现分布式锁存在问题 三、Redisson 1、Redisson快速入门 2、Redisson可重入锁原理 3、Redisson可重试…

从某一点出发沿任意一方向旋转矩阵计算思考与实现

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 上期讲到 绕任一向量旋转矩阵计算思考与实现 点击前往 点击前往 问题提出 之前讲到绕任一向量旋转矩阵实现&#xff0c;原来的向量都是从原点出发&#xff0c;现在把…

Ajax面试题目

更多Ajax面试题目可以查看专栏内容 1.AJAX应用和传统Web应用有什么不同&#xff1f; 传统的web前端与后端的交互中&#xff0c;浏览器直接访问Tomcat的Servlet来获取数据。Servlet通过转发把数据发送给浏览器。当我们使用AJAX之后&#xff0c;浏览器是先把请求发送到XMLHttpR…

Swift之struct二进制大小分析

随着Swift的日渐成熟和给开发过程带来的便利性及安全性&#xff0c;京喜App中的原生业务模块和基础模块使用Swift开发占比逐渐增高。本次讨论的是struct对比Class的一些优劣势&#xff0c;重点分析对包体积带来的影响及规避措施。 一、基础知识 1、类型对比 引用类型&#xff…

独立看门狗与窗口看门狗

定义 看门狗的本质是一个定时器&#xff0c;在启动后&#xff0c;需要在一定时间内再给它一个信号&#xff0c;俗称“喂狗”&#xff0c;如果没有按时“喂狗”&#xff0c;说明MCU可能处于非正常状态&#xff0c;这时看门狗就向MCU发送个复位信号&#xff0c;使整个系统重启&a…

51单片机数码管显示

文章目录前言一、数码管简介二、数码管原理图三、数码管显示原理四、静态数码管代表编写五、动态数码管总结前言 这篇文章将介绍数码管的显示其中包含了动态数码管和静态数码管两种。 一、数码管简介 数码管其实就是由多个发光二极管封装在一起组成“8”字型的器件当分别点亮…

【数据结构】超详细——堆的实现

一、堆的概念及性质 1.1 什么是堆&#xff1f; 堆是一种完全二叉树&#xff08;具体在下一章讲述&#xff09;&#xff0c;若二叉树的深度h&#xff0c;除了第h层外其余各层节点数满了&#xff0c;只有第h层缺额且该层结点靠左&#xff1b;任何一个数组可以看作完全二叉树&…

【14】C语言_函数简介

目录 1、C语言中函数的分类: 2、库函数 3、自定义函数 1、C语言中函数的分类: 1.库函数 2.自定义函数 2、库函数 为什么会有库函数? 1.我们知道在我们学习C语言编程的时候&#xff0c;总是在一个代码编写完成之后迫不及待的想知道结果&#xff0c;想把这个结果打印到我们的屏…

ESP32设备驱动-LX1972可见光传感器驱动

LX1972可见光传感器驱动 1、LX1972介绍 LX1972 是一款低成本硅光传感器,其光谱响应非常接近人眼。专利电路在 520nm 处产生峰值光谱响应,IR 响应小于峰值响应的 5%,高于 900nm。 光电传感器是一个 PIN 二极管阵列,具有线性、准确和非常可重复的电流传递函数。 芯片上的…