Я уже несколько лет методично выбирал устройства для умного дома, работающие по протоколу Zigbee, чтобы не зависеть от облака вендора. Однако прошлой осенью в моей жизни появилась кошка. В силу ограниченности вариантов, пришлось обзавестись рядом устройств, работающих только по Wi‑Fi и только с приложением вендора.
Под катом расскажу, как я преодолел это недоразумение.

Интро
Зачем
Напрягало, что в домашней сети есть устройства, которые я фактически не контролирую (а в кормушке, например, есть микрофон).
Альтернативно можно пользоваться LocalTuya, но для его использования вам придётся зарегистриовать аккаунт на платформе разработчиков Tuya, и раз в пару месяцев продлевать бесплатный пробный период для доступа к ключам девайса. Так что отказом от облака это назвать сложно.

Что необходимо из оборудования
В минимальный комплект для анализа умных девайсов входят:
Мультиметр, он поможет прозвонить контакты и определить интерфейсы
Паяльник (nuff said)
UART адаптер. Подойдёт любой популярный на чипах FT232, CP2102, CH340. Главное чтобы была поддержка выбора рабочего напряжения 3.3V или 5V.
Щипцы чтобы не трогать элементы при пайке голыми руками
Припой
Несмываемый флюс
Оплётка чтобы лишний припой убрать при выпаивании элементов
Опционально ещё можно добавить антистатический браслет для того, чтобы заземлиться и ничего не убить статикой, а также паяльный фен — с ним выпаивать контроллеры гораздо удобнее.
Крайне советую перед началом пайки умных устройств потренироваться паять на чём-то ненужном. Хороший гайд по пайке есть например у Гайвера.

Кондиционер
Первым исследуемым устройством стал кондиционер Midea. Поставлялся он с вот таким наборчиком ранее мне незнакомого вендора Daichi. В комплекте “таблетка” с контроллером, двусторонний скотч с велкро для крепления под крышку внутреннего блока сплит-системы и набор проводов для подключения.

Начнём с поиска информации об этом чудо-девайсе. Главное пока гуглишь информацию не перепутать Daichi и какой-нибудь Daikin. Daikin также производит “умные” кондиционеры и даже продаёт подписку на них в развивающихся рынках. Платите за дни использование кондиционера — настоящий киберпанк!
Недовольные жители Арасаки правда уже написали альтернативную прошивку для контроллеров, чтобы управлять кондиционером локально — ESP32-Faikout.
Разобрав “таблетку” можем изучить её внутренний мир. Видим что плата называется Daichi DW23-B Ver. 05. Также видим что распаян контроллер TYWE3S на базе ESP8266.

Можем сразу загуглить этот чип и найти распиновку интерфейса UART:

Однако он нам не понадобится т.к. распаяный на плате порт USB на самом деле является UART портом о чём пишут на 4PDA. А загадочный переключатель с подписью UART переключает устройство между управлением кондиционером по UART и возможностью прошивки контроллера.

Переместив выключатель в обратную стрелочке сторону можем подключить контроллер к UART адаптеру и с помощью утилиты ESPTool создать резервную копию прошивки контроллера.
esptool.py --port /dev/ttyUSB0 read_flash 0 0 backup.bin

Взамен оригинальной прошивки я записал на контроллер ESPHome. Этот проект предоставляет удобный веб интерфейс для управления устройствами и обновления их конфигурации по Wi-Fi.

Для кондиционеров Midea достаточно добавить в конфигурационный файл ESPHome следующие строки:
climate: - platform: midea autoconf: True beeper: True name: AC bedroom
Полный конфиг контроллера выглядит так:
esphome: name: ac-bedroom friendly_name: AC-bedroom esp8266: board: d1_mini # Enable logging logger: # Enable Home Assistant API api: encryption: key: "********" ota: - platform: esphome password: "********" wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "AC-Bedroom Fallback Hotspot" password: "********" uart: - baud_rate: 9600 tx_pin: GPIO1 rx_pin: GPIO3 climate: - platform: midea autoconf: True beeper: True name: AC bedroom captive_portal:
Используя интеграцию ESPHome для HomeAssistant получаем в HA удобный интерфейс для управления кондиционером:

Кормушка
Первым кошачьим девайсом стала кормушка. Управлялась она с приложения Smart Life через интернет.

Разборка
Анализ начинаем с разборки устройства. Плата находится за панелькой с кнопками. Чтобы до неё добраться, приходится разобрать весь механизм. Полезно делать фотографии на каждом шаге разборки, чтобы потом собрать обратно.

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

