Интеграция устройства в экосистему Samsung SmartThings на примере «Умного чайника» Часть 2: переход с ESP8266 на ESP32

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

Обновить

Рейтинг


Tutorial

Вступление

В первой части статьи на примере “Умного чайника” я описал процесс разработки приложения, интегрированного в экосистему SmartThings. В этой части я усложняю пример: добавляю датчик температуры и делаю плавную индикацию RGB-светодиода. Я опишу, чем отличаются ESP8266 и ESP32, и почему в этом примере нам больше подходит именно ESP32. Также будет описана передача сообщений между потоками/тасками на примере очередей в FreeRTOS. Таким образом, данная статья предназначена для всех, кто хочет перейти от самых простых умных устройств с минимумом функционала, к устройствам чуть более сложным как с программной, так и с железной точки зрения.

Вспоминаем функции/capability

Напомню функции “чайника”, которые будем реализовывать в приложении.

Ниже в формате таблицы представлены:

  • Функции «чайника»

  • Какие из частей устройства, облака и телефона будут выполнять эти функции

  • Как это будет происходить

По сравнению с предыдущей версией приложения, изменилась индикация RGB-светодиода при назначении температуры нагрева. Также вместо эмуляции температурного сенсора в этом примере мы получаем реальные данные от датчика температуры DS18B20.

В приложении для представления этих функций и возможностей устройства используются следующие Capabilities:

  • Switch — переключатель, которым начинаем и завершаем процесс кипячения

  • Temperature Measurement — показывает текущую температуру “чайника”

  • Thermostat Heating Setpoint (или его кастомный вариант) — выбор температуры нагрева

Выбор микроконтроллера

В прошлой версии “Умного чайника” не было датчика температуры, а RGB-светодиод принимал только дискретные значения (логические 0 и 1) для каждого из 3 выводов. В новой версии устройства RGB-светодиод во время процесса подогрева должен плавно изменять цвет по спектру от зелёного до красного, проходя жёлтый и оранжевый цвета. Для этого на плате микроконтроллера нужны 2 пина, подключенных к каналам цифро-аналоговых преобразователей. Для датчика температуры DS18B20 нужен 1 пин, подключенный к каналу аналого-цифрового преобразователя. И если встроенный АЦП в модулях ESP8266 есть, то встроенных ЦАП нет. Поэтому такой вариант для нашего примера не подойдёт. А ESP32 как раз поддерживает два 8-битных канала ЦАП.

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

Различия ESP8266 и ESP32

Чип ESP32 является преемником ESP8266 и превосходит его во всех характеристиках, кроме стоимости. Интересно, что оба чипа представляют собой 32-разрядные микроконтроллеры, таким образом, интуиция здесь не срабатывает.

В ESP32 больше GPIO пинов с различными функциями, более быстрый Wi-Fi и поддержка Bluetooth. ESP32 поддерживает множество периферийных интерфейсов: 2 канала ЦАП, 10 портов для подключения емкостных датчиков, Ethernet, CAN шину, датчик Холла, а также больше SPI, I2C, I2S, UART, каналов АЦП. В ESP32 есть встроенная flash-память, поддерживается её шифрование. ESP8266 дешевле ESP32, хотя цена во много зависит от того, какую модель вы выбираете и где покупаете. Исходный код для ESP32 и ESP8266 совместим, поэтому при переходе с одного на другой код менять почти не придётся. Таким образом, ESP32 предлагает много больше возможностей, а ESP8266 может быть более предпочтительным разве что в очень простых IoT приложениях и из-за более низкой цены. Подробнее о различиях ESP32 и ESP8266 вы можете прочитать в этой статье, а о программировании ESP32 в статье на Хабре.

Микроконтроллер ESP32-C3

В 2020 году Espressif анонсировала новый микроконтроллер ESP32-C3, который совместим по пинам с ESP8266. Этот микроконтроллер основан на ядре с RISC-V архитектурой. ESP32-C3 потенциально может заменить ESP8266, имея весь его функционал, но добавляя больше оперативной памяти, постоянную память, Bluetooth и шифрование. Разработка ESP32-C3 ведется с использованием фреймворка ESP-IDF, в отличие от ESP8266 с его ESP8266 RTOS SDK. Это не влияет на совместимость кода, но процесс сборки немного отличается, что даже более удобно для пользователей ESP32, которые тоже используют ESP-IDF. Таким образом, ESP32-C3 является сбалансированным по цене и функционалу микроконтроллером, который как и ESP8266 подходит под небольшие приложения, но обладает большими возможностями и лучшей безопасностью. Подробнее о ESP32-C3 вы можете прочитать в этой статье на Хабре. 

