Перейти к содержимому

Термостат на ESP32 с удаленным управлением. Часть 5. Добавляем выгрузку данных на внешние сервисы

Добрый день, уважаемый читатель! В прошлых статьях я рассказывал, как собрать устройство телеметрии на базе микроконтроллера ESP32 DevKitC WROOM-32x и фреймdорка Espressif IoT Development Framework. Если вы ещё не знакомы с данными статьями – рекомендую начать с них, иначе может быть непонятно – “что, зачем и почему”.

 

Но данное устройство пока что умеет работать только с протоколом MQTT. Теперь давайте расширим функционал – добавим к нашему устройству функции отправки данных на внешние сервисы для хранения данных и построения графиков:

Вы можете использовать любой из предложенных сервисов или любую их комбинацию, в том числе и все сразу. Ранее я публиковал мини-обзор этих сервисов в другой статье на этом сайте.

Данную статью, в принципе, можно рассматривать не только как продолжение серии про термостат с удаленным управлением, но и как “самостоятельное” руководство по использованию библиотеки reDataSend.

 


Используемая библиотека

Для отправки данных на все три сервиса используется одна и та же библиотека – reDataSend. Я пока что не буду касаться “внутренностей” этой библиотеки, так как для этого придется обсуждать различные тонкости вроде TLS-подключений и HTTPS-запросов. Поэтому в рамках данной статьи рассматривать подробности не будем, пока достаточно понять принцип работы библиотеки.

Перед началом работы (один раз при запуске программы) необходимо инициализировать контроллер с помощью функции dsChannelInit(). При вам потребуется указать:

  • kind – тип контроллера (EDS_OPENMON / EDS_NARODMON / EDS_THINGSPEAK)
  • uid – идентификатор контроллера/канала/устройства, обычно он выдается самим сервисом при создании контроллера (но для народного мониторинга вы можете взять любой случайный)
  • key – ключ доступа к сервису (токен или MAC-адрес)
  • min_interval – минимальный интервал отправки данных на сервис в секундах в нормальном режиме
  • err_interval – минимальный интервал повторной отправки данных на сервис в секундах в случае ошибки

Инициализация – это громкое название для простого добавления указанных выше сведений в общем списке, дабы не указывать их при каждой отправке данных, а также для контроля минимального интервала обращения к сервису (иначе вы запросто можете словить бан от сервиса). Никакого физического обмена данными с сервисом при инициализации не происходит. При всех последующих обращениях к контроллеру вам потребуется указывать только его uid.

Для отправки данных в канал/контроллер заранее подготовьте данные в формате используемого сервиса:

  • для народного мониторинга имена параметров могут быть произвольными, например Tout=10.00&Tin=25.00&...
  • для open-monitoring имена полей обозначены буквой p и номером по порядку: p1=10,00&p2=25.00&...
  • для thing speak используется похожий принцип, но имена полей обозначены словом field: field1=10,00&field2=25.00&...

Никакие дополнительные поля к этим данным добавлять не нужно – библиотека сама добавит идентификатор и токен доступа при отправке.

// Инициализация контроллеров OpenMon
#if CONFIG_OPENMON_ENABLE
  dsChannelInit(EDS_OPENMON, CONFIG_OPENMON_CTR01_ID, CONFIG_OPENMON_CTR01_TOKEN, CONFIG_OPENMON_MIN_INTERVAL, CONFIG_OPENMON_ERROR_INTERVAL);
#endif // CONFIG_OPENMON_ENABLE

// Инициализация контроллеров NarodMon
#if CONFIG_NARODMON_ENABLE
  dsChannelInit(EDS_NARODMON, CONFIG_NARODMON_DEVICE01_ID, CONFIG_NARODMON_DEVICE01_KEY, CONFIG_NARODMON_MIN_INTERVAL, CONFIG_NARODMON_ERROR_INTERVAL);
#endif // CONFIG_NARODMON_ENABLE

// Инициализация каналов ThingSpeak
#if CONFIG_THINGSPEAK_ENABLE
  dsChannelInit(EDS_THINGSPEAK, CONFIG_THINGSPEAK_CHANNEL01_ID, CONFIG_THINGSPEAK_CHANNEL01_KEY, CONFIG_THINGSPEAK_MIN_INTERVAL, CONFIG_THINGSPEAK_ERROR_INTERVAL);
