• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Делюсь опытом Разработка умных устройств на примере контроллера теплого пола на ESP8266

valerivp

Member
Хочу поделиться своим опытом разработки умного устройства. Демонстрацию и описание устройства можно посмотреть на сайте Контроллер теплых полов - Наш дом.
В этой публикации я опишу аппаратное (кратко) и программное (более подробно) обеспечение.

Контроллер предназначен анализировать показания датчиков (проводных и беспроводных) и поддерживать заданную (с учетом расписания, в т.ч. по дням недели) температуру в каждой отдельной зоне, путем включения/выключения котла и управления петлями водяного теплого пола с помощью термоголовок на коллекторе.

Копия на Хабре: Разработка умных устройств на примере контроллера теплого пола на ESP8266
 
Последнее редактирование:

valerivp

Member
Аппаратное обеспечение

В качестве контроллера был выбран ESP8266 (WeMos D1 mini), как т.к. имеет WiFi на борту. Вместо ESP8266 может быть использован любой другой контроллер или микрокомпьютер – общие идеи останутся неизменными, главное чтобы на выбранной системе можно было бы развернуть WEB-сервер с поддержкой WEB-сокетов. В проекте так же были использованы:
  • RTC: DS3231 – надо же определять день недели и текущее время. Проект задумывался как автономное устройство, способное работать без интернета, поэтому NTP не подходит.
  • Беспроводные датчики температуры: NoName, 433МГц, от китайской метеостанции – готовое решение, работают от батареек. Что еще надо? А надо чтобы период передачи данных был не фиксированный. Проблема в том, что период передачи 35 секунд и не много плавает. И бывают ситуации, когда сигналы двух датчиков накладываются. В этом случае один или два датчика выпадают из системы на некоторое время. Проблему можно решить, используя похожие датчики, в которых переключение каналов так же изменяет период передачи данных.
  • Приемник 433МГц: Rxb6 – по обзорам в интернетах и личному опыту не плохой приемник.
  • Проводные датчики температуры: DS18B20 – очень удобны тем, что не требуется заранее знать количество датчиков.
  • Мастер шины 1Wire: DS2482-100 – протокол 1Wire очень чувствителен к таймингам, поэтому все реализации программного мастера шины используют delay, что очень не хорошо для многозадачности. Используя эту микросхему, можно использовать преимущества шины 1Wire и избавиться от ее недостатков за счет трансляции 1Wire <-> i2c. Протокол i2c имеет линию синхронизации, за счет чего не критичен к таймингам и часто в контроллерах реализован аппаратно.
  • Сторожевой таймер: TPL5000DGST – для данного проекта не столь важен непрерывный аптайм, однако доступность очень важна. В ESP8266 есть встроенный сторожевой таймер. Но как показала практика, иногда все же бывают ситуации, когда он не справляется и система зависает. Внешний аппаратный сторожевой таймер призван бороться с нештатными ситуациями. Сконфигурирован на задержку в 64сек. Присоединен к ноге TX – при работе система постоянно пишет в Serial отладочную информацию и отсутствие активности более одной минуты свидетельствует о зависании системы.
  • Расширитель портов: 74HC595 – использование этого расширителя требует 4х ног контроллера – три на передачу состояния и одну для того, чтобы при подаче питания реле не щелкали. В следующий раз буду использовать PCF8574 – шина i2c все равно используются, т.е. дополнительных ног MCU не требуется и при подаче питания выходы в 1 установлены.
  • Релейный модуль: NoName, 8-ми канальный, 5В – сказать нечего, кроме того, что включение реле происходит по низкому уровню на входах модуля. Твердотельные реле в данном проекте использовать недопустимо, т.к. коммутация контактов котла должна выполняться сухим контактом – в общем случае я не знаю напряжение и постоянный или переменный ток на контактах.
 

valerivp

Member
Операционная система

Операционная система – все то ПО, которое обеспечивает работоспособность прикладной программы. Операционная система так же является прослойкой между железом и прикладной программой, предоставляя высокоуровневый интерфейс доступа к аппаратным ресурсам. В случае ESP компонентами операционной системы можно считать:

Файловая система

В проекте используется SPIFFS, тут вроде все понятно, это самый простой путь.
Для простого доступа к файлам на устройстве извне, я использую библиотеку nailbuster/esp8266FTPServer.

Система распределения процессорного времени