На плате видим контроллер WBR3, пару чёрных прямоугольных контроллеров и 4 площадки, очень похожие на площадки UART (помеченные как X14).

Определяем UART

Первым делом я решил посмотреть какие данные будут видны в UART. Для этого надо идентифицировать площадки. Тут может помочь гайд от проекта HardwareAllTheThings.
В кратком пересказе примерно так:
GND — прозванивается на землю
VCC — стабильные +3.3 или +5V со старта девайса
TX — много прыгает напряжение сразу после старта, затем уровень становится как у VCC, либо продолжает прыгать
RX — напряжение не прыгает, низкий уровень

Однако, подключившись и запитав устройство, мы получаем непонятный набор нечитаемых символов. Ситуация не меняется со сменой Baud rate.
Забегая вперёд скажу, что на этом этапе получалось видеть пакеты сообщений между контроллерами.
Tuya
Тут стоит сделать лирическое отступление и немножко рассказать про Tuya. Эти ребята продают производителям умных устройств свою облачную платформу, включающую в себя приложения для конечного пользователя “Tuya Smart” и “Smart Life” и панель управления облаком для производителей. Производители используют Wi-Fi контроллеры от Tuya (например WBR3) и готовую прошивку для них для подключения устройств к облаку.

В такой схеме, именуемой TuyaMCU, Wi-Fi контроллер подключается через вашу домашнюю сеть к облаку Tuya и регистрирует в нём устройство. Далее он опрашивает через UART другой контроллер — MCU, из которого получает данные с датчиков и ему же посылает команды, если вы в приложении нажали кнопку “Покормить кошку”. Вся логика работы устройства при этом реализована в MCU.
Таким образом Wi-Fi контроллер исполняет роль прокси между облаком Tuya и MCU, и даже если его насовсем выпаять то лоток продолжит самоочищаться, а кормушка продолжит давать корм по нажатию кнопки. Кормление по расписанию работать не будет т.к. при перезапуске в кормушке собьётся время.

Однако цель у меня была сохранить весь штатный функционал, поэтому я решил всё же перепрошить контроллер на что-то поддерживаемое HomeAssistant.
Перед перепрошивкой стоит вспомнить мою прошлую попытку подпаяться к UART. Это как раз был интерфейс между Wi-Fi контроллером и MCU. Если вы повторяете шаги за этой статьёй, то советую к тем пинам всё же подпаяться и с помощью TuyaMCUAnalyzer записать коммуникацию между контроллерами активно используя устройство через облако. Это очень упростит вам настройку устройства в дальнейшем.

Далее гуглим как вообще этот WBR3 прошивать и на что.

На сайте Tuya находим даташит, из которого выясняется, что пины, используемые для извлечения и замены прошивки находятся на нераспаяных пинах A_0 и A_15, из-за чего необходимо выпаять контроллер.

OpenBeken
Из-за того что WBR3 сделан на основе RTL8720CF, а не на основе чипов Espressif, ESPHome на него поставить не удастся. Можно конечно поменять контроллер на совместимый с ESPHome, но зачем?
Тем болле что на форуме elektroda.com сделали проект OpenBeken, представляющий альтернативу ESPHome/Tasmota для таких нишевых чипов как наш. Также они эксплицитно заявляют о поддержке TuyaMCU.
Список поддерживаемых платформ поистине внушителен.

Для простоты подключения к чипу нарисовал схему, тут указаны точки подключения TX и RX UART адаптера:

Как это выглядело у меня:

Для получения бекапа прошивки используем ltchiptool:
ltchiptool flash read realtek-abz2 backup.bin
Если у вас возникают ошибки на этом этапе, то кроме всего прочего проблема может быть в недостаточной мощности тока, выдаваемой UART адаптером. WBR3 могут быть очень прожорливы. Рекомендуется использовать внешний источник питания на 3.3V.
Записываем OpenBeken им же:
ltchiptool flash write OpenRTL87X0C_1.18.295.bin

