Делаем “устрашающе умный” (с) модуль из TOMZN TOB9-VAP. Реле всего.

2 года назад заказал “wifi-автомат” от TOMZN – TOB9-VAP. Доехало, подключил, поигрался. Работает. Даже добавил интеграцию с Tuya в Home Assistant. Модуль разобрал поисследовать, с тех пор так и лежал разобранный без дела, да и привязка к сторонним сервисам меня не особо радовала. Внутри – небольшая платка с БП, измерителем на базе BL0942 и модулем CBU на BK7231N. Уже не помню, была ли поддержка таких контроллеров в конце 2023-го года, да и опыта было поменьше, чтобы легко прошить такое:

На сейчас ESP Home обзавелся поддержкой еще ряда контроллеров, в том числе BK72**, а желание отвязать железку от внешних сервисов, собрать все обратно и поставить точку в этой теме и сподвигло заняться модулем вплотную.

По  привычке иногда называю модуль “автоматом”, хотя автоматическим выключателем в обычном понимании он не является. Да, в штатной прошивке тут есть возможность выключения по превышению тока и напряжения – потому он и зовется VAP с настраиваемым лимитом тока 1-63А. Разглядывая страницу товара и отличия с TOB9-63M понимаю, что отличия моделей должны быть уже чисто софтовыми – и это дало несколько идей, которые и реализовал. Но это было потом…

Сначала была прошивка из ESP Home.

  • Для начала – подключаем pl2303. Тут есть pinout подобного модуля и пример корректировки показаний. Шили вроде как через esphome.
  • Тут есть базовый конфиг, показ внутренностей и отсылка к утилитам прошивки.
  • Создаем прошивку в esphome, там есть BK7231N. Скачиваем первый вариант файла – *.uf2. Распаиваем 5 проводов на модуль, подключаем на pl2303. В этот раз уже делал все изначально через левел-шифтер, чтобы исключить возможные подводные камни с уровнями.
  • Описание вариантов утилит для прошивки. Рекомендуется ltchiptool, но запустить на Linux’е так и не смог (отсылка на ImportError: cannot import name ‘Endianness’ from ‘datastruct’ – и там реально такого нет). В итоге первый раз шил виндой, чтобы убедиться, что со стороны железа косяков нет и оно реально шьется (отсюда брал дрова для винды для pl2303).
  • Потом скачал bk7231tools – это работает, так что дальше пробовал шить уже в Linux’е. Ставим
    $ pip3 install bk7231tools
    потом запускаем:

    $ python3 .local/bin/bk7231tools write_flash -d /dev/ttyUSB0 -s 0 -S 0 -l 0x200000 --bootloader downloads/tomzn-switch-1.uf2
    Жмем кнопку сброса контроллера, получаем 
    BK72xx connected - protocol: FULL, chip: BK7231N, bootloader: BK7231N_1_0_1, chip ID: 0x7231c, boot version: None
    Connected! Chip info: BK7231N / Flash ID: 1c 70 15 / Flash size: 0x200000 / Protocol: FULL
    Writing 2097152 bytes to 0x0
    Trying to unprotect flash memory...
    Erasing and writing at 0x0 (0.00%)
     - Checking block pre-erase @ 0x0
     - Trying to erase block @ 0x0
     - Checking block post-erase @ 0x0
     - Erase succeeded @ 0x0
    Erasing and writing at 0x1000 (0.20%)
    Erasing and writing at 0x2000 (0.39%)
    Erasing and writing at 0x3000 (0.59%)
    Erasing and writing at 0x4000 (0.78%)
    **********
    Erasing and writing at 0x1FF000 (99.80%)
    OK!
    

    [свернуть]

    Но… я не знаю, в какой момент что-то пошло не так или где я ошибся – но модуль “окирпичился” и не грузился. Пробовал шить снова под виндой – ругалось на bootloader (1, 2), но что-то шило. Перебирал разные варианты опций, пробовал шить OpenBK. Хуже то, что у меня не осталось заводской прошивки, а в сети найти не мог. Так или иначе, но в какой-то момент все заработало. Дамп своей прошивки через bk7231tools и заливка ее потом обратно тоже дают работающее устройство. В общем, чтобы повторить все шаги снова на заводской прошивке – мне надо будет провести эксперименты на новом устройстве.
    Для истории

    rain@walkbook:~$ python3 .local/bin/bk7231tools chip_info -d /dev/ttyUSB0
    BK72xx connected - protocol: FULL, chip: BK7231N, bootloader: BK7231N_1_0_1, chip ID: 0x7231c, boot version: None
    Connected! Chip info: BK7231N / Flash ID: 1c 70 15 / Flash size: 0x200000 / Protocol: FULL
    

    [свернуть]

Еще полезные ссылки:

