E25-230B to programowalny regulator temperatury przeznaczony do ogrzewania podłogowego lub grzejnikowego. Montowany jest natynkowo i oferuje czytelny wyświetlacz LCD oraz przyciski, którymi można zmienić nastawę grzania. Dodatkowo posiada też komunikację radiową 868MHz oraz ZigBee i jest zgodny z bramką Tuya, co pozwala na zdalne sterowanie przez Internet z każdego miejsca na świecie. Może sterować aż 6 głowicami grzejnikowymi w jednym pomieszczeniu. Może pracować offline w trybie dobowym (bez harmonogramu).
W tym temacie przedstawię jego wnętrze a potem spróbuję samodzielnie go obsłużyć w języku Python poprzez użycie dongle CC2531.
W zestawie otrzymujemy samo urządzenie, ramkę montażową oraz śruby. Urządzenie z tyłu posiada zaciski śrubowe - zasilanie 230 V oraz beznapięciowy przekaźnik o obciążalności do 3 A. Dodatkowo importer załącza polskojęzyczną instrukcję, która szczegółowo opisuje konfigurację urządzenia:
Wyświetlacz jest wyraźny i czytelny. Odpowiednio można wprowadzić urządzenie w tryb parowania. Chciałem je sparować z Home Assistant przy użyciu Zigbee2MQTT, ale okazało się, że nie jest wspierane.
Stąd pomysł na eksperymenty z samodzielnym odczytem danych, ale o tym za chwilę.
Najpierw - rzut oka do wnętrza. Gdzie jest czujnik temperatury?
Środek wydaje się być dość bogaty. Nie mamy tu całkiem typowego przykładu miniaturyzacji. W środku jest osobno główny mikrokontroler, tutaj w obudowie TQFP, moduł Zigbee (ZTU od Tuya), oraz moduł RF.
Oznaczenie płytki to T1015-06ZA-V1-1, 2024-12-12. Całość zasila malutka przetwornica obniżająca napięcie na OB2222MCP. Co ciekawe, nawet widzę tam filtry i warystor. Tym razem chyba producent nie oszczędzał.
Za przetwornicą jest LDO 3.3 V AMS1117-3.3 a potem układy - MCU M307LM (rdzeń RM® CortexTM-M0), moduł od RF to Holtek BC3601 (CMOS RF FSK/GFSK transceiver), a i jakaś pamięć Flash (25D20ATIG) też się tam znajdzie. Ciekawe, do czego?
Ciekawość była silniejsza, odlutowałem pamięć i zgrałem za pomocą CH341 (niby jest metoda na klips, ale tak rzadko mi ona działa, że nawet nie próbuję). Zawartość okazała się być pusta.
Na koniec jeszcze pokażę czujnik temperatury - znajduje się w rogu obudowy:
Zostało jednak to, co najciekawsze. Spróbujemy odczytać dane z urządzenia bezpośrednio na komputerze, wykorzystując w tym celu prosty skrypt w języku Python oraz naszą powszechnie znaną "antenkę":
Zacznijmy od ważnego faktu dotyczącego układu CC2531: w zależności od wgranego firmware, ten moduł może być widoczny w systemie operacyjnym jako standardowy wirtualny port szeregowy (tryb CDC - Communication Device Class). Daje nam to prostą i wygodną możliwość sterowania stosem Zigbee (Z-Stack) za pomocą precyzyjnych poleceń binarnych wysyłanych bezpośrednio przez interfejs UART.
Szukając po Internecie, znalazłem dokument od Texas Instruments z opisem API Z-Stack na repozytorium Tasmoty:
https://tasmota.github.io/docs/_media/zigbee/Z-Stack_API_1_2.pdf
Całość jednak jest dość zawiła, więc pomocne okazało się być przeszukiwanie GitHuba pod kątem implementacji konkretnych komend i stałych (jak np. ZDO_STARTUP_FROM_APP). Pozwoliło to podejrzeć, jak szybko nawiązać komunikację z modułem:
https://github.com/search?q=ZDO_STARTUP_FROM_APP&type=code
W oparciu o powyższe materiały przygotowałem niewielki, testowy skrypt w języku Python. Inicjalizuje on CC2531 i rozpoczyna nasłuchiwanie nadchodzących ramek w sieci Zigbee:
Kod: Python
Pełen kod:
https://github.com/openshwprojects/CC2531test/blob/main/test1.py
Rezultat, po sparowaniu danych:
[*] READY! The script is fully listening.
[*] 1. If it's already paired, just press a button on the thermostat.
[*] 2. If it's not paired, put it in PAIRING MODE now.
[*] (Press Ctrl+C to stop)
[ZNP] Cmd: 64:00 | Length: 1 | Data: 00
[ZNP] Cmd: 65:40 | Length: 1 | Data: 00
[ZNP] Cmd: 45:C0 | Length: 1 | Data: 09
[ZNP] Cmd: 65:36 | Length: 1 | Data: 00
[ZNP] Cmd: 45:B6 | Length: 3 | Data: 000000
[ZNP] Cmd: 45:CA | Length: 12 | Data: ab32e10e39feff9d1c780000
[ZNP] Cmd: 45:C1 | Length: 13 | Data: ab32ab32e10e39feff9d1c788e
[ZNP] Cmd: 45:C4 | Length: 3 | Data: ab3200
[inContentAd]
============================================================
📦 INCOMING ZIGBEE PACKET!
📍 Device Network Addr: 0x32AB
🎯 Zigbee Cluster ID : 0xEF00 (identifies if it's Temp, HVAC, etc.)
📄 Raw Data (Hex) : 0900240600
============================================================
[ZNP] Cmd: 45:C4 | Length: 3 | Data: ab3200
Urządzenie wykrywa tzw. klaster (typ?) 0xEF00. Szybkie poszukiwania pokazują, że to jest coś od Tuya. To by się zgadzało:
https://github.com/zigbeefordomoticz/wiki/blob/master/en-eng/Technical/Tuya-0xEF00.md
Cytat:
Seems to be the foundation of a number of Tuya devices. The tuya Cluster 0xEF00 is more or less one tunnel for tuyas MQTT commands from there MCUs to there cloud MQTT servers.
Trop wskazuje na protokół TuyaMCU, który już omawiałem.
Protokół TuyaMCU - komunikacja pomiędzy mikrokontrolerem a modułem WiFi
A więc ta ramka Zigbee niesie w sobie ramkę TuyaMCU, nawet zgadzają się rodzaje typów, 0x01 to boolean, 0x03 to string... można spróbować to parsować, najpierw filtr po 0xEF00:
Kod: Python
Potem właściwy parsing (typu 0x01 i 0x02 z obrazka):
Kod: Python
Czy to wystarczy, by poprawnie odczytać jakiekolwiek zmienne? Sprawdzamy:
============================================================
📦 INCOMING ZIGBEE PACKET!
📍 Device Network Addr: 0x5362
🎯 Zigbee Cluster ID : 0xEF00
📄 Raw Data (Hex) : 095b02005f18020004000000cd
-> [Tuya Data] DP ID: 24 | Type: 2 | Value: 205 (Raw Value)
Sukces! Coś się złapało, i nawet pasuje do wskazanej ramki:
095b
02 - wersja
005f - numer sekwencji
18 - dpID (identyfikator) zmiennej
02 - typ payload - integer (dpType)
0004 - rozmiar payload
000000cd - czterobajtowy payload - 220
Tak się też składa, że odkodowana liczba, to jest odczyt temperatury podany z dokładnością do 1 miejsca po przecinku, specjalnie przemnożony przez 10 by był typem całkowitym. Zgadza się to z odczytem.
Pora na finalny test - nagrzewamy czujnik:
Działa, oto ostateczne potwierdzenie. Temperatura wzrosła do 24°C a prosty program poprawnie to odczytał.
📦 INCOMING ZIGBEE PACKET!
📍 Device Network Addr: 0x5362
🎯 Zigbee Cluster ID : 0xEF00
📄 Raw Data (Hex) : 096502006418020004000000f0
-> [Tuya Data] DP ID: 24 | Type: 2 | Value: 240 (Raw Value)
============================================================
Podsumowując, Zigbee okazało się być znacznie bardziej zrozumiałe niż mogłoby się to na początku wydawać. Oczywiście, tu i tak większość pracy jest wykonywana przez firmware na CC2531, ale i tak nie stoi to na przeszkodzie by samodzielnie obsługiwać nowe urządzenia. Termostat Tuya komunikuje się pakietami bardzo podobnymi do tych, z których korzysta TuyaMCU w urządzeniach z Wi-Fi. Dane są przekazywane w formacie zmiennych o unikalnych identyfikatorach (dpID), typach (od 0x00 do 0x05) i odpowiednio kodowanej zawartość. Temperaturę z dokładnością do jednego miejsca po przecinku przesyłaną jako liczbę całkowitą to ja widziałem już kilka lat temu, nawet w moim firmware mam typ Temperature_div10.
Pomysł własnego urządzenia obsługującego Zigbee można by rozwinąć, a nawet zrealizować bez komputera, przykładowo na samym ESP32. Spróbuję to pokazać w kolejnym temacie.
Czy próbowaliście samodzielnie kiedyś obsłużyć urządzenia Zigbee, albo zmieniać im firmware?
Fajne? Ranking DIY Pomogłem? Kup mi kawę.