ESP32接入米家-小爱同学-IDF环境-巴法平台

慈云数据 2024-04-19 技术支持 63 0

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 可以基于以下选项过滤消息:

            1. 基于主题
            2. 基于内容
            3. 基于类型

            服务质量级别

            1. QoS 0:最多传送一次

              反正 receiver 有没有响应我都之发送一次。

            2. QoS 1:要实施至少一次传送

              我要确保 receiver 至少收到了一次消息。

            3. 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 例程分析

              1. 首先通过 uri 字段 配置 broker 。URI字段为 mqtt://mqtt.eclipseprojects.io,主题mqtt,主机名 mqtt.eclipseprojects.io,默认端口1883

              2. esp_mqtt_client_init(&mqtt_cfg);根据配置创建MQTT客户端句柄。

              3. esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); 注册MQTT事件。回调函数为 mqtt_event_handler

              4. 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 ,配置起来非常方便,官方文档。

              准备工作,注册巴法平台:

              1. 在官网注册一个账号。
              2. 新建一个MQTT设备云主题。具体可以查阅官方文档的 3、平台使用教程 章节。这里注意创建的是MQTT设备云而不是TCP设备云就行。新建好之后点击昵称可以修改昵称,我这里修改为“卧室灯”。

                设备命名是有讲究的,这里引用官方文档的内容:

              11.1 接入介绍 巴法云物联网平台默认接入米家,仅支持以下类型的设备:插座、灯泡、风扇、传感器、空调、开关、窗帘。 用户可以自主选择是否接入米家,根据主题名字判定。 当主题名字后三位是001时为插座设备。 当主题名字后三位是002时为灯泡设备。

              当主题名字后三位是003时为风扇设备。 当主题名字后三位是004时为传感器设备。 当主题名字后三位是005时为空调设备。

              当主题名字后三位是006时为开关设备。 当主题名字后三位是009时为窗帘设备。

              在这里插入图片描述

              1. 保存好 私钥,主题名称。

              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 设置小爱同学

              1. 米家添加设备,我的 -> 其他平台设备 -> 找到巴法,然后绑定

                在这里插入图片描述

              2. 小爱同学添加训练

                在这里插入图片描述

              3. 使用小爱同学控制

                这里我先后给了“打开卧室灯”与“关闭卧室灯”的指令。

                在这里插入图片描述

              可以看到,ESP32已经收到控制指令啦,至于怎么响应指令,就可以随意开发了,我后续打算添加一个舵机控制,来实现传统开关的通断控制。

              参考:

              esp8266接入小爱同学,通过mqtt

              ESP8266、ESP32实现小爱语音控制灯

微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon