陈拓 2021/04/20-2021/04/25

 

1. 概述

我们在《Win10-Ubuntu子系统构建ESP8266_RTOS_SDK开发环境》

https://zhuanlan.zhihu.com/p/346072018

https://blog.csdn.net/chentuo2000/article/details/112973413

和《用乐鑫国内镜像构建ESP8266_RTOS_SDK开发环境》

https://zhuanlan.zhihu.com/p/347711847

https://blog.csdn.net/chentuo2000/article/details/113349010

两篇文章中介绍了怎样安装ESP8266_RTOS_SDK开发环境。

本文在官方所给例子的基础上实现ESP8266与MQTT服务器之间收发送数据。

有关MQTT服务器的搭建可以参考:

《MQTT服务器Mosquitto 2.x编译安装配置》

https://zhuanlan.zhihu.com/p/365103802

https://blog.csdn.net/chentuo2000/article/details/115731687

2. 更新ESP8266_RTOS_SDK

  • 打开Win10-Ubuntu子系统

  • ESP8266_RTOS_SDK官方GitHub

https://github.com/espressif/ESP8266_RTOS_SDK/tree/master/examples/protocols/mqtt/tcp

  • 乐鑫的gitee国内镜像

https://gitee.com/EspressifSystems

  • 用git pull命令更新本地仓库

git pull会将本地库更新至远程库的最新状态。

3. ESP8266_RTOS_SDK的例子

3.1 ESP8266_RTOS_SDK中mqtt的例子


在ESP8266_RTOS_SDK中有一个mqtt收发数据的例子,位于:

esp/ESP8266_RTOS_SDK/examples/protocols/mqtt/tcp

  • 复制项目

进入esp目录:

cd esp

复制项目:

cp -r ./ESP8266_RTOS_SDK/examples/protocols/mqtt/tcp/ .

  • 查看例子代码

cat tcp/main/app_main.c

完整代码见附录。

3.2 日志库

在下面的C语言程序中有多处用到了日志库,我们先来熟悉一下。

3.2.1 官方文档

https://docs.espressif.com/projects/esp-idf/zh_CN/v4.1/api-reference/system/log.html

日志库提供了两种设置日志详细程度的方法:

  • 编译时

在menuconfig中,使用选项CONFIG_LOG_DEFAULT_LEVEL设置详细级别。所有详细级别高于CONFIG_LOG_DEFAULT_LEVEL的日志记录都将被预处理器删除。

  • 运行时

默认情况下,所有详细级别低于配置日志默认级别的日志都处于启用状态。函数esp_log_level_set()可用于按模块设置日志记录级别。模块由它们的标记标识,这些标记是以零结尾的ASCII字符串。

函数esp_log_level_set()不能将日志记录级别设置为高于CONFIG_LOG_DEFAULT_LEVEL指定的级别。要在编译时增加特定文件来增加日志级别,使用宏LOG_LOCAL_LEVEL。

3.2.2 日志的详细级别

  • Error (lowest)
  • Warning
  • Info
  • Debug
  • Verbose (highest)

3.2.3 怎样使用日志库

在使用日志功能的C文件中,定义TAG变量,如下下面的例子中所示:

static const char *TAG = "MQTT_EXAMPLE";

然后使用一个日志宏生成输出,例如:

ESP_LOGI(TAG, "[APP] Startup..");

串口显示:

3.2.4 有几个宏可用于不同的详细级别

  • ESP_LOGE - error (lowest)
  • ESP_LOGW - warning
  • ESP_LOGI - info
  • ESP_LOGD - debug
  • ESP_LOGV - verbose (highest)

3.2.5 ESP_ERROR_CHECK宏

https://docs.espressif.com/projects/esp-idf/zh_CN/v4.1/api-guides/error-handling.html

在 ESP-IDF 的示例代码中,很多都会使用 ESP_ERROR_CHECK 来处理各种 API 引发的错误,虽然这不是应用程序的最佳做法,但可以让示例代码看起来更加简洁。例如:ESP_ERROR_CHECK(nvs_flash_init());

4. 修改例子程序

4.1 修改代码和注释

nano main/app_main.c

