Co takiego oferuje bojler z modułem Wi-Fi w środku? Jak wygląda jego budowa, co oferuje aplikacja? Czy można mu zmienić firmware? Zapraszam na pierwszą część przygody z Ariston Velis 80 i aplikacją Ariston NET - dziś zaczniemy od zakupu, wypakowania, testu aplikacji oraz zaprezentujemy płytki ze środka. Potem rozpoczniemy prace nad analizą protokołu komunikacji, przechwycimy dane analizatorem logicznym.. zobaczymy, ile uda się zrobić.
Zacznijmy od zakupu. Sprzęt kupował do domu nasz kolega z forum @DeDaMrAz , który równocześnie rozwija ze mną nasz elektrody firmware open source dla urządzeń IoT. Wszystkie zdjęcia dostałem od kolegi, a temat opracowaliśmy wspólnie.
Urządzenie kosztowało 322€, ale było kupowane w miejscu zamieszkania kolegi, czyli w Serbii - cena w Polsce może się różnić.
Paczka dotarła dość szybko, kurier przyniósł takie pudło:
Całość jest dobrze zapakowana na czas transportu. W zestawie mamy pełną instrukcję obrazkową i szczegółowy, wielojęzykowy manual. Znajdziemy tam również ulotkę deklarującą klasę energetyczną B produktu.
Manual też jest dostępny w sieci. Określa on parametry takie, jak (dla wersji 80) waga 27 kg, pojemność 65 litrów, poziom hałasu 15 dB oraz deklarowane zużycie energii w tygodniu około 27 kWh w trybie smart i 35 kWh w trybie standardowym.
Aplikacja Ariston NET
Na opakowaniu znajdziemy kod QR prowadzący nas do wybranego sklepu - Google Play lub Apple Appstore:
Parowanie jest bardzo proste, po prostu po rejestracji w apce wprowadzamy urządzenie w tryb łączenia zgodnie z instrukcją.
Otrzymujemy wtedy dostęp do panelu bojlera, gdzie możemy edytować jego program i harmonogram, tworzyć automatyzację, podejrzeć bieżącą i docelową temperaturę, sprawdzić czas pozostały do nagrzania oraz szacunkową ilość "pryszniców" na które pozwoli jego stan:
Ciekawą funkcją jest opcja ECO - opiera się ona na analizie dotychczasowych zwyczajów użytkownika. Najpierw przez tydzień czasu ta funkcja "uczy się" tego, jak korzystamy z bojlera, a potem dopasowuje ogrzewanie do naszych zwyczajów. Ma to na celu redukcję zużycia energii i ograniczenie grzania, wtedy gdy nie jest ono potrzebne.
Proces nauki jest dalej kontynuowany i uzyskuje największą sprawność po kilku tygodniach. Algorytm jednocześnie pilnuje, by nawet w nieoczekiwanej sytuacji bojler posiadał minimum ciepłej wody zdatnej do użytku.
Bojler posiada również funkcje takie jak antifreeze, czyli automatyczną ochronę przed zamarzaniem wody wewnątrz urządzenia poprzez uruchomienie grzałki, gdy temperatura spadnie poniżej 5 °C, oraz anti-legionella, czyli cykl termicznej dezynfekcji podgrzewający wodę do 60 °C na godzinę w celu eliminacji bakterii Legionella.
Wnętrze urządzenia
Pora zacząć demontaż. Co takie kontroluje to urządzenie?
Elektronika na stole w trakcie testów:
Główny panel ma wyświetlacz 7-segmentowy dwucyfrowy, moduł Wi-Fi z ESP32, oraz dodatkowy mikrokontroler.
Osobno mamy zasilacz z przekaźnikiem. Zasilacz opiera się na LNK623DG. Oznaczenie to 740190023201 TFE-RL. Ciekawe, że w środku są już nawet filtry przeciwko zakłóceniom i warystor. To nie jest jednak najtańszy produkt z Chin.
Skupmy się na module Wi-Fi. Mamy tu ESP32-WATG-32D:
Wyprowadzenia:
| Name | No. | Type | Function | RESET | 1 | I | Module enable signal (Internal pull-up by default). Active high. | I36 | 2 | I | GPIO36, ADC1_CH0, RTC_GPIO0 | I37 | 3 | I | GPIO37, ADC1_CH1, RTC_GPIO1 | I38 | 4 | I | GPIO38, ADC1_CH2, RTC_GPIO2 | I39 | 5 | I | GPIO39, ADC1_CH3, RTC_GPIO3 | I34 | 6 | I | GPIO34, ADC1_CH6, RTC_GPIO4 | I35 | 7 | I | GPIO35, ADC1_CH7, RTC_GPIO5 | IO32 | 8 | I/O | GPIO32, XTAL_32K_P (32.768 kHz crystal oscillator input), ADC1_CH4, TOUCH9, RTC_GPIO9 | IO33 | 9 | I/O | GPIO33, XTAL_32K_N (32.768 kHz crystal oscillator output), ADC1_CH5, TOUCH8, RTC_GPIO8 | IO25 | 10 | I/O | GPIO25, DAC_1, ADC2_CH8, RTC_GPIO6 | I2C_SDA | 11 | I/O | GPIO26, I2C_SDA | I2C_SCL | 12 | I | GPIO27, I2C_SCL | TMS | 13 | I/O | GPIO14, MTMS | TDI | 14 | I/O | GPIO12, MTDI | +5V | 15 | PI | 5 V power supply input | GND | 16,17 | PI | Ground | VIN | 18 | I/O | 12 V / 24 V power supply input | TCK | 19 | I/O | GPIO13, MTCK | TDO | 20 | I/O | GPIO15, MTDO | EBUS2 | 21,35 | I/O | GPIO19/GPIO22, EBUS2 | IO2 | 22 | I/O | GPIO2, ADC2_CH2, TOUCH2, RTC_GPIO12, HSPIWP, HS2_DATA0 | IO0_FLASH | 23 | I/O | Download Boot: 0 SPI Boot: 1 (Default). | IO4 | 24 | I/O | GPIO4, ADC2_CH0, TOUCH0, RTC_GPIO10, HSPIHD, HS2_DATA1 | IO16 | 25 | I/O | GPIO16, HS1_DATA4 | 5V_UART1_TXD | 26 | O | GPIO23, 5V UART Data Transmit | 5V_UART1_RXD | 27 | I | GPIO18, 5V UART Data Receive | IO17 | 28 | - | GPIO17, HS1_DATA5 | IO5 | 29 | I/O | GPIO5, VSPICS0, HS1_DATA6 | U0TXD | 30 | I/O | GPIO1, U0TXD | U0RXD | 31 | I/O | GPIO3, U0RXD | IO21 | 32 | I/O | GPIO21, VSPIHD | GND | 33 | PI | EPAD, Ground | +3.3V | 34 | PO | 3.3V Power supply output |
Wylutowaliśmy go za pomocą topnika i gorącego powietrza.
Zajrzeliśmy też pod ekran, by dostać się do pamięci Flash:
Obok ESP jest pamięć Flash z interfejsem SPI - FM25Q64A13, czyli 64M-BIT. Dzielone na 8 daje nam 8Mb.
Kopia wsadu (sprzed parowania) wykonana za pomocą esptool.py: https://github.com/openshwprojects/FlashDumps/commit/5e772e8573257f2cd69224823297cade7d9a13ba
Analiza protokołu komunikacji
Do testów użyliśmy konwertera USB na UART oraz analizatora stanów logicznych. Prezentowałem go już wcześniej:
Inżynieria wsteczna nieznanego protokołu I2C z analizatorem Sigrok na przykładzie kontrolera LED
Płytka podłączona do testów:
Boot log z ESP32:
@0V@Ђ ets Jun 8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff00b8,len:2704
load:0x40078000,len:22680
load:0x40080400,len:4
ho 8 tail 4 room 4
load:0x40080404,len:2676
entry 0x4008055c
ARES_MODULE_VERSION 02.09.10
RELOC Platform v0.0.22 (31d8388b-dirty)
flash chip id 10567703
flash size 8388608
=======================================================
[ ATG_REM4A_EWH - EARTH ]
Build timestamp: Jul 8 2024 11:14:50
FW Version : 14.115.22"a"
HW Version : 0.0
Author : ATG - RELOC
Branch : trunk
Notes :
=======================================================
> [nvs] online
Key partition found
nvs_flash_read_security_cfg retcode(bis): 0
Reserved build encrypted OK
[reserved] online
flash_crypt_cnt: 7
vee encryption disabled
getting echo area!
Bus ready!
Nie ma tu zbyt wiele informacji, choć można tym namierzyć użyte SDK i wyczytać czas kompilacji.
Log z parowania:
A 1
----------HEAP-------------
Total DRAM free heap 8 NOW: 56924
Total IRAM free heap 32 NOW: 97376
---------BLOCK MEM---------
Maximum largest free block DRAM: 45056
Maximum largest free block IRAM: 45056
Malloc of appConfigHtml_Prototype_buf: 2166free heap pre xml: 56120
free heap post xml build: 51884
free heap: 50308
free heap: 56120
free heap post free appConfigXml_buf: 56120
free heap pre xml: 56120
free heap post xml build: 53380
free heap: 50228
free heap: 54544
free heap post free appConfigXml_buf: 54544
free heap pre xml: 54584
free heap post xml build: 49824
free heap: 46840
free heap: 51600
free heap post free appConfigXml_buf: 51600
[certs] online
inbuf tls len: 16717
largest free block DRAM: 27648
outbuf tls len: 16717
largest free block DRAM: 27648
ATGGetIdentTable!
inbuf tls len: 16717
largest free block DRAM: 19456
outbuf tls len: 16717
largest free block DRAM: 19456
Removed an element from log cfg
$I Removed an element from log cfg
r Removed an element from log cfg
Removed an element from log cfg
Removed an element from log cfg
Removed an element from log cfg
Removed an element from log cfg
Removed an element from log cfg
Removed an element from log cfg
Removed an element from log cfg
Removed an element from log cfg
A Removed an element from log cfg
B Removed an element from log cfg
Ten log został odebrany na pinie OTXD 31 przy baud 115200. Dodatkowo mamy też UART do rozmowy z MCU, 5V_UART przy 9600 baud.
To pewnie ten główny MCU kontroluje grzanie, a moduł z ESP32 tylko się z nim komunikuje. Jakby poznać ten protokół, to dałoby się tylko zmienić wsad ESP32 i tak kontrolować całość.
Zebraliśmy też tam próbki:
00 3C 3C 3C 3C 3C 3C 3C 3C C3 14 24 01 00 FC 3C
C3 14 52 04 1D FE 00 13 5B 3C C3 14 25 04 01 00
01 00 02 3C C3 14 25 04 01 01 FF 01 02 3C C3 14
25 04 01 00 01 01 03 3C C3 14 25 04 00 00 01 00
01 3C C3 14 25 04 00 00 01 00 01 3C C3 14 25 04
01 00 01 01 03 3C C3 14 25 04 01 00 01 01 03 3C
C3 14 25 08 00 00 00 00 FF FF 00 00 02 3C C3 14
25 08 00 00 00 00 FF FF 00 00 02 3C C3 14 25 04
00 00 FF 00 FF 3C C3 14 25 08 00 00 00 00 FF FF
00 00 02 3C C3 14 25 08 00 00 00 00 FF FF 00 00
02 3C C3 14 25 08 20 03 8A 02 20 03 20 03 F9 3C
C3 14 25 08 BC 02 5E 01 20 03 BC 02 02 3C C3 14
25 08 32 00 00 00 C8 00 32 00 30 3C C3 14 25 08
00 00 00 80 FF 7F 00 00 02 3C C3 14 25 08 00 00
00 80 FF 7F 00 00 02 3C C3 14 25 04 00 00 FF 00
FF 3C C3 14 25 04 00 00 FF 00 FF 3C C3 14 25 04
40 05 96 2C 07 3C C3 14 25 04 01 00 FF FF FF 3C
C3 14 25 08 00 00 00 80 FF 7F 00 00 02 3C C3 14
25 04 00 00 01 00 01 3C C3 14 25 04 00 00 01 00
To samo można podejrzeć w Pulse View. Podłączmy nasz analizator:
Najpierw przechwytujemy sygnały:
Widać tu grupy transakcji, powiększmy:
Widać też poszczególne bity:
Potem dodajemy dekoder UART:
Dekoder UART pozwala łatwo nanieść bajty na przebiegi. Wtedy też widzimy, jak odbywają się transakcje.
Ale w zasadzie to nawet bez Saleae można szybko wyróżnić ramki. Widać to po powtarzających się nagłówkach pakietów:
C3 41 23 0C 45 DC 4A DC 4B DC 4C DC 4D DC C0 F2 A4
C3 41 23 0C C2 F9 C3 F9 C4 F9 C5 F9 41 FA D1 40 71
C3 41 23 0C DE 40 DF 40 DD 40 DC 40 DB 40 DA 40 DE
C3 41 23 0C C7 9C C8 9C 18 45 D1 3D CF 3D C0 F9 2A
C3 41 23 0C 7F FE B0 42 C3 F0 DD 9A D4 3D D9 3E F4
C3 41 23 06 D0 3D D2 3D C4 3E 4B
C3 41 25 02 11 23 5F
C3 41 25 02 44 24 93
C3 41 25 02 45 24 94
C3 41 25 02 46 24 95
C3 41 25 02 47 24 96
C3 41 23 0C 60 10 68 13 69 13 6A 13 6B 13 43 51 29
C3 41 23 0C D3 4B C4 4B D9 46 5E 47 CB 4B CC 4B 51
C3 41 23 0C 4F 9D 50 9D 4C 9E 4D 9E 57 D1 4A D8 2B
C3 41 23 0C 4B D8 5E D9 5F D9 6E 9E 71 9E 44 DC 00
C3 41 23 0C 45 DC 4A DC 4B DC 4C DC 4D DC C0 F2 A4
C3 41 23 0C C2 F9 C3 F9 C4 F9 C5 F9 41 FA D1 40 71
C3 41 23 0C DE 40 DF 40 DD 40 DC 40 DB 40 DA 40 DE
C3 41 23 0C C7 9C C8 9C 18 45 D1 3D CF 3D C0 F9 2A
C3 41 23 0C 7F FE B0 42 C3 F0 DD 9A D4 3D D9 3E F4
C3 41 23 06 D0 3D D2 3D C4 3E 4B
C3 41 25 02 11 23 5F
C3 41 25 02 44 24 93
C3 41 25 02 45 24 94
C3 41 25 02 46 24 95
C3 41 25 02 47 24 96
Tak spisane ramki od razu nam też pozwalają wyszukać długość pakietu - to czwarty bajt. Nie jest to w żaden sposób ukryte (przykładowo - nie ma XOR ze stałą lub ID pakietu). Policzcie sami - pasuje. Przykładowo, pakiet z 0x02 argumentem ma jeszcze po 0x02 dwa bajty, a na końcu pewnie... CRC. Suma kontrolna z reguły jest na końcu.
Ta sytuacja jest też ciekawa:
C3 41 25 02 11 23 5F
C3 41 25 02 44 24 93
C3 41 25 02 45 24 94
C3 41 25 02 46 24 95
C3 41 25 02 47 24 96
Tu widać, że coś rośnie, a suma kontrolna wraz z nim. Można spróbować wyprowadzić tę sumę. 0x45 zwiększyło się o 1 i suma kontrola też urosła o jeden... więc to pewnie suma modulo 256. Szybkie sprawdzenie potwierdza:
Teraz można to zweryfikować. PulseView pozwala pisać wtyczki, a wtyczki mogą bazować na innych wtyczkach - tak np. Modbus opiera się na UART. Dokumentacja:
http://sigrok.org/wiki/Protocol_decoder_HOWTO
Przygotowałem zatem plugin sprawdzający CRC:
Kod: Python
Po sprawdzeniu wynik umieszczam pod zdekodowanym pakietem. Pozwala to szybko sprawdzić całą komunikację. Te pluginy są naprawdę bardzo wygodne.
Kod: Python
Jednak coś poszło nie tak. Wygląda na to że połowa pakietów ma jeden bajt z przodu, który jest pomijany w CRC.
Poprawione:
Kod: Python
O wiele lepiej:
Można iść o krok dalej i weryfikować też długość. Pozwali nam to szybko sprawdzić, czy poprawnie zrozumiałem znaczenie tego pola w ramce. Porównuję długość z pola ramki z rzeczywistością długością ramki (zmniejszoną o nagłówek i CRC):
Analogicznie można wystawić informacje o typie pakietu:
Dodałem też wyświetlanie danych przenoszonych przez pakiet:
Teraz można przejść do następnego etapu. Rozważmy ten fragment:
Wygląda na to, że pakiet 0x25 może być swego rodzaju odczytem wartości.
Mamy tu:
- zapytanie co jest na adresie 11 23
- odpowiedź, że 00 00 01 00
- zapytanie o adres 44 24
- odpowiedź, DC 05 C8 00 B8 0B DC 05
- zapytanie o adres 45 24
- odpowiedź DC 05 00 00 B8 0B E8 03
- zapytanie o adres 46 24
- odpowiedź 64 00 32 00 B8 0B 64 00
- zapytanie o adres 47 24
- odpowiedź 0A 00 01 00 64 00 0A 00
Potem kończą się pakiety 0x25 i zaczynają 0x23:
Wróćmy do 0x23. Wygląda na to, że adresy są kolejno po sobie - 0x4424, 0x4524, 0x4624. W takim razie można by obstawić, że pierwszy jest mniej znaczący bajt, i w praktyce mamy tu 0x2444, 0x2445, itd.
Ten fragment też to potwierdza - 00 00 01 00 - to jest po prostu 1.
W takim razie co mamy tutaj?
DC 05 C8 00 B8 0B DC 05
DC 05 -> 05DC -> 1500
C8 00 -> 00C8 -> 200
B8 0B -> 0BB8 -> 3000
DC 05 -> 05DC -> 1500
Ciekawe co to za wartości - godziny? Ale to może później.
To teraz jeszcze pakiet 0x23:
Z - zapytanie, O - odpowiedź.
Z: 60 10 68 13 69 13 6A 13 6B 13 43 51
O: 24 01 23 01 26 01 23 01 25 01 FF FF
Z: D3 4B C4 4B D9 46 5E 47 CB 4B CC 4B
O: 00 00 00 00 00 1B 00
Z: 4F 9D 50 9D 4C 9E 4D 9E 57 D1 4A D8
O: C9 00 00 00 00 00 00 00 00 00 00 00
Dalej:
Z: D0 3D D2 3D C4 3E
O: 00 00 00
Na ten moment dziwi mnie zmiana długości odpowiedzi przy tej samej długości zapytania. Myślałem, że to są zapisy ustawień, ale trudno powiedzieć na ten moment.
Pełny kod pluginu PulseView do wglądu
Należy go umieścić w C:\Program Files (x86)\sigrok\PulseView\share\libsigrokdecode\decoders\uart_exporter jako pd.py wraz z odpowiednim wywołaniem z init. W razie wątpliwości wystarczy podejrzeć inne dekodery, np. Modbus.
Kod: Python
Dalsze plany
Do drugiej części tematu:
- zebrać więcej danych
- zacząć porównywać dane z pakietów z tymi z aplikacji
- spróbować zebrać dane przy włączaniu i wyłączeniu bojlera, porównać, znaleźć co go włącza/wyłącza
- analogicznie - dla różnych temperatur
- wgrać na ESP własny wsad i spróbować coś wysłać
- albo wgrać wsad z ESP na nasz ESP i spróbować udawać bojler własnym programem
Podsumowanie
Dekodowanie protokołu nie jest kompletne, ale temat zrobił się dość długi, więc podzielę go na dwie części.
Czy ktoś wie coś więcej, co to za protokół może być?
Docelowo chcielibyśmy uruchomić ten bojler z własnym firmware i kontrolować go z poziomu Home Assistant. Ilość pakietów jest dość duża, ale może nie trzeba obsługiwać wszystkich, by zyskać kontrolę nad temperaturą.
Załączam przechwycone dane. Można pobrać PulseView, dodać tam mój plugin i przejrzeć ramki. Pomysły?
Fajne? Ranking DIY Pomogłem? Kup mi kawę.