А дальше – пошло-поехало.

  • Реализовал сначала базовый вариант по примеру конфига выше.
  • Добавил отключаемую защиту от выхода за лимиты напряжения, тока и мощности. Почему-то простой вариант с on_value_range не заработал, поэтому пришлось описывать все чуть более сложной логикой. Устанавливать лимиты можно через веб-интерфейс, значения нижнего и верхнего порога взаимосвязаны (т.е., нельзя поставить низ выше верха).
  • Добавил отключаемый и настраиваемый таймер возврата в рабочий режим.
  • Расширил функции кнопки: кроме включения/выключения и сброса устройства – добавил переключение режима защиты (нажатие от 1 до 5 секунд) и подумал, что неплохо было бы сбрасывать еще счетчик энергии. Но чуть позже…
  • Добавил светодиодную индикацию режима. Кроме включено/выключено из базового конфига – быстрое мигание при аварии (выходе за лимиты) и медленное мигание при выдержке включения.
  • Внутренняя скорость опроса измерителя была выставлена в 0.1s для напряжения и тока, чтобы модуль реально мог от чего-то защищать. При этом в веб-интерфейсе параметры публикуются раз в 15 секунд.
  • Появились и были устранены неожиданные подводные камни.
    • Например, логика включения и выключения реле была завязана на “маркеры” аварии и таймаута включения. И выяснилось, что в момент включения и выключения устройства состояние темплейтов меняется. А дальше состояние записывается в память и в итоге получаем отсутствие сохранения состояния реле при перезагрузке.
    • Еще один косяк был связан со скоростью опроса измерителя. Похоже, в момент выключения в какую-то долю секунды он начинает отдавать “0” (или типа того) – и при включенной защите это дает выключение реле. Что опять же, приводит к отсутствию сохранения состояния.
    • Обе проблемы были решены добавлением флага перезагрузки, который взводится на ранних этапах загрузки и в начале перезагрузки, а при обычной работе сброшен. При работе с реле делается проверка состояния этого флага.
  • Самая большая работа была проделана вокруг счетчика суточной энергии.
    • Во-первых, обычный total_daily_energy не умеет сбрасываться иначе, кроме как в 0 часов 0 минут. Отсюда несколько проблем:
      • Не могу сбросить его в произвольный момент времени.
      • Что, если в 12 ночи питания не было?
    • Первое решилось добавлением своих счетчиков и шаблонов вокруг базового счетчика энергии. Если значение базового изменилось – увеличиваем значение своего. А его уже можем сбрасывать и устанавливать так и тогда, когда захотим.
    • Для второго пришлось наконец-то доделать давно задуманный к реализации для солнечных измерителей алгоритм обнаружения смены суток – там это актуально, так как в 12 ночи солнца нет и измерители обычно выключены. В целом все просто (но поначалу у меня что-то не выходило и для измерителей тему я надолго отложил) – при синхронизации (т.е., при включении  устройства прежде всего) времени сравниваем текущий день и сохраненный в памяти. Если отличается – значит, у нас новый день и можно сбросить счетчик (а в память записать новое значение дня).
  • Все перечисленное позволило добавить сброс реализованного счетчика энергии из веб-интерфейса или удержанием кнопки от 5 до 10 секунд.
  • Кроме того – что мне изначально хотелось сделать – на базе этого я добавил отключаемый ограничитель суточной энергии. Т.е., например, за сутки можем тратить на какой-нибудь нагреватель не больше 3 кВт*ч. Есть флаг сработки лимита. Возврат в рабочий режим (при сбросе или смене суток) не делал (пока? Можно, но надо тогда сохранять состояние реле до сработки лимита и немного перепахать логику).

Да! А еще измеритель может показывать направление перетока энергии, поэтому лимиты по мощности сделал как положительные, так и отрицательные. В целом, можно использовать при управлении нагрузкой для on grid-инверторов без лимитеров, чтобы не начинать отдавать энергию наружу. Судя по всему, показания между устройствами зависят в том числе от того, куда (в какой “полярности”) прицепили провода при сборке модуля, поэтому при прошивке другого (точно такого же) модуля тот может показывать не положительные (я добавил у себя в фильтры “*-1”), а отрицательные значения.

Как уже писал выше, разница с TOB9-63M должна быть чисто софтовой, так что можно набрать их, как более дешевые и сделать аналогичную переделку. На сейчас на Али можно найти их по 8$/шт. Что, кстати, дешевле, чем Sonoff POW R3.

А для Sonoff’а можно будет тоже портировать все эти наработки, так как основные принципы не меняются – только платформа и измеритель.

Конфиг для ESP Home. Пожалуй, самый объемный на сейчас для всех моих устройств – хотя местами напрашивается сокращение какими-нибудь функциями или чем-то таким.

PS: цитата в названии – с Муськи.