/* MQTT (over TCP) Example
   This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
 
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
 
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
 
#include "esp_log.h"
#include "mqtt_client.h"
 
static const char *TAG = "MQTT_EXAMPLE";
 
// MQTT事件回调函数
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    // your_context_t *context = event->context;
    switch (event->event_id) {
        case MQTT_EVENT_CONNECTED: // 连接
            ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
            msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "Connect successful!", 0, 1, 0); // 向服务发布一个主题为/topic/qos1的消息
            ESP_LOGI(TAG, "sent publish Connect successful!, msg_id=%d", msg_id);
 
            msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0); // 订阅主题/topic/qos0,QoS等级0
            ESP_LOGI(TAG, "sent subscribe /topic/qos0 successful!, msg_id=%d", msg_id);
 
            msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1); // 订阅主题/topic/qos1,QoS等级1,多主题订阅
            ESP_LOGI(TAG, "sent subscribe /topic/qos1 successful!, msg_id=%d", msg_id);
 
            msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1"); // 关闭订阅主题/topic/qos1
            ESP_LOGI(TAG, "sent unsubscribe /topic/qos1 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", "Subscribe successful!", 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");
            break;
        default: // 默认
            ESP_LOGI(TAG, "Other event id:%d", event->event_id);
            break;
    }
    return ESP_OK;
}
 
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);
    mqtt_event_handler_cb(event_data); // 调用事件回调处理函数
}
 
static void mqtt_app_start(void)
{
    esp_mqtt_client_config_t mqtt_cfg = {
        //.uri = CONFIG_BROKER_URL, // 在make menuconfig中配置的MQTT代理服务器URL
        .uri = "mqtt://192.168.3.8", // 也可以在这里直接写,会覆盖在make menuconfig中的配置
        //.host = "192.168.3.8", // uri或者host选用一个即可
        .port = 1883,
        .username = "ct",
        .password = "1qaz2wsx",
    }; // MQTT配置
 
    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); // 根据MQTT配置创建的客户端句柄
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client); // 注册MQTT事件处理回调函数
    esp_mqtt_client_start(client); // 使用已创建的客户端句柄启动mqtt客户端。
}
 
void app_main(void)
{
    ESP_LOGI(TAG, "[APP] Startup..");
    ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
    ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
 
    ESP_ERROR_CHECK(nvs_flash_init()); // 初始化Flash
    ESP_ERROR_CHECK(esp_netif_init()); // 初始化底层TCP/IP堆栈
    ESP_ERROR_CHECK(esp_event_loop_create_default()); // 创建默认事件循环
    ESP_ERROR_CHECK(example_connect()); // 在esp-idf中已经封装好的连接WIFI的API,在protocol_examples_common.h中声明
 
    mqtt_app_start(); // 启动MQTT应用
}

5. 配置

  • 查看USB-串口

Windows中的串口COM3对应Linux中的设备/dev/ttyS3

  • 在树莓派上安装MQTT服务

见《MQTT服务器Mosquitto 2.x编译安装配置》

https://zhuanlan.zhihu.com/p/365103802

https://blog.csdn.net/chentuo2000/article/details/115731687

  • 设置树莓派设置固定IP地址

见《树莓派WiFi设置固定IP地址》

https://zhuanlan.zhihu.com/p/367982385

https://blog.csdn.net/chentuo2000/article/details/116158902

查看MQTT服务器的IP地址:

 

 

  • 项目配置

make menuconfig

(1) 设置串口和Flash存储器

(2) 设置代理的URL

前面我们已经设置了MQTT服务器的固定IP地址。

(3) 修改WiFi的用户名和密码

保存Save:

配置文件保存位置:/home/ccdc/esp/tcp/

退出Exit:

6. 编译烧写

  • 编译

cd ~/esp/tcp

make

第一次编译需要较长的时间。

生成了二进制文件放在build目录下:

  • 烧写

make flash

7. 运行测试

7.1 发布消息

  • 打开终端窗口连接树莓派

  • 在树莓派上启动MQTT订阅程序

mosquitto_sub -p 1883 -u ct -P 1qaz2wsx -t "#" -v

使用通配符#可以接收任何主题消息。

有关MQTT服务器安装见《MQTT服务器Mosquitto 2.x编译安装配置》

https://zhuanlan.zhihu.com/p/365103802

https://blog.csdn.net/chentuo2000/article/details/115731687

  • 打开串口调试助手

Win10的串口调试助手的使用参考《Win10使用CH340 USB-SERIAL串口》

https://zhuanlan.zhihu.com/p/343013801

https://blog.csdn.net/chentuo2000/article/details/112323488

  • ESP8266开发板的RST键,程序运行

  • MQTT订阅程序收到数据ESP8266发布的消息

7.2 订阅消息

  • 再打开一个终端窗口
  • 发布主题为/topic/qos0的消息hello!

mosquitto_pub -p 1883 -u ct -P 1qaz2wsx -t /topic/qos0 -m "hello!"

  • ESP8266收到消息

  • 树莓派的订阅窗口也同时收到消息

 

附录:app_main.c

/* MQTT (over TCP) Example
   This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
 
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
 
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
 
#include "esp_log.h"
#include "mqtt_client.h"
 
static const char *TAG = "MQTT_EXAMPLE";
 
 
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    // your_context_t *context = event->context;
    switch (event->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);
            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");
            break;
        default:
            ESP_LOGI(TAG, "Other event id:%d", event->event_id);
            break;
    }
    return ESP_OK;
}
 
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);
    mqtt_event_handler_cb(event_data);
}
 
static void mqtt_app_start(void)
{
    esp_mqtt_client_config_t mqtt_cfg = {
        .uri = CONFIG_BROKER_URL,
    };
#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 < 128) {
            int c = fgetc(stdin);
            if (c == '\n') {
                line[count] = '\0';
                break;
            } else if (c > 0 && c < 127) {
                line[count] = c;
                ++count;
            }
            vTaskDelay(10 / portTICK_PERIOD_MS);
        }
        mqtt_cfg.uri = line;
        printf("Broker url: %s\n", line);
    } else {
        ESP_LOGE(TAG, "Configuration mismatch: wrong broker url");
        abort();
    }
#endif /* CONFIG_BROKER_URL_FROM_STDIN */
 
    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
    esp_mqtt_client_start(client);
}
 
void app_main(void)
{
    ESP_LOGI(TAG, "[APP] Startup..");
    ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
    ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
 
    esp_log_level_set("*", ESP_LOG_INFO);
    esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
    esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
    esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE);
    esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE);
    esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
    esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);
 
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
 
    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());
 
    mqtt_app_start();
}