После прошивки запаиваем всё обратно, собираем устройство и включаем. Появится новая точка доступа, подключившись к которой можно будет в веб интерфейсе задать название и пароль Wi-Fi сети, к которой подключается устройство.
Далее необходимо задать конфигурацию OpenBeken. Нажимаем кнопку “Launch web app”, и переходим на вкладку Filesystem. Тут создаём файл “/autoexec.bat”. Каждому датчику и настраиваемому значению в MCU присвоен свой идентификатор dpID. Ему нужно задать тип данных. Также нужно активировать драйвер TuyaMCU для подключения функциональности общения с MCU и NTP для получения времени. Какой dpID за что отвечает вы уже знаете, если по совету выше прослушивали общение контроллера и MCU по UART с помощью TuyaMCUAnalyzer. Подробная документация и примеры по настройке есть в Wiki OpenBeken. Ниже приведу конфигурацию своей кормушки:
Содержимое autoexec.bat:
startDriver TuyaMCU # необходим для работы с TuyaMCU startDriver NTP # необходим для работы с NTP и получения времени для кормления по расписанию ntp_timeZoneOfs +3:00 # задаём временную зону waitFor NTPState 1 # ждём пока NTP получит данные setChannelType 4 TextField # состояние устройства linkTuyaMCUOutputToChannel 4 3 4 setChannelType 10 BatteryLevelPercent # состояние аккумуляторов, используемых при отключении электричества linkTuyaMCUOutputToChannel 10 int 10 setChannelType 3 Toggle # кнопка "покормить" linkTuyaMCUOutputToChannel 3 int 3 # tuyaMcu_defWiFiState 4 # многие устройства некорректно работают без этого параметра tuyaMcu_sendCurTime; # отправляет в MCU текущее время для работы кормления по расписанию
В самом OpenBeken интерфейс устройства абсолютно вырвиглазный:

Однако на стартовой странице OpenBeken мы можем задать адрес и учётные данные от MQTT сервера и через интеграцию c MQTT подключить его в HomeAssistant:

Также был написан простенький скрипт для кормления по нажатию кнопки изменением состояния dpID 3:
sequence: - device_id: 7154562b9a74904a0c1e9a0a4d11ccab #кормушка domain: text entity_id: 9b44f0b33e5989d3417e9a14ec95ec70 #dpID 3 type: set_value value: "0" - device_id: 7154562b9a74904a0c1e9a0a4d11ccab #кормушка domain: text entity_id: 9b44f0b33e5989d3417e9a14ec95ec70 #dpID 3 type: set_value value: "1" - delay: hours: 0 minutes: 0 seconds: 3 milliseconds: 0 - device_id: 7154562b9a74904a0c1e9a0a4d11ccab #кормушка domain: text entity_id: 9b44f0b33e5989d3417e9a14ec95ec70 #dpID 3 type: set_value value: "0" alias: Feed cat description: ""
Внимательный читатель может заметить, что тут нет настройки расписания кормушки, а ведь это важная функция! Проблема только в том, что она представляет из себя большую структуру, с которой не умеет работать OpenBeken. Увидеть расписание в dpID=3 можно выполнив команду tuyaMcu_sendQueryState в терминале OpenBeken во вкладке Logs.
Результат выполнения команды tuyaMcu_sendQueryState:
Info:CMD:[WebApp Cmd 'tuyaMcu_sendQueryState' Result] OK Info:TuyaMCU:Received: 55 AA 03 07 00 1D 01 00 00 19 7F 09 00 01 00 2B 0D 00 01 00 7F 0F 00 01 00 2B 11 00 01 00 7F 15 00 01 00 63 Info:TuyaMCU:ProcessIncoming[v=3]: cmd 7 (State) len 36 Info:TuyaMCU:ParseState: id 1 type 0-raw len 25 Info:TuyaMCU:Received: 55 AA 03 07 00 08 0A 02 00 04 00 00 00 3F 60 Info:TuyaMCU:ProcessIncoming[v=3]: cmd 7 (State) len 15 Info:TuyaMCU:ParseState: id 10 type 2-val len 4 Info:TuyaMCU:ParseState: int32 63 Info:GEN:No change in channel 10 (still set to 63) - ignoring Info:TuyaMCU:Received: 55 AA 03 07 00 05 04 04 00 01 00 17 Info:TuyaMCU:ProcessIncoming[v=3]: cmd 7 (State) len 12 Info:TuyaMCU:ParseState: id 4 type 4-enum len 1 Info:TuyaMCU:ParseState: byte 0 Info:GEN:No change in channel 4 (still set to 0) - ignoring Info:TuyaMCU:Received: 55 AA 03 34 00 01 04 3B
Интересующий нас пакет: 55 AA 03 07 00 1D 01 00 00 19 7F 09 00 01 00 2B 0D 00 01 00 7F 0F 00 01 00 2B 11 00 01 00 7F 15 00 01 00 63
Разберём его:
55 AA: Константный заголовок03: Версия протокола07: Тип комманды (0x07=report_status).00 1D: Длинна блока данных
Далее идут несколько значений расписания:
7F 09 00 01 012B 0D 00 01 007F 0F 00 01 002B 11 00 01 007F 15 00 01 00
И 63: Контрольная сумма.
Разберём формат:
7F: Битовое поле дней недели. 7F —01111111, каждый день09: Часы00: Минуты01: Количество порций01: Активно или нет правило
На основе этого можно навайбкодить шаблон для генерации такого пакета в HomeAssistant:
template: - sensor: - name: "Cat Feeder Outbound Payload" state: >- {%- set header = "55aa0006001d01000019" -%} {# Macro to split the time picker string and build a 5-byte slot #} {%- macro build_slot(prefix, time_id, port_id, en_id) -%} {%- set sun = 1 if states('input_boolean.' ~ prefix ~ '_sun') == 'on' else 0 -%} {%- set mon = 2 if states('input_boolean.' ~ prefix ~ '_mon') == 'on' else 0 -%} {%- set tue = 4 if states('input_boolean.' ~ prefix ~ '_tue') == 'on' else 0 -%} {%- set wed = 8 if states('input_boolean.' ~ prefix ~ '_wed') == 'on' else 0 -%} {%- set thu = 16 if states('input_boolean.' ~ prefix ~ '_thu') == 'on' else 0 -%} {%- set fri = 32 if states('input_boolean.' ~ prefix ~ '_fri') == 'on' else 0 -%} {%- set sat = 64 if states('input_boolean.' ~ prefix ~ '_sat') == 'on' else 0 -%} {%- set days_mask = sun + mon + tue + wed + thu + fri + sat -%} {%- set time_val = states(time_id) -%} {%- set h = time_val.split(':')[0]|int(0) if ':' in time_val else 0 -%} {%- set m = time_val.split(':')[1]|int(0) if ':' in time_val else 0 -%} {%- set p = states(port_id)|int(1) -%} {%- set e = 1 if states(en_id) == 'on' else 0 -%} {{- "%02x"|format(days_mask) ~ "%02x"|format(h) ~ "%02x"|format(m) ~ "%02x"|format(p) ~ "%02x"|format(e) -}} {%- endmacro -%} {# Compile all 5 slots seamlessly #} {%- set s1 = build_slot('feeder_s1', 'input_datetime.feeder_s1_time', 'input_number.feeder_s1_portions', 'input_boolean.feeder_s1_enabled') -%} {%- set s2 = build_slot('feeder_s2', 'input_datetime.feeder_s2_time', 'input_number.feeder_s2_portions', 'input_boolean.feeder_s2_enabled') -%} {%- set s3 = build_slot('feeder_s3', 'input_datetime.feeder_s3_time', 'input_number.feeder_s3_portions', 'input_boolean.feeder_s3_enabled') -%} {%- set s4 = build_slot('feeder_s4', 'input_datetime.feeder_s4_time', 'input_number.feeder_s4_portions', 'input_boolean.feeder_s4_enabled') -%} {%- set s5 = build_slot('feeder_s5', 'input_datetime.feeder_s5_time', 'input_number.feeder_s5_portions', 'input_boolean.feeder_s5_enabled') -%} {%- set full_packet_no_checksum = (header ~ s1 ~ s2 ~ s3 ~ s4 ~ s5)|trim -%} {# Dynamic Checksum Processor (Modulo 256) #} {%- set ns = namespace(total=0) -%} {%- for i in range(0, full_packet_no_checksum|length, 2) -%} {%- set ns.total = ns.total + ("0x" ~ full_packet_no_checksum[i:i+2])|int(0, 16) -%} {%- endfor -%} {%- set checksum = "%02x"|format(ns.total % 256) -%} {{ (full_packet_no_checksum ~ checksum)|trim }}
И скриптом с помощью MQTT отправлять в OpenBeken сырой пакет в MCU:
alias: Cat Feeder - Send Calendar Configuration description: "" triggers: - entity_id: sensor.cat_feeder_outbound_payload trigger: state conditions: [] actions: - delay: "00:00:02" - data: topic: cmnd/catfeeder-s/uartSendHex payload: "{{ states('sensor.cat_feeder_outbound_payload') }}" action: mqtt.publish
Также можно навайбкодить вот такой конфигуратор календаря:

Исходный код конфигуратора:
type: vertical-stack
cards:
- type: markdown
content: "## 