#endif // CONFIG_THINGSPEAK_ENABLE

 

Затем отправьте подготовленные данные в очередь с помощью функции

bool dsSend(ext_data_service_t kind, uint32_t uid, char *data, bool free_data)

где:

  • kind – тип контроллера (EDS_OPENMON / EDS_NARODMON / EDS_THINGSPEAK)
  • uid – идентификатор контроллера/канала/устройства
  • data – указатель на строку данных в куче
  • free_data – если указать здесь true, то после добавления задания в очередь строка data будет удалена из кучи, в противном случае вы должны сделать это самостоятельно.

Как работает отправка? После вызова dsSend() данные попадают в исходящую очередь. Если добавление задания в очередь прошло успешно, функция вернет true.

Далее специальная задача проверяет, как давно происходила отправка в этот самый контроллер. Если время превышает минимальный интервал отправки, то делается попытка немедленно отправить данные. Если минимальный интервал ещё не истёк, то данные остаются в очереди. Но если во время ожидания поступят новые данные в этот же самый контроллер, то они будут заменены новыми данными. Этот механизм позволяет отправлять данные в очередь не заботясь об минимальных интервалах отправки.

Но я, как правило, добавляю ещё по одному псевдотаймеру (читай – счетчику) для возможности run-time изменения интервалов отправки с помощью настроек.

 


Народный мониторинг

Для начала, разумеется, необходимо зарегистрироваться на сервисе, если вы это не сделали ранее. Перед подключением устройства к сайту пожалуйста убедитесь, что вы можете добавить устройство, так как лимиты на народном мониторинге довольно жёсткие:

Я могу подключить ещё один прибор

Далее нам потребуется узнать MAC-адрес микроконтроллера. Проще всего его найти, посмотрев список подключенных WiFi устройств на вашем роутере:

В вашем случае интерфейс может быть совершенно другим

Открываем проект, файл project_config.h, и ищем в нем секцию для https://narodmon.ru/. Для подключения отправки данных на сайт необходимо включить всего два параметра:

#define CONFIG_NARODMON_ENABLE 1
#define CONFIG_NARODMON_DEVICE01_KEY "aa:bb:cc:dd:ee:ff"

Например так:

// -----------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------- EN - https://narodmon.ru/ ----------------------------------------------
// ---------------------------------------------- RU - https://narodmon.ru/ ----------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------

// EN: Enable sending data to narodmon.ru
// RU: Включить отправку данных на narodmon.ru
#define CONFIG_NARODMON_ENABLE 1
#if CONFIG_NARODMON_ENABLE
// EN: Frequency of sending data in seconds
// RU: Периодичность отправки данных в секундах
#define CONFIG_NARODMON_SEND_INTERVAL 300
#define CONFIG_NARODMON_DEVICE01_ID 00001
#define CONFIG_NARODMON_DEVICE01_KEY "9c:9e:9f:77:18:f7"
#endif // CONFIG_NARODMON_ENABLE

CONFIG_NARODMON_DEVICE01_ID никакого существенного значения не имеет, его можно не изменять. Главное чтобы этот идентификатор отличался от других идентификаторов устройств на внешних сервисах внутри прошивки.

Но это ещё не всё. Теперь нам необходимо написать код для отправки данных на этот сервис. Откройте в редакторе кода файл <каталог_проекта>\lib\sensors\sensors.cpp и найдите в нем функцию задачи void sensorsTaskExec(void *pvParameters). В нем есть несколько секций кода, оформленных макросами #if CONFIG_NARODMON_ENABLE ... #endif

В первой секции происходит инициализация устройства. Пока в прошивке только одно устройство НМ, то здесь ничего корректировать не требуется.

Чуть ниже инициализируются “псевдотаймеры” (на самом деле это простые счетчики тиков) для отправки данных. Здесь тем более ничего делать не нужно.

#if CONFIG_NARODMON_ENABLE
  esp_timer_t nmSendTimer;
  timerSet(&nmSendTimer, iNarodMonInterval*1000);
#endif // CONFIG_NARODMON_ENABLE

Ну и наконец, находим третью секцию, которая как раз нам требуется:

