使用stm32+esp8266-01s与电脑进行mqtt交互
注意:
本文以简单易理解易实现为主,仅实现最基本的交互通信功能,性能和稳定性暂无考虑。
需要材料:
硬件:stm32及下载线、esp8266-01s(wifi模块)
软件:emqx、keil
可选:wireshark,python
开始:
- 配置stm32工程
首先,我们需要一个stm32的基础工程,为了调试需要,我们需要两个usart串口分别与电脑和wifi模块进行通信。
打开stm32cube 需要配置的有RCC、SYS、USART、时钟、project manager几部分。
配置好usart1和2之后,引脚如图,将wifi模块按照tx-rx;rx-tx;EN、3v3接3.3v;GND接GND;其余悬空接入单片机。
至此,stm32基础工程和硬件配置完毕。
2、配置emqx
- https://docs.emqx.net/broker/latest/cn/
在官网下载windows版本的emqx,下载后解压,在解压的bin目录下打开cmd,输入
emqx console即可。
至此emqx配置完毕,可以在 http://127.0.0.1:18083通过控制台查看mqtt。
控制台账号为admin 密码为public
3、初始化wifi模块-AT指令
至此,基本条件已经配置完毕,可以开始代码部分的编写了。
详细的AT指令可以在乐鑫官网查看AT 命令集 — ESP-AT 用户指南 文档 (espressif.com)
但我相信你绝对不愿意去看也不一定能理解官网的AT指令集,这里我按照必要的配置流程梳理一下我们会用到的AT指令。
AT指令 | 作用 |
+++ | 关闭透传 |
AT+RESTORE\r\n | 恢复出厂设置 |
AT\r\n | AT查询 |
ATE0\r\n | 关闭回显 |
AT+CWMODE_CUR=1\r\n | 设置透传模式 |
AT+CWJAP_CUR="****","*********"\r\n | 连接wifi,后跟wifi的用户名和密码 |
AT+CIPSTART="TCP","xxx.xxx.x.x",1883\r\n | 配置连接的设备和端口号,注意这里的端口号是数字而不是字符串 |
AT+CIPMODE=1\r\n | 设置为AP模式 |
AT+CIPSEND\r\n | 开始发送数据 |
这些做完之后,wifi模块就配置好了,可以开始发送数据了。但是我们这个时候还不知道如何去发送数据,这个时候我们可以去查看mqtt的协议然后自己组包,但是学习mqtt协议也是很大的工作量;我们也可以使用mqtt的库,通过调用接口的方式来实现我们想要的功能,但我找不到库(xs);还有最后一种方法,我们可以通过空中数据来了解mqtt交互的时候传递了哪些数据。
我们可以使用python来实现一个简单的mqtt通信,这个也可以用来测试mqtt服务器是否配置完成。代码如下:
from paho.mqtt import client as mqtt_client
import random,timeurl = "127.0.0.1"
port = 1883
topic = "my/test"client_id = f'python-mqtt-{random.randint(0,100)}'def mqtt_connect():def on_connect(client,userdata,flags,rc):if rc == 0:print("connect success")else:print("connect failed ",rc)client = mqtt_client.Client(client_id)client.on_connect = on_connectclient.connect(url,port)return clientdef publish(client):msg_count = 0while True:time.sleep(1)msg = f"message:{msg_count}"result = client.publish(topic,msg)if result[0] == 0:print("send msg{",msg,"}success")else:print("send msg{",msg,"}failed")msg_count+=1def run():client = mqtt_connect()client.loop_start()publish(client)if __name__ == "__main__":run()
from paho.mqtt import client as mqtt_client
import random,timeurl = "127.0.0.1"
port = 1883
topic = "my_test"client_id = f'python-mqtt-{random.randint(0,100)}'def commect_mqtt():def on_connect(client,data,flags,rc):if rc == 0:print("connect success")else:print("connect failed ",rc)client = mqtt_client.Client(client_id)client.on_connect = on_connectclient.connect(url,port)return clientdef subscribe(client):def on_message(client,userdata,msg):print("receive",msg.payload.decode(),"from{",msg.topic,"}topic")client.subscribe(topic)client.on_message = on_messagereturndef run():client = commect_mqtt()subscribe(client)client.loop_forever()if __name__ == "__main__":run()
上述分别为发布和订阅mqtt服务的python例程,那么我们要怎样模拟一次数据交互呢?我们可以先使用服务器中的虚拟客户端。
然后按照上图方式创建一个虚拟客户端,
先运行第二个python历程,向my_test发送报文,报文内容为123。这样就可以在python显示框中看到我们发送过来的123了。
再运行第一个,也就是发布的python例程,可以在服务器端看到发布的报文。
已经有了通信过程,接下来就需要抓包了。下载并打开wireshark,
在如图所示过滤器下进行抓包,然后重新运行我们的第一个python例程,并在服务器端点击发送,之后就可以点击红色按键暂停抓包了,我们来看看我们抓到的数据:
connect command:连接请求
cnnect ack:连接回应
这张就是连接请求的报文。
再来运行第二个python例程,同样方式进行抓包:
有一个connect command,这个就是连接请求,服务器会返回一个connect ack。
向后翻,有一个public message,这个就是发布消息。
点选进去后,点击红框这一行,下面对应的蓝色框内即为发布的数据,从上到下以此为连接、订阅、发布消息。
有了数据包之后,我们就可以尝试通过串口连接wifi模块直接进行连接发布尝试了。
4、mqtt包简介迈向物联网第一步——MQTT理论知识详解_哔哩哔哩_bilibili
虽然但是,我们还是先来大概认识一下mqtt数据包吧,通过wireshark抓的数据包我们可能看的出来这些报文的含义,再次稍微总结一下。
连接:10 1a 00 04 4d 51 54 54 04 02 00 3c 00 0e 70 79 74 68 6f 6e 2d 6d 71 74 74 2d 38 35
共28位,这个数据长度并非固定,以这29位为例进行举例,其中标红的均可不修改。
位数 | 报文 | 含义 | ||
1 | 10 | 连接请求 | 固定报头 | |
2 | 1a | 十进制为26 | 后续字节长度 | 固定报头 |
3-8 | 00 04 4d 51 54 54 | ASCII为00 04 MQTT | 协议名 | 可变报头 |
9 | 04 | 协议级别 | 可变报头 | |
10 | 02 | 连接标志 | 可变报头 | |
11-12 | 00 3c | 十进制是60 | 保活时间 | 可变报头 |
13-14 | 00 0e | 十进制为14 | 设备名长度 | 负载 |
15-28 | 70 79 74 68 6f 6e 2d 6d 71 74 74 2d 38 35 |
ASCII为 python-mqtt-85 |
设备名 | 负载 |
发布:30 0c 00 07 6d 79 5f 74 65 73 74 31 32 33
位数 | 报文 | 含义 | ||
1 | 30 | 发布请求 | 固定报头 | |
2 | 0c | 十进制为12 | 后续字节长度 | 固定报头 |
3-4 | 00 07 | 十进制为7 | 主题的长度 | 可变报头 |
5-11 | 6d 79 5f 74 65 73 74 | ASCII为my/test | 主题名 | 可变报头 |
12-14 | 31 32 33 | ASCII为123 | 发送的内容 | 负载 |
订阅:82 0c 00 01 00 07 6d 79 5f 74 65 73 74 00
位数 | 报文 | 含义 | ||
1 | 82 | 订阅请求 | 固定报头 | |
2 | 0c | 十进制为12 | 后续字节长度 | 固定报头 |
3-4 | 00 01 | msgid | 可变报头 | |
5-6 | 00 07 | 十进制为7 | 主题长度 | 可变报头 |
7-13 | 6d 79 5f 74 65 73 74 | ASCII为my/test | 主题名 | 负载 |
14 | 00 | 质量要求Qos | 负载 |
心跳:c0 00 emmm,这个就先记住就好。
5、了解完报文之后,终于可以开始代码的编写了。
配置一些宏定义,上半部分为AT指令,下半部分为接收消息的buff。
#define START "+++" //退出透传模式
#define RESTORE "AT+RESTORE\r\n" //恢复出厂设置
#define AT "AT\r\n" //
#define ATE0 "ATE0\r\n" //关闭回显
#define CWMODE "AT+CWMODE_CUR=1\r\n" //STA模式
#define CWJAP "AT+CWJAP_CUR=\"****\",\"*********\"\r\n" //连接WIFI
#define CIPSTART "AT+CIPSTART=\"TCP\",\"xxx.xxx.xx.xx\",1883\r\n" //连接服务器
#define CIPMODE "AT+CIPMODE=1\r\n" //开启透传模式
#define CIPSEND "AT+CIPSEND\r\n" //开启发送模式#define DATA_LEN (50)
#define LOST_LEN (200)
#define LOG_LEN (50)
#define TIMEOUT (1000)
我们先定义一个log函数,用来在串口一打印调试信息,我们将串口一连接在电脑上,可以在电脑上观察log来调试代码,这一段记得加头文件 #include "stdarg.h"
void log_printf(const char *format, ...)
{char buff[LOG_LEN] = {0};va_list pArgs ;va_start (pArgs, format) ;vsnprintf (buff, LOG_LEN, format, pArgs);va_end (pArgs) ;HAL_UART_Transmit(&huart1, (uint8_t *)buff, strlen(buff), HAL_MAX_DELAY);return;
}
通过AT指令初始化wifi模块
bool with_ok(uint8_t *buff, uint16_t len)
{if (len == 0) {log_printf("with_ok input len error\r\n");}int i = 0;for(i = 0; i < len - 1; i++) {if((*(buff+i) == 'O') && ((*(buff+i+1)) == 'K')) {return true;}}return false;
}void esp_init(void)
{log_printf("esp_init begin\r\n");uint8_t recv[DATA_LEN] = {0};uint8_t lost[LOST_LEN] = {0};uint8_t retry = 0;for(retry = 3; retry; retry--) {memset(recv, 0, DATA_LEN);HAL_UART_Transmit(&huart2, (uint8_t *)START, sizeof(START)-1, HAL_MAX_DELAY); //start -1 is \0 need to retryHAL_UART_Receive(&huart2, recv, DATA_LEN, TIMEOUT);if ((recv[0] != '+') || (recv[1] != '+') || (recv[2] != '+')) {if (retry) {continue;} else {log_printf("esp_init START error\r\n");return;}} else {break;}}log_printf("esp_init START success\r\n");memset(recv, 0, DATA_LEN);HAL_UART_Transmit(&huart2, (uint8_t *)RESTORE, sizeof(RESTORE)-1, HAL_MAX_DELAY); //RESTORE -1 is \0 need lost buffHAL_UART_Receive(&huart2, recv, DATA_LEN, TIMEOUT);if (!with_ok(recv, DATA_LEN)) {log_printf("esp_init RESTORE error\r\n");return;} else {log_printf("esp_init RESTORE success\r\n");}memset(recv, 0, DATA_LEN);while(HAL_UART_Receive(&huart2, lost, LOST_LEN, TIMEOUT) == HAL_OK);HAL_UART_Transmit(&huart2, (uint8_t *)AT, sizeof(AT)-1, HAL_MAX_DELAY); //AT -1 is \0 HAL_UART_Receive(&huart2, recv, DATA_LEN, TIMEOUT);if (!with_ok(recv, DATA_LEN)) {log_printf("esp_init AT error\r\n");return;} else {log_printf("esp_init AT success\r\n");}memset(recv, 0, DATA_LEN);HAL_UART_Transmit(&huart2, (uint8_t *)ATE0, sizeof(ATE0)-1, HAL_MAX_DELAY); //ATE0 -1 is \0 HAL_UART_Receive(&huart2, recv, DATA_LEN, TIMEOUT);if (!with_ok(recv, DATA_LEN)) {log_printf("esp_init ATE0 error\r\n");return;} else {log_printf("esp_init ATE0 success\r\n");}memset(recv, 0, DATA_LEN);HAL_UART_Transmit(&huart2, (uint8_t *)CWMODE, sizeof(CWMODE)-1, HAL_MAX_DELAY); //CWMODE -1 is \0 HAL_UART_Receive(&huart2, recv, DATA_LEN, TIMEOUT);if (!with_ok(recv, DATA_LEN)) {log_printf("esp_init CWMODE error\r\n");return;} else {log_printf("esp_init CWMODE success\r\n");}memset(recv, 0, DATA_LEN);HAL_UART_Transmit(&huart2, (uint8_t *)CWJAP, sizeof(CWJAP)-1, HAL_MAX_DELAY); //CWJAP -1 is \0 need more timeoutHAL_UART_Receive(&huart2, recv, DATA_LEN, 8 * TIMEOUT);if (!with_ok(recv, DATA_LEN)) {log_printf("esp_init CWJAP error\r\n");return;} else {log_printf("esp_init CWJAP success\r\n");}memset(recv, 0, DATA_LEN);HAL_UART_Transmit(&huart2, (uint8_t *)CIPSTART, sizeof(CIPSTART)-1, HAL_MAX_DELAY); //CIPSTART -1 is \0 HAL_UART_Receive(&huart2, recv, DATA_LEN, TIMEOUT);if (!with_ok(recv, DATA_LEN)) {log_printf("esp_init CIPSTART error\r\n");return;} else {log_printf("esp_init CIPSTART success\r\n");}memset(recv, 0, DATA_LEN);HAL_UART_Transmit(&huart2, (uint8_t *)CIPMODE, sizeof(CIPMODE)-1, HAL_MAX_DELAY); //CIPMODE -1 is \0 HAL_UART_Receive(&huart2, recv, DATA_LEN, TIMEOUT);if (!with_ok(recv, DATA_LEN)) {log_printf("esp_init CIPMODE error\r\n");return;} else {log_printf("esp_init CIPMODE success\r\n");}memset(recv, 0, DATA_LEN);HAL_UART_Transmit(&huart2, (uint8_t *)CIPSEND, sizeof(CIPSEND)-1, HAL_MAX_DELAY); //CIPSEND -1 is \0 HAL_UART_Receive(&huart2, recv, DATA_LEN, TIMEOUT);if (!with_ok(recv, DATA_LEN)) {log_printf("esp_init CIPSEND error\r\n");return;} else {log_printf("esp_init CIPSEND start\r\n");}return;
}
多次尝试发送+++,等待串口准备就绪,发送恢复出厂设置, 在这之后需要丢弃多余的串口信息,发送AT、ATE0、CWMODE、CWJAP ,此时会比较慢,继续发送CIPSTART 、CIPMODE、CIPSEND,完成wifi模块初始化。
接下来连接mqtt、发送心跳、发送消息。
void mqtt_init(void)
{uint8_t load[] = {0x10, 0x1a, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, 0x04, 0x02, 0x00, 0x3c, 0x00, 0x0e, 0x70, 0x79, 0x74, 0x68, 0x6f, 0x6e, 0x2d, 0x6d, 0x71, 0x74, 0x74, 0x2d, 0x31, 0x39};uint8_t recv[4] = {0};int8_t retry = 5;while(retry--) {HAL_UART_Transmit(&huart2, load, sizeof(load), HAL_MAX_DELAY);HAL_UART_Receive(&huart2, recv, 4, TIMEOUT);if((recv[0] == 0x20) && (recv[1] == 0x02) && (recv[2] == 0x00) && (recv[3] == 0x00) ) {break;}}if(retry > 0) {log_printf("mqtt load success\r\n");mqtt_send_alive();} else {log_printf("mqtt load failed!\r\n");}return;
}
void mqtt_send_alive(void)
{uint8_t alive[] = {0xc0, 0x00};uint8_t recv[2] = {0};int8_t retry = 10;while(retry--) {HAL_UART_Transmit(&huart2, alive, sizeof(alive), HAL_MAX_DELAY);HAL_UART_Receive(&huart2, recv, 4, TIMEOUT);if((recv[0] == 0xD0) && (recv[1] == 0x00)) {break;}}if (retry < 0) {log_printf("mqtt disconnected\r\n");}
}void mqtt_send_data(uint8_t *key, uint8_t *data, uint16_t len)
{uint8_t send[] = {0x30, 0x0e, 0x00, 0x09, 0x6d, 0x73, 0x7a, 0x79, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x31, 0x32, 0x33};HAL_UART_Transmit(&huart2, send, sizeof(send), HAL_MAX_DELAY);
}
这里发送数据我是直接写死了,仅作调试使用,后续根据需要自由组包。
最后,我们在初始化的时候初始化wifi模块和mqtt,在循环里面发送心跳和数据进行测试,在服务器上观察是否接收到消息。
esp_init();mqtt_init();
mqtt_send_alive();mqtt_send_data(NULL,NULL,0);
至此,就算搞定了。
最后,本文仅为自己学习理解过程的呈现,若有疑误欢迎批评指正,若有建议欢迎互相交流。
使用stm32+esp8266-01s与电脑进行mqtt交互相关推荐
- stm32 esp8266 ota升级-自建mqtt和文件服务器全量升级
stm32 esp8266 ota系列文章: stm32 esp8266 ota-快速搭建web服务器之docker安装openresty stm32 esp8266 ota升级-tcp模拟http ...
- stm32 esp8266配网-smartConfig和BT串口方式配网
stm32 esp8266 ota系列文章: stm32 esp8266 ota-快速搭建web服务器之docker安装openresty stm32 esp8266 ota升级-tcp模拟http ...
- 阿里云MQTT + STM32 + MQTT + ESP8266 01S WIFI 实现远程继电器控制开关和采集温湿度 登录阿里云网站,进入物联网云平台
单片机型号: STM32F103C8T6 WIFI型号: ESP8266 01S WIFI 运行协议: TCP STM32运行MQTT协议 登录阿里云网站,进入物联网云平台 进入阿里云官网并登录账号后 ...
- STM32+ESP8266+MQTT微信小程序SoftAP一键配网接入腾讯物联网平台
STM32+ESP8266+MQTT微信小程序SoftAP一键配网接入腾讯物联网平台 Wi-Fi 配网,指由外部向 Wi-Fi 设备提供 SSID 和密码(PSW),让 Wi-Fi 设备可以连接指 ...
- 【开源】手机APP通过IoT点亮LED灯(STM32+ESP8266+阿里云+MQTT+Android)
(STM32+ESP8266+阿里云+MQTT+Android) 本项目利用Android Studio编写了连接阿里云和MQTT的APP,并在UI界面简单设计了标题,连接按钮,点灯按钮,灭灯按钮.如 ...
- STM32+ESP8266连接电脑Qt网络上位机——QT篇
本文简单介绍下手写网络调试器并连接ESP8266模块 上篇: STM32+ESP8266连接电脑Qt网络上位机--准备工作 目录 一.部分Qt代码及实现过程 二.实现过程--使用ESP8266连接上 ...
- STM32+ESP8266+MQTT协议连接腾讯物联网开发平台
一.环境介绍 单片机采用:STM32F103C8T6 上网方式:采用ESP8266,也可以使用其他设备代替,只要支持TCP协议即可.比如:GSM模块.有线网卡等. 开发软件:keil5 物联网平台: ...
- STM32+ESP8266(ESP-12F)物联网温度计-移植paho MQTT协议连接阿里云
STM32+ESP8266(ESP-12F)物联网温度计-移植paho MQTT协议连接阿里云 目录 STM32+ESP8266(ESP-12F)物联网温度计-移植paho MQTT协议连接阿里云 一 ...
- STM32+ESP8266+MQTT协议连接阿里云物联网平台
一.环境介绍 单片机采用:STM32F103C8T6 上网方式:采用ESP8266,也可以使用其他设备代替,只要支持TCP协议即可.比如:GSM模块.有线网卡等. 开发软件:keil5 硬件连接功能: ...
最新文章
- 微服务网关Kong 1.0正式发布!提供100+项功能
- VS2010插件编写学习总结
- Py之pyecharts:python包之数据可视化包pyecharts简介、安装、使用方法之详细攻略
- 手机app常见bug积累
- C++中如何判断文件是否存在
- C++中的cin cout
- 【UAV】串级 PID 控制原理及应用
- 解决Ubuntu下载缓慢问题
- 阿里国际站出口通升级金品诚企详细流程
- OpenCV threshold函数详解
- 集成学习方法及思想总结
- keba驱动器_KEBA控制器说明书
- 自动化爬虫selenium基础教程
- 英语语法---四种句子类型的介绍
- C语言学习笔记—链表(四)链表的删除
- 安兔兔 android 4.4,适配智能电视 安兔兔评测V4.4.3发布
- 文献阅读-Clinical and Biological subtypes of B-cell lymphoma revealed by microenvironment signature
- 独领风骚,卡兹特投影仪大热香港电子展
- 掌机发展简史及未来趋势分析
- i.MX6ULL开发板【终结者】硬件资源说明
热门文章
- 前端框架vue3的node安装及项目构建的4种方法
- php 支付宝实名认证
- uni-app项目配置UrlSchemes在外部打开APP
- openssl 1.0.2k-fips 升级到 openssl-3.0.3
- 微信聊天自动解析html文本,微信小程序纯文本实现@功能
- 多时点DID实证流程笔记(Aggregate Effects from Public Works: Evidence from India)
- 转java通过身份证号码获取出生日期、性别、年龄
- 从零搭建Spring Boot脚手架:手写Mybatis通用Mapper4
- 从零搭建Spring Boot脚手架:增加通用的功能2
- Holt Winter时间序列模型