PS2: в базовом коде делалась обработка значений тока/мощности, потому что “измеритель что-то показывает без нагрузки”. Что-то показывает он из-за собственного потребления – электроника включена после токового трансформатора. Если надо не учитывать свое потребление, но иметь возможность работать со значениями в 2 стороны – лучше тогда делать сдвиг параметра на определенное значение, а не обнуление. Или переподключить один проводок внутри.

Update 2025-11-06: получил TOMZN TOB9-63M. Внутри точно такой же, так что да, отличия, похоже, только в софте. Дампим оригинальную прошивку:

Дампим...

$ python3 ~/.local/bin/bk7231tools read_flash -d /dev/ttyUSB0 file.bin
BK72xx connected - protocol: FULL, chip: BK7231N, bootloader: BK7231N_1_0_1, chip ID: 0x7231c, boot version: None
Connected! Chip info: BK7231N / Flash ID: c8 40 15 / Flash size: 0x200000 / Protocol: FULL
Reading 2097152 bytes from 0x0
Reading 4k page at 0x000000 (0.00%)
Reading 4k page at 0x001000 (0.20%)
***********

[свернуть]

tomzn-tob9-63m.bin

Update 2025-11-07: в общем, да, в первый раз я просто убил загрузчик, а потом чем-то (одним из скачанных файлов) я его восстановил. При попытке записи созданного в ESP Home “рекомендуемого” UF2 package – ругается на неподходящий размер. Таки да, файлик-то 3,3 МБ, а флешка только 2. Если явно указываем “-l 0x200000” (т.е., обрезаем файл по длине флешки) – то записывается, но в итоге во флешке получаем нечто непонятное, что не грузится (не просто ж так файлик 3,3 МБ выходит? А мы с него берем произвольные данные). ltchiptool как-то разбирает тот образ, поэтому с его помощью получилось в первый раз под виндой прошить.

Корректный (на сейчас) вариант – не трогать загрузчик и записать только Beken Application Image (последняя опция при загрузке в ESP Home). Там файлик на 1,1 МБ. Пишем со смещением 0x011000:

Пишем...

$ python3 ~/.local/bin/bk7231tools write_flash -d /dev/ttyUSB0 -s 0x011000 -S 0  --bootloader /home/rain/downloads/tomzn-switch-2-0x011000.rbl        BK72xx connected - protocol: FULL, chip: BK7231N, bootloader: BK7231N_1_0_1, chip ID: 0x7231c, boot version: None
Connected! Chip info: BK7231N / Flash ID: c8 40 15 / Flash size: 0x200000 / Protocol: FULL
Writing 1150832 bytes to 0x11000
Trying to unprotect flash memory...
Erasing and writing at 0x11000 (0.00%)
 - Checking block pre-erase @ 0x11000
 - Trying to erase block @ 0x11000
 - Checking block post-erase @ 0x11000
 - Erase succeeded @ 0x11000
Erasing and writing at 0x12000 (0.36%)
Erasing and writing at 0x13000 (0.71%)
*****

[свернуть]

–bootloader, пожалуй, тут лишний, осталось со старой команды.

Немного циферок. Flash size: 0x200000. Это в hex, т.е., 2097152 bytes в десятичной. До адреса 0x011000 у нас загрузчик, после – application image (не знаю, надо ли оставлять штатный китайский загрузчик? Или искать альтернативу?). 0x200000-0x011000 = 0x1ef000 (2027520 bytes). Это у нас максимальный размер нашей прошивки. 1,1 МБ с ESP Home туда влазит. В свою очередь, 0x011000 – это 69632 bytes. Значит, загрузчик у нас располагается до 69631 байта. Путем несложного dd – срезаем 69631 байт штатной прошивки:

dd if=tomzn-tob9-63m.bin of=bootloader.bin bs=1 count=69631

Т.е., это загрузочная часть, которую можно при необходимости прошивать по 0-му смещению. Слияние этого файла и *-0x011000.rbl из ESP Home и даст нам полный образ прошивки (опять же, если он вдруг понадобится).

Update: разбирает образ не только ltchiptool, но и аплоадер у ESP Home:

Building UF2 OTA image
|-- esphome_2025.10.4_cbu_bk7231n_lt1.9.1.uf2
|-- firmware.uf2
|-- firmware.bin
**********************
INFO Uploading /srv/esphome/config/.esphome/build/tomzn-switch-2/.pioenvs/tomzn-switch-2/firmware.uf2 (3417600 bytes)

При этом все перечисленные файлы – одинаковые.

Update 2025-11-10: большой апдейт и куча багфиксов.

Прежде всего – “по просьбам телезрителей” (с) взялся добавить еще одну фичу – таймаут выключения. В частности хотелось отключать устройство через время в том случае, если ток снижался ниже определенного порога (окончание зарядки). Ну и иметь возможность запустить вручную по кнопке – опять же, с отключением через время, если условия не соответствуют нормальным.

В процессе разглядывания кода (чтобы вспомнить, что и как там) и задумчивого перебирания опций на подключенном втором экземпляре модуля внезапно столкнулся с неправильной работой – модуль выключался по аварии (тут ок), а вот после задержки начинал быстро включаться/выключаться, даже если авария держалась. Мало того, индикатор аварии в процессе был постоянно выключен. Почему такое не проявилось раньше – не представляю. Итак, баг первый: один сенсор, где была сработка защиты, ставил алерт, но другой (где по параметрам было все ок) сразу же снимал этот алерт. Т.е., требовался учет состояния сразу всех отслеживаемых сенсоров. Решилось введением дополнительных флагов – отдельно для каждого из сенсоров. Т.е., сработка алерта по любому из сенсоров взводит общий флаг + собственный. А вот снятие общего флага происходит только если все внутренние флаги пришли в норму.

Вторая проблема – скорее, недоработка – возможность включить реле вручную даже после того, как взведен алерт. Т.е., пришло, например, на модуль 280В, тот выключился, а кто-то (или автоматизация из Home Assistant, если не отслеживается флаг alert) взял и включил его обратно. И алерт светится, и 280В на устройство идет. Пришлось добавить прослойку – теперь “наружу торчит” только шаблон, а уже он управляет реальным реле. У шаблона есть запрет на включение реле в том случае, если взведен алерт.

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

В идеале – иметь разные алерты по верхней и нижней границе параметра и по-разному их обрабатывать. Т.е., превышение тока или мощности должно вызывать моментальное срабатывание реле, а вот срабатывание по нижней границе можно сделать и с таймаутом. Но пока так.

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

Решил также попробовать слайдеры, выглядят симпатично. Правда, со стороны Home Assistant может быть неудобно точно настраивать значение – в веб-интерфейсе контроллера можно кликнуть на значение и вызвать box-вариант, а вот со стороны HA такого нет.

И да, при включении “local: on” для веб-сервера со всей этой красотой – прошивка уже не лезет во флешку через OTA. Да и даже с первой версией на некоторых версиях ESP Home сталкивался с тем, что образ получался больше, чем свободное место.

Ну и напоследок нашел еще один баг. Всегда при тестировании защит реле было включено, поэтому возврат состояния “включено” был естественным моментом. Получился недосмотр. Если реле было выключено, но сработал алерт (по любой из причин), то после снятия алерта реле включалось. Пришлось добавить сохранение состояния реле до взведения алерта и при его снятии принимать решение в том числе по этому флагу.

Обновленный вариант: tomzn-2.yaml

Update 2025-11-27: после описанных событий решил заказать уже побольше таких модулей – в хозяйстве всегда пригодятся. На bundle deals от Manhot Store попались очень-очень похожие: один в один от TOMZN, но без надписи TOMZN.

Заказал для начала 3 штучки, приехали, получил, разобрал. Да, внутри все тот же CBU-модуль. Подготовил прошивку; шьем, зашил, перезапуск.

А фиг там.

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

BL7238

$ python3 ~/.local/bin/bk7231tools read_flash -d /dev/ttyUSB0 manhot.7238.bin
Unknown bootloader CRC - 0xF42F8C32 - please report this on GitHub issues!
Reading 4k page at 0x011000 (0.00%)
BK72xx connected - protocol: FULL, chip: BK7238, bootloader: None, chip ID: 0x7238, boot version: None
Connected! Chip info: BK7238 / Flash ID: 85 20 15 / Flash size: 0x200000 / Protocol: FULL
Reading 2097152 bytes from 0x0
Reading 4k page at 0x000000 (0.00%)
Reading 4k page at 0x001000 (0.20%)

[свернуть]

Модуль я прошивкой своего образа успешно окирпичил – благо, у меня их несколько и смог с другого считать прошивку.

Прошивка manhot.BL7238.bin

Дополнительно – dissect

Залил в первый – успешно восстановил работу. Для прошивки модулей, кстати, я изобразил такую конструкцию:

Правда, ни прищепка (которой подключается питание), ни прижимной блок с pogo-пинами по размеру чуть не совпадали: нижний ряд имеет шаг 1,8 мм вместо двух, а боковые – 1,4 мм вместо 1,27. Но на нужные контакты я кое-как попадаю – да и ладно.

Так или иначе, ни в libretiny, ни, соответственно, в ESP Home пока поддержки BL7238 нет. Из того, что можно прошить – разве что openbeken. В общем, пока ждем, а модули остаются лежать без дела.

3 мысли о “Делаем “устрашающе умный” (с) модуль из TOMZN TOB9-VAP. Реле всего.”

Добавить комментарий