Это одна из основных функций операционной системы и ESP не станет исключением. За параллельное выполнение различных потоков алгоритма отвечает глобальный объект (синглтон) Timers. Класс достаточно простой и предоставляет следующий функционал:
  • Периодическое выполнение функции, с заданным интервалом. Пример инициализации таймера:
    Код:
    Timers.add(doLoop, 6000, F("OneWireSensorsClass::doLoop")); // третий параметр – отладочная информация
  • Однократное выполнение функции через указанный промежуток времени. Например так выполняется отложенное сканирование WiFi сетей:
    Код:
    Timers.once([]() { WiFi.scanNetworks(true);}, 1);
Таким образом функция loop выглядит подобным образом:


Код:
void loop(void) {
   ESP.wdtFeed();

   Timers.doLoop();

   CPULoadInfo.doLoop();
}

На практике функция loop содержит еще несколько строк, о них будет рассказано ниже.
Листинг класса Timers приложен.

Учет процессорного времени

Сервисная функция, не имеющая практического применения. Тем не менее она есть. Реализуется сингтоном CPULoadInfo. При инициализации объекта, выполняется замер количества итераций пустого цикла за небольшой промежуток времени:

Код:
void CPULoadInfoClass::init() {
   uint32_t currTime = millis();
   // определяем максимальное число циклов в 1сек
   while ((millis() - currTime) < 10) {
       delay(0);
       MaxLoopsInSecond++;
   }
   MaxLoopsInSecond *= 100;
}

Затем, считаем количество вызовов процедуры loop в секунду, считаем загрузку процессора в процентах и сохраняем данные в буфер:

Код:
void CPULoadInfoClass::doLoop() {
   static uint32_t prevTime = 0;
   uint32_t currTime = millis();
   LoopsInSecond++;
   if ((currTime - prevTime) > 1000) {
       memmove(CPULoadPercentHistory, &CPULoadPercentHistory[1], sizeof(CPULoadPercentHistory) - 1);

       int8_t load = ((MaxLoopsInSecond - LoopsInSecond) * 100) / MaxLoopsInSecond;
       CPULoadPercentHistory[sizeof(CPULoadPercentHistory) - 1] = load;

       prevTime = currTime;
       LoopsInSecond = 0;
   }
}

Использование этого подхода позволяет получить так же использование процессора каждым отдельным потоком (если соединить эту подсистему с классом Timers), но как я уже сказал – практического применения этому я не вижу.

Система ввода-вывода

Для общения с пользователем используется UART-USB и WEB интерфейс. Про UART думаю подробно рассказывать не требуется. Единственно на что стоит обратить внимание – для удобства и совместимости с не ESP реализована функция serialEvent():


Код:
void loop(void) {
   // …
   if (Serial.available())
       serialEvent();
   // …
}

С WEB интерфейсом все гораздо интереснее. Посвятим ему отдельный раздел.
 

Вложения

valerivp

Member
WEB интерфейс

В случае с умными устройствами, по моему мнению, WEB интерфейс самое удобное для пользователя решение.
Использование экрана, подключенного к устройству, я считаю устаревшей практикой – невозможно при использовании небольшого экрана и ограниченного набора кнопок создать простой, удобный, красивый интерфейс.
Использование специфических программ для управления устройством – накладывает ограничения на пользователя, добавляет необходимость разработки и поддержки этих программ, а также требует от разработчика заботиться о доставке этих программ на терминальные устройства пользователя. По-хорошему, приложение должно быть опубликовано в магазинах приложений Google, Apple, Windows, а также доступно в репозиториях Linux в виде deb и rpm пакетов – в противном случае, для какой-то части аудитории может быть затруднен доступ к интерфейсу устройства.
Доступ к WEB интерфейсу устройства доступен с любой операционной системы – Linux, Windows, Android, MacOS, на десктопе, ноутбуке, планшете, смартфоне – лишь бы был браузер. Конечно разработчику WEB интерфейса необходимо учитывать особенности различных устройств, но это в основном касается размера и разрешения. Доступ к WEB интерфейсу умного устройства в доме/квартире/на даче легко обеспечивается извне через интернет – сейчас сложно представить дом/квартиру, в которых есть умные устройства и нет роутера и интернета, а в роутере такой доступ настраивается в несколько кликов (тем, кто совсем не в теме, помогут ключевые слова – «проброска портов» и «динамический DNS»). В случае дачи доступ можно обеспечить с помощью 3G роутера.
Для реализации WEB интерфейса необходим WEB сервер. Я использую библиотеку me-no-dev/ESPAsyncWebServer. Эта библиотека обеспечивает следующий функционал:
  • Отдачу статического контента, в т.ч. с поддержкой сжатия gzip. Поддерживаются виртуальные каталоги, с возможностью указания главного файла (который обычно index.htm) для каждого каталога.
  • Назначение callback функций на различные URL с учетом типа запроса (GET, POST, …)
  • Поддержка WEB сокетов на том же порту (это имеет значение при проброске портов).
  • BASIC авторизацию. Причем авторизация устанавливается индивидуально для каждого URL. Это важно, т.к. например Google Chrome при создании ярлыка страницы на главном экране, запрашивает иконку и файла манифеста и не передает данные авторизации. Поэтому часть файлов помещены в виртуальный каталог и для этого каталога авторизация отключена.


HTTP сервисы операционной системы

В текущем проекте все настройки операционной системы выполняются с помощью HTTP сервисов. HTTP сервис – это небольшой независимый функционал получения/изменения данных, доступный по HTTP. Далее рассмотрим список этих сервисов.

HELP

Реализацию любого списка команд я считаю правильным начать с реализации команды HELP. Ниже блок инициализации WEB сервера:
Код:
void HTTPserverClass::init()
{
   //SERVER INIT
   help_info.concat(F("/'doc_hame.ext': load file from server. Allow methods: HTTP_GET\n"));
   AsyncStaticWebHandler& handler = server.serveStatic("na/", SPIFFS, "na/");
   serveStaticHandlerNA = &handler; // виртуальный каталог в котором Нет Авторизации
   server.serveStatic("/", SPIFFS, "/"); // все остальные файлы
   
   //info
   // перед реализацией сервиса сформируем его описание
   help_info.concat(F("/info: get system info. Allow methods: HTTP_GET\n"));
   server.on("/info", HTTP_GET, handleInfo);
   
   …
   
   server.on("/help", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(200, ContentTypesStrings[ContentTypes::text_plain], help_info.c_str()); });

   // устанавливаем параметры авторизации
   setAuthentication(ConfigStore.getAdminName(), ConfigStore.getAdminPassword());
   
   DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); // разрешает кросс-доменные запросы

   // запускаем сервер
   server.begin();

   // макросы отладочной информации будут приложены отдельно
   DEBUG_PRINT(F("HTTP server started"));

}


Зачем нужна справочная система, рассказывать думаю не стоит. Некоторые разработчик откладывают реализацию справочной системы на потом, но этот «потом» как правило не наступает. Гораздо проще ее реализовать в начале проекта и при развитии проекта дополнять.
В моем проекте список возможных сервисов выводится так же и при ошибке 404 – страница не найдена. На данный момент реализованы следующие сервисы:
http://tc-demo.vehs.ru/help

Код:
/'doc_hame.ext': load file from server. Allow methods: HTTP_GET
/info: get system info. Allow methods: HTTP_GET
/time: get time as string (eg: 20140527T123456). Allow methods: HTTP_GET
/uptime: get uptime as string (eg: 123D123456). Allow methods: HTTP_GET
/rtc: set RTC time. Allow methods: HTTP_GET, HTTP_POST
/list ? [dir=...] & [format=json]: get file list as text or json. Allow methods: HTTP_GET
/edit: edit files. Allow methods: HTTP_GET, HTTP_PUT, HTTP_DELETE, HTTP_POST
/wifi: edit wifi settings. Allow methods: HTTP_GET, HTTP_POST
/wifi-scan ? [format=json]: get wifi list as text or json. Allow methods: HTTP_GET
/wifi-info ? [format=json]: get wifi info as text or json. Allow methods: HTTP_GET
/ap: edit soft ap settings. Allow methods: HTTP_GET, HTTP_POST
/user: edit user settings. Allow methods: HTTP_GET, HTTP_POST
/user-info ? [format=json]: get user info as text or json. Allow methods: HTTP_GET
/update: update flash. Allow methods: HTTP_GET, HTTP_POST
/restart: restart system. Allow methods: HTTP_GET, HTTP_POST
/ws: web socket url. Allow methods: HTTP_GET
/help: list allow URLs. Allow methods: HTTP_GET

Как видно, в списке сервисов отсутствуют какие-либо прикладные сервисы. Все HTTP сервисы относятся к операционной системе. Каждый сервис выполняет одну небольшую задачу. При этом если сервис требует ввода каких-либо данных, то по запросу GET возвращает минималистичную форму ввода:


