2026/4/5 22:03:45
网站建设
项目流程
三、开发客户端ESP82661、自定义AT指令集1实际上Wi-Fi模块通常配有官方的AT指令集只要刷写官方提供的固件即可但可能功能不全不过AT指令本身就是基于串口通信实现的一种指令其原理并不复杂如果由自己实现那么就可以自定义指令集。2以下是本例程中使用的AT指令集读者可自行设计也可使用官方提供的固件需要注意的是调试时串口监视器发送消息必须将结束符设置为“换行 和 回车 两者都是”传输指令时无需单独输入换行和回车。①Wi-Fi连接指令AT指令功能ESP8266执行动作ATCWJAP\ssid\,\password\\r\n例ATCWJAPZevalin_Computer,00114514\r\n连接Wi-Fi网络ssid欲连接Wi-Fi的名password欲连接Wi-Fi的密码调用ESP8266WiFi库的begin函数连接目标Wi-Fi网络②发送HTTP请求指令AT指令功能ESP8266执行动作ATHTTPCONNECT\host,port\\r\n与服务器建立连接host被连接网络服务器的网址/IP地址port被连接网络服务器的端口编号HTTP协议的熟知端口号为80调用WiFiClient库的connect函数与服务器建立连接ATHTTPREQ\message\\r\n向服务器发送请求报文message报文内容需要程序员按照通信协议的格式自行构建调用WiFiClient库的print函数向服务器发送请求报文ATHTTPSTOPNULL\r\n与服务器的连接断开调用WiFiClient库的stop函数与服务器断开连接③订阅MQTT主题AT指令功能ESP8266执行动作ATMQTTCONNECT\mqttsever,port\\r\n连接MQTT服务器mqttsever欲连接的MQTT服务器地址port欲连接的MQTT服务器端口号调用PubSubClient库的setServer函数设置目标MQTT服务器及端口号然后调用connect函数连接MQTT服务端ATMQTTSUB\topic\\r\n订阅主题topic欲订阅的主题调用PubSubClient库的subscribe函数向MQTT服务端订阅主题ATMQTTDISCONNECTNULL\r\n断开与MQTT服务器的连接mqttsever欲断开的MQTT服务器地址port欲断开的MQTT服务器端口号调用PubSubClient库的disconnect函数断开与MQTT服务器的连接3以下是本例程中使用的回复数据集读者可自行设计也可使用官方提供的固件。①Wi-Fi连接回复回复数据功能IPD,16:Wi-Fi Connected!\r\n回复Wi-Fi网络连接成功16数据长度IPD,24:Wi-Fi Connection Failed!\r\n回复Wi-Fi网络连接失败24数据长度②接收HTTP响应回复回复数据功能IPD,16:Sever Connected!\r\n回复服务器连接成功16数据长度IPD,24:Sever Connection Failed!\r\n回复服务器连接失败24数据长度IPD,数据长度:响应报文\r\n返回HTTP响应报文数据长度数据长度响应报文HTTP响应报文③接收MQTT报文回复数据功能IPD,15:MQTT Connected!\r\n回复MQTT服务器连接成功15数据长度IPD,23:MQTT Connection Failed!\r\n回复MQTT服务器连接失败23数据长度IPD,17:Subscribe Success!\r\n回复主题订阅成功17数据长度IPD,16:Subscribe Failed!\r\n回复主题订阅失败16数据长度IPD,数据长度:\主题\,\消息主体\\r\n返回MQTT报文数据长度数据长度主题MQTT主题消息主体MQTT消息2、自定义AT指令集实现1需要说明的是以下代码不一定考虑到所有可能发生的场景实际开发中可能会面临各种各样的异常情况由于篇幅有限部分异常情况仅做判断不做后处理方案可参考注释的发送内容STM32根据“ERROR”捕捉异常情况码做相应的后处理。2将以下代码文件编译下载到ESP8266中。#include ESP8266WiFi.h #include ESP8266WiFiMulti.h #include PubSubClient.h ESP8266WiFiMulti wifiMulti; WiFiClient httpClient, espClient; // 同一个节点与两台服务器交互因而有两个抽象客户端 PubSubClient mqttClient(espClient); bool isConnected false; //HTTP连接状态 bool mqttConnected false; //MQTT连接状态 String inputString ; int state 0; // 0: 正常; 1: 收到\r; 2: 收到\r\n 完成 void setup(){ Serial.begin(9600); Serial.println(); } void loop(){ while (Serial.available()) { char c (char)Serial.read(); inputString c; // 状态机检测 \r\n根据AT指令结束标志获取AT指令 if (c \r state 0) { state 1; //Serial.println(state - 1 (got \\r)); //调试使用 } else if (c \n state 1) { state 2; //Serial.println(state - 2 (got \\n)); //调试使用 } else { // 如果出现不匹配的字符重置状态但保留已读数据 state 0; //Serial.print(Resetting state due to char: ); Serial.println(c); //调试使用 } // 当完成序列时处理AT指令 if (state 2) { // 去掉末尾的 \r\n if (inputString.length() 2) { inputString.remove(inputString.length() - 2); } AT_work(inputString); // 根据AT指令执行动作 // 清空准备下一条指令接收 inputString ; state 0; } } if(mqttConnected true) { mqttClient.loop(); //如果连接了MQTT服务器就保持心跳 } } // 连接MQTT服务端的具体函数 bool connectToMqtt(String host, int port) { // 1. 设置MQTT服务器地址和端口 mqttClient.setServer(host.c_str(), port); // 2. 生成一个唯一的客户端ID String clientId ESP8266Client- String(random(0xffff), HEX); // 3. 尝试连接使用最基本的连接方式无用户名密码 if (mqttClient.connect(clientId.c_str())) { mqttConnected true; // 连接成功后可以在这里设置回调函数用于处理收到的消息 mqttClient.setCallback(callback); return true; } else { mqttConnected false; return false; } } void callback(char* topic, byte* payload, unsigned int length) { // 将消息主体转换为 String假设 payload 是文本 String message ; for (unsigned int i 0; i length; i) { message (char)payload[i]; } // 构造要输出的主体部分topic,message // 如果 topic 或 message 中包含双引号需要转义为 \ String dataPart \ String(topic) \,\ message \; int dataLen dataPart.length(); // 数据部分的字节长度 // 构造完整的 AT 指令输出行 String output IPD, String(dataLen) : dataPart \r\n; // 通过串口发送 Serial.print(output); } void AT_CWJAP(String inputString) { // 提取 ATCWJAP 之后的部分包括双引号 int pos inputString.indexOf(ATCWJAP); String params inputString.substring(pos 9); // 查找第一个双引号的位置 int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing SSID quote\r\n); return; } int secondQuote params.indexOf(, firstQuote 1); if (secondQuote -1) { //Serial.print(ERROR: missing closing SSID quote\r\n); return; } String ssid params.substring(firstQuote 1, secondQuote); // 查找第二个双引号后的逗号 int comma params.indexOf(,, secondQuote); if (comma -1) { //Serial.print(ERROR: missing comma\r\n); return; } // 查找密码部分的起始双引号 int pwdFirstQuote params.indexOf(, comma 1); if (pwdFirstQuote -1) { //Serial.print(ERROR: missing password quote\r\n); return; } int pwdSecondQuote params.indexOf(, pwdFirstQuote 1); if (pwdSecondQuote -1) { //Serial.print(ERROR: missing closing password quote\r\n); return; } String password params.substring(pwdFirstQuote 1, pwdSecondQuote); /* 打印调试信息 */ //Serial.print(SSID: ); Serial.println(ssid); //Serial.print(PWD: ); Serial.println(password); // 传递String对象如果addAP支持 const String若不支持可转换为字符数组并复制 wifiMulti.addAP(ssid.c_str(), password.c_str()); int count 0; while (wifiMulti.run() ! WL_CONNECTED count 15) { delay(1000); count; } if (wifiMulti.run() WL_CONNECTED) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(16); Serial.print(:); Serial.print(Wi-Fi Connected!\r\n); // 回复Wi-Fi连接成功 Serial.print(\r\n); } else { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(24); Serial.print(:); Serial.print(Wi-Fi Connection Failed!\r\n); // 回复Wi-Fi连接失败 Serial.print(\r\n); } } void AT_HTTPCONNECT(String inputString) { // 提取 ATHTTPCONNECT 之后的部分 int pos inputString.indexOf(ATHTTPCONNECT); if (pos -1) return; String params inputString.substring(pos 16); // ATHTTPCONNECT 长度为16 // 查找第一个双引号提取 host int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing host quote\r\n); return; } int secondQuote params.indexOf(, firstQuote 1); if (secondQuote -1) { //Serial.print(ERROR: missing closing host quote\r\n); return; } String host params.substring(firstQuote 1, secondQuote); // 查找逗号位置然后提取 port可能在双引号外 int comma params.indexOf(,, secondQuote); if (comma -1) { //Serial.print(ERROR: missing comma\r\n); return; } // 提取逗号后面的部分去除可能的空白和引号 String portStr params.substring(comma 1); portStr.trim(); // 如果端口号也被引号包围某些格式去除引号 if (portStr.startsWith(\) portStr.endsWith(\)) { portStr portStr.substring(1, portStr.length() - 1); } int port portStr.toInt(); if (port 0) { //Serial.print(ERROR: invalid port number\r\n); return; } /* 打印调试信息 */ //Serial.print(Connecting to ); //Serial.print(host); //Serial.print(:); //Serial.println(port); // 尝试建立 TCP 连接 if (httpClient.connect(host.c_str(), port)) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(16); Serial.print(:); Serial.print(Sever Connected!\r\n); // 回复服务器连接成功 Serial.print(\r\n); isConnected true; // 置位连接状态 } else { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(24); Serial.print(:); Serial.print(Sever Connection Failed!\r\n); // 回复服务器连接失败 Serial.print(\r\n); httpClient.stop(); isConnected false; // 置位连接状态 } } void AT_HTTPREQ(String inputString) { // 检查是否已建立 TCP 连接 if (!isConnected || !httpClient.connected()) { //Serial.print(ERROR: no TCP connection\r\n); return; } // 提取 ATHTTPREQ 之后的部分 int pos inputString.indexOf(ATHTTPREQ); if (pos -1) return; String params inputString.substring(pos 10); // ATHTTPREQ 长度 10 // 查找双引号包裹的 message int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing request message\r\n); return; } int lastQuote params.lastIndexOf(); if (lastQuote firstQuote) { //Serial.print(ERROR: invalid message format\r\n); return; } String request params.substring(firstQuote 1, lastQuote); // 直接发送请求 //Serial.print(Sending HTTP request...\r\n); size_t sent httpClient.write((const uint8_t*)request.c_str(), request.length()); if (sent ! request.length()) { //Serial.print(ERROR: send incomplete\r\n); return; } // 等待并读取响应设置超时时间5秒 unsigned long timeout millis() 5000; String response ; while (millis() timeout) { if (httpClient.available()) { // 读取一行或全部数据这里简单读取所有可用数据 while (httpClient.available()) { response (char)httpClient.read(); } break; } delay(10); } if (response.length() 0) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(response.length()); Serial.print(:); Serial.print(response); Serial.print(\r\n); } else { //Serial.print(ERROR: no response from server\r\n); } // 是否保持连接由请求头决定Connection: close 则服务器会关闭 // 若服务器关闭了连接需要更新 isConnected 状态 if (!httpClient.connected()) { httpClient.stop(); isConnected false; //Serial.print(Server closed connection\r\n); } } void AT_HTTPSTOP(String inputString) { // 1. 解析指令 if (!inputString.startsWith(ATHTTPSTOP)) { //Serial.print(ERROR: invalid command\r\n); return; } // 2. 检查连接状态 if (!isConnected !httpClient.connected()) { //Serial.print(ERROR: HTTP not connected\r\n); return; } // 3. 关闭连接 httpClient.stop(); // 调用 stop() 关闭连接 isConnected false; // 更新连接状态标志 // 4. 返回响应 //Serial.print(OK\r\n); } void AT_MQTTCONNECT(String inputString) { // 1. 检查指令前缀 if (!inputString.startsWith(ATMQTTCONNECT)) { //Serial.print(ERROR: invalid command\r\n); return; } // 2. 提取参数部分 (ATMQTTCONNECT 的长度为 15) String params inputString.substring(15); // 3. 查找并提取 host (服务器地址) int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing host\r\n); return; } int secondQuote params.indexOf(, firstQuote 1); if (secondQuote -1) { //Serial.print(ERROR: missing closing host quote\r\n); return; } String host params.substring(firstQuote 1, secondQuote); // 4. 提取 port (端口号) int comma params.indexOf(,, secondQuote); if (comma -1) { //Serial.print(ERROR: missing comma\r\n); return; } String portStr params.substring(comma 1); portStr.trim(); int port portStr.toInt(); if (port 0) { //Serial.print(ERROR: invalid port number\r\n); return; } /* 打印解析结果方便调试 */ //Serial.print(Connecting to MQTT server: ); //Serial.print(host); /Serial.print(:); Serial.println(port); // 进行实际的 MQTT 连接 if (connectToMqtt(host, port)) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(15); Serial.print(:); Serial.print(MQTT Connected!); Serial.print(\r\n); } else { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(23); Serial.print(:); Serial.print(MQTT Connection Failed!); Serial.print(\r\n); } } void AT_MQTTSUB(String inputString) { // 1. 检查指令前缀 if (!inputString.startsWith(ATMQTTSUB)) { //Serial.print(ERROR: invalid command\r\n); return; } // 2. 检查 MQTT 是否已连接 if (!mqttConnected || !mqttClient.connected()) { //Serial.print(ERROR: MQTT not connected\r\n); return; } // 3. 提取参数部分 (ATMQTTSUB 的长度为 10) String params inputString.substring(10); // 4. 查找双引号提取主题 int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing topic quote\r\n); return; } int secondQuote params.indexOf(, firstQuote 1); if (secondQuote -1) { //Serial.print(ERROR: missing closing topic quote\r\n); return; } String topic params.substring(firstQuote 1, secondQuote); // 5. 检查主题是否为空 if (topic.length() 0) { //Serial.print(ERROR: empty topic\r\n); return; } // 6. 执行订阅 if (mqttClient.subscribe(topic.c_str(), 1)) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(17); Serial.print(:); Serial.print(Subscribe Success!); Serial.print(\r\n); } else { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(16); Serial.print(:); Serial.print(Subscribe Failed!); Serial.print(\r\n); } } void AT_MQTTDISCONNECT(String inputString) { // 检查指令前缀忽略后面的任何内容 if (!inputString.startsWith(ATMQTTDISCONNECT)) { //Serial.print(ERROR: invalid command\r\n); return; } // 检查当前 MQTT 是否已连接 if (!mqttConnected !mqttClient.connected()) { //Serial.print(ERROR: MQTT not connected\r\n); return; } // 断开MQTT连接 mqttClient.disconnect(); // 强制关闭底层TCP连接disconnect通常会自动关闭但为保险可调用 espClient.stop(); // 更新连接状态标志 mqttConnected false; // 返回成功响应 //Serial.print(OK\r\n); } void AT_work(String inputString){ // 连接Wi-Fi网络的AT指令 int pos inputString.indexOf(ATCWJAP); if(pos ! -1) // 判断是否为该AT指令 { AT_CWJAP(inputString); return; } // 与服务器建立连接的AT指令 pos inputString.indexOf(ATHTTPCONNECT); if(pos ! -1) // 判断是否为该AT指令 { AT_HTTPCONNECT(inputString); return; } // 向服务器发送请求报文的AT指令 pos inputString.indexOf(ATHTTPREQ); if(pos ! -1) // 判断是否为该AT指令 { AT_HTTPREQ(inputString); return; } // 与服务器的连接断开的AT指令 pos inputString.indexOf(ATHTTPSTOP); if(pos ! -1) // 判断是否为该AT指令 { AT_HTTPSTOP(inputString); return; } // 连接MQTT服务器的AT指令 pos inputString.indexOf(ATMQTTCONNECT); if(pos ! -1) // 判断是否为该AT指令 { AT_MQTTCONNECT(inputString); return; } // 订阅主题的AT指令 pos inputString.indexOf(ATMQTTSUB); if(pos ! -1) // 判断是否为该AT指令 { AT_MQTTSUB(inputString); return; } // 断开与MQTT服务器的连接 pos inputString.indexOf(ATMQTTDISCONNECT); if(pos ! -1) // 判断是否为该AT指令 { AT_MQTTDISCONNECT(inputString); return; } }四、开发用户端STM321、准备工作1拷贝一份STM32教程中按键控制LED的工程文件夹并更名为“A区支持OTA实验工程”。