#if CONFIG_NARODMON_ENABLE
  if (statesInetIsAvailabled() && timerTimeout(&nmSendTimer)) {
    timerSet(&nmSendTimer, iNarodMonInterval*1000);
    char * nmValues = nullptr;

    // Отправляем данные
    if (nmValues) {
      dsSend(EDS_NARODMON, CONFIG_NARODMON_DEVICE01_ID, nmValues, false); 
      free(nmValues);
    };
  };
#endif // CONFIG_NARODMON_ENABLE

Напомню, у нас в устройстве “по умолчанию” три сенсора – уличный, комнатный и температура теплоносителя. Давайте добавим их все.

Так как НМ позволяет задавать произвольные названия полям запрос, то условимся считать данные с уличного сенсора как Tout и Hout, с комнатного – Tin и Hin, температура теплоносителя – Tboiler. Впрочем, вы можете использовать другие обозначения – как вам более удобно. Немного модифицируем код:

#if CONFIG_NARODMON_ENABLE
  if (statesInetIsAvailabled() && timerTimeout(&nmSendTimer)) {
    timerSet(&nmSendTimer, iNarodMonInterval*1000);
    char * nmValues = nullptr;
    // Улица
    if (sensorOutdoor.getStatus() == SENSOR_STATUS_OK) {
      nmValues = concat_strings_div(nmValues, 
        malloc_stringf("Tout=%.2f&Hout=%.2f", 
          sensorOutdoor.getValue2(false).filteredValue, sensorOutdoor.getValue1(false).filteredValue),
        "&");
    };
    // Комната
    if (sensorIndoor.getStatus() == SENSOR_STATUS_OK) {
      nmValues = concat_strings_div(nmValues, 
        malloc_stringf("Tin=%.2f&Hin=%.2f", 
          sensorIndoor.getValue2(false).filteredValue, sensorIndoor.getValue1(false).filteredValue),
        "&");
    };
    // Котёл
    if (sensorBoiler.getStatus() == SENSOR_STATUS_OK) {
      nmValues = concat_strings_div(nmValues, 
        malloc_stringf("Tboiler=%.2f", 
          sensorBoiler.getValue(false).filteredValue),
        "&");
    };
    // Отправляем данные
    if (nmValues) {
      dsSend(EDS_NARODMON, CONFIG_NARODMON_DEVICE01_ID, nmValues, false); 
      free(nmValues);
    };
  };
#endif // CONFIG_NARODMON_ENABLE

Что здесь происходит? Если статус последнего чтения данных с сенсора “НОРМА”, то берем с него обработанные (фильтрованные) значения температуры ( getValue2(false) ) и влажности ( getValue1(false) ) и “склеиваем” с исходной строкой. false в данном означает, что физического чтения данных у устройства не происходит, а берем последние полученные данные.

Что такое rawValue “сырые” и  filteredValue “обработанные” значения, я постараюсь рассказать в одной из следующих статей. А пока примите это как данность, что для целей автоматизации, да и для отображения требуется использованием именно filteredValue. Почему вначале идет влажность, а только потом температура? Потому что так было в драйвере какого-то сенсора от Adafruit, а потом так и закрепилось.

В итоге, если все сенсоры исправны, получим примерно такую строку для отправки данных: Tout=-18,78&Hout=87.12&Tin=22,33&Hout=37.88&Tboiler=55.00

Её и отправляем в очередь задачи отправки на сервер с помощью dsSend(). Далее задача отправки сама проверит время последней отправки данных на сайт CONFIG_NARODMON_SEND_INTERVAL и отправит данные.

Осталось скомпилировать прошивку, загрузить её в устройство и дождаться первой отправки данных на сайт. Только после этого вы сможете добавить устройство в свой профиль по заданному MAC-адресу.

 


Open Monitoring

Отправка данных на open-monitoring.online происходит аналогичным образом, отличие состоит в том, что вы можете зарегистрировать на сайте сразу множество контроллеров. Поэтому – ни в чем себе не отказывайте. Более подробно вы можете прочитать об этом в другой статье: https://kotyara12.ru/iot/open-monitoring/

Перво-наперво разблокируем работу с сервисом:

// EN: Enable sending data to open-monitoring.online
// RU: Включить отправку данных на open-monitoring.online
#define CONFIG_OPENMON_ENABLE 1

