
Witajcie moi drodzy
Przedstawię tutaj mój sieciowy sterownik przekaźników kompatybilny z Home Assistant poprzez Tasmota HTTP. Sterownik oparty jest o PIC18F67J60 i dodatkowo oferuje m. in. odczyt temperatury, budzik, przyciski, niezależny panel WWW oraz szerokie możliwości konfiguracji.
Wstęp
Projekt powstał by lepiej zapoznać się z działaniem IoT i HA.
Projekt oferuje:
- kompatybilność z Home Assistant i aplikacją Tasmota Control na Androida (dzięki użyciu Tasmota HTTP)
- niezależny od Home Assistant panel WWW (ale przez Home Assistant też można wszystkim sterować)
- sterowanie 4 przekaźnikami poprzez panel lub HA
- możliwość dodania karty rozszerzeń z dodatkowymi 10 przekaźnikami (lub wiecej)
- funkcjonalność budzika/buzzera
- odczyt temperatury z termometrów na I2C (wraz z wykresami na własnej stronie WWW oraz przesyłaniem ich do Home Assistant)
- możliwość dokonania konfiguracji przez USB
- synchronizacja z czasem z internetu poprzez NTP
- własna pamięć EEPROM (przydatna gdy chcemy używać sterownika bez parowania go z Home Assistant)
- wsparcie statycznego IP oraz DHCP
Zawartość płytki i jej projekt
Większość elementów użytych w projekcie już miałem i znałem z wcześniejszych prób.
Sercem układu jest 'Ethernetowy' mikrokontroler PIC18F67J60 który zasadniczo ma wszystko w sobie co potrzebne do zrealizowania takiej komunikacji (to tak jakby ENC28J60 + mikrokontroler w jednym).
Do niego oczywiście podłączone jest odpowiednio gniazdo na złącze RJ45 ze zintegrowanym transformatorem sygnałowym, tzw. 'magjack', tutaj JXD1-0008NL.
Zasilanie układu podłączane jest przez wtyk usb typu C. PIC zasilany jest z 3.3V, więc dodatkowo na płytce jest regulator LDO 3.3V - MIC2940.
Złącze USB na płytce też pełnić funkcję komunikacyjną - do niego podłączony jest MCP2221, konwerter USB<->UART, który z kolei podpięty jest do UART od PICa.
Dodatkowo w wolnym miejscu na płytce umieściłem 6 pamięci 24AA256, wszystkie na jednej szynie I2C. Trochę tych pamięci mam, z tego powodu wybrałem je zamiast karty SD.
Na magistrali I2C jest jeszcze zamontowany powierzchniowo malutki termometr TC74 (bardziej do testów i bo 'było miejsce', bo odczytana przez niego temperatura będzie nieco zawyżona przez grzanie się układu).
Piny IO PICa prowadzą do 4 niezależnych przycisków, 4 diod LED oraz 4 przekaźników sterowanych tranzystorami BC847.
Same przekaźniki to SRD-05VDC-SL-C.
W środku dodatkowo jest buzzer, by móc realizować też np. funkcję budzika.
Płytka posiada też diodę zieloną 'standby' oraz wyprowadzony przycisk RESET.
RESET można też wywołać zdalnie poprzez MCP2221 (poprzez jego GPIO). Służy do tego osobny tranzystor i zworka 'CDC_RST'. To z myślą o ew. programowaniu poprzez bootloader UART.
Płytka posiada też wyprowadzone dodatkowe piny oraz I2C w celu umożliwienia jej rozwoju poprzez zrobienie jej prostej nakładki z dodatkowymi przekaźnikami.
Z boku płytki wyprowadzone jest złącze ICSP by można było tez ją łatwo programować PICKITem.
Projekt został zrobiony w Eagle, wymiary to 10cm na 7.6cm. Limit 10cm jest podyktowany cenami w płytkarni (płytki o wymiarach do 100mm na 100mm są najtańsze).
Projekt PCB:

(co do tych dziwnych zworek na płytce - są one dane na wypadek gdybym chciał łatwo podpiąć np. przekaźnik na inny pin. Po prostu projekt był robiony po trochę nocami/weekendami i nie byłem pewny, czy wszystko jest ok, to chciałem sobie zostawić furtkę do łatwych modyfikacji)
Schemat:
Pliki źródłowe Eagle:
Gerbery które wysłałem płytkarni:
Lutowanie
Samo PCB zleciłem w płytkarni. Zamówiłem 5 sztuk, czyli najtańsza opcja:

Puste PCB:

Początek lutowania:

Pierwszy funkcjonalny układ - PIC, rezonator, układ zasilania, MCP2221, złącze programowania. Na tym etapie już sprawdzałem czy PIC działa (lutuję tanią lutownicą i do tego słabo widzę, więc chcę na wczesnym etapie wiedzieć czy wszystko jest OK):

Już gotowe układy Ethernet (komunikacja też już działa), pierwszy przekaźnik:

Ciąg dalszy lutowania (po paru miesiącach przerwy), dostawa złącz KF300 3 pin:

Już z buzzerem oraz z dodatkowym termometrem TC74A na dłuższym przewodzie (jeden TC74A jest w obudowie SMD na płytce, drugi jest w obudowie TO220 na kabelku):

Prawie gotowa wersja, częściowo wyczyszczona z topnika:

Gotowa wersja, jeden telefon z Tasmota Control drugi ze stroną WWW z PICa:

Obudowa
Obudowę zaprojektowałem w Blenderze i wydrukowałem z filamentu PLA na mojej drukarce Ender 3 PRO.
Na początek naniosłem sobie dokładniejsze wymiary płytki:

Przeniosłem je do Blendera.

Pierwszy wydruki, przymiarka:

Druga wersja:

Przymiarka, wersja bez wierzchu:


Końcowy projekt:

Końcowy efekt:



Obudowa oczywiście wymaga dodatkowo wydrukowania przycisków które by wystawały z obudowy i dotykały tactile switchy.
Program na PC do resetowania układu przez USB
Program ten powstał ponieważ początkowo planowałem opracowanie bootloadera na USB (a właściwie UART), lecz ostatecznie uznałem, że taki bootloader jest zbędny bo i tak mam ICSP wyprowadzone na obudowę.
Mimo wszystko go tu opiszę.
Program ten wykorzystuje dodatkowe cyfrowe GPIO od MCP2221 w celu zwarcia pinu MCLR od PICa do masy.

Program korzysta z dwóch bibliotek:
- MCP2221 HID Library- Author: Zak Kemble, contact(malpa)zakkemble.co.uk
- HIDAPI - Multi-Platform library for communication with HID devices. - Alan Ott Signal 11 Software
Całość zmieściłem w pojedynczym, prostym do skompilowania projekcie Visual Studio - nie trzeba pobierać żadnych dependencji, załącznik poniżej:
Zrzut ekranu z działania:

Kod:
Code: c
Dodam tylko, że jest tutaj potencjalna pułapka dla początkujących. MCP2221 pozwala zapisać sobie w pamięci konfigurację i pamięta ją po odłączeniu zasilania. Jeśli skonfigurujemy go tak, że domyślnie pin resetujący mój układ jest wysoki i zapiszemy tą konfiguracje poprzez mcp2221_saveGPIOConf(myDev, &gpioConf); to potem już nie uruchomimy w żaden sposób normalnie mojej płytki, bo PIC będzie ciągle w stanie RESET.
Czas z internetu poprzez NTP
Program na PIC18F67J60 regularnie pobiera czas z internetu poprzez protokół NTP. Oczywiście czas odliczany jest też lokalnie, ale na płytce nie ma rezonatora kwarcowego zegarkowego i na dłuższą metę bez synchronizacji z internetem powstałyby szybko duże przekłamania w pokazywanej godzinie.
Pobór czasu z internetu sprowadza się tylko do wysłania jednego pakietu w trybie bezpołączeniowym (UDP) i odebrania na niego odpowiedzi. PIC radzi sobie z tym i jednocześnie może obsługiwać UDP i TCP (jako serwer HTTP).
Struktura pakietu:
Code: c
Szczegóły wysyłania/odbioru znajdują się w kodzie.
Ostateczna wersja firmware
Kod napisany jest w Mikro C ale nie ma to większego znaczenia, bo funkcje są wydzielone tak, że można łatwo go przenieść na inny kompilator czy tam platformę. Całość podzielona jest na pliki:
Nazwa pliku | Krótki opis |
interrupt.c | implementacja przerwań, obsługa timera zliczającego czas, bufor kołowy od UART, funkcja odczytująca linię z UART (dla linii komend) |
memory.c | obsługa pamięci poprzez I2C (wspierane zarówno są single-byte write oraz page-write, lecz funkcje od page nie obsługują multi page write, szczegóły w nocie katalowej pamięci) |
settings.c | zapis i odczyt ustawień IP i nie tylko z pamięci |
files.c | niedokończony system plików który miał być na pamięciach EEPROM, listowanie plików, tworzenie ich, usuwanie |
tc74.c | obsługa TC74 poprzez I2C (odczyt temperatury jako integer) |
relays_and_leds.c | podstwowa obsługa portów w trybie wyjść cyfrowych, służy do sterowania przekaźnikami i LEDami |
helpers.c | pomocnicze funkcje, np. funkcja licząca checksumę, używana jest ona m. in. do weryfikacji konfiguracji z pamięci EEPROM |
main.c | główny plik, odbiór pakietów oraz główna pętla, dodatkowo linia komend UART |
alarm.c | plik budzika, parsing godziny, ustawienia budzika, odczyt i zapis godziny z pamięci EEPROM |
ntp.c | obsługa pobierania czasu z internetu poprzez NTP (wysyłanie pakietu poprzez UDP i odbiór odpowiedzi) |
main.h | zbiorczy nagłówek dla całego projektu |
Kod pisałem z założeniem, że nie napotka on na złośliwego użytkownika próbującego dokonać np. przepełnienia bufora. Możliwe, że wkrótce to poprawię.
Ostateczna wersja do pobrania:
Dowolność konfiguracji przez USB bez potrzeby programowania
Konwerter USB<->UART MCP2221 zapewnia możliwość konfiguracji całości bez dostępu do sieci LAN i bez programatora.
Po podłączeniu do komputera przewodem USB uzyskujemy wirtualny port COM:

Potem wystarczy uruchomić tylko jakiś terminal UART, np. RealTerm i można odbierać logi z urządzenia:

Można też wysyłać komendy. Każda komenda zakończona jest znakami CR LF:

Poniżej umieszczam skróconą listę komend:
Komenda | Argumenty | Krótki opis |
date/time/now | brak | wyświetla bieżący czas (regularnie synchronizowany z internetem poprzez NTP) |
temp | brak | wyświetla ostatnie pomiary temperatur |
state | brak | wyświetla stan przekaźników |
toggle | indeks | przełącza stan danego przekaźnika |
off | indeks | wyłącza dany przekaźnik |
on | indeks | włącza dany przekaźnik |
dhcp | 0 lub 1 | włącza lub wyłącza DHCP |
ip | adres IP jako A B C D | ustawia statyczne IP |
ip | brak | wyświetla bieżące IP |
mask | maska jako A B C D | ustawia maskę |
dns | IP jako A B C D | ustawia IP serwera DNS |
gw | IP jako A B C D | ustawia IP bramy domyślnej |
mac | MAC jako A B C D E F | ustawia adres fizyczny MAC urządzenia |
save | brak | zapisuje ustawienia w pamięci EEPROM |
scan | brak | wykonuje skan urządzeń na magistrali I2C i wypisuje ich adresy |
buzz | brak | wymusza dźwięk buzzera przez pół sekundy |
getalarm | brak | wyświetla informacje o ustawionym alarmie (budziku) |
Kości pamięci EEPROM na I2C
Na płytce znajduje się 6 kości EEPROM na jednej magistrali I2C.

Program automatycznie skanuje magistralę I2C na starcie systemu i wypisuję liste znalezionych urządzeń. Kod obsługi pamięci z mojego pliku memory.c wspiera zapis i odczyt z tych pamięci w dwóch tryb: pojedynczego bajtu i całej strony (page read/page write). Jedna z pamięci służą do przechowywania konfiguracji sieciowej, ustawień budzika oraz historii odczytów z czujników (data/godzina + wynik pomiaru).
Czujniki temperatury na I2C
Magistrala I2C pozwala na podłączenie dużej ilości urządzeń korzystających z tego protokołu, w tym też czujników temperatury. W projekcie użyłem czujnika TC74:

TC74 dostępny jest w wersjach o różnych adresach, co pozwala umieścić wiele takich czujników na jednej linii I2C jednocześnie:

Na płytce jest jeden TC74 w obudowie SMD, ale on jest bardziej do testów, bo grzanie się w środku obudowy przekłamywałoby trochę wyniki. Ale na I2C można podłączyć też dodatkowe czujniki, nawet na dość długich przewodach (w zależności m. in. od zegara I2C). Tutaj jeden podłączony:

Wyniki pomiarów z TC74 są liczbami całkowitymi. Płytka może wyświetlać historię temperatury na własnej stronie WWW (wyniki pomiarów zapisane są w jednej z pamięci EEPROM) oraz też może przekazywać je do Home Assistant.
Generowanie wykresu na PIC18F
Do generowania wykresu użyta oczywiście jest zewnętrzna biblioteka CanvasJS i jej plik javascript nie znajduje się na PICu (choć mógłby, bo te rozwiązanie na pewne wady, jednak go nie ulepszam bo docelowo jednak będę używać sterownika stąd w połączeniu z Home Assistant).
Poniżej zamieszczam kod strony (HTML + Javascript tworzący wykres) dla zainteresowanych:
Code: html
Skrypt cc.js:
Code: javascript
Te dataPoints są generowane proceduralnie, pomiary odczytywane są z EEPROM.
Własna strona WWW
Ta strona postawiona jest w pełni na PICu. Pozwala sterować przekaźnikami, sprawdzać odczyt temperatury (teraz na WWW jest jeden wykres, ale można dać kilka), oraz ustawiać alarm.
Strona główna:

Wykres temperatury:

Alarm:

Dodatkowo zacząłem robić też prosty system plików (jego obsługę przez panel WWW oraz implementację na EEPROM), ale ostatecznie porzuciłem ten pomysł. Zrzut ekranu z ostatniej wersji jego panelu:

Wsparcie modułu rozszerzeń
Na bazową płytke można dodać moduł rozszerzeń (np. z kolejnymi przekaźnikami), który opierać się będzie o expander pinów na I2C. Będzie to oczywiście wymagało zmiany obudowy, też jeszcze nie zadecydowałem która płytka będzie na górze a która na spodzie, ale PCB modułu rozszerzeń wygląda tak:

Kompatybilność z Tasmota HTTP
Urządzenie obsługuje komendy Tasmoty wysyłane jako żądania GET na HTTP (po TCP) zgodne z następującym formatem:
Code:
http://<ip>/cm?user=<username>&password=<password>&cmnd=Power%20On
Username/password nie jest w tej chwili używane, więc całość się upraszcza do np.:
- http://192.168.0.111/cm?cmnd=State
- http://192.168.0.111/cm?cmnd=Power%20On
- http://192.168.0.111/cm?cmnd=Power2%20Off
Urządzenie z PIC jest tutaj serwerem HTTP, klient wysyła do niego te żądania by je kontrolować.
Kompatybilność z Tasmota Control
Skoro wspieramy Tasmota HTTP, to mamy kompatybilność z aplikacją Tasmota Control z Google Play:

W Tasmota Control wystarczy podać namiary na urządzenie i aplikacja od razu je wykrywa:

Test przechodzi pomyślnie (sposób w jaki identyfikuje się urządzenie oczywiście mogę określić u siebie w kodzie, "Build Date Time" jest niepoprawna, wpisana na sztywno, te DS18B20 to też naprawdę nie jest DS tylko TC74):

Główny panel sterowania (ilość przekaźników i to, że jest dostępna temperatura jest pobierane automatycznie przez Tasmota Control):

Kompatybilność z Home Assistant
Kompatybilność z Tasmota HTTP zapewnia też nam kompatybilność z Home Assistant - a to dzięki bibliotece httas:
https://github.com/JiriKursky/httas
Niestety biblioteka ta w wersji z githuba nie będzie działać z najnowszym Home Assistant, ale o tym za chwilę.
Jeśli interesuje was ogólnie czym jest Home Assistant to odsyłam do tego tematu:
https://www.elektroda.pl/rtvforum/viewtopic.php?p=19247020#19247020
W tym temacie użyłem swojego serwera Home Assistant postawionego na Banana Pi:

Więc najpierw - instalacja httas dla Home Assistant. Nie robi się tego jednak przez Plugin Store, lecz ręcznie.
Z repozytorium należy pobrać folder custom_components/httas:

Folder ten umieszczamy w folderze konfiguracji Home Assistant. Można to zrobić przez FTP, lub przez SMB lub 'ręcznie' przez File Editor:

Zawartość custom_components:

Zawartość folderu httas po wrzuceniu (pliki wysyłamy przez tą ikonkę strzałki):

Teraz trzeba dać namiary na urządzenia. W tym przypadku na mój sterownik, ale procedura jest taka sama dla każdego urządzenia kompatybilnego z Tasmota HTTP.
Robi się to w configuration.yaml:
Code: yaml
Sekcję "logger: default: debug" można pominąć, dopisałem ją w celu ułatwienia testowania.
Niestety to nie wszystko, w takiej konfiguracji po uruchomieniu otrzymamy błąd:

Quote:Logger: homeassistant.components.switch
Source: core.py:176
Integration: Przełącznik (documentation, issues)
First occurred: 7:46:39 (2 occurrences)
Last logged: 7:46:44
Error adding entities for domain switch with platform httas
Error while setting up httas platform for switch
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 315, in async_add_entities
await asyncio.gather(*tasks)
File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 506, in _async_add_entity
await entity.add_to_platform_finish()
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 530, in add_to_platform_finish
await self.async_added_to_hass()
File "/config/custom_components/httas/switch.py", line 200, in async_added_to_hass
await self._do_update()
File "/config/custom_components/httas/switch.py", line 193, in _do_update
async_call_later(self.hass, scan_interval, self._do_update())
File "/usr/src/homeassistant/homeassistant/helpers/event.py", line 1179, in async_call_later
return async_track_point_in_utc_time(
File "/usr/src/homeassistant/homeassistant/helpers/event.py", line 1133, in async_track_point_in_utc_time
job = action if isinstance(action, HassJob) else HassJob(action)
File "/usr/src/homeassistant/homeassistant/core.py", line 176, in __init__
raise ValueError("Coroutine not allowed to be passed to HassJob")
ValueError: Coroutine not allowed to be passed to HassJob
Bład to ValueError: Coroutine not allowed to be passed to HassJob.
Jest to jednak wina samej biblioteki. Autorzy Home Assistant jakoś w 2020 musieli zmienić system HassJob tak, że nie można dawać mu korutyn w formie argumentów.
Trzeba to samodzielnie naprawić (chyba, że wkrótce autor biblioteki z githuba ją zaktualizuje).
Winna jest m. in. ta linijka:

Można to łatwo naprawić poprzez wydzielenie wywołania update do asynchronicznej funkcji:

Tego samego typu poprawki trzeba zrobić jeszcze w kilku miejscach w kodzie.
Dodatkowo poprawiłem tam jeszcze kilka innych rzeczy, np. nie podobał mi się ten fragment kodu (dodaje zbędny & gdy zmienne logowania są puste):

To samo musiałem zrobić dla drugiego pliku - sensors.py.
Tutaj zamieszczam pełną, ostateczną wersję componentu z moimi poprawkami:
Poniżej ostateczna konfiguracja (4 przekaźniki + 1 sensor temperatury):
Code: yaml
I prezentacja w Home Assistant:

Oczywiście to '21°C' to też z mojego sterownika jest:

A skoro Home Assistant ma dostęp zarówno do przekaźników jak i do odczytu temperatury z mojej płytki, to można łatwo w nim zrobić automatyzacje typu np. "włącz przekaźnik 2 gdy temperatura spadnie poniżej 15°C".
PIC18F67J60 i automatyzacje w Home Assistant
Kompatybilność mojej płytki z Home Assistant umożliwia tworzenie różnego rodzaju automatyzacji pomiędzy nią i urządzeniami różnych producentów. Dla przykładu użyję tutaj czujnika otwarcia drzwi Aqara (MCCGQ11LM) i skonfiguruje go tak by 'otwarcie drzwi' włączało jeden przekaźnik:


Czujnik miałem już wcześniej sparowany z Home Assistant poprzez CC2531 (gdyż z Home Assistant komunikuje się on poprzez Zigbee):

Tworzenie automatyzacji:


Niestety w celu podpięcia komponentu httas trzeba przejść do trybu edycji Yaml i tam sfinalizować tworzenie automatyzacji w ten sposób:

Finalna wersja automatyzacji:
Code: yaml
Dodatkowo (tylko na potrzeby prezentacji) zrobiłem podobną regułę ale wyłączającą przekaźnik jak 'drzwi' zostaną otwarte.
Code: yaml
Rezultat działania na filmie:
Dioda LED połączona jest z pierwszym przekaźnikiem. Jak widać (i słychać) całość jest bardzo responsywna, nie czuć w ogóle opóźnień.
Drugi przykład automatyzacji - wyzwalanie przekaźnika odczytaną temperaturą
Druga automatyzacja jaką tu pokażę będzie korzystać tylko z mojego sterownika - po prostu powiąże odczyt temperatury z TC74 ze stanem przekaźnika.
Temperatura wyższa niż 22 stopnie będzie włączać przekaźnik.
Odczyt temperatury już mamy:

Teraz wystarczy dodać automatyzację w Home Assistant. Użyjemy wyzwalania poprzez numeric_state.
Gotowy skrypt YAML:
Code: yaml
Analogicznie możemy dodać wyzwalacz wyłaczający ("turn_off" oraz przykładowo "below: '20'").
Sprawdzenie działania (z użyciem TC74 na płytce i lutownicy jako źródła ciepła).
Przed:

Po:

Telefon z Tasmota Control obok pokazany tylko w celu wizualizacji (Tasmota Control często odpytuje HA o stan, a HA często odpytuje sterownik przekaźników, więc kolor przycisków szybko się aktualizuje).
Napotkane problemy i ich rozwiązania
Opiszę tutaj kilka konkretnych problemów które napotkałem przy tworzeniu tego projektu.
Dla urozmaicenia lektury rozwiązania tych problemów umieszczę w 'spoilerach', by czytający mogli spróbować się sami zastanowić co było nie tak.
Problem 1:
TC74, czujnik temperatury na I2C działał poprawnie i dawał dobre odczyty temperatury dopóki nie uruchomiłem modułu Ethernet z PICa. Sama inicjacja modułu Ethernet z poziomu kodu psuła odczyty temperatury z I2C. Temperatura zaczynała pływać i dawać nawet odczyty rzędu 80°C
Rozwiązanie 1:
Spoiler:
Okazało się, że zapomniałem dać większy kondensator elektrolityczny za stabilizator 3.3V. Miało być 22uF, a było 1uF + kilka odsprzęgających 100nF przy pinach. I to działało dopóki w kodzie nie uruchomiłem prądożernego Ethernetu. Sam Ethernet dobrze działał, ale jak widać TC74 jednak miał zakłócenia. Oczywiście swój wpływ pewnie też miał dłuższy przewód do zasilacza 5V i może sama jego jakość.
(początkowo szukałem też rozwiązania w kodzie, że może zmieniam coś z pinami, oraz w erracie PICa, ale jednak to nie to)
Okazało się, że zapomniałem dać większy kondensator elektrolityczny za stabilizator 3.3V. Miało być 22uF, a było 1uF + kilka odsprzęgających 100nF przy pinach. I to działało dopóki w kodzie nie uruchomiłem prądożernego Ethernetu. Sam Ethernet dobrze działał, ale jak widać TC74 jednak miał zakłócenia. Oczywiście swój wpływ pewnie też miał dłuższy przewód do zasilacza 5V i może sama jego jakość.
(początkowo szukałem też rozwiązania w kodzie, że może zmieniam coś z pinami, oraz w erracie PICa, ale jednak to nie to)
Problem 2:
Po zaimplementowaniu biblioteki Chart i wstępnym jej przetestowaniu na PICu nagle strona z wykresami zaczęła w nieskończoność się ładować i zawieszać przeglądarkę. Zauważyłem, że zawiesza się tylko wtedy, gdy ma podpięte wyniki pomiarów temperatur z pamięci I2C, te dane 'na sztywno' w kodzie działają.
Rozwiązanie 2:
Spoiler:
Okazało się, że ta biblioteka zawiesza przeglądarkę gdy dostanie dane na wykres z szerokiego zakresu czasu, a w pamięci EEPROM trafił się przez przypadek wynik pomiarów z niby roku 1970, obok niego był wynik już z 2021. Biblioteka więc zaczynała generować wykres, i zaczynała alokować pionowe kreseczki podziałki i próbowała utworzyć dość dużo tych podziałek, bo po jednej na każdą godzinę od 1970 do 2021... co skutkowało ostatecznie zawieszeniem przeglądarki.
Okazało się, że ta biblioteka zawiesza przeglądarkę gdy dostanie dane na wykres z szerokiego zakresu czasu, a w pamięci EEPROM trafił się przez przypadek wynik pomiarów z niby roku 1970, obok niego był wynik już z 2021. Biblioteka więc zaczynała generować wykres, i zaczynała alokować pionowe kreseczki podziałki i próbowała utworzyć dość dużo tych podziałek, bo po jednej na każdą godzinę od 1970 do 2021... co skutkowało ostatecznie zawieszeniem przeglądarki.
Problem 3:
Większe strony HTML (na przykład wykres pomiarów z dłuższego czasu) w ogóle się nie otwierały - otrzymywały brak odpowiedzi od PICa.
Rozwiązanie 3:
Spoiler:
Użyty PIC ma ograniczoną ilość pamięci RAM a tylko część z jego RAMu przeznaczona jest na pakiety. Protokół TCP na którym opiera się HTTP musi pamiętać cały pakiet ponieważ obsługuje on automatyczną fragmentację i retransmisję, gdyby pakiet nie był trzymany w RAM to PIC nie wiedziałby co wysłać ponownie w razie problemu. Problem rozwiązałem jednak nie poprzez zwiększenie przydziału RAMu dla Ethernet, lecz poprzez wydzielenie tworzenia danych wykresu do osobnego skryptu javascript który ładuje się osobno już po wczytaniu podstawowej strony wraz z CSS.
Użyty PIC ma ograniczoną ilość pamięci RAM a tylko część z jego RAMu przeznaczona jest na pakiety. Protokół TCP na którym opiera się HTTP musi pamiętać cały pakiet ponieważ obsługuje on automatyczną fragmentację i retransmisję, gdyby pakiet nie był trzymany w RAM to PIC nie wiedziałby co wysłać ponownie w razie problemu. Problem rozwiązałem jednak nie poprzez zwiększenie przydziału RAMu dla Ethernet, lecz poprzez wydzielenie tworzenia danych wykresu do osobnego skryptu javascript który ładuje się osobno już po wczytaniu podstawowej strony wraz z CSS.
Dodatek - pakiety wysyłane przez HTTAS
Tutaj jeszcze chciałbym informacyjnie zamieścić zawartość przykładowych pakietów używanych do komunikacji z Home Assistant. Może się ona przydać komuś kto próbuje sam zaimplementować ten protokół by sparować własne urządzenie z Home Assistant (mam wrażenie, że jest to prostsze niż MQTT a równie skuteczne).
Pokazuję tu zawartość TCP pakietów, czyli też ich nagłówki HTTP.
Konfiguracja Home Assistant:
Code: yaml
Zapytanie Home Assistant wysyłane regularnie (pytanie o stan):
Quote:
GET /cm?cmnd=POWER1 HTTP/1.1
Host: 192.168.0.110
User-Agent: HomeAssistant/2020.12.1 aiohttp/3.7.1 Python/3.8
Accept: */*
Accept-Encoding: gzip, deflate
Odpowiedź mojego urządzenia z PIC18F:
Quote:
HTTP/1.1 200 OK
Content-type: application/json
{"POWER1":"OFF"}
Zapytanie Home Asistant gdy chcemy zmienić stan przekaźnika:
Quote:
GET /cm?cmnd=Power1%20Off HTTP/1.1
Host: 192.168.0.110
User-Agent: HomeAssistant/2020.12.1 aiohttp/3.7.1 Python/3.8
Accept: */*
Accept-Encoding: gzip, deflate
Odpowiedź mojego urządzenia z PIC18F:
Quote:
HTTP/1.1 200 OK
Content-type: application/json
{"POWER1":"OFF"}
Tak jak wyżej, ale teraz dla trybu czujnika temperatury.
Konfiguracja YAML:
Code: yaml
Zapytanie wysyłane przez Home Assistant:
Quote:
GET /cm?cmnd=status%208 HTTP/1.1
Host: 192.168.0.110
User-Agent: HomeAssistant/2020.12.1 aiohttp/3.7.1 Python/3.8
Accept: */*
Accept-Encoding: gzip, deflate
Odpowiedź mojego urządzenia z PIC18F:
Quote:
HTTP/1.1 200 OK
Content-type: application/json
{"StatusSNS":{"Time":"2021-01-10T19:27:53","DS18B20":{ "Temperature":14},"TempUnit":"C"}}
Dodatkowo - ogólne zapytanie o stan:
Quote:
GET /cm?cmnd=status HTTP/1.1
Host: 192.168.0.110
User-Agent: HomeAssistant/2020.12.1 aiohttp/3.7.1 Python/3.8
Accept: */*
Accept-Encoding: gzip, deflate
Odpowiedź mojego urządzenia z PIC18F:
Quote:
HTTP/1.1 200 OK
Content-type: application/json
{"Status":{"Module":0,"DeviceName":"Tasmota","FriendlyName":["Tasmota","Tasmota2","Tasmota3","Tasmota4"],"Topic":"tasmota_EC3F8F","ButtonTopic":"0","Power":0,"PowerOnState":3,"LedState":1,"LedMask":"FFFF","SaveData":1,"SaveState":1,"SwitchTopic":"0","SwitchMode":[0,0,0,0,0,0,0,0],"ButtonRetain":0,"SwitchRetain":0,"SensorRetain":0,"PowerRetain":0},"StatusFWR":{"Version":"0.1(pic18f)","BuildDateTime":"2020-11-07T11:57:45","Boot":6,"Core":"0","SDK":"0","CpuFrequency":80,"Hardware":"ESP8266EX","CR":"367/699"},"StatusNET":{"Hostname":"tasmota_EC3F8F-8079","IPAddress":"192.168.0.50","Gateway":"192.168.0.1","Subnetmask":"255.255.255.0","DNSServer":"192.168.0.1","Mac":"5C:CF:7F:EC:3F:8F","Webserver":2,"WifiConfig":4,"WifiPower":17.0},"StatusSNS":{"Time":"2021-01-10T19:27:53","DS18B20":{ "Temperature":16},"TempUnit":"C"},"StatusSTS":{"Time":"2021-01-10T19:27:54","Uptime":"0T00:41:19","UptimeSec":2479,"Heap":24,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":37,"MqttCount":0,"POWER1":"OFF","POWER2":"OFF","POWER3":"OFF","POWER4":"OFF","Wifi":{"AP":1,"SSId":"5G_FULL_POWER","BSSId":"30:B5:C2:5D:70:72","Channel":9,"RSSI":98,"Signal":-51,"LinkCount":1,"Downtime":"0T00:00:03"}}}
(jeśli chodzi o powyższą zawartość pakietu, to pewne informacje tam mogą być nieaktualne bo tworząc firmware na swojego PICa kopiowałem odpowiedzi oryginalnej Tasmoty z ESP i część z nich jest w kodzie na sztywno, np. te 'SSId' oczywiście nie jest używane, pewnie można by usunąć i pakiet byłby krótszy, ale też nie wszystko można tu sobie dowolnie zmienić, bo inne urządzenia muszą "myśleć", że to jest Tasmota/ESP bo z nią mają kompatybilność).
Podsumowanie
Wychodzi na to, że względnie łatwo jest zrobić 100% własną płytkę która będzie kompatybilna z Home Assistant i z gotowymi aplikacjami takimi jak Tasmota Control. Zrobiłem to w oparciu o protokół HTTAS, który opiera się na HTTP (które z kolei działa na TCP). Następnym krokiem teraz będzie dodatkowa implementacja MQTT, bo to też się przyda, ale nie jest niezbędne - system radzi sobie dobrze bez tego.
Trochę zabawy z projektem było - przy samodzielnej implementacji tego typu systemu trzeba nawet zaprogramować obsługę znaków specjalnych z zapytań HTTP, chociażby słynnego %20 (spacji) czy też %3A (dwukropek, tutaj używany przy ustawianiu alarmu).
Projekt cały czas się rozwija. Możliwe, że wkrótce dam na forum rozszerzoną wersję (dodatkowe przekaźniki) i przy okazji ulepszę kod oraz wygląd strony (tak by prezentowała się lepiej np. na urządzeniach mobilnych, większe przyciski). Oczywiście potrzebna też będzie większa obudowa, ale w czasach drukarek 3D to żaden problem.
Cool? Ranking DIY