Вот красивая фотка, чтобы не скучать, ведь сегодня картинок не будет- только техническое душнилово. Хотел бы сказать наслаждайтесь, но как-то не к месту..
Оглавление.
- Часть 3: Компонент ESPHome. <-- Сейчас Вы здесь.
- Часть 4: Готовый модуль.
- Часть 5: Хакер в деле или как самому понять протокол связи.
Компонент ESPHome.
Итак, Вам люто захотелось посмотреть, что там где в этом проклятом коде, как оно вообще шуршит и куда какие команды шуруют. Ладно, сами напросились, но я не любитель долго расписывать то, в чем дОлжно разбираться, раз уж решили поковыряться в коде. Потому все буду расписывать плюс-минус так же, как это сделано в документации на ESPHome. А дальше уже как-нибудь самостоятельно думайте...
Значится, сам компонент для ESPHome является внешним, располагается на github, хотя может быть перенесен и локально. Ради удобства и совместимости с максимальным числом платформ, компонент сделан не полностью самостоятельным. Для работы компонента кондиционера обязательно соблюдать 2 момента:
1) Должен быть отключен логгер таким образом:
# ОБЯЗАТЕЛЬНО отключаем логгирование через UART
logger:
baud_rate: 0
2) Столь же обязательно должен быть инициализирован порт UART для общения модуля с кондиционером, например так:
uart:
baud_rate: 9600
data_bits: 8
parity: EVEN
stop_bits: 1
rx_pin: ${uart_rx}
tx_pin: ${uart_tx}
Подробнее о настройке UART можно посмотреть в официальной документации по ESPHome.
Теперь к самому компоненту, он подключается в ESPHome довольно просто. Сначала, подключаем его с указанием его месторасположения:
# Компонент климата
external_components:
- source:
url: https://github.com/I-am-nightingale/tclac.git
type: git
ref: master
components: [ tclac ]
refresh: 30s
А после уже инициализируем:
climate:
- platform: tclac
name: "${device_name}"
id: ${device_id}
Так как мой компонент использует в качестве основы стандартный компонент Climate, то и все обычные параметры (имя, id, настройка температуры, режимов, пресетов, заслонок и скорости вентилятора и т.д.) полностью повторяют стандартный компонент ESPHome Climate.
Нестандартные параметры:
- rx_led (необязательный, пин)- пин модуля, к которому подключен светодиод о приеме сигнала с кондиционера
- tx_led (необязательный, пин)- пин модуля, к которому подключен светодиод о передаче сигнала в кондиционер
- show_display (необязательный, логический)- управление LED дисплеем уставки температуры на корпусе внутреннего блока, включен или выключен. При выключении кондиционер переходит в автоматический режим!
- beeper (необязательный, логический)- управление пищалкой кондиционера, включена или выключена.
- force_mode (необязательный, логический)- принудительное применение режимов заслонок, пищалки и LED-дисплея сразу же после изменения. Если значение false, то применение этих режимов будет только после следующей подачи команды из карточки компонента.
- show_module_display (необязательный, логический)- управление индикацией приема/передачи на модуле, если были указаны параметры rx_led и tx_led.
- supported_modes (необязательный, список): список доступных режимов работы кондиционера. По умолчанию: OFF, AUTO, COOL, HEAT, DRY, FAN_ONLY
- supported_fan_modes (необязательный, список): список доступных режимов вентилятора. По умолчанию: AUTO, QUIET, LOW, MIDDLE, MEDIUM, HIGH, FOCUS, DIFFUSE
- supported_swing_modes (необязательный, список): список доступных режимов качания заслонок. По умолчанию: OFF, VERTICAL, HORIZONTAL, BOTH
- supported_presets (необязательный, список): список доступных пресетов кондиционера. По умолчанию: NONE, ECO, SLEEP, COMFORT
Далее предстоит разобраться с тем, как взаимодействовать с компонентом непосредственно. Это делается либо через действия YAML, либо через вызовы LAMBDA. Подробнее можно почитать в официальной документации к ESPHome, и там же можно почитать о стандартных действиях с компонентом Climate- вентилятор, режим, температура и вот это все, я же поведу речь о том, чего в стандартном компоненте нет.
Но и тут моментик: так как карточке кондиционера на панели управления полагается (разработчиками Home Assistant) уметь только определенный список действий, то все эти действия, по сути, делятся на 2 части: запрос состояния атрибутов и запрос изменения состояния. В первом случае вызовы можно выполнять напрямую и ничего не меняется, во втором- меняется состояние компонента, что отображается в карточке НЕ ВСЕГДА СРАЗУ! Запрос отправляется в кондиционер вместе с публикацией нового состояния в карточке, после чего считывается состояние кондиционера и полученные данные снова публикуются в карточке. Но а хуже всего то, что компоненты типа Climate являются классом PollingComponent, что означает, что запрос состояния кондиционера выполняется по таймеру. Таким сомнительным образом (я не знаю, как выстраивается очередь запросов в Home Assistant) может сложиться 3 ситуации:
- Часто: изменение состояния кондиционера было опубликовано в карточке, запрос был отправлен в кондиционер, кондиционер изменил состояние и на запрос состояния из кондиционера ответил уже измененными атрибутами, которые не вызвали изменений в карточке
- Редко: изменение состояния кондиционера было опубликовано в карточке, запрос в кондиционер был поставлен в очередь, но еще до этого из кондиционера было запрошено по таймеру состояние атрибутов, которые, разумеется, отличаются, а, следовательно, карточка кондиционера меняет состояние на предыдущее (считанное из кондиционера), уже после этого кондиционер изменил состояние и на запрос состояния ответил уже измененными атрибутами, которые вновь вызвали изменение состояния карточки кондиционера. Таким образом, атрибуты в карточке будут "прыгать", но в итоге все отработает как надо.
- Крайне редко: изменение состояния кондиционера было опубликовано в карточке, запрос не успел встать в очередь, и непосредственно перед отправкой из кондиционера было запрошено по таймеру состояние атрибутов, на что кондиционер вернул старые атрибуты, что привело к возврату карточки кондиционера в предыдущее состояние, теперь уже запрос формируется по старому состоянию карточки, в итоге в кондиционер отправляется старое состояние, на что кондиционер не реагирует (и так ведь все уже как надо, по его мнению). Таким образом, атрибуты в карточке сначала сменятся на новые, но ничего не будет происходить и через время карточка вернется в предыдущее состояние.
Увы, такое положение дел никак не обойти теми средствами, которые есть прямо сейчас. Можно было бы дважды запрашивать атрибуты кондиционера- до и после отправки, для сравнения и, при необходимости, повторного изменения состояния, однако, компонент уже сейчас блокируется на слишком долгий промежуток времени, необходимый для отправки и приема данных, уже сейчас есть некоторая "вязкость" управления кондиционером, а увеличение еще на один запрос вообще похоронило бы какую-либо отзывчивость. Технически, есть вариант неблокирующей работы UART с использованием DMA, однако, в ESP8266 его нет, и использование такового существенно сократит возможности использования компонента. Так или иначе, код на github, прошу знающих, умеющих и просто толковых при желании посмотреть на предмет каких-либо интересных вариантов решения.
И вот еще что: запросов атрибутов уникальных параметров нет, я их не реализовал подразумевая, что все состояния меняются в настройках и по повелению пользователя, а режимы сохраняются в памяти модуля и после перезагрузки применяются при запуске. Делать или нет запрос атрибутов- ХЗ, лично я против, поскольку обновление состояния будет крайне костыльным, чего не хотелось бы.
Действия YAML и вызовы LAMBDA:
ВНИМАНИЕ! Действия через YAML мной не проверены и в данный момент указаны без гарантий на работоспособность! Компонент писался с поддержкой YAML, но я в данный момент используются вызовы через LAMBDA и проверял только их! В остальном же следует употреблять стандартный синтаксис действий автоматизации.
В отличие от непосредственных действий через YAML, любые операции в LAMBDA выполняются через вызовы, для которых надо помнить id инициализированного компонента. Ну или просто посмотреть вначале, вот, в моем примере здесь, он указан, как ${device_id}, его и будем использовать. Как и в случае с YAML, стандартные запросы к компонентам Climate я не буду расписывать и отправляю учить оригинальную документацию ESPHome, а описывать я буду только особенности моего компонента.
Действие climate.tclac.display_on
Включает LED-дисплей уставки температуры на корпусе внутреннего блока.
YAML:
on_...:
then:
climate.tclac.display_on: device_id
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_display_state(true);
Действие climate.tclac.display_off
Выключает LED-дисплей уставки температуры на корпусе внутреннего блока.
YAML:
on_...:
then:
climate.tclac.display_off: device_id
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_display_state(false);
Действие climate.tclac.beeper_on
Включает пищалку внутреннего блока.
YAML:
on_...:
then:
climate.tclac.beeper_on: device_id
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_beeper_state(true);
Действие climate.tclac.beeper_off
Выключает пищалку внутреннего блока.
YAML:
on_...:
then:
climate.tclac.beeper_off: device_id
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_beeper_state(false);
Действие climate.tclac.module_display_on
Включает индикацию приема/передачи на модуле.
YAML:
on_...:
then:
climate.tclac.module_display_on: device_id
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_module_display_state(true);
Действие climate.tclac.module_display_off
Выключает индикацию приема/передачи на модуле.
YAML:
on_...:
then:
climate.tclac.module_display_off: device_id
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_module_display_state(false);
Действие climate.tclac.force_mode_on
Включает принудительное применение режимов заслонок, пищалки и LED-дисплея сразу же после изменения.
YAML:
on_...:
then:
climate.tclac.force_mode_on: device_id
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_force_mode_state(true);
Действие climate.tclac.force_mode_off
Выключает принудительное применение режимов заслонок, пищалки и LED-дисплея сразу же после изменения.
YAML:
on_...:
then:
climate.tclac.force_mode_off: device_id
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_force_mode_state(false);
Действие climate.tclac.set_vertical_airflow
Установка (фиксация) вертикальной заслонки, когда качание вертикальной заслонки отключено. Возможные значения: Last, Max_Up, Up, Center, Down, Max_Down.
YAML:
on_...:
then:
- climate.tclac.set_vertical_airflow
id: device_id
vertical_airflow: Up
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_vertical_airflow(esphome::tclac::AirflowVerticalDirection::UP);
Действие climate.tclac.set_horizontal_airflow
Установка (фиксация) горизонтальных заслонок, когда качание горизонтальных заслонок отключено. Возможные значения: Last, Max_Left, Left, Center, Right, Max_Right.
YAML:
on_...:
then:
- climate.tclac.set_horizontal_airflow
id: device_id
horizontal_airflow: Right
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_horizontal_airflow(esphome::tclac::AirflowHorizontalDirection::RIGHT);
Действие climate.tclac.set_vertical_swing_direction
Установка режима качания вертикальной заслонки. Возможные значения: UP_DOWN, UPSIDE, DOWNSIDE
YAML:
on_...:
then:
- climate.tclac.set_vertical_swing_direction
id: device_id
vertical_swing_direction: UP_DOWN
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_vertical_swing_direction(esphome::tclac::VerticalSwingDirection::UP_DOWN);
Действие climate.tclac.set_horizontal_swing_direction
Установка режима качания горизонтальных заслонок. Возможные значения: LEFT_RIGHT, LEFTSIDE, CENTER, RIGHTSIDE
YAML:
on_...:
then:
- climate.tclac.set_horizontal_swing_direction
id: device_id
horizontal_swing_direction: LEFT_RIGHT
LAMBDA:
on_...:
then:
lambda: !lambda |-
id(${device_id}).set_horizontal_swing_direction(esphome::tclac::HorizontalSwingDirection::LEFT_RIGHT);
Всякие-разные моменты
Следует знать некоторые моменты, важные для правильной работы компонента. И первый из них- старт модуля.
Так уж я решил сделать, что компонент кондиционера не сохраняет состояние переключателей и атрибуты. Они же не сохраняются и в коде прошивки модуля. Почему я так сделал? Очень просто- можно было бы серьезно поиметь проблем с надежностью и адекватностью работы, когда в процессе конфигурации можно было бы получить взаимоисключающие настройки, безостановочно сохраняющиеся во flash. Как же тогда организовано сохранение настроек? Просто- через настройки!
У переключателей, выпадающих меню и прочего есть возможность сохранить значение во flash, для этого надо лишь накрутить настройку restore_mode. Эта настройка будет сохранять состояние, а после перезагрузки пытаться восстановить значение. Можно сразу же настроить значение, если этого не удается (например свежезалита прошивка или как-то попортилось сохранение), например, используя значение RESTORE_DEFAULT_ON - так, если восстановить предыдущее значение не удается, то будет принято значение "включено" или "true". Вот живой пример:
switch:
- name: Beeper
platform: template
device_class: switch
id: beep_mode
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
on_turn_on:
then:
lambda: !lambda |-
id(${device_id}).set_beeper_state(true);
on_turn_off:
then:
lambda: !lambda |-
id(${device_id}).set_beeper_state(false);
Наметанный глаз заметит, что здесь только 2 события: on_turn_on и on_turn_off, которые срабатывают при взаимодействии с переключателем "Beeper". Да, состояние сохраняется, но как теперь его передать в компонент кондиционера? Выставить значение при запуске, конечно! Как теперь сделать это? Покажу:
esphome:
name: ${device_name}
on_boot:
priority: -100
then:
lambda: !lambda |-
if (id(beep_mode).state){
id(${device_id}).set_beeper_state(true);
} else {
id(${device_id}).set_beeper_state(false);
}
Здесь после инициализации всего железа и соединения с Home Assistant (priority: -100), будет принудительно установлено то состояние атрибута, в каком находится переключатель.
Второй момент- когда включен атрибут force_mode (принятие настроек сразу при изменении) и при этом включена пищалка, то принятие сохраненных состояний в процессе запуска будет сопровождаться писком. Как-бы, нет проблем, да? А вот и есть! Что если связь с WiFI плохая, и время от времени связь теряется вообще? А модуль будет перезагружаться или пересоединяться, и в процессе этого снова будет проходить приоритет -100. И снова бип. Мой личный рекорд- 2 часа безостановочных бипов, точка доступа у меня накрылась.. Поэтому процесс принятия состояний надо начинать с того, что отключать пищалку и force_mode, и принимать их сохраненные значения только в самом конце, причем force_mode обрабатывать в последнюю очередь.
В качестве эпилога
Жуткая, жуткая душнота! Я открыл форточку, включил вытяжку, распахнул входную дверь и балкон- ничего не помогает! Ну, что-ж поделать, войти в IT, блин! Впрочем, кому-то это пригодится, может, у кого-то появятся хорошие идеи или твердое желание пойти точить болванки на завод, а не жать на клавиши, отсиживая задницу- от души поддерживаю.
Но так или иначе, и с этим разобрались. Может, это просто моя нелюбовь к программированию, и я почем зря душню, но в следующий раз поговорим снова о железе. Подписывайтесь, чтобы не прошляпить! А поддержать меня можно либо кнопкой "Поддержать" в конце статьи, либо вот этой ссылкой: dzen.ru/solovey_with_payalnik?donate=true . Донаты здорово поддерживают настрой и желание продолжать. Но кидайте донаты только добровольно и не в ущерб себе любимому!
До встречи в следующей части!