Код:
#ifdef USE_RTC_CLOCK
   help_info.concat(F("/rtc: set RTC time. Allow methods: HTTP_GET, HTTP_POST\n"));
   const char* urlNTP = "/rtc";
   server.on(urlNTP, HTTP_GET, [](AsyncWebServerRequest *request) { DEBUG_PRINT(F("/rtc")); request->send(200, ContentTypesStrings[ContentTypes::text_html], String(F("<head><title>RTC time</title></head><body><form method=\"post\" action=\"rtc\"><input name=\"newtime\" length=\"15\" placeholder=\"yyyyMMddThhmmss\"><button type=\"submit\">set</button></form></body></html>"))); });
   server.on(urlNTP, HTTP_POST, handleSetRTC_time);
#endif // USE_RTC_CLOCK




В последствии этот сервис используется в более симпатичном интерфейсе:

 

valerivp

Member
Прикладное ПО

Наконец подошли к тому, ради чего система создавалась. А именно – к реализации прикладной задачи.
Любая прикладная программа должна получать исходные данные, обрабатывать их и выдавать результат. Так же, возможно, система должна сообщать текущее состояние.
Исходными данными для контроллера теплого пола являются:
  • Данные датчиков – система не привязана к конкретным датчикам. Для каждого датчика формируется уникальный идентификатор. Для радио-датчиков их идентификатор дополняется до 16 бит нулями, для датчиков 1Wire на основании их внутреннего идентификатора вычисляется CRC16 и используется в качестве идентификатора датчика. Таким образом, все датчики имеют идентификаторы длиной 2 байта.
  • Данные о зонах отопления – число зон не фиксировано, максимальное количество ограничено используемым модулем реле. С учетом этого ограничения так же разрабатывался WEB интерфейс.
  • Целевая температура и расписание – я постарался сделать максимально гибкие настройки, можно создать несколько схем отопления и можно даже на каждую зону назначить свою схему настроек.

Таким образом есть некоторое количество настроек, которые надо как-то задавать, и есть некоторое количество параметров, которые система сообщает как текущее состояние.
Для общения контроллера с внешним миром я реализовал командный интерпретатор, который позволил реализовать как управление контроллером, так и получать данные о состоянии. Команды передаются контроллеру в человекочитаемом виде, и могут передаваться через UART или WEB сокет (при желании можно реализовать поддержку других протоколов, например telnet).
Строка команд начинается со знака '#' и оканчивается нулевым символом либо символом перевода строки. Все команды состоят из имени команды и операнда, разделенных двоеточием. Для некоторых команд операнд не обязателен, в этом случае двоеточие и операнд не указываются. Команды в строке разделяются запятой. Например:
#ZonesInfo:1,SensorsInfo
И конечно же список команд начинается с команды Help, которая выводит список всех допустимых команд (передаваемые команды для удобства восприятия начинаются со знака '>' вместо '#'):

Код:
>help
Help
SetZonesCount
Zone
SetName
SetSensor
...
LoadCfg
SaveCfg
#Cmd:Help,CmdRes:Ok
Особенностью реализации командного интерпретатора является то, что информация о результате выполнения команды выдается так же в виде команды или набора команд:

Код:
>help
…
#Cmd:Help,CmdRes:Ok

>zone:123
#Cmd:Zone,Value:123,CmdRes:Error,Error:Zone 123 not in range 1-5

>SchemasInfo
#SchemasCount:2
#Schema:1,Name:Основная,DOWs:0b0000000
#Schema:2,Name:Гараж,DOWs:0b0000000
#Cmd:SchemasInfo,CmdRes:Ok

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

Код:
>zonesInfo:3
#Zone:3,Name:Спальня,Sensor:0x5680,Schema:1,DeltaT:-20
#Cmd:ZonesInfo,CmdRes:Ok
WEB интерфейс передал запрос контроллеру о зоне номер 3, и получил в ответ название зоны, идентификатор датчика, привязанного к зоне, идентификатор схемы, назначенной зоне и коррекцию температуры для зоны. Командный интерпретатор не понимает дробных чисел, поэтому температура передается в десятых долях градуса, т.е. 12.3 градуса это 123 десятых долей.

Ключевой особенностью является то, что на любую команду, вне зависимости от способа ввода команды, контроллер отвечает сразу всем клиентам. Это позволяет отображать изменение состояния сразу во всех сеансах WEB интерфейса. Т.к. основной транспорт обмена между контроллером и WEB интерфейсом это WEB сокеты, то контроллер может передавать данные без запроса, например когда приходят новые данные от датчиков:

Код:
#sensor:0x5A20,type:w433th,battery:1,button_tx:0,channel:0,temperature:228,humidity:34,uptime_label:130308243,time_label:20180521T235126
Или о том например, что данные зоны необходимо актуализировать:

Код:
#Zone:2,TargetTemp:220,CurrentTemp:228,Error:Ok

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

valerivp

Member
Заключение

Использование подобного подхода, а именно:
  • Разделение ПО на операционную систему и прикладную программу
  • Реализация настроек операционной системы в виде минималистичных HTTP сервисов
  • Отделение логики системы от представления данных
  • Использование человекочитаемых протоколов обмена данными

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


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

valerivp

Member
Например работало и перестало - не греют полы. или еще круче наоборот перегреваются
Как всегда - искать проблему. Вне зависимости от устройства и производителя.

Вариантов два:
  1. Ошибка ПО - интерфейс показывает состояние и цель. Если они не сходятся - обращаться к производителю.
  2. Проблема железа - менять железо
Вероятна ситуация, когда отображаемое состояние не соответствует факту - см. пп 1 и 2
 

valerivp

Member
А вы можете привести пример любого устройства, которое само может себя починить?
 

valerivp

Member
устройство должно содержать тестирующую исправность всех элементов прогу.
А где содержится прога, которая контролирует корректность работы этой проги?

Не вижу практического смысла в вашем замечании.
Девайс умеет общаться с внешним миром следующими путями:

  1. UART - USB. Достаточно надежен, но не удобен
  2. WEB интерфейс. Достаточно удобен, но может отвалиться - роутеры там и т.п.
Добавить пищалку? кому она будет пищать, если все уехали в отпуск?

посредством чего вы считаете надо выдать ошибку? Я думал добавить GSM модуль, чтобы SMS слал, но это в будущем. И систему бесперебойного питания.
А на данный момент - только внешний аппаратный сторожевой таймер.

Если уж делать контроль - то делать его на отдельном устройстве, заказанном у другого разработчика. Так всегда делают в критичных системах.
Например контроль фактической температуры - какой датчик будет отвечать за нее? тот который подключен к контроллеру? А если этот датчик заглючит, и повиснет например на +20, в не зависимости от температуры?
А термоголовки - в них нет обратной связи. Я не знаю, вдруг ее вообще выкрутили.

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

valerivp

Member
теперь по существу
объясните зачем эти элементы особо второй.
  • Приемник 433МГц: Rxb6 – по обзорам в интернетах и личному опыту не плохой приемник
Система работает с беспроводными датчиками от метеостанций. Датчики передают сигнал на частоте 433. Надо же как-то ловить сигнал.

  • Мастер шины 1Wire: DS2482-100 – протокол 1Wire
У меня некоторое число датчиков. Сейчас - десяток. Опрос без мастера шины - критичен к таймингам. А у меня обработка данных приемника 433, в прерывании. Там тоже критичны временные интервалы. Добавляем мастер - решаем проблему.
 
Последнее редактирование:

valerivp

Member
идем далее
Расширитель портов: 74HC595
может управляться двумя пинами, вместо четырех
поясните зачем нужен расширитель?
Какие два пина не нужны? Или какие два нужны?

Система управляет блоком из 8 реле. Каким образом ESP8266 без расширителя сможет это сделать?
С учетом того что используются UART, I2C и один пин на приемик.
Да, и еще пины на переключатель демо режима - GPIO0 и GPIO2 (вроде. Ну те, которые отвечают за режим загрузки ESP8266)
 

valerivp

Member
если не ошибаюсь, вы использовали Arduino IDE для разработки? Вы читали лицензионное соглашение на её использование?
Ошибаетесь. Я использую купленную Visual Micro
Вместе с тем, Arduino IDE имеет лицензию LGPL , которая не обязывает публиковать исходные тексты.
 

valerivp

Member

Сергей_Ф

Moderator
Команда форума
@valerivp обратите внимание на пункт 6.2. Если ваша цель поделиться технической информацией, то вы нашли правильное место. Если цель - реклама вашей продукции, то рекомендую обратиться на рекламные площадки или напрямую к Администратору форума для продвижения вашего замечательного продукта.
 

valerivp

Member
@valerivp обратите внимание на пункт 6.2. Если ваша цель поделиться технической информацией, то вы нашли правильное место. Если цель - реклама вашей продукции, то рекомендую обратиться на рекламные площадки или напрямую к Администратору форума для продвижения вашего замечательного продукта.
Если считаете, что публикация имеет исключительно рекламный характер - удалите.
Вообще, мне кажется, что это флуд в тематической ветке. Есть претензии - пишите в личку.
 
Сверху Снизу