Для работы с open-monitoring.online создадим два отдельных контроллера. На первый мы будем отправлять данные о температуре и состоянии реле, на второй – системную информацию с устройства. Вообще, их можно создать сколько угодно, но для примера вполне достаточно двух.

Создаем контроллер для климатических данных.

Для примера создадим такие поля:

Не забудьте оставить несколько полей “в резерв”, так как потом в этот контроллер добавить поля уже не получится. После сохранения контроллера можно увидеть его идентификатор и код для записи:

Эти данные сохраняем в project_config.h:

// EN: Controller ids and tokens for open-monitoring.online
// RU: Идентификаторы контроллеров и токены для open-monitoring.online
#define CONFIG_OPENMON_CTR01_ID 9998
#define CONFIG_OPENMON_CTR01_TOKEN "3W3aYt"

 


Создаем контроллер для служебных данных.

Если вы не стали отключать в настройках проекта проверку доступа в интернет посредством ping-а и желаете отправлять на сервис служебные данные, то вам потребуется создать ещё один специальный контроллер. Но вы вполне можете обойтись без этого, просто отключив опцию CONFIG_OPENMON_PINGER_ENABLE.

Для климатических данных вы могли создавать поля произвольно – какие хотите и в какой хотите последовательности, ведь вы сами формируете HTML-запрос. Для работы библиотеки отправки служебной информации требуется набор полей в строгом формате:

По умолчанию “пингуется” два хоста – google и yandex, поэтому создаем такой контроллер:

Переносим данные этого контроллера в project_config.h:

// EN: Allow publication of ping results и системной информации on open-monitoring.online
// RU: Разрешить публикацию результатов пинга и системной информации на open-monitoring.online
#define CONFIG_OPENMON_PINGER_ENABLE 1
#if CONFIG_OPENMON_PINGER_ENABLE
#define CONFIG_OPENMON_PINGER_ID 9999
#define CONFIG_OPENMON_PINGER_TOKEN "LUs2hg"
#define CONFIG_OPENMON_PINGER_INTERVAL 300000
#define CONFIG_OPENMON_PINGER_RSSI 1
#define CONFIG_OPENMON_PINGER_HEAP_FREE 1
#define CONFIG_OPENMON_PINGER_HOSTS 1
#endif // CONFIG_OPENMON_PINGER_ENABLE

Теперь нам вновь необходимо написать код для отправки данных. Для отправки служебной информации никакого кода писать не требуется, всё делается автоматически. А вот для климатических данных все делается практически точно так же, как мы обсуждали выше для народного мониторинга:

// open-monitoring.online
#if CONFIG_OPENMON_ENABLE
  if (statesInetIsAvailabled() && timerTimeout(&omSendTimer)) {
    timerSet(&omSendTimer, iOpenMonInterval*1000);
    char * omValues = nullptr;
    // Улица
    if (sensorOutdoor.getStatus() == SENSOR_STATUS_OK) {
      omValues = concat_strings_div(omValues, 
        malloc_stringf("p1=%.3f&p2=%.2f", 
          sensorOutdoor.getValue2(false).filteredValue, sensorOutdoor.getValue1(false).filteredValue),
        "&");
    };
    // Комната
    if (sensorIndoor.getStatus() == SENSOR_STATUS_OK) {
      omValues = concat_strings_div(omValues, 
        malloc_stringf("p3=%.3f&p4=%.2f", 
          sensorIndoor.getValue2(false).filteredValue, sensorIndoor.getValue1(false).filteredValue),
        "&");
    };
    // Котёл
    if (sensorBoiler.getStatus() == SENSOR_STATUS_OK) {
      omValues = concat_strings_div(omValues, 
        malloc_stringf("p5=%.3f", 
          sensorBoiler.getValue(false).filteredValue),
        "&");
    };
    // Отправляем данные
    if (omValues) {
      dsSend(EDS_OPENMON, CONFIG_OPENMON_CTR01_ID, omValues, false); 
      free(omValues);
    };
  };
#endif // CONFIG_OPENMON_ENABLE

Разница только в названиях полей и переменных. В дальнейшем мы сможем добавить сюда отправку состояния реле управления климатом.

Компилируем проект, заливаем в устройство и наслаждаемся видами:

Совмещенные графики температуры за последнюю неделю. Видны изменения режима работы котла

