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

Подключение DS18B20 к ESP32
Датчик температуры DS18B20 широко используется в электронных проектах, где для сбора данных используется один провод, поэтому он позиционируется как программируемый по одному проводу. Он использует один вывод GPIO микроконтроллера для вывода текущего показания температуры окружающей среды. Используя минимальное количество выводов, мы можем удобно получать доступ к нескольким показаниям температуры, подключая датчики DS18B20 к одному и тому же выводу GPIO.
Для подключения платы ESP32 к датчику DS18B20 нам понадобятся следующие компоненты:
- Плата ESP32.
- Датчик DS18B20.
- Подтягивающий резистор 4,7 кОм.
- Соединительные провода.
- Макетная плата.
Датчик DS18B20 может включаться в двух режимах: обычном и паразитном. В обычном режиме датчик питается от внешнего источника через вывод VCC и резистор 4,7 кОм. Однако в паразитном режиме датчик питается от линии данных, поэтому внешний источник питания не требуется. Мы будем питать наш датчик DS18B20 в обычном режиме.
В таблице ниже показано подключение трех контактов датчика DS18B20 к плате ESP32.
| DS18B20 | ESP32 |
| VCC | 3.3V / Vin |
| Data | GPIO4 |
| GND | GND |
Подключите вывод VCC датчика к выводу 3,3 В или Vin платы ESP32 и соедините заземление обоих устройств. Кроме того, подключите вывод данных датчика к соответствующему выводу GPIO платы ESP32, который можно настроить как выходной вывод. Обратите внимание, что выводы GPIO34-39 являются только входными и не могут использоваться для управления однопроводной шиной. Мы используем GPIO4 для подключения к выводу данных через подтягивающий резистор 4,7 кОм. Один конец резистора будет подключен к выходу данных, а другой — к источнику питания.
Обратите внимание, что если вы используете модуль DS18B20, то вам не нужно добавлять внешний подтягивающий резистор. Это связано с тем, что модуль уже оснащен датчиком и встроенным подтягивающим резистором.
Следуйте приведенной ниже принципиальной схеме, чтобы соединить два устройства.

Публикация показаний DS18B20 в Node-Red по MQTT с использованием ESP32 и ESP-IDF
Мы создадим проект в VS Code с расширением ESP-IDF. Код будет считывать показания температуры с помощью библиотеки DS18B20, а затем использовать библиотеку mqtt_client для подключения ESP32 к MQTT-брокеру и публикации показаний в различных темах. Мы будем использовать простую библиотеку DS18B20 для одного датчика DS18B20 с ESP32. Она предоставлена feelfreelinux на GitHub.
Создание примера проекта
Откройте 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» и добавьте в неё следующие файлы:

CMakeLists.txt
|
1 2 |
idf_component_register(SRCS "ds18b20.c" INCLUDE_DIRS "include") |
ds18b20.h
|
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 |
/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <esp_system.h> #ifndef DS18B20_H_ #define DS18B20_H_ #define noInterrupts() portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;taskENTER_CRITICAL(&mux) #define interrupts() taskEXIT_CRITICAL(&mux) #define DEVICE_DISCONNECTED_C -127 #define DEVICE_DISCONNECTED_F -196.6 #define DEVICE_DISCONNECTED_RAW -7040 #define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) #define pgm_read_byte(addr) (*(const unsigned char *)(addr)) typedef uint8_t DeviceAddress[8]; typedef uint8_t ScratchPad[9]; // Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0 // Tiny 2x16 entry CRC table created by Arjen Lentz // See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table static const uint8_t dscrc2x16_table[] = { 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, 0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8, 0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74 }; /* *INDENT-OFF* */ #ifdef __cplusplus extern "C" { #endif /* *INDENT-ON* */ void ds18b20_init(int GPIO); #define ds18b20_send ds18b20_write #define ds18b20_send_byte ds18b20_write_byte #define ds18b20_RST_PULSE ds18b20_reset void ds18b20_write(char bit); unsigned char ds18b20_read(void); void ds18b20_write_byte(char data); unsigned char ds18b20_read_byte(void); unsigned char ds18b20_reset(void); bool ds18b20_setResolution(const DeviceAddress tempSensorAddresses[], int numAddresses, uint8_t newResolution); bool ds18b20_isConnected(const DeviceAddress *deviceAddress, uint8_t *scratchPad); void ds18b20_writeScratchPad(const DeviceAddress *deviceAddress, const uint8_t *scratchPad); bool ds18b20_readScratchPad(const DeviceAddress *deviceAddress, uint8_t *scratchPad); void ds18b20_select(const DeviceAddress *address); uint8_t ds18b20_crc8(const uint8_t *addr, uint8_t len); bool ds18b20_isAllZeros(const uint8_t * const scratchPad); bool isConversionComplete(); uint16_t millisToWaitForConversion(); void ds18b20_requestTemperatures(); float ds18b20_getTempF(const DeviceAddress *deviceAddress); float ds18b20_getTempC(const DeviceAddress *deviceAddress); int16_t calculateTemperature(const DeviceAddress *deviceAddress, uint8_t* scratchPad); float ds18b20_get_temp(void); void reset_search(); bool search(uint8_t *newAddr, bool search_mode); /* *INDENT-OFF* */ #ifdef __cplusplus } #endif /* *INDENT-ON* */ #endif |
ds18b20.c
|
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 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 |
/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "esp32/rom/ets_sys.h" #include "ds18b20.h" // OneWire commands #define GETTEMP 0x44 // Tells device to take a temperature reading and put it on the scratchpad #define SKIPROM 0xCC // Command to address all devices on the bus #define SELECTDEVICE 0x55 // Command to address all devices on the bus #define COPYSCRATCH 0x48 // Copy scratchpad to EEPROM #define READSCRATCH 0xBE // Read from scratchpad #define WRITESCRATCH 0x4E // Write to scratchpad #define RECALLSCRATCH 0xB8 // Recall from EEPROM to scratchpad #define READPOWERSUPPLY 0xB4 // Determine if device needs parasite power #define ALARMSEARCH 0xEC // Query bus for devices with an alarm condition // Scratchpad locations #define TEMP_LSB 0 #define TEMP_MSB 1 #define HIGH_ALARM_TEMP 2 #define LOW_ALARM_TEMP 3 #define CONFIGURATION 4 #define INTERNAL_BYTE 5 #define COUNT_REMAIN 6 #define COUNT_PER_C 7 #define SCRATCHPAD_CRC 8 // DSROM FIELDS #define DSROM_FAMILY 0 #define DSROM_CRC 7 // Device resolution #define TEMP_9_BIT 0x1F // 9 bit #define TEMP_10_BIT 0x3F // 10 bit #define TEMP_11_BIT 0x5F // 11 bit #define TEMP_12_BIT 0x7F // 12 bit uint8_t DS_GPIO; uint8_t init=0; uint8_t bitResolution=12; uint8_t devices=0; DeviceAddress ROM_NO; uint8_t LastDiscrepancy; uint8_t LastFamilyDiscrepancy; bool LastDeviceFlag; /// Sends one bit to bus void ds18b20_write(char bit){ if (bit & 1) { gpio_set_direction(DS_GPIO, GPIO_MODE_OUTPUT); noInterrupts(); gpio_set_level(DS_GPIO,0); ets_delay_us(6); gpio_set_direction(DS_GPIO, GPIO_MODE_INPUT); // release bus ets_delay_us(64); interrupts(); } else { gpio_set_direction(DS_GPIO, GPIO_MODE_OUTPUT); noInterrupts(); gpio_set_level(DS_GPIO,0); ets_delay_us(60); gpio_set_direction(DS_GPIO, GPIO_MODE_INPUT); // release bus ets_delay_us(10); interrupts(); } } // Reads one bit from bus unsigned char ds18b20_read(void){ unsigned char value = 0; gpio_set_direction(DS_GPIO, GPIO_MODE_OUTPUT); noInterrupts(); gpio_set_level(DS_GPIO, 0); ets_delay_us(6); gpio_set_direction(DS_GPIO, GPIO_MODE_INPUT); ets_delay_us(9); value = gpio_get_level(DS_GPIO); ets_delay_us(55); interrupts(); return (value); } // Sends one byte to bus void ds18b20_write_byte(char data){ unsigned char i; unsigned char x; for(i=0;i<8;i++){ x = data>>i; x &= 0x01; ds18b20_write(x); } ets_delay_us(100); } // Reads one byte from bus unsigned char ds18b20_read_byte(void){ unsigned char i; unsigned char data = 0; for (i=0;i<8;i++) { if(ds18b20_read()) data|=0x01<<i; ets_delay_us(15); } return(data); } // Sends reset pulse unsigned char ds18b20_reset(void){ unsigned char presence; gpio_set_direction(DS_GPIO, GPIO_MODE_OUTPUT); noInterrupts(); gpio_set_level(DS_GPIO, 0); ets_delay_us(480); gpio_set_level(DS_GPIO, 1); gpio_set_direction(DS_GPIO, GPIO_MODE_INPUT); ets_delay_us(70); presence = (gpio_get_level(DS_GPIO) == 0); ets_delay_us(410); interrupts(); return presence; } bool ds18b20_setResolution(const DeviceAddress tempSensorAddresses[], int numAddresses, uint8_t newResolution) { bool success = false; // handle the sensors with configuration register newResolution = constrain(newResolution, 9, 12); uint8_t newValue = 0; ScratchPad scratchPad; // loop through each address for (int i = 0; i < numAddresses; i++){ // we can only update the sensor if it is connected if (ds18b20_isConnected((DeviceAddress*) tempSensorAddresses[i], scratchPad)) { switch (newResolution) { case 12: newValue = TEMP_12_BIT; break; case 11: newValue = TEMP_11_BIT; break; case 10: newValue = TEMP_10_BIT; break; case 9: default: newValue = TEMP_9_BIT; break; } // if it needs to be updated we write the new value if (scratchPad[CONFIGURATION] != newValue) { scratchPad[CONFIGURATION] = newValue; ds18b20_writeScratchPad((DeviceAddress*) tempSensorAddresses[i], scratchPad); } // done success = true; } } return success; } void ds18b20_writeScratchPad(const DeviceAddress *deviceAddress, const uint8_t *scratchPad) { ds18b20_reset(); ds18b20_select(deviceAddress); ds18b20_write_byte(WRITESCRATCH); ds18b20_write_byte(scratchPad[HIGH_ALARM_TEMP]); // high alarm temp ds18b20_write_byte(scratchPad[LOW_ALARM_TEMP]); // low alarm temp ds18b20_write_byte(scratchPad[CONFIGURATION]); ds18b20_reset(); } bool ds18b20_readScratchPad(const DeviceAddress *deviceAddress, uint8_t* scratchPad) { // send the reset command and fail fast int b = ds18b20_reset(); if (b == 0) return false; ds18b20_select(deviceAddress); ds18b20_write_byte(READSCRATCH); // Read all registers in a simple loop // byte 0: temperature LSB // byte 1: temperature MSB // byte 2: high alarm temp // byte 3: low alarm temp // byte 4: DS18B20 & DS1822: configuration register // byte 5: internal use & crc // byte 6: DS18B20 & DS1822: store for crc // byte 7: DS18B20 & DS1822: store for crc // byte 8: SCRATCHPAD_CRC for (uint8_t i = 0; i < 9; i++) { scratchPad[i] = ds18b20_read_byte(); } b = ds18b20_reset(); return (b == 1); } void ds18b20_select(const DeviceAddress *address){ uint8_t i; ds18b20_write_byte(SELECTDEVICE); // Choose ROM for (i = 0; i < 8; i++) ds18b20_write_byte(((uint8_t *)address)[i]); } void ds18b20_requestTemperatures(){ ds18b20_reset(); ds18b20_write_byte(SKIPROM); ds18b20_write_byte(GETTEMP); unsigned long start = esp_timer_get_time() / 1000ULL; while (!isConversionComplete() && ((esp_timer_get_time() / 1000ULL) - start < millisToWaitForConversion())) vPortYield(); } bool isConversionComplete() { uint8_t b = ds18b20_read(); return (b == 1); } uint16_t millisToWaitForConversion() { switch (bitResolution) { case 9: return 94; case 10: return 188; case 11: return 375; default: return 750; } } bool ds18b20_isConnected(const DeviceAddress *deviceAddress, uint8_t *scratchPad) { bool b = ds18b20_readScratchPad(deviceAddress, scratchPad); return b && !ds18b20_isAllZeros(scratchPad) && (ds18b20_crc8(scratchPad, 8) == scratchPad[SCRATCHPAD_CRC]); } uint8_t ds18b20_crc8(const uint8_t *addr, uint8_t len){ uint8_t crc = 0; while (len--) { crc = *addr++ ^ crc; // just re-using crc as intermediate crc = pgm_read_byte(dscrc2x16_table + (crc & 0x0f)) ^ pgm_read_byte(dscrc2x16_table + 16 + ((crc >> 4) & 0x0f)); } return crc; } bool ds18b20_isAllZeros(const uint8_t * const scratchPad) { for (size_t i = 0; i < 9; i++) { if (scratchPad[i] != 0) { return false; } } return true; } float ds18b20_getTempF(const DeviceAddress *deviceAddress) { ScratchPad scratchPad; if (ds18b20_isConnected(deviceAddress, scratchPad)){ int16_t rawTemp = calculateTemperature(deviceAddress, scratchPad); if (rawTemp <= DEVICE_DISCONNECTED_RAW) return DEVICE_DISCONNECTED_F; // C = RAW/128 // F = (C*1.8)+32 = (RAW/128*1.8)+32 = (RAW*0.0140625)+32 return ((float) rawTemp * 0.0140625f) + 32.0f; } return DEVICE_DISCONNECTED_F; } float ds18b20_getTempC(const DeviceAddress *deviceAddress) { ScratchPad scratchPad; if (ds18b20_isConnected(deviceAddress, scratchPad)){ int16_t rawTemp = calculateTemperature(deviceAddress, scratchPad); if (rawTemp <= DEVICE_DISCONNECTED_RAW) return DEVICE_DISCONNECTED_F; // C = RAW/128 // F = (C*1.8)+32 = (RAW/128*1.8)+32 = (RAW*0.0140625)+32 return (float) rawTemp/128.0f; } return DEVICE_DISCONNECTED_F; } // reads scratchpad and returns fixed-point temperature, scaling factor 2^-7 int16_t calculateTemperature(const DeviceAddress *deviceAddress, uint8_t* scratchPad) { int16_t fpTemperature = (((int16_t) scratchPad[TEMP_MSB]) << 11) | (((int16_t) scratchPad[TEMP_LSB]) << 3); return fpTemperature; } // Returns temperature from sensor float ds18b20_get_temp(void) { if(init==1){ unsigned char check; char temp1=0, temp2=0; check=ds18b20_RST_PULSE(); if(check==1) { ds18b20_send_byte(0xCC); ds18b20_send_byte(0x44); vTaskDelay(750 / portTICK_RATE_MS); check=ds18b20_RST_PULSE(); ds18b20_send_byte(0xCC); ds18b20_send_byte(0xBE); temp1=ds18b20_read_byte(); temp2=ds18b20_read_byte(); check=ds18b20_RST_PULSE(); float temp=0; temp=(float)(temp1+(temp2*256))/16; return temp; } else{return 0;} } else{return 0;} } void ds18b20_init(int GPIO) { DS_GPIO = GPIO; gpio_pad_select_gpio(DS_GPIO); init = 1; } // // You need to use this function to start a search again from the beginning. // You do not need to do it for the first search, though you could. // void reset_search() { devices=0; // reset the search state LastDiscrepancy = 0; LastDeviceFlag = false; LastFamilyDiscrepancy = 0; for (int i = 7; i >= 0; i--) { ROM_NO[i] = 0; } } // --- Replaced by the one from the Dallas Semiconductor web site --- //-------------------------------------------------------------------------- // Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing // search state. // Return TRUE : device found, ROM number in ROM_NO buffer // FALSE : device not found, end of search bool search(uint8_t *newAddr, bool search_mode) { uint8_t id_bit_number; uint8_t last_zero, rom_byte_number; bool search_result; uint8_t id_bit, cmp_id_bit; unsigned char rom_byte_mask, search_direction; // initialize for search id_bit_number = 1; last_zero = 0; rom_byte_number = 0; rom_byte_mask = 1; search_result = false; // if the last call was not the last one if (!LastDeviceFlag) { // 1-Wire reset if (!ds18b20_reset()) { // reset the search LastDiscrepancy = 0; LastDeviceFlag = false; LastFamilyDiscrepancy = 0; return false; } // issue the search command if (search_mode == true) { ds18b20_write_byte(0xF0); // NORMAL SEARCH } else { ds18b20_write_byte(0xEC); // CONDITIONAL SEARCH } // loop to do the search do { // read a bit and its complement id_bit = ds18b20_read(); cmp_id_bit = ds18b20_read(); // check for no devices on 1-wire if ((id_bit == 1) && (cmp_id_bit == 1)) { break; } else { // all devices coupled have 0 or 1 if (id_bit != cmp_id_bit) { search_direction = id_bit; // bit write value for search } else { // if this discrepancy if before the Last Discrepancy // on a previous next then pick the same as last time if (id_bit_number < LastDiscrepancy) { search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); } else { // if equal to last pick 1, if not then pick 0 search_direction = (id_bit_number == LastDiscrepancy); } // if 0 was picked then record its position in LastZero if (search_direction == 0) { last_zero = id_bit_number; // check for Last discrepancy in family if (last_zero < 9) LastFamilyDiscrepancy = last_zero; } } // set or clear the bit in the ROM byte rom_byte_number // with mask rom_byte_mask if (search_direction == 1) ROM_NO[rom_byte_number] |= rom_byte_mask; else ROM_NO[rom_byte_number] &= ~rom_byte_mask; // serial number search direction write bit ds18b20_write(search_direction); // increment the byte counter id_bit_number // and shift the mask rom_byte_mask id_bit_number++; rom_byte_mask <<= 1; // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask if (rom_byte_mask == 0) { rom_byte_number++; rom_byte_mask = 1; } } } while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 // if the search was successful then if (!(id_bit_number < 65)) { // search successful so set LastDiscrepancy,LastDeviceFlag,search_result LastDiscrepancy = last_zero; // check for last device if (LastDiscrepancy == 0) { LastDeviceFlag = true; } search_result = true; } } // if no device found then reset counters so next 'search' will be like a first if (!search_result || !ROM_NO[0]) { devices=0; LastDiscrepancy = 0; LastDeviceFlag = false; LastFamilyDiscrepancy = 0; search_result = false; } else { for (int i = 0; i < 8; i++){ newAddr[i] = ROM_NO[i]; } devices++; } return search_result; } |
- Теперь перейдите к файлу main.c. В папке main находится исходный код, то есть файл main.c будет находиться здесь. Перейдите в папку main > main.c и откройте его. Скопируйте приведенный ниже код из этого файла и сохраните его.
Код ESP32 для работы с MQTT и DS18B20
|
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 |
#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 "rom/ets_sys.h" #include "driver/gpio.h" #include "sdkconfig.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 "ds18b20.h" const int DS_PIN = 4; static const char *TAG = "MQTT_EXAMPLE"; #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_TEMPC_DS18B20 "esp32/ds18b20/temperatureC" #define MQTT_PUB_TEMPF_DS18B20 "esp32/ds18b20/temperatureF" 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; 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 *pvParameters){ ds18b20_init(DS_PIN); while (1) { ds18b20_requestTemperatures(); float TempC = ds18b20_get_temp(); printf("Temperature: %.2fC\n", TempC); char temperatureC[12]; sprintf(temperatureC, "%.2f", TempC); float TempF = TempC*9/5 + 32; printf("Temperature: %.2fF\n", TempF); char temperatureF[12]; sprintf(temperatureF, "%.2f", TempF); if (MQTT_CONNECTED){ esp_mqtt_client_publish(client, MQTT_PUB_TEMPC_DS18B20, temperatureC, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_TEMPF_DS18B20, temperatureF, 0, 0, 0); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } void app_main() { nvs_flash_init(); wifi_init(); xTaskCreate(&Publisher_Task, "Publisher_Task", 2048, NULL, 5, NULL ); } |
Как работает код?
Для начала мы подключим необходимые библиотеки, включая драйвер GPIO, библиотеку DS18B20, библиотеки FreeRTOS для генерации задержек, esp_wifi.h для включения Wi-Fi-соединения, esp_event.h для мониторинга событий Wi-Fi и MQTT, mqtt_client.h для настройки протокола MQTT для публикации/подписки и esp_log.h в качестве библиотеки для ведения журналов.
|
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 |
#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 "rom/ets_sys.h" #include "driver/gpio.h" #include "sdkconfig.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 "ds18b20.h" |
Переменная 'DS_PIN' содержит номер однопроводного GPIO, который будет использоваться для подключения к выводу данных датчика. Мы используем GPIO4.
|
1 |
const int DS_PIN = 4; |
Этот код использует информационное логирование. Функция логирования принимает два аргумента. Первый аргумент — это тег, а второй — отформатированная строка. Поэтому эта глобальная переменная будет полезна при вызове функций ESP_LOGI(). Здесь «MQTT_EXAMPLE» — это тег, который будет использоваться при логировании.
|
1 |
static const char *TAG = "MQTT_EXAMPLE"; |
Далее мы определили параметры для 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/ds18b20/temperatureC для показаний температуры в градусах Цельсия и esp32/ds18b20/temperatureF для показаний температуры в градусах Фаренгейта.
|
1 2 |
#define MQTT_PUB_TEMPC_DS18B20 "esp32/ds18b20/temperatureC" #define MQTT_PUB_TEMPF_DS18B20 "esp32/ds18b20/temperatureF" |
Обработка событий 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 |
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; 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_CONNEECTED = 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_CONNEECTED = 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() инициализируется датчик DS18B20 и происходит сбор данных с него. Затем эти данные публикуются в соответствующие темы каждую секунду с использованием протокола 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 |
void Publisher_Task(void *pvParameters){ ds18b20_init(DS_PIN); while (1) { ds18b20_requestTemperatures(); float TempC = ds18b20_get_temp(); printf("Temperature: %.2fC\n", TempC); char temperatureC[12]; sprintf(temperatureC, "%.2f", TempC); float TempF = TempC*9/5 + 32; printf("Temperature: %.2fF\n", TempF); char temperatureF[12]; sprintf(temperatureF, "%.2f", TempF); if (MQTT_CONNECTED){ esp_mqtt_client_publish(client, MQTT_PUB_TEMPC_DS18B20, temperatureC, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_TEMPF_DS18B20, temperatureF, 0, 0, 0); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } |
Сначала мы инициализируем датчик DS18B20 с помощью функции ds18b20_init() и передаем в качестве параметра контакт GPIO с одним проводом.
|
1 |
ds18b20_init(DS_PIN); |
Внутри бесконечного цикла while мы начнем получать показания датчика DS18B20, сначала запросив температуру с помощью функции ds18b20_requestTemperatures(). Затем мы получим показания температуры в градусах Цельсия с помощью функции ds18b20_get_temp(). Эта функция возвращает показания температуры в виде числа с плавающей запятой. Сначала мы преобразуем число с плавающей запятой в массив символов и округлим до двух десятичных знаков. Аналогично, мы преобразуем показания температуры в градусах Цельсия в градусы Фаренгейта, умножив их на 9/5 и прибавив 32. Оба показания считываются непрерывно с задержкой в одну секунду и выводятся на терминал ESP-IDF.
Если MQTT_CONNECTED имеет значение true, то показания датчиков будут публиковаться в соответствующие темы каждую секунду.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
while (1) { ds18b20_requestTemperatures(); float TempC = ds18b20_get_temp(); printf("Temperature: %.2fC\n", TempC); char temperatureC[12]; sprintf(temperatureC, "%.2f", TempC); float TempF = TempC*9/5 + 32; printf("Temperature: %.2fF\n", TempF); char temperatureF[12]; sprintf(temperatureF, "%.2f", TempF); if (MQTT_CONNECTED){ esp_mqtt_client_publish(client, MQTT_PUB_TEMPC_DS18B20, temperatureC, 0, 0, 0); esp_mqtt_client_publish(client, MQTT_PUB_TEMPF_DS18B20, temperatureF, 0, 0, 0); } vTaskDelay(1000 / portTICK_PERIOD_MS); } |
app_main()
Внутри функции app_main() сначала инициализируется NVS путем вызова функции nvs_flash_init(). Затем вызывается функция wifi_init(), которая инициализирует Wi-Fi в режиме станции для ESP32. После этого мы используем xTaskCreate() для создания задачи Publisher_Task, которая будет публиковать показания датчиков в соответствующие темы каждую секунду.
|
1 2 3 4 5 6 |
void app_main() { nvs_flash_init(); wifi_init(); xTaskCreate(&Publisher_Task, "Publisher_Task", 2048, NULL, 5, NULL ); } |
Компиляция скетча
Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.
|
1 |
idf.py -p COMX flash monitor |
После успешной прошивки кода вы сможете просматривать различные сообщения на терминале ESP-IDF. Сначала устанавливается соединение Wi-Fi, а затем — соединение MQTT. Вы также сможете постоянно отслеживать показания датчика DS18B20, обновляющиеся на терминале.
Теперь давайте настроим панель мониторинга Node-Red для просмотра данных с датчиков.
Панель мониторинга Node-Red в качестве подписчика показаний MQTT DS18B20
Наш ESP32 Publisher отправляет показания отдельных датчиков DS18B20 в определенные темы каждую секунду. В этом разделе мы покажем, как настроить Node-Red в качестве MQTT-подписчика для тем, связанных с датчиком DS18B20 на нашем ESP32. Используя панель управления Node-Red, мы сможем просматривать показания датчиков на нашем смартфоне/ноутбуке/компьютере. Давайте сначала разберемся, как создать панель управления.
Для подписки на темы MQTT можно использовать любой другой сервис подписки MQTT, например, MQTTx Desktop Client, который мы использовали ранее.
Для доступа к 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.

Далее добавим виджеты. Добавим одну группу на вкладку для датчика. Чтобы создать группу, нажмите кнопку «+группа» на вкладке.
Мы назвали эту группу «DS18B20 Readings».

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

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

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


Соедините узлы, как показано ниже. Затем нажмите кнопку Deploy («Развернуть») вверху.
Статус MQTT-соединения в узлах меняется на ‘connected’ («подключено»).

Чтобы открыть панель управления, введите в новом окне веб-браузера: http://Your_RPI_IP_address:1880/ui и нажмите Enter. Откроется пользовательский интерфейс, отображающий два индикатора с показаниями датчика DS18B20.
Так выглядит панель управления Node-Red при просмотре на смартфоне.