На данный момент этой платы пока нет в российских магазинах, но вы уже можете купить ее на Aliexpress.

Влияние RISC-V на выбор микроконтроллера

RISC-V архитектура находит признание благодаря своей открытости, которая позволяет кастомизировать дизайн процессора, и свободе от лицензионных сборов в пользу разработчиков архитектуры. Это увеличивает конкуренцию на рынке и количество различных по характеристикам чипов, поскольку производители могут свободнее подходить к их изменению и улучшению. Архитектура процессора не всегда напрямую влияет на выбор микроконтроллера. Для производителя открытая архитектура позволяет экономить на процессорах и вкладывать в развитие других частей микроконтроллера. Также открытая архитектура способствует расширению семейства процессоров, что опять же позволяет экономить на процессоре, поскольку используется наиболее близкий к потребностям пользователей микроконтроллер. В целом открытая архитектура имеет положительные долгосрочные эффекты для производителей, и, соответственно, пользователей, поскольку открывает возможности свободного развития продуктов и обогащения рынка разнообразием новых устройств.

Сборка на макетной плате

От теории перейдем к практической реализации.

Схема подключения:

На схеме используется датчик температуры с типом корпуса TO-92. Но также датчик может быть в водонепроницаемой форме с длинным кабелем. На следующем фото слева — датчик в корпусе ТО-92, справа — в водонепроницаемом кабеле.

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

Организация кода

Структура директорий

Файлы проекта распределены по директориям. В основной директории — конфигурационные файлы, необходимые для сборки. Их можно просто скопировать, и только в CMakeLists.txt изменить название проекта, если вы склонировали проект не в директорию kettle_example. Директория build генерируется при сборке и не содержит исходного кода. Основное внимание на директории main и components, которые содержат компоненты с исходным кодом. В директории components располагаются все сторонние компоненты/библиотеки. В main — написанный нами код, а именно файлы работы с Capabilities, файлы управления периферией и файл main.c с бизнес-логикой. Также в main есть файл CMakeLists.txt, необходимый для сборки проекта. Процесс сборки рассмотрим далее.

Отличие сборки проекта по сравнению с ESP8266

В примерах для ESP8266 используется система сборки Make. Для успешной сборки нужно иметь Makefile в директории проекта со следующим минимальным содержимым:

PROJECT_NAME := myProject
include $(IDF_PATH)/make/project.mk

В сборке под ESP32 в дополнение используется CMake, который позволяет использовать и другие системы сборки помимо Make, например Ninja. 

В каждом проекте содержится один или несколько компонентов. Компонент — это директория, в которой есть файл CMakeLists.txt. Директория main — это особенный компонент, который содержит исходный код самого проекта. Поэтому помимо файла CMakeLists.txt в директории проекта, необходимо создать файл CMakeLists.txt в директории main. Содержимое этого файла можно скопировать с других примеров от SmartThings. В нашем примере CMakeLists.txt отличается лишь тем, что в idf_component_register, который регистрирует компонент, перечисляются не исходные файлы, а директории с исходными файлами. Для этого в idf_component_register используем не SRCS с перечислением файлов, а SRC_DIRS с перечислением директорий:

idf_component_register(SRC_DIRS
                           .
                           ../components/esp32-ds18b20
                           ../components/esp32-owb
                   EMBED_FILES "device_info.json"
                               "onboarding_config.json"
                   )

Подробнее о системах сборки ESP-проектов можно прочитать в соответствующих разделах документации для ESP32 и для ESP8266.

Подгрузка компонентов/библиотек для датчика температуры

У нас используется датчик температуры DS18B20, который работает по 1-Wire шине. В проекте используется 2 компонента, совместимых с ESP32: esp32-owb и esp32-ds18b20.

  • Компонент esp32-owb — это библиотека для работы с протоколом 1-Wire. 

  • Компонент esp32-ds18b20 — это библиотека, которая предоставляет удобное api для работы с датчиком температуры, без необходимости напрямую работать по 1-Wire протоколу.

В нашем гит-проекте эти компоненты используются как подмодули/submodules, и чтобы их подгрузить, при клонировании проекта нужно использовать флаг —recurse-submodules:

 $ git clone --recurse-submodules https://github.com/flisoch/SmartThings-Smart-Kettle-Example-esp32.git kettle_example

Подгрузить подмодули можно и после обычного клонирования без флага —recurse-submodules.  Для этого в основной директории проекта kettle_example инициализируем и обновляем подмодуль следующими командами:

$ git submodule init
$ git submodule update

Подробнее о подмодулях вы можете прочитать в соответствующей главе книги “Pro Git”.

Бизнес-логика в коде приложения

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

Считывание показаний температуры с датчика

В отличие от примера для ESP8266, в этом примере добавляются события от настоящего датчика. Это взаимодействие организовано в виде передачи сообщений через очередь (FreeRTOS Queue) между двумя тасками/потоками: основным таском с управлением и мониторингом остальной периферии и таском, считывающим данные с датчика температуры. В основном таске создаётся очередь temperature_events_q, а также запускается таск, считывающий температуру.

static void app_main_task(void *arg)
{
  /* . . . */
  // создаём очередь с 1 элементом - для последнего значения температуры
  QueueHandle_t temperature_events_q = xQueueCreate(1, sizeof(double));
  // нужно для управления таском, например чтобы удалить/остановить его
  TaskHandle_t xHandle = NULL;
  // создаём таск, передаем аргументом очередь и xHandle
  // 4096 - размер стека, 10 - приоритет таска/потока
  xTaskCreate(temperature_events_task, "temperature_events_task", 4096, (void *)temperature_events_q, 10, &xHandle);
  /* . . . */
}

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

static void app_main_task(void *arg)
{
  /* . . . */
  for (;;) {
    /* . . . */
    xQueueReceive(temperature_events_q, &temperature_value, portMAX_DELAY);
    if(prev_temp_value != temperature_value) {
      prev_temp_value = temperature_value; cap_temperature_data->set_temperature_value(cap_temperature_data, temperature_value);           cap_temperature_data->attr_temperature_send(cap_temperature_data);
    }
    /* . . . */
  }
}

Подробнее о работе с тасками и очередями вы можете прочитать в 3 и 4 главе книги «Mastering the FreeRTOS Real Time Kernel», рекомендуемой на странице с ресурсами в документации FreeRTOS.

Светодиодная индикация

Помимо нового источника событий в коде бизнес-логики меняется управление RGB светодиодом при установке значения температуры подогрева “чайника” в коллбэке Thermostat Heating Setpoint Capability, а также в начале и завершении процесса подогрева. 

static void cap_thermostat_cmd_cb(struct caps_thermostatHeatingSetpoint_data *caps_data)
{
  heating_setpoint = caps_data->get_value(caps_data);
  // индикация светодиода в зависимости от выбранной температуры нагрева
  setpoint_rgb_indication(heating_setpoint);
}
static void app_main_task(void *arg)
{
  /* . . . */
  if (thermostat_enable) {
    // изменяем цвет светодиода а начале и в процессе  нагревания
    change_rgb_led_boiling(heating_setpoint, temperature_value);
  }
  if (thermostat_enable && temperature_value >= heating_setpoint) {
    // завершаем процесс нагрева
    thermostat_enable = false;
    // в последний раз меняем цвет светодиода
    change_rgb_led_heating(heating_setpoint, temperature_value);
    temperature_value = 0;
    buzzer_enable = true;
  }
  /* . . . */
  if (buzzer_enable) {
    beep();
    buzzer_enable = false;
    // возвращаем синий цвет по завершении нагревания
    change_rgb_state(DAC_OUTPUT_RGBLED_G, LED_OFF);
    change_rgb_state(DAC_OUTPUT_RGBLED_R, LED_OFF);
    change_rgb_state(GPIO_OUTPUT_RGBLED_B, LED_ON);
    cap_switch_data->set_switch_value(cap_switch_data, caps_helper_switch.attr_switch.value_off);
    cap_switch_data->attr_switch_send(cap_switch_data);
  }
}

Периферия

Инициализация пинов

Отличаться будет инициализация пинов для RGB светодиода и пина для датчика температуры. В RGB светодиоде 2 пина будут ЦАП, чтобы выдавать аналоговые значения для плавного изменения красного и зеленого цветов светодиода. В функции iot_gpio_init вместо gpio_config вызовем dac_output_enable, передав значения этих двух пинов:

void iot_gpio_init(void)
{
  /* . . . */
  dac_output_enable(DAC_OUTPUT_RGBLED_R);
  dac_output_enable(DAC_OUTPUT_RGBLED_G);
  /* . . . */
}

И установим начальное значение напряжения на этих пинах в ноль:

void iot_gpio_init(void)
{
  /* . . . */
  dac_output_voltage(DAC_OUTPUT_RGBLED_R, 0);
  dac_output_voltage(DAC_OUTPUT_RGBLED_G, 0);
  /* . . . */
}

Инициализация датчика температуры

Прежде чем использовать датчик через структуру DS18B20_Info, нужно инициализировать доступ к шине 1-Wire. Для этого используется функция owb_rmt_initialize из owb_rmt.h, которая возвращает указатель на структуру OneWireBus.При этом можно использовать другую функцию, owb_gpio_initialize из owb_gpio.h. В первом случае используется не GPIO драйвер, а RMT (Remote Control) — специфический для ESP, предназначенный для работы с инфракрасными сигналами и не только. Этот вариант рекомендован разработчиками библиотеки esp32-owb, т.к. приводит к более надёжной работе и очень точным временным интервалам чтения/записи. Также включаем использование алгоритма нахождения контрольной суммы CRC.

void temperature_events_task(void *arg)
{
  /* . . . */
  OneWireBus *owb;
  owb = owb_rmt_initialize(&rmt_driver_info, ADC_TEMPERATURE_SENSOR, RMT_CHANNEL_1, RMT_CHANNEL_0);
  owb_use_crc(owb, true);  // Включить CRC проверку для ROM кода
  /* . . . */
}

Затем, используя готовую структуру шины, инициализируем структуру DS18B20_Info:

void temperature_events_task(void *arg)
{
  /* . . . */
  DS18B20_Info * ds18b20_info;
  // выделяем память
  ds18b20_info = ds18b20_malloc();
  // инициализируем один датчик, не несколько
  ds18b20_init_solo(ds18b20_info, owb);
  // включаем использование алгоритма нахождения контрольной суммы CRC
  ds18b20_use_crc(ds18b20_info, true);
  // устанавливаем разрешение АЦП на 12 бит(шаг в 0.0625°С)
  ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION_12_BIT);
  /* . . . */
}

Считывание и передача температуры (Очередь FreeRTOS)

После инициализации 1-Wire шины и структуры датчика DS18B20_Info в бесконечном цикле раз в секунду происходит считывание температуры и отправка её в очередь. Поскольку значение температуры считывается в переменную типа float, а в остальных частях программы, в том числе при работе с Capability, используется double, мы используем 2 переменные разных типов и преобразуем float в double:

void temperature_events_task(void *arg) {
  /* . . . */
  float temperature_value;
  double send_value;
  for (;;)
  {
    ds18b20_convert_all(owb);
    ds18b20_wait_for_conversion(ds18b20_info);
    ds18b20_read_temp(ds18b20_info, &temperature_value);
    printf("TEMP READ: %fn", temperature_value);
    send_value = (double) temperature_value;
    //передача температуры в очередь
    xQueueSendToBack(queue, &send_value, 0);
    vTaskDelay(pdMS_TO_TICKS(TEMPERATURE_EVENT_MS_RATE));
  }
}

Управление RGB светодиодом

Здесь я опишу, как именно выставляются значения всем пинам светодиода при установке значения температуры подогрева “чайника” в коллбэке Thermostat Heating Setpoint Capability, а также в процессе подогрева. 

Начнём с функции setpoint_rgb_indication в коллбэке. В зависимости от выбранной температуры нагрева, светодиод в течение нескольких секунд может светиться синим, жёлтым, оранжевым или красным. Для каждого из цветов выбраны соответствующие значения температур: синий — меньше 30°С, жёлтый — меньше 50°С, оранжевый — меньше 75°С, красный — меньше 100°С.

void setpoint_rgb_indication(double heating_setpoint)
{
  if (heating_setpoint <= 30) {
    // синий
    change_rgb_state(GPIO_OUTPUT_RGBLED_B, LED_ON);
  }
  else if (heating_setpoint <= 50)
  {
    // жёлтый
    change_rgb_state(GPIO_OUTPUT_RGBLED_B, LED_OFF);
    change_rgb_led_state(255, 255, 0);
  }
  else if (heating_setpoint <= 75) {
    //оранжевый
    change_rgb_state(GPIO_OUTPUT_RGBLED_B, LED_OFF);
    change_rgb_led_state(255, 195, 0);
  }
  else if (heating_setpoint <= 100) {
    //красный
    change_rgb_state(GPIO_OUTPUT_RGBLED_B, LED_OFF);
    change_rgb_led_state(255, 0, 0);
  }
  else {
    printf("heating setpoint is more than 100 or not set!nPlease, set correct number");
  }
  // блокировка на несколько секунд
  vTaskDelay(pdMS_TO_TICKS(HEATING_SETPOINT_RGB_DURATION));
  // возвращение свечения светодиода синим цветом
  change_rgb_state(DAC_OUTPUT_RGBLED_G, LED_OFF);
  change_rgb_state(DAC_OUTPUT_RGBLED_R, LED_OFF);
  change_rgb_state(GPIO_OUTPUT_RGBLED_B, LED_ON);
}

В процессе подогрева цвет RGB светодиода плавно меняется от зелёного к жёлтому и затем от жёлтого к красному. В главном таске/потоке в бесконечном цикле при получении нового температурного события запускается функция change_rgb_led_heating. Цвет меняется в зависимости от прогресса нагревания. Прогресс вычисляется на основе текущей температуры и двух граничных значений — минимальной температуры и конечной заданной температуры. Пока прогресс нагревания меньше 50%, меняется красная компонента цвета. Таким образом от зелёного, добавляя красный, мы идём к жёлтому. Как только прогресс нагревания превысит 50%, красная компонента становится максимальной — подаем значение 255 на ЦАП пин, соединенный с красным светодиодом. Затем, чтобы от жёлтого дойти до красного, нужно уменьшать зелёную компоненту, пока прогресс нагревания не дойдёт до 100%.

Как менять цвет от зелёного к красному
Как менять цвет от зелёного к красному
void change_rgb_led_heating(double heating_setpoint, double current_temperature)
{
  // Green 0, 255, 0    --   0%
  // Yellow 255, 255, 0 --  50%
  // Red 255, 0, 0      -- 100%
  double min = 0;
  double max = heating_setpoint;
  // вычисляем прогресс на основе текущей температуры и граничных значений.
  //Например 26°C при минимуме 0 и температуре нагрева 50°C даст прогресс 52%
  double progress_f = (current_temperature - min)/(max - min) * 100;
  int progress = (int)round(progress_f);
  printf("heating progress: %d %% ", progress);
  int red = RGB_LED_R;
  int green = RGB_LED_G;
  int blue = RGB_LED_B;
  if (progress < 50) {
    // устанавливаем значение красной компоненты
    red += 5 * progress;
  }
  else if (progress >= 50) {
    // фиксированное значение красной компоненты после 50% прогресса
    red = 255;
    // устанавливаем значение зелёной компоненты
    green -= 5 * (progress - 50);
  }
  printf("Boiling RGB: %d, %d, %dn", red, green, blue);
  change_rgb_led_state(red, green, blue);
  vTaskDelay(pdMS_TO_TICKS(RGB_BOILING_ADJUSTMENT_DURATION));
}
void change_rgb_led_state(int red, int green, int blue) {
  // ЦАП 0-255 to 0-3.3v
  dac_output_voltage(DAC_OUTPUT_RGBLED_G, green);
  dac_output_voltage(DAC_OUTPUT_RGBLED_R, red);
  // синий светодиод всегда выключен
  gpio_set_level(GPIO_OUTPUT_RGBLED_B, blue);
}

Заключение

На более продвинутом примере устройства “Умный чайник” мы разобрали, как собирается проект под ESP32, как используются таски и очереди FreeRTOS и датчик DS18B20 на 1-Wire шине. Мы рассмотрели различия ESP32 и ESP8266 и выбрали подходящий микроконтроллер для проекта. Теперь вам будет проще разобраться в линейке устройств ESP, использовать возможности FreeRTOS и работать с датчиками от Dallas Semiconductor.

Видео того, что получилось:

Об авторе

Ниез Юлдашев — Студент магистратуры ИТИС КФУ по специальности Программная инженерия (профиль: Аналитика, управление разработкой и FinTech),  стажёр Исследовательского центра Samsung.

Всего голосов 7: ↑6 и ↓1 +5

  • 18 августа в 14:10
    Всего голосов 14: ↑14 и ↓0 +14

    Просмотры 4.3K

  • 31 мая в 12:07
    Всего голосов 1: ↑1 и ↓0 +1

    Просмотры 2.8K

  • 21 января 2014 в 12:20
    Всего голосов 15: ↑12 и ↓3 +9

    Просмотры 13K

  • вчера в 12:00
    Всего голосов 46: ↑43 и ↓3 +40

    Просмотры 5K

  • вчера в 14:35
    Всего голосов 41: ↑39 и ↓2 +37

    Просмотры 8.2K

  • вчера в 10:17
    Всего голосов 19: ↑19 и ↓0 +19

    Просмотры 3.4K

  • вчера в 16:30
    Всего голосов 22: ↑20 и ↓2 +18

    Просмотры 2.9K

  • вчера в 12:30
    Всего голосов 18: ↑18 и ↓0 +18

    Просмотры 4.1K

  • вчера в 11:04
  • 8 сентября в 15:48
  • 18 августа в 14:10
  • 8 августа в 20:29
  • 4 августа в 20:19

Source: habr1

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.