При необходимости, вы можете создать ещё несколько контроллеров и организовать отправку из одного устройства сразу в несколько. Не проблема. Только не забудьте добавить данные об этих контроллерах не только в код отправки, но и в блок инициализации.

 


Thing Speak

Ну и в заключение добавим аналогичную отправку данных на буржуинский сервис – ThingSpeak. В принципе, отправка данных на него выглядит абсолютно аналогично, я даже не совсем уверен, стоит ли повторяться. Ну, пусть будет.

Регистрируемся на сервисе thingspeak.com (если у вас ещё нет аккаунта) и создаем новый канал (канал это тоже самое, что и контроллер или устройство). Данный сервис довольно прижимист в бесплатном режиме, поэтому тестовый канал я создать для примера не могу. Но сложного там ничего нет.

Активируем отправку на этот сервис с помощью макроса CONFIG_THINGSPEAK_ENABLE 1, указываем ID канала и токен записи в него:

// EN: Enable sending data to thingspeak.com
// RU: Включить отправку данных на thingspeak.com
#define CONFIG_THINGSPEAK_ENABLE 1
#if CONFIG_THINGSPEAK_ENABLE
// EN: Frequency of sending data in seconds
// RU: Периодичность отправки данных в секундах
#define CONFIG_THINGSPEAK_SEND_INTERVAL 300
// EN: Tokens for writing on thingspeak.com
// RU: Токены для записи на thingspeak.com
#define CONFIG_THINGSPEAK_CHANNEL01_ID 99999999
#define CONFIG_THINGSPEAK_CHANNEL01_KEY "LAJ6AA9AKBVTZ7AP"
#endif // CONFIG_THINGSPEAK_ENABLE

Допустим набор полей у канала будет таким же, как и в предыдущем случае.

По аналогии, добавляем код отправки:

// thingspeak.com
#if CONFIG_THINGSPEAK_ENABLE
  if (statesInetIsAvailabled() && timerTimeout(&tsSendTimer)) {
    timerSet(&tsSendTimer, iThingSpeakInterval*1000);

    char * tsValues = nullptr;
    // Улица
    if (sensorOutdoor.getStatus() == SENSOR_STATUS_OK) {
      tsValues = concat_strings_div(tsValues, 
        malloc_stringf("field1=%.3f&field2=%.2f", 
          sensorOutdoor.getValue2(false).filteredValue, sensorOutdoor.getValue1(false).filteredValue),
        "&");
    };
    // Комната
    if (sensorIndoor.getStatus() == SENSOR_STATUS_OK) {
      tsValues = concat_strings_div(tsValues, 
        malloc_stringf("field3=%.3f&field4=%.2f", 
          sensorIndoor.getValue2(false).filteredValue, sensorIndoor.getValue1(false).filteredValue),
        "&");
    };
    // Котёл
    if (sensorBoiler.getStatus() == SENSOR_STATUS_OK) {
      tsValues = concat_strings_div(tsValues, 
        malloc_stringf("field5=%.3f", 
          sensorBoiler.getValue(false).filteredValue),
        "&");
    };
    // Отправляем данные
    if (tsValues) {
      dsSend(EDS_THINGSPEAK, CONFIG_THINGSPEAK_CHANNEL01_ID, tsValues, false); 
      free(tsValues);
    };
  };
#endif // CONFIG_THINGSPEAK_ENABLE

 

Компилируем проект, загружаем в устройство и проверяем. Если вы всё сделали правильно, можно настраивать виджеты:

Данные с моей персональной метеостанции


 


Где скачать пример?

Как всегда – на GitHub. Только в данном случае нам нужна ветка “02_telemeter_charts” или “master” (в master последнее состояние проекта, со всеми доработками на текущий момент):

На этом пока всё, до встречи на сайте и на dzen-канале!


Все статьи цикла “Термостат и ОПС”:

Прошивка K12 для ESP32 и ESP-IDF:

Дополнительные статьи, которые применимы к любым устройствам, запрограммированным с помощью тех же самых библиотек.

 

💠 Полный архив статей вы найдете здесь


Пожалуйста, оцените статью:
[ 0 из 5, всего 0 оценок ]

1 комментарий для “Термостат на ESP32 с удаленным управлением. Часть 5. Добавляем выгрузку данных на внешние сервисы”

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *