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

Под катом расскажу, как я преодолел это недоразумение.

Интро

Зачем

Напрягало, что в домашней сети есть устройства, которые я фактически не контролирую (а в кормушке, например, есть микрофон).

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

Продление подписки на "Локальную" Tuya
Продление подписки на "Локальную" 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:

распиновка TYWE3S
распиновка TYWE3S

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

Переключатель UART
Переключатель UART

Переместив выключатель в обратную стрелочке сторону можем подключить контроллер к UART адаптеру и с помощью утилиты ESPTool создать резервную копию прошивки контроллера.

esptool.py --port /dev/ttyUSB0 read_flash 0 0 backup.bin
Уроборос в сборе
Уроборос в сборе

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

ESPHome UI
ESPHome UI

Для кондиционеров 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 удобный интерфейс для управления кондиционером:

Интерфейс кондиционера в HomeAssistant
Интерфейс кондиционера в HomeAssistant

Кормушка

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

Карточка кормушки
Карточка кормушки

Разборка

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

Внутрянка кормушки
Внутрянка кормушки

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

Общий вид платы кормушки, пометка проводов маркером
Общий вид платы кормушки, пометка проводов маркером

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

клоузап WBR3 на плате
клоузап WBR3 на плате

Определяем 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, и даже если его насовсем выпаять то лоток продолжит самоочищаться, а кормушка продолжит давать корм по нажатию кнопки. Кормление по расписанию работать не будет т.к. при перезапуске в кормушке собьётся время.

Схема UART-MCU подключения
Схема UART-MCU подключения

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

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

Интерфейс TuyaMCUAnalyzer
Интерфейс TuyaMCUAnalyzer

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

Распиновка WBR3
Распиновка WBR3

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

Отдыхает
Отдыхает

OpenBeken

Из-за того что WBR3 сделан на основе RTL8720CF, а не на основе чипов Espressif, ESPHome на него поставить не удастся. Можно конечно поменять контроллер на совместимый с ESPHome, но зачем?

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

Список поддерживаемых платформ поистине внушителен.

Поддерживаемые OpenBeken устройства
Поддерживаемые OpenBeken устройства

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

Распиновка WBR3
Распиновка WBR3

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

Подключение к UART адаптеру
Подключение к 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
Кормушка в OpenBeken

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

Кормушка в HomeAssistant
Кормушка в 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 01

  • 2B 0D 00 01 00

  • 7F 0F 00 01 00

  • 2B 11 00 01 00

  • 7F 15 00 01 00

И 63: Контрольная сумма.

Разберём формат:

  • 7F : Битовое поле дней недели. 7F —01111111, каждый день

  • 09: Часы

  • 00: Минуты

  • 01: Количество порций

  • 01: Активно или нет правило

На основе этого можно навайбкодить шаблон для генерации такого пакета в HomeAssistant:

Шаблон для HA
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

Также можно навайбкодить вот такой конфигуратор календаря:

Календарь в HomeAssistant
Календарь в HomeAssistant

Исходный код конфигуратора:

