В этом проекте мы научимся публиковать показания датчика BME280 в Node-Red с помощью ESP32 и MQTT, используя ESP-IDF. Мы будем осуществлять MQTT-связь между ESP32 и Node-Red, а также публиковать и подписываться на MQTT-темы. ESP32 будет выступать в роли издателя MQTT-клиента, а Node-Red — в роли подписчика. Используя протокол MQTT, ESP32 будет публиковать показания датчика BME280 на панели управления Node-Red, которая будет подписываться на соответствующие темы с показаниями BME280. Данные датчика, включающие температуру, давление и влажность, будут интерактивно отображаться на панели управления Node-Red.
Мы будем использовать MQTT-брокер Mosquitto на Raspberry Pi, но вы также можете установить его на свои компьютеры с Windows и Linux, если вы их используете. Издатель и подписчик установят соединения с MQTT-брокером, установленным на Raspberry Pi. После этого ESP32 будет публиковать данные с датчиков на панель управления Node-Red по указанным темам.
Чтобы лучше понять материал данной статьи рекомендуем прочитать предыдущую статью данного цикла - работа с клиентом MQTT в ESP32 с использованием ESP-IDF.
MQTT-клиент в ESP32 с помощью ESP-IDF: публикация показаний датчика BME280 в Node-Red
Наша цель — создать проект, использующий протокол MQTT на ESP32 с помощью ESP-IDF для публикации показаний датчика BME280 в Node-Red. Рассмотрим этапы, необходимые для реализации этого проекта.
- Плата ESP32 подключена к датчику BME280, используемому для измерения температуры окружающей среды, барометрического давления и относительной влажности. Эта плата ESP32 будет подключаться к MQTT-брокеру. В нашем случае мы будем использовать брокер Mosquitto на Raspberry Pi. Если вы хотите узнать, как успешно установить брокер на ваш Raspberry Pi, вы можете обратиться к следующему руководству: установка Mosquitto MQTT брокера на Raspberry Pi.
- После получения данных с датчика BME280 плата ESP32 публикует эти показания в отдельных темах MQTT: esp32/bme280/temperature — для публикации температуры BME280, esp32/bme280/humidity — для публикации влажности. Аналогично, esp32/bme280/pressure — для публикации давления.
- Node-Red будет подписан на эти три темы. Таким образом, он сможет получать показания датчиков и интерактивно отображать их на своей панели управления с помощью индикаторов, линейных графиков и т. д.

Подключение BME280 к ESP32
Датчик BME280 широко используется в электронных проектах для измерения температуры окружающей среды, барометрического давления и относительной влажности, где требуется низкое энергопотребление. Для передачи данных на микроконтроллер можно использовать протоколы связи I2C и SPI. Хотя датчик BME280 выпускается в различных версиях, мы будем использовать связь I2C для обмена данными с платой ESP32. Этот протокол основан на синхронной многомастерной системе, где плата ESP32 выступает в роли ведущего устройства, а датчик BME280 — в роли ведомого.
На приведенной ниже схеме показана распиновка датчика BME280, который мы будем использовать.

Для подключения датчика BME280 к плате ESP32 нам понадобятся следующие компоненты.
- Плата ESP32.
- Датчик BME280.
- Соединительные провода.
- Макетная плата.
Подключение датчика BME280 к плате ESP32 довольно простое, так как оно включает в себя лишь соединение 4 контактов (GND, VCC, SDA и SCL) с ESP32. Необходимо соединить вывод VCC с контактом 3,3 В, заземление с общим заземлением, контакт SCL датчика с контактом SCL платы ESP32, а контакт SDA датчика с контактом SDA платы ESP32.
В ESP32 по умолчанию для вывода SDA используется GPIO21, а для SCL — GPIO22.
Соединения между устройствами указаны в таблице ниже:
| Датчик BME280 | ESP32 |
| GND | GND |
| VCC | 3.3V |
| SDA | GPIO21(I2C SDA) |
| SCL | GPIO22 (I2C SCL) |
Публикация показаний BME280 в Node-Red по MQTT с использованием ESP32 и ESP-IDF
Мы создадим проект в VS Code с расширением ESP-IDF. Код будет считывать значения температуры, влажности и давления, используя драйвер I2C master, предоставляемый ESP-IDF, а затем использовать библиотеку mqtt_client для подключения ESP32 к брокеру MQTT и публикации данных с датчика BME280 в различных темах. Мы будем использовать библиотеку BME280, разработанную BoschSensortec.
Создание примера проекта
Откройте VS Code и перейдите в меню View > Command Palette («Вид» > «Палитра команд»). Введите в строке поиска ESP-IDF: New Project и нажмите Enter.
Укажите название проекта и каталог, а также плату и целевое устройство ESP-IDF. Для платы ESP-IDF мы выбрали опцию custom board ("пользовательская плата"). Для целевого устройства ESP-IDF мы выбрали модуль ESP32. Нажмите кнопку ‘Choose Template’ ("Выбрать шаблон"), чтобы продолжить.
В разделе «Расширение» выберите опцию ESP-IDF:
На вкладке get-started («Начало работы») мы щелкнем по пункту «sample_project». Затем нажмем ‘Create project using template sample_project’) («Создать проект, используя шаблон sample_project»).
Вы получите уведомление о создании проекта. Чтобы открыть проект в новом окне, нажмите «Да».
Это откроет проект, который мы создали на вкладке «Проводник». Внутри папки проекта находится несколько папок. Это справедливо для каждого проекта, который вы будете создавать с помощью ESP-IDF Explorer.
- Для начала добавим необходимые заголовочные файлы для библиотек, требуемых для этого проекта. Создайте новую папку с именем 'components/bme280' и добавьте в неё следующие файлы, доступные по этой ссылке. Сюда входят все файлы, перечисленные ниже:

