基于MQTT的智能家居系统的学习
1.移植MQTT代码
将已经编写好的MQTT代码移植到自己程序上,首先出现的问题是编译后找不到头文件:

遇到这种情况,可以手动将该头文件的路径添加到项目路径 (Include Paths) 中:


2. 编写ESP8266驱动
2.1 程序分层

* platform_net_socket.c:执行什么AT命令才能连接、收、发网络数据
* ESP8266:提供AT命令函数
* UART驱动抽象层:执行UART的写、读(从buffer读)
* UART硬件驱动(最底层):发送UART数据,接收UART数据存入buffer
2.1.1 UART硬件驱动
这部分对串口进行驱动,通过串口将数据读进某个缓冲区buffer中,这里通过UART3将单片机与ESP8266连接起来,通过UART1将单片机与电脑连接起来。
2.1.2 AT发送命令
参考代码路径:
E:\桌面\韦东山\01_裸机_RTOS项目\02_毕业设计级别项目\01_使用MQTT实现智能家居\source\01_100ASK_STM32F103_Pro\供参考的RTT代码
ESP8266模块接收和发送数据的格式:
接收:
+IPD格式自动报告
工作原理:
模块自动计算:ESP8266自动计算接收到的数据长度
格式:
+IPD,<length>:<data>
接收示例:
+IPD,15:Hello, ESP8266! // 收到15字节数据
+IPD,28:HTTP/1.1 200 OK\r\nContent-Type: // 收到28字节数据也就是说,当检测到“IPD,<length>:”时,表示后面的内容即为要接收的数据内容,这也是AT命令判断是否有数据要接收的判断方法。
发送:
数据发送状态标识
对于数据发送,ESP8266有专门的响应:
SEND OK:数据发送到网络成功SEND FAIL:数据发送失败ERROR:命令格式错误
正确的数据发送成功判断方法
检查SEND OK
at_response_t resp = at_create_resp(128, 0, 5000);// 发送数据准备命令
at_obj_exec_cmd(client, resp, "AT+CIPSEND=%d", data_length);// 等待">"提示符,然后发送实际数据
rt_thread_mdelay(100);
at_client_obj_send(client, data, data_length);// 检查响应
if (strstr(resp->buf, "SEND OK") != NULL) {LOG_I("Data sent successfully to network");
} else if (strstr(resp->buf, "SEND FAIL") != NULL) {LOG_E("Data send failed");
} else if (strstr(resp->buf, "ERROR") != NULL) {LOG_E("Command error");
} else {LOG_W("Unexpected response: %s", resp->buf);
}at_delete_resp(resp);实用的判断函数
typedef enum {SEND_STATUS_SUCCESS, // 发送成功SEND_STATUS_FAIL, // 发送失败SEND_STATUS_ERROR, // 命令错误SEND_STATUS_TIMEOUT, // 超时SEND_STATUS_UNKNOWN // 未知状态
} send_status_t;send_status_t check_send_status(at_response_t resp)
{if (resp == NULL) return SEND_STATUS_UNKNOWN;if (strstr(resp->buf, "SEND OK") != NULL) {return SEND_STATUS_SUCCESS;} else if (strstr(resp->buf, "SEND FAIL") != NULL) {return SEND_STATUS_FAIL;} else if (strstr(resp->buf, "ERROR") != NULL) {return SEND_STATUS_ERROR;} else if (strstr(resp->buf, "CLOSED") != NULL) {return SEND_STATUS_FAIL; // 连接关闭导致失败} else {return SEND_STATUS_UNKNOWN;}
}核心数据结构
1. AT响应结构 (at_response_t)
struct at_response {char *buf; // 响应数据缓冲区rt_size_t buf_size; // 缓冲区大小rt_size_t buf_len; // 当前数据长度rt_size_t line_num; // 期望的行数rt_size_t line_counts; // 实际接收的行数rt_int32_t timeout; // 超时时间
};2. AT客户端结构 (at_client_t)
struct at_client {rt_device_t device; // 底层设备(如串口)rt_mutex_t lock; // 互斥锁rt_sem_t rx_notice; // 接收信号量rt_sem_t resp_notice; // 响应信号量at_response_t resp; // 当前响应对象// ... 其他字段
};核心功能模块
1. 响应管理
at_create_resp(): 创建响应对象at_delete_resp(): 删除响应对象at_resp_set_info(): 设置响应信息at_resp_get_line(): 获取指定行响应at_resp_get_line_by_kw(): 通过关键字获取响应行
2. 命令执行
at_obj_exec_cmd(): 执行AT命令并等待响应支持可变参数格式化命令
支持超时控制和响应状态检查
3. 数据收发
at_client_obj_send(): 发送数据at_client_obj_recv(): 接收数据at_client_getchar(): 获取单个字符
4. URC处理 (Unsolicited Result Code)
at_obj_set_urc_table(): 设置URC处理表get_urc_obj(): 匹配URC模式支持多组URC表,动态扩展
5. 解析器线程
client_parser(): 主解析循环at_recv_readline(): 读取一行数据自动区分响应数据和URC数据
工作流程
初始化:
at_client_init()创建客户端,启动解析线程发送命令:
at_obj_exec_cmd()发送AT命令等待响应: 解析线程收集响应数据
处理完成: 收到结束标志后通知发送线程
URC处理: 异步处理设备主动上报的数据
关键技术特点
1. 线程安全
使用互斥锁保护共享资源
信号量用于线程间同步
2. 内存管理
动态内存分配和释放
缓冲区大小可配置
3. 超时控制
可配置的命令响应超时
连接等待超时
4. 灵活的数据解析
支持按行号获取数据
支持按关键字搜索
支持sscanf格式解析
5. 多客户端支持
支持多个AT设备同时工作
客户端对象表管理
2.1.3 自己编写AT发送命令
自己编写AT发送命令,需要写3个过程:
1.数据发送过程:A线程
2.数据解析过程:解析线程
3.UART3串口的中断设置:UART3_IRQHandle
具体实现:
在UART3_IRQHandle中,当串口接收到数据时,每接收到一个数据字符就会将其存放进循环缓冲区Buffer中,然后产生中断唤醒解析线程,发出任务通知。而在解析线程中,时刻等待任务通知,当UART3_IRQHandle产生中断,发出任务通知后,解析线程就会读取环形缓冲区Buffer中的数据,但是并不是每读取一个字符就分析一次数据,而是要读取字符“\r”和“\n”之后,表示读取到完整的数据之后,再分析数据。


发送AT命令的线程(任务):

发送AT命令:

等待结果:

等待解析线程:

解析线程会一直读取串口数据:


串口3会一直读取环形缓冲区的内容


但是如果环形缓冲区内没有内容,就会陷入休眠状态:

当有数据输入串口时,串口中断(stm32_uart3.c)会释放信号量,唤醒解析线程:


3. 网络分层
3.1网络连接函数
host:连接某个服务器
port:连接某个端口
proto:选择使用TCP协议或者UDP协议传输数据

3.2 断开网络连接
AT+CIPCLOSE为ESP8266模块断开网络连接的命令

3.3 网络发送函数
sprintf() 是一个非常重要且常用的C语言标准库函数。
核心定义
sprintf() 是一个用于将格式化数据写入字符串的函数

4.部分代码分析

在头文件中使用宏声明函数:

在.c文件定义函数:

所以上面代码实现的含义是将结构Client中成员mqtt_port="1883":


左边发布主题,右边订阅主题topic,下面是MQTT服务器

从软件上发送消息

5. 调试过程中遇到的问题
1.内存分配失败
使用pvPortMalloc()分配内存时失败,无法进入后续程序:

解决方法:将堆设置的大一些。
![]()
2.超时时间timeout设置太短
使用宏定义设计超时时间为1秒,但是模块连接WiFi时需要一定的时间,当timeout=1秒时,模块还没有连接上WiFi就已经超时,所以返回结果显示WiFi连接失败。
解决方法:将超时时间设置的大一点,可以将timeout=5秒。
6. 该项目实现的流程
将单片机如STM32F103系列通过串口与WiFi模块ESP8266相连,单片机可以通过AT命令向ESP8266发送数据,同时单片机也可以通过编写的解析线程部分,分析由ESP8266发送的数据内容。ESP8266通过路由器与MQTT服务器(称为MQTT Broker)相连接,MQTT Broker可以部署在云服务器如腾讯云、阿里云上,也可以部署在本地电脑上。然后在电脑上或手机上可以安装MQTT软件向MQTT Broke订阅和发送主题,这样就可以通过MQTT Broke将MQTT软件上订阅或发布主题内容通过ESP8266传送到单片机上,从而控制单片机进行下一步操作,采集、控制、逻辑等进行点灯,读取温湿度传感器(DHT11)的数据等操作。