UI календаря
type: vertical-stack
cards:
  - type: markdown
    content: "## ? Cat Feeder Schedule Configuration"
  - type: entities
    title: Slot 1 Settings
    show_header_toggle: false
    entities:
      - entity: input_boolean.feeder_s1_enabled
        name: Enable Feeding
        icon: mdi:power
  - type: conditional
    conditions:
      - condition: state
        entity: input_boolean.feeder_s1_enabled
        state: "on"
    card:
      type: vertical-stack
      cards:
        - type: entities
          entities:
            - entity: input_datetime.feeder_s1_time
              name: Feeding Time
            - entity: input_number.feeder_s1_portions
              name: Portions
        - type: grid
          columns: 7
          square: false
          cards:
            - type: tile
              entity: input_boolean.feeder_s1_mon
              name: M
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s1_tue
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s1_wed
              name: W
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s1_thu
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s1_fri
              name: F
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s1_sat
              name: S
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s1_sun
              name: S
              icon_tap_action:
                action: toggle
  - type: entities
    title: Slot 2 Settings
    show_header_toggle: false
    entities:
      - entity: input_boolean.feeder_s2_enabled
        name: Enable Feeding
        icon: mdi:power
  - type: conditional
    conditions:
      - condition: state
        entity: input_boolean.feeder_s2_enabled
        state: "on"
    card:
      type: vertical-stack
      cards:
        - type: entities
          entities:
            - entity: input_datetime.feeder_s2_time
              name: Feeding Time
            - entity: input_number.feeder_s2_portions
              name: Portions
        - type: grid
          columns: 7
          square: false
          cards:
            - type: tile
              entity: input_boolean.feeder_s2_mon
              name: M
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s2_tue
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s2_wed
              name: W
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s2_thu
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s2_fri
              name: F
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s2_sat
              name: S
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s2_sun
              name: S
              icon_tap_action:
                action: toggle
  - type: entities
    title: Slot 3 Settings
    show_header_toggle: false
    entities:
      - entity: input_boolean.feeder_s3_enabled
        name: Enable Feeding
        icon: mdi:power
  - type: conditional
    conditions:
      - condition: state
        entity: input_boolean.feeder_s3_enabled
        state: "on"
    card:
      type: vertical-stack
      cards:
        - type: entities
          entities:
            - entity: input_datetime.feeder_s3_time
              name: Feeding Time
            - entity: input_number.feeder_s3_portions
              name: Portions
        - type: grid
          columns: 7
          square: false
          cards:
            - type: tile
              entity: input_boolean.feeder_s3_mon
              name: M
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s3_tue
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s3_wed
              name: W
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s3_thu
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s3_fri
              name: F
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s3_sat
              name: S
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s3_sun
              name: S
              icon_tap_action:
                action: toggle
  - type: entities
    title: Slot 4 Settings
    show_header_toggle: false
    entities:
      - entity: input_boolean.feeder_s4_enabled
        name: Enable Feeding
        icon: mdi:power
  - type: conditional
    conditions:
      - condition: state
        entity: input_boolean.feeder_s4_enabled
        state: "on"
    card:
      type: vertical-stack
      cards:
        - type: entities
          entities:
            - entity: input_datetime.feeder_s4_time
              name: Feeding Time
            - entity: input_number.feeder_s4_portions
              name: Portions
        - type: grid
          columns: 7
          square: false
          cards:
            - type: tile
              entity: input_boolean.feeder_s4_mon
              name: M
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s4_tue
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s4_wed
              name: W
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s4_thu
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s4_fri
              name: F
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s4_sat
              name: S
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s4_sun
              name: S
              icon_tap_action:
                action: toggle
  - type: entities
    title: Slot 5 Settings
    show_header_toggle: false
    entities:
      - entity: input_boolean.feeder_s5_enabled
        name: Enable Feeding
        icon: mdi:power
  - type: conditional
    conditions:
      - condition: state
        entity: input_boolean.feeder_s5_enabled
        state: "on"
    card:
      type: vertical-stack
      cards:
        - type: entities
          entities:
            - entity: input_datetime.feeder_s5_time
              name: Feeding Time
            - entity: input_number.feeder_s5_portions
              name: Portions
        - type: grid
          columns: 7
          square: false
          cards:
            - type: tile
              entity: input_boolean.feeder_s5_mon
              name: M
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s5_tue
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s5_wed
              name: W
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s5_thu
              name: T
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s5_fri
              name: F
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s5_sat
              name: S
              icon_tap_action:
                action: toggle
            - type: tile
              entity: input_boolean.feeder_s5_sun
              name: S
              icon_tap_action:
                action: toggle

Таким образом кормушка со всем её функционалом добавлена в HomeAssistant.

Лоток

Вторым устройство с маркетплейсов был автоматический лоток.

Лоток
Лоток

Кошка сначала очень удивлялась происходящему, но очень быстро привыкла.

Кто перевернул
Кто перевернул

Разборка

