Отучаем IoT от облака

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Я уже несколько лет методично выбирал устройства для умного дома, работающие по протоколу 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:

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

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

type: vertical-stack
cards:
  - type: markdown
    content: "## 						
Источник: https://habr.com/ru/companies/dsec/articles/1077806/


Интересные статьи

Интересные статьи

Работа с файлами в REST API Битрикс24 — та задача, где документация заканчивается ровно там, где начинаются проблемы. Официальные примеры показывают, как загрузить один файл в одно поле. А дальше выяс...
Material 3 Expressive – обновление дизайна ОС Android, после которого он выйдет на новый уровень или всего лишь очередные концепты для Behance и Dribbble?
Язык ядра Linux, его модулей и утилит написаны на языке C. Хоть он и является старым языком и прародителем многих других, но его до сих пор используют. В экосистему линукса постепенно проникают и боле...
В этой статье мы популярно объясняем на собственном опыте как организовать массовую выгрузку, обработку и загрузку фотографий товаров из Bitrix, используя Python и минимальное количество SQL. Для проч...
В Челябинске проходят митапы системных администраторов Sysadminka, и на последнем из них я делал доклад о нашем решении для работы приложений на 1С-Битрикс в Kubernetes. Битрикс, Kubernetes, Сep...