0 引言
冬天床边没有开关,睡觉懒得关灯,想通过小爱同学控制灯的开关,但是不想换开关。
所以 想用ESP32接入米家,控制一个舵机实现开关控制。
文章目录
- 0 引言
- 1 MQTT协议
- 2 ESP32 MQTT例程
- 2.1 ESP-MQTT 库
- 2.2.1 配置结构体 esp_mqtt_client_config_t
- 2.2.2 事件
- 2.2 例程调试
- 2.3 例程分析
- 3 连接巴法平台
- 3.1 配置巴法平台
- 3.2 修改例程代码
- 3.3 设置小爱同学
1 MQTT协议
Message Queuing Telemetry Transport,消息队列传输探测
ISO 标准下的一种基于发布-订阅模式的消息协议,基于 TCP/IP 协议簇,用于 IoT 即物联网上。
相对于HTTP的优点:
- 数据包开销小,易于传输。
- MQTT客户端容易实现。
发布-订阅模式pub/sub
传统的 客户端-服务器架构:服务器与客户端直接通信。
发布-订阅模式:发布者publisher 与 订阅者 subscribers 分离,不会直接通信,由第三方组件broker代理。
publisher 与 subscriber 在 空间、时间和同步 三个维度解耦。
MQTT协议数据包结构
巴法支持支持 MQTT3.1.1 协议,支持Qos0 Qos1,支持retian保留消息,所以我也只看了一下MQTT3.1.1 协议
MQTT3.1.1 协议文档
- Fixed header, present in all MQTT Control Packets
- Variable header, present in some MQTT Control Packets
- Payload, present in some MQTT Control Packets
broker过滤消息的选项
broker 使得 subscriber 只接收自己需要的消息。broker 可以基于以下选项过滤消息:
- 基于主题
- 基于内容
- 基于类型
服务质量级别
- QoS 0:最多传送一次
反正 receiver 有没有响应我都之发送一次。
- QoS 1:要实施至少一次传送
我要确保 receiver 至少收到了一次消息。
- QoS 2:正好一次传送
我要确保消息不丢失且不重复。
有了这些基础知识,就可以基于ESP32 的官方例程来实现我们想要的功能了。
2 ESP32 MQTT例程
2.1 ESP-MQTT 库
2.2.1 配置结构体 esp_mqtt_client_config_t
通过 esp_mqtt_client_config_t 结构体配置,配置结构体有以下子结构来配置客户端操作的不同方面。
- broker : 设置地址和安全验证。
可以通过 uri 字段或 hostname 、transport 和 port 的组合来配置。
uri组成:scheme://hostname:port/path,MQTT TCP例程uri为mqtt://mqtt.eclipseprojects.io,默认端口1883
- credentials : 用于身份验证的客户端凭证。
username:指向连接到broker的username的指针,也可以通过URI设置。
client_id:指向客户端id的指针,默认为ESP32_%CHIPID%,其中%CHIPID%是MAC地址的最后3个字节,采用十六进制格式
- session : MQTT会话方面的配置。
topic: 指向 LWT(Last Will and Testament) message topic,LWT就是客户死(断开)前用来通知别人的信息。
msg: 指向 LWT message
msg_len: LWT message 长度
qos: 指向 LWT message qos
retain: LWT message 保留标志
- network : 组网相关配置。
- task : 配置FreeRTOS任务。
- buffer : 输入和输出的缓冲区大小。
esp_mqtt_client_config_t 结构:
/** * MQTT client configuration structure */ typedef struct { mqtt_event_callback_t event_handle; /*!user_context`` */ int task_prio; /*!
2.2.2 事件
MQTT客户端可以发布以下事件:
MQTT_EVENT_BEFORE_CONNECT:客户端已初始化,即将开始连接到代理。
MQTT_EVENT_CONNECTED:客户端已成功建立到代理的连接。客户机现在已经准备好发送和接收数据了。
MQTT_EVENT_DISCONNECTED:客户端由于无法读取或写入数据而终止了连接,例如由于服务器不可用。
MQTT_EVENT_SUBSCRIBED:代理已经确认客户机的订阅请求。事件数据将包含订阅消息的消息ID。
MQTT_EVENT_UNSUBSCRIBED:代理已经确认客户端的取消订阅请求。事件数据将包含取消订阅消息的消息ID。
MQTT_EVENT_PUBLISHED:代理已经确认客户机的发布消息。这将仅针对服务质量级别1和2发布,因为级别0不使用确认。事件数据将包含发布消息的消息ID。
MQTT_EVENT_DATA:客户端已收到发布消息。事件数据包含:消息ID、消息发布到的主题的名称、接收到的数据及其长度。对于超过内部缓冲区的数据,将发布多个MQTT_EVENT_DATA,并更新来自事件数据的current_data_offset和total_data_len,以跟踪碎片消息。
MQTT_EVENT_ERROR:客户端遇到错误。事件数据中的Esp_mqtt_error_type_t from error_handle可用于进一步确定错误的类型。错误的类型将决定error_handle结构体的哪些部分被填充。
2.2 例程调试
例程路径:[安装路径]\Espressif\frameworks\esp-idf-v4.4\examples\protocols\mqtt\tcp
在vscode 使用 menuconfig 配置WIFI名称和密码
调试打印输出:
可以看到,例程接入测试接口然后完成了一些消息的传输。
2.3 例程分析
-
首先通过 uri 字段 配置 broker 。URI字段为 mqtt://mqtt.eclipseprojects.io,主题mqtt,主机名 mqtt.eclipseprojects.io,默认端口1883
-
esp_mqtt_client_init(&mqtt_cfg);根据配置创建MQTT客户端句柄。
-
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); 注册MQTT事件。回调函数为 mqtt_event_handler
-
esp_mqtt_client_start(client);使用已经创建的客户句柄启动MQTT客户端。
下面分析回调函数 mqtt_event_handler 中 MQTT MQTT_EVENT_CONNECTED 事件处理:
MQTT_EVENT_CONNECTED:客户端已成功建立到代理的连接。客户机现在已经准备好发送和接收数据了。
esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0); 客户端向代理发送发布消息。client为例子初始化的client,topic为"/topic/qos1",数据为"data_3",长度设为0表示自己计算,1表示服务质量为Qos 1,LWT message 不保留。
sp_mqtt_client_subscribe(client, "/topic/qos0", 0);
esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
esp_mqtt_client_unsubscribe(client, "/topic/qos1");从已定义的主题取消订阅客户端。
可以看到,我们调试的输出与代码是吻合的:
I (9116) MQTT_EXAMPLE: sent publish successful, msg_id=14555 I (9116) MQTT_EXAMPLE: sent subscribe successful, msg_id=37860 I (9116) MQTT_EXAMPLE: sent subscribe successful, msg_id=49262 I (9126) MQTT_EXAMPLE: sent unsubscribe successful, msg_id=28076
3 连接巴法平台
3.1 配置巴法平台
巴法平台作为 broker ,配置起来非常方便,官方文档。
准备工作,注册巴法平台:
- 在官网注册一个账号。
- 新建一个MQTT设备云主题。具体可以查阅官方文档的 3、平台使用教程 章节。这里注意创建的是MQTT设备云而不是TCP设备云就行。新建好之后点击昵称可以修改昵称,我这里修改为“卧室灯”。
设备命名是有讲究的,这里引用官方文档的内容:
11.1 接入介绍 巴法云物联网平台默认接入米家,仅支持以下类型的设备:插座、灯泡、风扇、传感器、空调、开关、窗帘。 用户可以自主选择是否接入米家,根据主题名字判定。 当主题名字后三位是001时为插座设备。 当主题名字后三位是002时为灯泡设备。
当主题名字后三位是003时为风扇设备。 当主题名字后三位是004时为传感器设备。 当主题名字后三位是005时为空调设备。
当主题名字后三位是006时为开关设备。 当主题名字后三位是009时为窗帘设备。
- 保存好 私钥,主题名称。
3.2 修改例程代码
修改例程代码:
app_main.c 加入ID_MQTT
const char* bemfa_uri = "mqtt://bemfa.com:9501/"; // uri, scheme://hostname:port/path const char* ID_MQTT = "7fe8xxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // 私钥作为MQTT 的ID const char* topic = "light002" ; // 对应的topic。
mqtt_app_start()函数中配置uri和ID,代码修改为:
static void mqtt_app_start(void) { esp_mqtt_client_config_t mqtt_cfg = { .uri = bemfa_uri, .client_id = ID_MQTT }; #if CONFIG_BROKER_URL_FROM_STDIN char line[128]; if (strcmp(mqtt_cfg.uri, "FROM_STDIN") == 0) { int count = 0; printf("Please enter url of mqtt broker\n"); while (count 0 && c
MQTT事件回调函数mqtt_event_handler() 修改订阅事件,修改订阅的topic为巴法中设置的light002,qos设为0或1都可以的;另外就不需要往broker发送测试数据了,全部注释掉:
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id); esp_mqtt_event_handle_t event = event_data; esp_mqtt_client_handle_t client = event->client; int msg_id; switch ((esp_mqtt_event_id_t)event_id) { case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); // msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0); // ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); // msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0); // ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); // msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1); // ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); // msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1"); // ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id); msg_id = esp_mqtt_client_subscribe(client, topic, 1); ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); // msg_id = esp_mqtt_client_publish(client, topic, "off", 0, 1, 0); // ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); break; case MQTT_EVENT_DISCONNECTED: ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); break; case MQTT_EVENT_SUBSCRIBED: ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); // msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0); // ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); break; case MQTT_EVENT_UNSUBSCRIBED: ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); break; case MQTT_EVENT_PUBLISHED: ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); break; case MQTT_EVENT_DATA: ESP_LOGI(TAG, "MQTT_EVENT_DATA"); printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); printf("DATA=%.*s\r\n", event->data_len, event->data); break; case MQTT_EVENT_ERROR: ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err); log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err); log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno); ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno)); } break; default: ESP_LOGI(TAG, "Other event id:%d", event->event_id); break; } }
3.3 设置小爱同学
-
米家添加设备,我的 -> 其他平台设备 -> 找到巴法,然后绑定
-
小爱同学添加训练
-
使用小爱同学控制
这里我先后给了“打开卧室灯”与“关闭卧室灯”的指令。
可以看到,ESP32已经收到控制指令啦,至于怎么响应指令,就可以随意开发了,我后续打算添加一个舵机控制,来实现传统开关的通断控制。
参考:
esp8266接入小爱同学,通过mqtt
ESP8266、ESP32实现小爱语音控制灯
-