Разбирается он в принципе несложно, также по ходу процесса делаем фотографии. Внутри видим плату с кучей проводов и таким же как у кормушки контроллером WBR3. Тут он располагается в углу платы. Оценив количество проводов, я принял решение дальше их не разбирать, а просто снять плату и выпаять её, подложив силиконовый коврик.

Плата лотка
Плата лотка

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

Очень удобно
Очень удобно

Контроллеру лотка не хватило питания от того же UART адаптера, поэтому я подключил его чуть более напрямую и немного поперебирал имеющиеся UART адаптеры. С одним из них у меня всё же мощности хватило и изобретать дополнительный источник питания не пришлось.

Подключение к адаптеру
Подключение к адаптеру

Настройка OpenBeken

От “умных” функций лотка мне интересен только мониторинг веса кошки, чтобы можно было в случае разких изменений заметить, что что-то не так или адаптировать кормление при смене корма. Просто посмотрев на отдаваемые командой tuyaMcu_sendQueryState можно заметить что на вес кошки похоже только значение с dpID 6.

Результат выполнения комманды tuyaMcu_sendQueryState:

Вывод комманды
Info:TuyaMCU:ProcessIncoming[v=3]: cmd 7 (State) len 120
Info:TuyaMCU:ParseState: id 105 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 1
Info:GEN:No change in channel 4 (still set to 1) - ignoring
Info:TuyaMCU:ParseState: id 6 type 2-val len 4
Info:TuyaMCU:ParseState: int32 3530
Info:GEN:No change in channel 6 (still set to 1500) - ignoring
Info:TuyaMCU:ParseState: id 101 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 0
Info:GEN:No change in channel 2 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 17 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 0
Info:GEN:No change in channel 1 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 102 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 0
Info:GEN:No change in channel 3 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 117 type 2-val len 4
Info:TuyaMCU:ParseState: int32 3
Info:GEN:No change in channel 12 (still set to 3) - ignoring
Info:TuyaMCU:ParseState: id 118 type 2-val len 4
Info:TuyaMCU:ParseState: int32 15
Info:GEN:No change in channel 13 (still set to 15) - ignoring
Info:TuyaMCU:ParseState: id 119 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 0
Info:GEN:No change in channel 8 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 114 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 0
Info:GEN:No change in channel 7 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 122 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 0
Info:GEN:No change in channel 9 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 108 type 0-raw len 1
Info:TuyaMCU:ParseState: byte 1
Info:GEN:No change in channel 19 (still set to 1) - ignoring
Info:TuyaMCU:ParseState: id 109 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 1
Info:GEN:No change in channel 5 (still set to 1) - ignoring
Info:TuyaMCU:ParseState: id 22 type 5-bitmap len 1
Info:TuyaMCU:ParseState: byte 0
Info:GEN:No change in channel 18 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 111 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 1
Info:GEN:No change in channel 6 (still set to 1) - ignoring
Info:TuyaMCU:ParseState: id 124 type 2-val len 4
Info:TuyaMCU:ParseState: int32 0
Info:GEN:No change in channel 14 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 125 type 2-val len 4
Info:TuyaMCU:ParseState: int32 3
Info:GEN:No change in channel 15 (still set to 3) - ignoring
Info:TuyaMCU:ParseState: id 126 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 1
Info:GEN:No change in channel 10 (still set to 1) - ignoring
Info:TuyaMCU:ParseState: id 127 type 2-val len 4
Info:TuyaMCU:ParseState: int32 1500
Info:GEN:No change in channel 16 (still set to 1500) - ignoring
Info:TuyaMCU:ParseState: id 128 type 2-val len 4
Info:TuyaMCU:ParseState: int32 0
Info:GEN:No change in channel 17 (still set to 0) - ignoring
Info:TuyaMCU:ParseState: id 130 type 1-bool len 1
Info:TuyaMCU:ParseState: byte 0
Info:GEN:No change in channel 11 (still set to 0) - ignoring

Содержимое autoexec.bat:

// Start the TuyaMCU driver
startDriver TuyaMCU
tuyaMcu_defWiFiState 4

// --- Active Weight Sensor ---
linkTuyaMCUOutputToChannel 6 val 0 0.001
setChannelLabel 0 "dpID 6 Cat Weight"