- Теперь перейдите к файлу main.c. В папке main находится исходный код, то есть файл main.c будет находиться здесь. Перейдите в папку main > main.c и откройте его. Скопируйте приведенный ниже код из этого файла и сохраните его.
Код ESP32 для работы с MQTT и BME280
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
#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" #include "driver/gpio.h" #include "driver/i2c.h" #include "esp_err.h" #include "bme280.h" static const char *TAG = "MQTT_EXAMPLE"; #define TAG_BME280 "BME280" #define EXAMPLE_ESP_WIFI_SSID "YOUR_SSID" #define EXAMPLE_ESP_WIFI_PASS "YOUR_PASSWORD" #define MAX_RETRY 10 static int retry_cnt = 0; #define MQTT_PUB_TEMP_BME280 "esp32/bme280/temperature" #define MQTT_PUB_HUM_BME280 "esp32/bme280/humidity" #define MQTT_PUB_PRES_BME280 "esp32/bme280/pressure" #define SDA_PIN GPIO_NUM_21 #define SCL_PIN GPIO_NUM_22 #define I2C_MASTER_ACK 0 #define I2C_MASTER_NACK 1 void i2c_master_init() { i2c_config_t i2c_config = { .mode = I2C_MODE_MASTER, .sda_io_num = SDA_PIN, .scl_io_num = SCL_PIN, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = 1000000}; i2c_param_config(I2C_NUM_0, &i2c_config); i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0); } s8 BME280_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt) { s32 iError = BME280_INIT_VALUE; esp_err_t espRc; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, reg_addr, true); i2c_master_write(cmd, reg_data, cnt, true); i2c_master_stop(cmd); espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS); if (espRc == ESP_OK) { iError = SUCCESS; } else { iError = FAIL; } i2c_cmd_link_delete(cmd); return (s8)iError; } s8 BME280_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt) { s32 iError = BME280_INIT_VALUE; esp_err_t espRc; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, reg_addr, true); i2c_master_start(cmd); i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true); if (cnt > 1) { i2c_master_read(cmd, reg_data, cnt - 1, I2C_MASTER_ACK); } i2c_master_read_byte(cmd, reg_data + cnt - 1, I2C_MASTER_NACK); i2c_master_stop(cmd); espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS); if (espRc == ESP_OK) { iError = SUCCESS; } else { iError = FAIL; } i2c_cmd_link_delete(cmd); return (s8)iError; } void BME280_delay_msek(u32 msek) { vTaskDelay(msek / portTICK_PERIOD_MS); } uint32_t MQTT_CONNECTED = 0; static void mqtt_app_start(void); static esp_err_t wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { switch (event_id) { case WIFI_EVENT_STA_START: esp_wifi_connect(); ESP_LOGI(TAG, "Trying to connect with Wi-Fi\n"); break; case WIFI_EVENT_STA_CONNECTED: ESP_LOGI(TAG, "Wi-Fi connected\n"); break; case IP_EVENT_STA_GOT_IP: ESP_LOGI(TAG, "got ip: startibg MQTT Client\n"); mqtt_app_start(); break; case WIFI_EVENT_STA_DISCONNECTED: ESP_LOGI(TAG, "disconnected: Retrying Wi-Fi\n"); if (retry_cnt++ < MAX_RETRY) { esp_wifi_connect(); } else ESP_LOGI(TAG, "Max Retry Failed: Wi-Fi Connection\n"); break; default: break; } return ESP_OK; } void wifi_init(void) { esp_event_loop_create_default(); esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL); esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL); wifi_config_t wifi_config = { .sta = { .ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, }; esp_netif_init(); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); esp_wifi_start(); } /* * @brief Event handler registered to receive MQTT events * * This function is called by the MQTT client event loop. * * @param handler_args user data registered to the event. * @param base Event base for the handler(always MQTT Base in this example). * @param event_id The id for the received event. * @param event_data The data for the event, esp_mqtt_event_handle_t. */ 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"); MQTT_CONNECTED = 1; break; case MQTT_EVENT_DISCONNECTED: ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); MQTT_CONNECTED = 0; break; case MQTT_EVENT_ERROR: ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); break; default: ESP_LOGI(TAG, "Other event id:%d", event->event_id); break; } } esp_mqtt_client_handle_t client = NULL; static void mqtt_app_start(void) { ESP_LOGI(TAG, "STARTING MQTT"); esp_mqtt_client_config_t mqttConfig = { .uri = "mqtt://YOUR_RPI_IP_ADDRESS:1883"}; client = esp_mqtt_client_init(&mqttConfig); esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client); esp_mqtt_client_start(client); } void Publisher_Task(void *params) { struct bme280_t bme280 = { .bus_write = BME280_I2C_bus_write, .bus_read = BME280_I2C_bus_read, .dev_addr = BME280_I2C_ADDRESS1, .delay_msec = BME280_delay_msek}; s32 com_rslt; s32 v_uncomp_pressure_s32; s32 v_uncomp_temperature_s32; s32 v_uncomp_humidity_s32; com_rslt = bme280_init(&bme280); com_rslt += bme280_set_oversamp_pressure(BME280_OVERSAMP_16X); com_rslt += bme280_set_oversamp_temperature(BME280_OVERSAMP_2X); com_rslt += bme280_set_oversamp_humidity(BME280_OVERSAMP_1X); com_rslt += bme280_set_standby_durn(BME280_STANDBY_TIME_1_MS); com_rslt += bme280_set_filter(BME280_FILTER_COEFF_16); com_rslt += bme280_set_power_mode(BME280_NORMAL_MODE); if (com_rslt == SUCCESS) { while (true) { vTaskDelay(40 / portTICK_PERIOD_MS); com_rslt = bme280_read_uncomp_pressure_temperature_humidity( &v_uncomp_pressure_s32, &v_uncomp_temperature_s32, &v_uncomp_humidity_s32); double temp = bme280_compensate_temperature_double(v_uncomp_temperature_s32); char temperature[12]; sprintf(temperature, "%.2f degC", temp); double press = bme280_compensate_pressure_double(v_uncomp_pressure_s32) / 100; // Pa -> hPa char pressure[10]; sprintf(pressure, "%.2f hPa", press); double hum = bme280_compensate_humidity_double(v_uncomp_humidity_s32); char humidity[10]; sprintf(humidity, "%.2f %%", hum); if (com_rslt == SUCCESS) { if (MQTT_CONNECTED) { esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME280, temperature, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME280, pressure, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME280, humidity, 0, 0, 0); vTaskDelay(5000 / portTICK_PERIOD_MS); } } else { ESP_LOGE(TAG_BME280, "measure error. code: %d", com_rslt); } } } else { ESP_LOGE(TAG_BME280, "init or setting error. code: %d", com_rslt); } } void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); wifi_init(); i2c_master_init(); xTaskCreate(Publisher_Task, "Publisher_Task", 1024 * 5, NULL, 5, NULL); } |
Также данный код можно скачать по следующей ссылке.
Как работает код?
Для начала мы подключим необходимые библиотеки, включая драйвер i2c, драйвер gpio, библиотеку BME280, библиотеки FreeRTOS для генерации задержек, esp_wifi.h для включения Wi-Fi-подключения, esp_event.h для мониторинга событий Wi-Fi и MQTT, mqtt_client.h для настройки протокола MQTT для публикации/подписки и esp_log.h в качестве библиотеки для логирования. Библиотека driver/i2c.h позволит ESP32 взаимодействовать с другими устройствами I2C, в нашем случае это датчик BME280.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#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" #include "driver/gpio.h" #include "driver/i2c.h" #include "esp_err.h" #include "bme280.h" |
Этот код использует информационное логирование. Функция логирования принимает два аргумента. Первый аргумент — это тег, а второй — отформатированная строка. Поэтому эта глобальная переменная будет полезна при вызове функций ESP_LOGI(). Здесь «MQTT_EXAMPLE» и «BME280» — это два тега, которые будут использоваться при логировании.
|
1 2 |
static const char *TAG = "MQTT_EXAMPLE"; #define TAG_BME280 "BME280" |
Далее мы определили параметры для SSID и пароля Wi-Fi. Укажите свои собственные учетные данные сети, чтобы успешно подключить плату ESP32 к сети Wi-Fi.
|
1 2 |
#define EXAMPLE_ESP_WIFI_SSID "YOUR_SSID" #define EXAMPLE_ESP_WIFI_PASS "YOUR_PASSWORD" |
Мы определим несколько переменных для мониторинга Wi-Fi-соединения, включая максимальное значение количества повторных попыток, установленное на 10, и количество повторных попыток, которое изначально имеет значение 0.
|
1 2 |
#define MAX_RETRY 10 static int retry_cnt = 0; |
После этого мы определяем темы, в которые будет публиковать данные плата ESP32. К ним относятся: esp32/bme280/temperature для показаний температуры, esp32/bme280/humidity для показаний влажности и esp32/bme280/pressure для показаний давления.
|
1 2 3 |
#define MQTT_PUB_TEMP_BME280 "esp32/bme280/temperature" #define MQTT_PUB_HUM_BME280 "esp32/bme280/humidity" #define MQTT_PUB_PRES_BME280 "esp32/bme280/pressure" |
Далее приведены определения контактов SCL и SDA, которые будут использоваться для связи по протоколу I2C между платой ESP32 и датчиком BME280. Мы используем контакты I2C по умолчанию, однако вы также можете определить другие контакты GPIO как контакты SDA/SCL.
|
1 2 |
#define SDA_PIN GPIO_NUM_21 #define SCL_PIN GPIO_NUM_22 |
Инициализация ведущего устройства I2C (ESP32)
Следующая функция отвечает за инициализацию ведущего устройства I2C.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void i2c_master_init() { i2c_config_t i2c_config = { .mode = I2C_MODE_MASTER, .sda_io_num = SDA_PIN, .scl_io_num = SCL_PIN, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = 1000000 }; i2c_param_config(I2C_NUM_0, &i2c_config); i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0); } |
Сначала определяются параметры структуры i2c_config_t. Это включает в себя режим работы (в данном случае — ведущий), контакты связи I2C, настройку внутренних подтягивающих резисторов ESP32 и установку тактовой частоты I2C.
|
1 2 3 4 5 6 7 8 |
i2c_config_t i2c_config = { .mode = I2C_MODE_MASTER, .sda_io_num = SDA_PIN, .scl_io_num = SCL_PIN, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = 1000000 }; |
После установки параметров выполняется инициализация конфигурации I2C для конкретного порта. Это достигается путем вызова функции i2c_param_config(), которая принимает два параметра. Первый параметр — это порт I2C, а второй — указатель на константную структуру конфигурации типа i2c_config_t. На этом этапе выполняется настройка порта I2C.
|
1 |
i2c_param_config(I2C_NUM_0, &i2c_config); |
Следующий шаг — установка драйвера I2C. Он принимает несколько параметров, перечисленных ниже:
- Номер порта I2C.
- Режим конфигурации, который мы устанавливаем (ведущий/ведомый)
- Отключение буфера приема. Это размер буфера приема, применяемый к ведомому устройству.
- Отключение буфера TX. Это размер буфера отправки, который также применяется к ведомому устройству.
- Последний параметр — 'intr_alloc_flags', который задает флаги для выделенного прерывания. В нашем случае он равен '0', поскольку в этом примере мы не используем прерывания.
|
1 |
i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0); |
Функции записи/чтения по шине I2C микроконтроллера BME280
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
s8 BME280_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt) { s32 iError = BME280_INIT_VALUE; esp_err_t espRc; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, reg_addr, true); i2c_master_write(cmd, reg_data, cnt, true); i2c_master_stop(cmd); espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS); if (espRc == ESP_OK) { iError = SUCCESS; } else { iError = FAIL; } i2c_cmd_link_delete(cmd); return (s8)iError; } |
Внутри функции BME280_I2C_bus_write() мы начинаем с создания и инициализации канала управления I2C с помощью функции i2c_cmd_link_create(). Эта функция вернет обработчик канала управления I2C.
|
1 |
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); |
После создания командной связи мы вызовем функцию i2c_master_start() и передадим ей командную связь в качестве параметра. Это позволит ведущему устройству I2C сгенерировать стартовый сигнал. Оно будет передавать все команды из очереди.
Примечание: эта функция вызывается только тогда, когда устройство выступает в роли ведущего устройства.
|
1 |
i2c_master_start(cmd); |
Затем вызывается функция i2c_master_write_byte(), которая используется для указания адреса ведомого устройства. Эта функция принимает три параметра: команду I2C link, однобайтовую команду I2C, которая будет записана в шину, и подтверждение разрешения для ведущего устройства. Здесь "(dev_addr << 1) | I2C_MASTER_WRITE" — это адрес ведомого устройства, который указывает, что ведущее устройство будет записывать данные в ведомое.
|
1 |
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true); |
Далее мы вызываем функцию i2c_master_write() для ведущего устройства I2C, чтобы записать буфер в шину I2C. Эта функция принимает четыре параметра: обработчик команд I2C, передаваемые данные, длину данных и подтверждение разрешения для ведущего устройства.
|
1 2 |
i2c_master_write_byte(cmd, reg_addr, true); i2c_master_write(cmd, reg_data, cnt, true); |
Затем мы вызываем функцию i2c_master_cmd_begin(), которая позволит ведущему устройству I2C начать отправку команд из очереди. Эта функция принимает три параметра. Первый параметр — номер порта I2C, второй — обработчик команд I2C, а третий — максимальное время ожидания, которое в данном случае установлено на 0,01 секунды.
|
1 2 3 4 5 6 |
espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS); if (espRc == ESP_OK) { iError = SUCCESS; } else { iError = FAIL; } |
После этого мы освободим канал связи, когда закончим отправку команд. Это достигается путем вызова функции i2c_cmd_link() и передачи в нее обработчика команд I2C в качестве параметра.
|
1 |
i2c_cmd_link_delete(cmd); |
Внутри функции BME280_I2C_bus_read() выполняются те же шаги для создания связи и запуска шины I2C Master и записи байтов, что и в функции BME280_I2C_bus_write(). На этот раз команда, отправленная ведущим устройством для чтения данных с ведомого устройства, содержит адрес ESP_SLAVE_ADDR << 1) | I2C_MASTER_READ, который указан в качестве второго параметра в функции i2c_master_write_byte(). Однако теперь мы добавим команду в условие if, которое проверяет, если длина данных больше 1, то вызывается функция i2c_master_read() для чтения нескольких байтов по шине I2C. Она принимает четыре параметра. Первый параметр — обработчик команд I2C. Второй параметр — данные для передачи. Третий параметр — длина буфера данных в байтах. И, наконец, четвертый параметр — разрешение сигнала ACK для ведущего устройства.
Далее вызовите функцию i2c_master_read_byte() для чтения одного байта по шине I2C. При этом настройка такова, что ведущее устройство не передает бит ACK. После этого вызовите функцию i2c_master_stop(), чтобы добавить сигнал «STOP» в список заданных команд.
Затем мы вызываем функцию i2c_master_cmd_begin(), которая позволит ведущему устройству I2C начать отправку команд из очереди. После этого мы освободим канал передачи команд, когда закончим их отправку.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
s8 BME280_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt) { s32 iError = BME280_INIT_VALUE; esp_err_t espRc; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, reg_addr, true); i2c_master_start(cmd); i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true); if (cnt > 1) { i2c_master_read(cmd, reg_data, cnt-1, I2C_MASTER_ACK); } i2c_master_read_byte(cmd, reg_data+cnt-1, I2C_MASTER_NACK); i2c_master_stop(cmd); espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS); if (espRc == ESP_OK) { iError = SUCCESS; } else { iError = FAIL; } i2c_cmd_link_delete(cmd); return (s8)iError; } |
Обработка событий Wi-Fi
У нас есть функция wifi_event_handler(), которая обрабатывает события Wi-Fi. Она действует как функция обратного вызова при возникновении любого события Wi-Fi. Драйвер Wi-Fi отправляет в задачу обработки событий различные события, в том числе: SYSTEM_EVENT_STA_START, SYSTEM_EVENT_STA_CONNECTED, SYSTEM_EVENT_STA_GOT_IP и SYSTEM_EVENT_STA_DISCONNECTED. Эти события происходят, если Wi-Fi подключен, станция подключается к точке доступа, Wi-Fi получает IP-адрес или станция отключается от точки доступа соответственно.
В функции wifi_event_handler() мы проверим, соответствует ли идентификатор события Wi-Fi одному из этих четырех событий. Если да, то на ESP-IDF будет выведено соответствующее сообщение в зависимости от типа события и вызвана соответствующая функция. Если произойдет событие SYSTEM_EVENT_STA_START, будет вызвана функция esp_wifi_connect(). Она будет отвечать за подключение станции Wi-Fi ESP32 к точке доступа. Если произойдет событие SYSTEM_EVENT_STA_DISCONNECTED, то функция esp_wifi_connect() будет вызываться до достижения максимального значения количества попыток, которое мы установили равным 10. Если Wi-Fi не подключается в течение заданного количества попыток, то терминал ESP-IDF выведет сообщение о том, что максимальное количество попыток не удалось. Если произойдет событие SYSTEM_EVENT_STA_GOT_IP, то будет вызвана функция mqtt_app_start() для инициирования подключения MQTT-клиента.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
static esp_err_t wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { switch (event_id) { case WIFI_EVENT_STA_START: esp_wifi_connect(); ESP_LOGI(TAG, "Trying to connect with Wi-Fi\n"); break; case WIFI_EVENT_STA_CONNECTED: ESP_LOGI(TAG, "Wi-Fi connected\n"); break; case IP_EVENT_STA_GOT_IP: ESP_LOGI(TAG, "got ip: startibg MQTT Client\n"); mqtt_app_start(); break; case WIFI_EVENT_STA_DISCONNECTED: ESP_LOGI(TAG, "disconnected: Retrying Wi-Fi\n"); if (retry_cnt++ < MAX_RETRY) { esp_wifi_connect(); } else ESP_LOGI(TAG, "Max Retry Failed: Wi-Fi Connection\n"); break; default: break; } return ESP_OK; } |
Инициализация Wi-Fi
Функция wifi_init() будет вызываться внутри функции app_main() для инициализации Wi-Fi в режиме станции для ESP32.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void wifi_init(void) { esp_event_loop_create_default(); esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL); esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL); wifi_config_t wifi_config = { .sta = { .ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, }; esp_netif_init(); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); esp_wifi_start(); } |
Для начала мы создаём цикл событий по умолчанию, вызвав функцию esp_event_loop_create_default(). Затем мы используем функцию esp_event_handler_register() для регистрации обработчиков событий в системном цикле событий для событий WIFI_EVENT и IP_EVENT.
|
1 2 3 |
esp_event_loop_create_default(); esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL); esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL); |
В этой части мы задаём SSID и пароль сети, к которой хотим подключить наш ESP32, а также назначаем параметры безопасности.
|
1 2 3 4 5 6 7 |
wifi_config_t wifi_config = { .sta = { .ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, }; |
Здесь мы инициализируем lwIP с помощью функции esp_netif_init() и создаём задачу IwIP, также известную как задача TCP/IP. lwIP — это стек библиотек TCP/IP, предоставляемый ESP-IDF, который используется для выполнения различных протоколов, таких как TCP, UDP, DHCP и т. д.
|
1 |
esp_netif_init(); |
Следующая строка используется для инициализации Wi-Fi по умолчанию в режиме станции. Этот API используется для инициализации, а также регистрации обработчиков событий для интерфейса по умолчанию, которым в нашем случае является станция. Он создает экземпляр сетевого интерфейса, связывающий драйвер Wi-Fi и стек TCP/IP. Когда станция находится в процессе подключения к точке доступа, различные процессы автоматически обрабатываются с помощью этой функции.
|
1 |
esp_netif_create_default_wifi_sta(); |
Затем мы инициализируем ресурс выделения Wi-Fi для драйвера Wi-Fi. Он отвечает за запуск задачи Wi-Fi. Он принимает единственный параметр cfg, который является указателем на уже инициализированную структуру конфигурации Wi-Fi, установленную в значение WIFI_INIT_CONFIG_DEFAULT(), так что инициализация конфигурации происходит со значениями по умолчанию. Здесь wifi_init_config_t — это структура, обозначающая параметры конфигурации стека Wi-Fi, которые передаются в функцию esp_wifi_init().
|
1 2 |
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); |
Кроме того, мы устанавливаем режим Wi-Fi как «станция». Для этого мы используем функцию esp_wifi_set_mode() и передаем в качестве параметра параметр WiFI_MODE_STA.
|
1 |
esp_wifi_set_mode(WIFI_MODE_STA); |
Затем мы назначаем SSID и пароль нашей сети. Таким образом, драйвер Wi-Fi настраивается с использованием учетных данных сети точки доступа и режима Wi-Fi. После этого мы запускаем Wi-Fi-соединение с заданными параметрами.
|
1 2 |
esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); esp_wifi_start(); |
Обработка событий MQTT
Далее у нас есть функция mqtt_event_handler(), которая обрабатывает события MQTT. Эта функция вызывается циклом обработки событий MQTT-клиента. Мы проверим, соответствует ли идентификатор события одному из трех возможных типов событий: MQTT_EVENT_CONNECTED, MQTT_EVENT_DISCONNECTED или MQTT_EVENT_ERROR. Если да, то на ESP-IDF будет выведено соответствующее сообщение в зависимости от типа события, и будет вызвана соответствующая функция.
В таблице ниже приведены описания событий MQTT, которые будут использоваться в этом примере.
| MQTT_EVENT_CONNECTED | Это событие отправляется, когда клиент успешно подключается к брокеру и готов отправлять/получать данные. |
| MQTT_EVENT_DISCONNECTED | Это событие регистрируется, когда клиент разрывает соединение с брокером из-за недоступности сервера. В этом случае клиент не может отправлять/получать данные. |
| MQTT_EVENT_ERROR | Это событие отправляется клиентом при возникновении ошибки. |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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"); MQTT_CONNECTED = 1; break; case MQTT_EVENT_DISCONNECTED: ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); MQTT_CONNECTED = 0; break; case MQTT_EVENT_ERROR: ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); break; default: ESP_LOGI(TAG, "Other event id:%d", event->event_id); break; } } |
Если клиент отправляет событие MQTT_EVENT_CONNECTED, переменная «MQTT_CONNECTED» устанавливается в значение 1, тогда как изначально она имела значение 0.
|
1 2 3 4 |
case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); MQTT_CONNECTED = 1; break; |
Если клиент отправляет событие MQTT_EVENT_DISCONNECTED, переменная MQTT_CONNECTED устанавливается в значение 0, и терминал выводит сообщение о том, что это событие произошло. Аналогично, для остальных событий на терминал выводится соответствующее сообщение вместе с идентификатором события (event_id), если это другое событие.
|
1 2 3 4 5 6 7 8 9 10 11 |
case MQTT_EVENT_DISCONNECTED: ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); MQTT_CONNECTED = 0; break; case MQTT_EVENT_ERROR: ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); break; default: ESP_LOGI(TAG, "Other event id:%d", event->event_id); break; |
mqtt_app_start()
Внутри функции mqtt_app_start() создается дескриптор MQTT-клиента в соответствии с параметрами конфигурации, заданными с помощью функции esp_mqtt_client_init(). Эта функция принимает один параметр — указатель на структуру конфигурации MQTT. Структура конфигурации MQTT содержит IP-адрес MQTT-брокера, который выступает в качестве MQTT-сервера. Убедитесь, что вы указали IP-адрес вашего Raspberry Pi, на котором запущен брокер Mosquitto, в структуре конфигурации MQTT.
После этого мы вызовем функцию esp_mqtt_client_register_event(), которая используется для регистрации события MQTT. Она принимает четыре параметра. Первый параметр — это дескриптор MQTT-клиента. Второй параметр — это тип события. Третий параметр — это функция обратного вызова обработчика, а последний параметр — это контекст дескриптора, который в нашем случае равен mqtt_client_handle, 'client'. Наконец, мы запустим MQTT-клиент с помощью функции esp_mqtt_client_start() и укажем уже созданный дескриптор клиента в качестве параметра внутри неё.
|
1 2 3 4 5 6 7 8 9 10 11 |
esp_mqtt_client_handle_t client = NULL; static void mqtt_app_start(void) { ESP_LOGI(TAG, "STARTING MQTT"); esp_mqtt_client_config_t mqttConfig = { .uri = "mqtt://YOUR_RPI_IP_ADDRESS:1883"}; client = esp_mqtt_client_init(&mqttConfig); esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client); esp_mqtt_client_start(client); } |
Publisher_Task()
Внутри функции Publisher_Task() инициализируется датчик BME280, а данные с датчика собираются и публикуются в соответствующие темы каждые 5 секунд с использованием протокола MQTT.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
void Publisher_Task(void *params) { struct bme280_t bme280 = { .bus_write = BME280_I2C_bus_write, .bus_read = BME280_I2C_bus_read, .dev_addr = BME280_I2C_ADDRESS1, .delay_msec = BME280_delay_msek}; s32 com_rslt; s32 v_uncomp_pressure_s32; s32 v_uncomp_temperature_s32; s32 v_uncomp_humidity_s32; com_rslt = bme280_init(&bme280); com_rslt += bme280_set_oversamp_pressure(BME280_OVERSAMP_16X); com_rslt += bme280_set_oversamp_temperature(BME280_OVERSAMP_2X); com_rslt += bme280_set_oversamp_humidity(BME280_OVERSAMP_1X); com_rslt += bme280_set_standby_durn(BME280_STANDBY_TIME_1_MS); com_rslt += bme280_set_filter(BME280_FILTER_COEFF_16); com_rslt += bme280_set_power_mode(BME280_NORMAL_MODE); if (com_rslt == SUCCESS) { while (true) { vTaskDelay(40 / portTICK_PERIOD_MS); com_rslt = bme280_read_uncomp_pressure_temperature_humidity( &v_uncomp_pressure_s32, &v_uncomp_temperature_s32, &v_uncomp_humidity_s32); double temp = bme280_compensate_temperature_double(v_uncomp_temperature_s32); char temperature[12]; sprintf(temperature, "%.2f degC", temp); double press = bme280_compensate_pressure_double(v_uncomp_pressure_s32) / 100; // Pa -> hPa char pressure[10]; sprintf(pressure, "%.2f hPa", press); double hum = bme280_compensate_humidity_double(v_uncomp_humidity_s32); char humidity[10]; sprintf(humidity, "%.2f %%", hum); if (com_rslt == SUCCESS) { if (MQTT_CONNEECTED) { esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME280, temperature, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME280, pressure, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME280, humidity, 0, 0, 0); vTaskDelay(5000 / portTICK_PERIOD_MS); } } else { ESP_LOGE(TAG_BME280, "measure error. code: %d", com_rslt); } } } else { ESP_LOGE(TAG_BME280, "init or setting error. code: %d", com_rslt); } } |
Сначала определяются параметры структуры bme280_t. Это включает в себя запись по I2C, чтение по I2C, адрес ведомого устройства и задержку.
|
1 2 3 4 5 6 |
struct bme280_t bme280 = { .bus_write = BME280_I2C_bus_write, .bus_read = BME280_I2C_bus_read, .dev_addr = BME280_I2C_ADDRESS1, .delay_msec = BME280_delay_msek }; |
Объявите следующие переменные для хранения данных датчика BME280:
|
1 2 3 4 |
s32 com_rslt; s32 v_uncomp_pressure_s32; s32 v_uncomp_temperature_s32; s32 v_uncomp_humidity_s32; |
Инициализируйте датчик BME280, вызвав функцию bme280_init() и указав в качестве параметра указатель на структуру bme280_t.
|
1 |
com_rslt = bme280_init(&bme280); |
Далее мы устанавливаем значение параметра передискретизации давления в регистре 0xF4 с битов 2 на 4, вызывая функцию bme280_set_overamp_pressure(). Эта функция принимает один параметр — значение передискретизации давления. Она может принимать любое из перечисленных ниже значений:
- Пропущено: значение равно 0x00
- BME280_OVERSAMP_1X: Значение равно 0x01
- BME280_OVERSAMP_2X: Значение равно 0x02
- BME280_OVERSAMP_4X: Значение равно 0x03
- BME280_OVERSAMP_8X: Значение равно 0x04
- BME280_OVERSAMP_16X: Значения равны 0x05, 0x06 и 0x07.
|
1 |
com_rslt += bme280_set_oversamp_pressure(BME280_OVERSAMP_16X); |
Аналогичным образом, мы устанавливаем значение параметра передискретизации температуры в битах регистра 0xF4 с 5 на 7, вызывая функцию bme280_set_overamp_temperature(). Эта функция принимает один параметр — значение передискретизации температуры. Она может принимать любое из перечисленных ниже значений:
- Пропущено: значение равно 0x00
- BME280_OVERSAMP_1X: Значение равно 0x01
- BME280_OVERSAMP_2X: Значение равно 0x02
- BME280_OVERSAMP_4X: Значение равно 0x03
- BME280_OVERSAMP_8X: Значение равно 0x04
- BME280_OVERSAMP_16X: Значения равны 0x05, 0x06 и 0x07.
|
1 |
com_rslt += bme280_set_oversamp_temperature(BME280_OVERSAMP_2X); |
Аналогичным образом, мы устанавливаем параметр передискретизации влажности в битах регистра 0xF2 с 0 на 2, вызывая функцию bme280_set_overamp_humidity(). Эта функция принимает один параметр — значение передискретизации влажности. Она может принимать любое из перечисленных ниже значений:
- Пропущено: значение равно 0x00
- BME280_OVERSAMP_1X
- BME280_OVERSAMP_2X
- BME280_OVERSAMP_4X
- BME280_OVERSAMP_8X
- BME280_OVERSAMP_16X
|
1 |
com_rslt += bme280_set_oversamp_humidity(BME280_OVERSAMP_1X); |
После этого мы запишем время работы в режиме ожидания с датчика в регистр 0xF5, биты 5-7, вызвав функцию bme280_set_standby_durn(). Эта функция принимает один параметр — значение времени работы в режиме ожидания. Она может принимать любое из перечисленных ниже значений:
- BME280_STANDBY_TIME_1_MS
- BME280_STANDBY_TIME_63_MS
- BME280_STANDBY_TIME_125_MS
- BME280_STANDBY_TIME_250_MS
- BME280_STANDBY_TIME_500_MS
- BME280_STANDBY_TIME_1000_MS
- BME280_STANDBY_TIME_2000_MS
- BME280_STANDBY_TIME_4000_MS
|
1 |
com_rslt += bme280_set_standby_durn(BME280_STANDBY_TIME_1_MS); |
Затем мы зададим параметры фильтра в регистре 0xF5, биты 3 и 4, вызвав функцию bme280_set_filter(). Эта функция также принимает один параметр — значение коэффициента IIR-фильтра. Она может принимать любое из перечисленных ниже значений:
- BME280_FILTER_COEFF_OFF
- BME280_FILTER_COEFF_2
- BME280_FILTER_COEFF_4
- BME280_FILTER_COEFF_8
- BME280_FILTER_COEFF_16
|
1 |
com_rslt += bme280_set_filter(BME280_FILTER_COEFF_16); |
Наконец, мы установим режим энергопотребления датчика, вызвав функцию bme280_set_power_mode(). Эта функция принимает один параметр, обозначающий режим работы в регистре 0xF4, биты 0 и 1. Он может принимать любое из перечисленных ниже значений:
- BME280_SLEEP_MODE
- BME280_FORCED_MODE
- BME280_NORMAL_MODE
|
1 |
com_rslt += bme280_set_power_mode(BME280_NORMAL_MODE); |
Если все параметры датчика установлены успешно, мы запустим бесконечный цикл, который считывает и публикует данные датчика. Для считывания нескомпенсированных показаний давления, температуры и влажности мы вызываем функцию bme280_read_uncomp_pressure_temperature_humidity().
|
1 |
com_rslt = bme280_read_uncomp_pressure_temperature_humidity(&v_uncomp_pressure_s32, &v_uncomp_temperature_s32, &v_uncomp_humidity_s32); |
Функция bme280_compensate_temperature_double() выводит фактическую температуру из нескомпенсированной. Функция bme280_compensate_pressure_double() выводит фактическое давление из нескомпенсированного давления. Аналогично, функция bme280_compensate_humidity_double() выводит фактическую влажность из нескомпенсированной влажности. Сначала мы преобразуем тип данных double в массив символов, округлим до двух десятичных знаков и добавим единицы измерения для каждого показания.
|
1 2 3 4 5 6 7 8 9 10 11 |
double temp = bme280_compensate_temperature_double(v_uncomp_temperature_s32); char temperature[12]; sprintf(temperature, "%.2f degC", temp); double press = bme280_compensate_pressure_double(v_uncomp_pressure_s32)/100; // Pa -> hPa char pressure[10]; sprintf(pressure, "%.2f hPa", press); double hum = bme280_compensate_humidity_double(v_uncomp_humidity_s32); char humidity[10]; sprintf(humidity, "%.2f %%", hum); |
Если датчик BME280 измеряет корректно и если MQTT_CONNECTED имеет значение true, то показания датчика будут публиковаться в соответствующие топики каждые 5 секунд. В случае ошибки измерения или инициализации датчика на консоль будет выведено соответствующее сообщение.
Функция esp_mqtt_client_publish() отвечает за публикацию сообщения. Она принимает шесть параметров, включая дескриптор MQTT-клиента, строку темы, данные для публикации, длину данных, QoS и флаг сохранения.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if (com_rslt == SUCCESS) { if (MQTT_CONNECTED) { esp_mqtt_client_publish(client, MQTT_PUB_TEMP_BME280, temperature, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_PRES_BME280, pressure, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_HUM_BME280, humidity, 0, 0, 0); vTaskDelay(5000 / portTICK_PERIOD_MS); } } else { ESP_LOGE(TAG_BME280, "measure error. code: %d", com_rslt); } |
app_main()
Внутри функции app_main() сначала инициализируется NVS путем вызова функции nvs_flash_init(). Затем проверяется, не содержит ли раздел NVS пустых страниц или содержит ли он данные в формате, отличном от текущей версии кода. Если выполняется хотя бы одно из этих условий, то флэш-память NVS очищается с помощью nvs_flash_erase() и инициализируется заново. Затем вызывается функция wifi_init(), которая инициализирует Wi-Fi в режиме станции для ESP32. После этого используется xTaskCreate() для создания задачи Publisher_Task, которая будет публиковать показания датчиков в соответствующие темы каждые 5 секунд.
|
1 2 3 4 5 6 7 8 9 10 11 |
void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); wifi_init(); xTaskCreate(Publisher_Task, "Publisher_Task", 1024 * 5, NULL, 5, NULL); } |
Компиляция скетча
Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.
|
1 |
idf.py -p COMX flash monitor |
После успешной прошивки кода вы сможете просматривать различные сообщения на терминале ESP-IDF. Сначала успешно устанавливается соединение Wi-Fi. Давайте настроим нашу панель управления Node-Red для просмотра данных с датчиков.
Панель мониторинга Node-Red как подписчик MQTT-сообщений показаний BME280
Наш ESP32 Publisher отправляет показания датчика BME280 в определенные темы каждые 5 секунд. В этом разделе мы покажем, как настроить Node-Red в качестве MQTT-подписчика для тем, связанных с датчиком BME280 на нашем ESP32. Используя панель управления Node-Red, мы сможем просматривать показания датчика на нашем смартфоне/ноутбуке/компьютере. Давайте сначала разберемся, как создать панель управления.
Для подписки на темы MQTT можно использовать любой другой сервис подписки MQTT, например, MQTTx Desktop Client, который мы использовали ранее.
Обратитесь к руководствам по началу работы с Node-Red на Raspberry Pi:
- начало работы с Node-RED на Raspberry Pi;
- начало работы с панелью инструментов Node-RED на Raspberry Pi.
Для доступа к Node-RED нам потребуется IP-адрес вашего Raspberry Pi и номер порта, на котором доступен Node-RED. По умолчанию он запускается на порту 1880. Откройте любой веб-браузер и введите IP-адрес вашего Raspberry Pi, а затем номер порта.
Например:
|
1 |
192.168.18.8:1880 |
Создание потока
Это откроет интерфейс Node-RED. Теперь начнём создавать поток.
Перейдите в правую часть экрана и найдите раздел dashboard («Панель управления»). Установите панель управления, если она еще не установлена.
Нажмите ‘Dashboard’ («Панель управления»), чтобы открыть вкладку. Теперь создадим узел вкладки панели управления. Чтобы создать новую вкладку, перейдите на вкладку «Панель управления» и нажмите кнопку «+вкладка». Присвойте ей имя по своему усмотрению и выберите значок из списка ссылок ниже:
После этого нажмите Update («Обновить»), чтобы сохранить изменения.
Мы создали вкладку под названием Home («Главная») и используем иконки из Angular Material Icons.

Далее добавим виджеты. Добавим одну группу на вкладку для датчика. Чтобы создать группу, нажмите кнопку «+группа» на вкладке.
Мы назвали эту группу ‘BME280 Readings’.
Теперь добавим виджеты в нашу группу. Для отображения показаний температуры и влажности выберем датчики, а для отображения показаний давления — линейный график. Перейдите в раздел Nodes («Узлы»), расположенный в левом углу, и прокрутите вниз, чтобы найти необходимые узлы в разделе Dashboard («Панель управления»).
Перетащите два индикатора, один график и три узла MQTT в поток, как показано ниже:
Теперь давайте отредактируем свойства узлов. Щёлкните по первому узлу mqtt. Откроются его свойства. Для сервера (брокера MQTT), которым является Mosquitto Broker на Raspberry Pi, установите значение localhost:1883. Добавьте имя темы, на которую нужно подписаться, укажите QoS и затем нажмите кнопку Done («Готово»).
Данный узел подписан на тему 'esp32/bme280/temperature'.

Аналогичным образом отредактируйте свойства второго MQTT-соединения в узле. Этот конкретный узел подписан на тему 'esp32/bme280/humidity'.

Аналогичным образом отредактируйте свойства третьего MQTT-сервера в узле. Этот конкретный узел подписан на тему 'esp32/bme280/pressure'.

Отредактируйте свойства первого и второго индикаторов, как показано ниже:


Показания давления мы отобразим на линейном графике. Для этого отредактируйте свойства линейного графика соответствующим образом.

Соедините узлы, как показано ниже. Затем нажмите кнопку Deploy «Развернуть» вверху.
Статус MQTT-соединения в узлах меняется на ‘connected’ («подключено»).
Скачать этот поток можно по следующей ссылке.
Чтобы открыть панель управления, введите в новом окне веб-браузера: http://Ваш_IP_адрес_RPI:1880/ui и нажмите Enter. Откроется пользовательский интерфейс, отображающий два индикатора и линейный график с показаниями датчика BME280.
Так выглядит панель управления Node-Red при просмотре на смартфоне.
17 просмотров