Итог

В HomeAssistant на дашборд добавлены:

  • Кнопка кормления кошки

  • Состояние батареи кормушки

  • Вес

  • Когда в последний раз кошка ела

  • Настройка расписания кормления

Дашборд
Дашборд

Комментарии (7)


  1. noidol
    03.09.2026 02:46

    Добрый день! Скажите, по Вашему мнению, HomeAssistant является ПО для "увлеченных? Насколько он дружелюбен к пользователям, которые не особо дружат с паяльником)? С чего стоит начинать освоение автономного "умного дома", без привязки к облакам?


    1. Etern0x
      03.09.2026 02:46

      без привязки к облакам

      Тут уже намекнули - ZigBee.


  1. Sosnin
    03.09.2026 02:46

    Кстати с ZigBee тоже всё неоднозначно. Есть куча устройств которые вполне дружелюбно работают с TuyaSmart, но никак не заводятся в Zigbee HA ... например "Регулятор температуры пола" - просто "непонятное устройство без сенсоров и переключателей"; есть 3 термометра достаточно умных, купленных одновременно партией. 2 - "почти норм" (время от НА не получает), 3-ий - как "непонятное устройство без сенсоров"
    4-Gang выключатель ZigBee пришлось снять... 0,фаза, 4 канала - всё подключено, все работает.. но в другой комнате люстра моргает в момент сработки. С таким же WiFi - моргания нет.


    1. aptk
      03.09.2026 02:46

      "Непонятное устройство без сенсоров и переключателей" решается с помощью zha quirk'ов. С высокой вероятностью под ваше устройство они уже написаны кем-то. Если нет - можно поискать конвертер для z2m, их энтузиасты еще быстрее пишут. На основе конвертера под z2m достаточно легко пишется квирк, самостоятельно или с ллм. А вот если ни того ни другого нет - предстоит увлекательный процесс выяснения того, для чего используется каждый из кастомных zigbee эндпоинтов.


  1. TraX325
    03.09.2026 02:46

    Тоже косо поглядываю на имеющиеся у меня кошачьи смарт вещи, но настолько радикально влезать точно не готов. Была мысль просто сделать свой сервер, почти как пиратские сервера вова создавали - реверс инжинирнуть запросы да и натравить девайсы днсами на собственный инстанс, а если надоест этим заниматься то переключить обратно. Да, нужно на чём-то его крутить, сильно больше мороки с софтом, все возможные интеграции придётся впиливать самому, но зато не придётся давать мне в руки паяльник :)


  1. LampGraph
    03.09.2026 02:46

    Прошел полный путь от самодельных датчиков температуры на ардуино, дальше wi-fi, zigbee, обрабатывал все домашний сервер на линуксе (где-то между была малина), разный софт: Majordomo, HA и в итоге остановился на Wirenboard и nodered для логики установленном на нем же. Решил для себя:
    Умный дом должен быть умным - это когда не участвуешь в его делах. Он сам поддерживает температуру, сам кормит кота и собаку, поливает огород и прочее. Единственное чем сейчас управляем - это включаем и выключаем свет в тех комнатах где работа по датчикам присутствия не применима. И то через Алису (Алиса только для взаимодействия, вся логика работает локально и не зависит от интернета).
    Ну и коммуникация по проводам, везде где это возможно (если устройству нужна розетка - значит и витуху можно проложить).
    На днях с каким то приятным чувством обнаружил что настроенный год назад климат-контроль в погребе (принудительная вентиляция, если на улице холоднее чем требуемая температура в погребе и закрытие заслонки если на улице теплее) работает. А я думал показания с датчиков температуры на дашборде не меняются потому что он отвалился и осенью надо будет чинить. Оказалось просто там все в порядке. температура стабилизировалась и за лето поднялась всего на несколько градусов, да так плавно что было не заметно.
    Теперь вот пишу софт который по изображению с камеры будет распознавать номер моей машины и открывать ворота. Локально, без облаков.


  1. mitzury
    03.09.2026 02:46

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