Prawdziwie inteligentne gniazdko na WiFi

Uwaga!

Jeżeli trafiłeś tutaj szukając opinii o inteligentnym gniazdku WiFi Ferguson FS1PG – to raczej nie kupuj go, o ile nie masz zamiaru bawić się nim tak jak ja. Dostarczone przez producenta rozwiązania są dalekie od ideału.

W moim mieszkaniu mam jeden mały, ale bardzo dokuczliwy problem z ogrzewaniem gazowym. Kilka lat temu zainstalowany w nim Junkers zaczął się co jakiś czas wyłączać samoczynnie, sygnalizując alarm – początkowo zdarzało się to raz na kilka tygodni, ale ostatnią wiosną częstotliwość wzrosła i problem występuje już co około dwie godziny. Defekt został rozpoznany, ale jest bardzo trudny do naprawienia ze względu na dostępność części – pozostało mi więc czekać na reakcję właściciela mieszkania i przyzwyczaić się do wyłączania alarmu za pomocą świecącego, czerwonego guzika.

Sprawca zamieszania

Przyzwyczajenie musiałem jednak szybko zmienić. Najpierw guzik się złamał i anulowanie alarmu możliwe było tylko przez wyłączenie i włączenie urządzenia, a dodatkowo nastąpiło nagłe załamanie pogody. A ja bardzo nie chcę spać bez ogrzewania i nie będę co godzinę wstawał żeby zresetować szwankujący piec – coś musi wyłączać ten alarm za mnie.

Pierwsze rozwiązanie o jakim pomyślałem to mikrokontroler z przekaźnikiem który symulowałby anulowanie alarmu, ale prawie od razu je odrzuciłem – grzebanie  w starym Junkersie to nie jest najbezpieczniejsza zabawa, tak samo jak anulowanie alarmów bez zastanowienia i dodatkowo zanim zbuduję coś takiego, będę musiał przetrwać kilka zimnych nocy.

A może rozwiązanie szybkie i zupełnie nieinwazyjne? Które dodatkowo będzie w stanie inteligentnie wykryć stan alarmowy? Piec w takim stanie uruchamia pompę, więc analizując sam pobór mocy powinienem być w stanie wykryć co się z nim dzieje. A co pozwoli mi zmierzyć moc i jednocześnie odcinać resetować prądowo piec? „Inteligentne” gniazdko sterowane przez WiFi! Inteligentne w cudzysłowie, bo zanim takie się stanie, trzeba będzie odciąć się od tego co dostarczył do niego producent.

Ferguson FS1PG

Gniazdko dotarło do mnie błyskawicznie i po jego uruchomieniu potwierdziła się pierwsza część moich obaw – aplikacja do sterowania nim mimo że wyglądała profesjonalnie, była toporna w obsłudze. Samo skonfigurowanie gniazdka udało mi się chyba tylko przypadkiem za trzecią z kolei próbą, a jego przełączanie działało losowo. Wyświetlanie poboru mocy nigdy nie zadziałało poprawnie. Koniec tej zabawy – pomyślałem – i zabrałem się za rozpracowywanie tego urządzenia. 

Skanowanie nmap-em

Szybkie skanowanie portów nie pokazało niczego ciekawego, poza nazwą producenta wywiedzioną z adresu MAC – jest nią firma o swojsko brzmiącej nazwie Shenzhen Ogemray Technology. Sprzedaje ona te urządzenia po 16 dolarów w paczkach po 100 sztuk. Nazwa producenta pozwala znaleźć jeszcze jedno – kilka projektów które starały się rozpracować sterowanie tym urządzeniem i które okazały się bardzo pomocne.

We see time

Wystarczy wysłać jeden pakiet UDP i… już! Sterowanie działa! Wszystko czego trzeba, to być w tej samej sieci co gniazdko, znać jego adres MAC (co nie jest trudne, bo co kilka sekund wysyła ono wiadomość rozgłoszeniową przedstawiając się wszystkim) i można nim sterować do woli. Typowy dla internetu rzeczy dramat.

Wyłączać i włączać mogę, więc połowa zadania już gotowa. Niestety żaden znaleziony projekt nie analizował kwestii analizy poboru mocy, który jest dla mnie kluczowy. Żeby to zrobić, włączyłem Wiresharka i w pętli wysyłałem pakiet z zapytaniem o stan urządzenia co jedną sekundę.

Pomiary czas zacząć

Odkurzacz był doskonałym urządzeniem testowym – nie tylko musiałem poodkurzać, ale dzięki regulowanej mocy mogłem sprawdzić czy jakieś pole w zrzucie będzie zmieniać się zgodnie z ruchem pokrętła. Z włączonym Wiresharkiem regulowałem moc od najniższej do najwyższej.

Zrzut komunikacji

Analiza była łatwiejsza niż myślałem – poza kilkoma licznikami w środku pakietu zwrotnego, zmienia się tylko jego końcówka. Łatwo można w niej zidentyfikować licznik sekund, licznik całkowitego poboru mocy i pobór chwilowy. Jednostek nie znam (miliwaty?), ale nie są dla mnie ważne – wszystko co muszę jeszcze zrobić to dopasować wartości tego pola do stanów pieca i zautomatyzować wyłączanie!

Napisałem krótki skrypt odpytujący gniazdko o pobieraną moc i podłączyłem Junkersa. Udało mi się zidentyfikować cztery poziomy wartości, zależne od stanu pieca:

Stan Pobór mocy
Wyłączony 5800
Ogrzewanie mieszkania 120000
Ogrzewanie wody 110000
Praca pompy 94000

Podczas stanu alarmowego pracuje tylko pompa i niestety nie da się go odróżnić od normalnej pracy pompy tylko za pomocą pomiaru prądu – pozostaje ona włączona przez około dwie minuty po tym, jak piec skończy ogrzewać mieszkanie. I właśnie połączenie wartości z tą obserwacją daje wszystko co jest potrzebne, aby ostatecznie rozwiązać mój problem.

Zaprogramowałem skrypt tak, aby resetował piec gdy pompa włączy się „sama” przy stanie czuwania i wtedy kiedy pompa pracuje dłużej niż 5 minut. Skrypt pracował przez całą noc i kiedy o poranku nie trząsłem się z zimna, już wiedziałem że spisał się doskonale – zresetował Junkersa poprawnie cztery razy.

Zostaje tylko przepisać skrypt tak, aby mógł pracować na jedynym ciągle włączonym komputerze w moim domu – routerze i oczywiście zablokować komunikację gniazda ze światem zewnętrznym. I można już spać spokojnie!

I już nigdy nie będzie online.

Uruchomienie reklamowej tablicy LED, część V

Skoro wszystko już gotowe, to co jeszcze zostało? Dwie rzeczy, które wydawały się niesamowicie proste, ale jednak takie nie były:

  • Pobieranie komentarzy z Instagrama
  • Ostateczna instalacja na wystawie

Co może być trudnego w pobieraniu komentarzy z jednego z popularniejszych internetowych molochów? Zwykle każdy taki serwis posiada banalnie łatwe w użyciu API które w kilka sekund zwróci nam to, czego chcemy. Najtrudniejszą przeszkodą na drodze do ich użycia jest najczęściej zdobycie klucza, za pomocą którego serwis identyfikuje pobierającego dane. Mimo tego, cały proces od początku do zdobycia danych to zwykle kilka minut.

Instagram takie API posiadał, ale kilka miesięcy temu zostało ono całkowicie wyłączone – zamiast tego zostało ono sprzęgnięte z dziełem które mógł stworzyć tylko kompletny psychopata – systemem Facebook Graph.

Graph Explorer

Facebook Graph to moloch posiadający dane o chyba wszystkim czym zarządza demoniczne imperium Marka Zuckerberga. O ile same jego użycie w celu zdobycia komentarzy pod postem z Instagrama jest dość jasno wyjaśnione, to zdobycie artefaktów do tego potrzebnych jest skomplikowaną grą przygodową której niestety nie ukończyłem.

Zdobycie klucza API jest w bardzo dziwny sposób utrudnione, podobnie jak wyciągnięcie identyfikatora postu z Instagrama którego oczekuje Graph. Identyfikator nie ma nic wspólnego z adresem postu (ale czasami ma) i na dzisiaj najpewniejszą metodą na jego zdobycie jest wydobycie go z kodu HTML posta którego chcemy zbadać.

Kiedy już te wszystkie dane miałem, okazało się że i tak nie zdobędę komentarzy dopóki nie dostarczę Facebookowi ikony, polityki prywatności i kilku innych informacji – dopiero wtedy zespół pracowników oceni, czy prawa do czytania komentarzy z Instagrama można mi przyznać.

Zamiast korzyć się przed dewastatorem Internetu poszedłem inną drogą – wyłuskanie ostatniego komentarza ze strony internetowej Instagrama z pomocą BeautifulSoup zajęło mi około 10 minut (6 razy mniej niż cała zabawa z Graphem).

def last_comment(postid):
    data = requests.get('https://instagram.com/p/'+postid).text

	soup = BeautifulSoup(data)

	scripts = soup.find_all('script')

	try:
		graph = None
		for x in scripts:
			if 'edge_media_to_comment' in x.text:
				js = x.text[x.text.find('{'):x.text.rfind(';')]
				j = json.loads(js)

				return j['entry_data']['PostPage'][0]['graphql']['shortcode_media']['edge_media_to_comment']['edges'][-1]['node']['text']
	except:
		pass

Pozostało przetestować cały skrypt w ostatecznej formie. Urządzeniem sterującym został leciwy laptop Samsung NC10 na którym doskonale radzi sobie najnowszy Debian (wciąż wspierający architekturę i686). Skrypt został skonfigurowany jako usługa systemd uruchamiana przy starcie systemu i automatycznie resetowana w wypadku błędu:

[Unit]
Description=Tablica LED
Wants=network-online.target
After=network-online.target

[Service]
WorkingDirectory=/home/led/led
ExecStart=/home/led/led/set.py
Restart=always
RestartSec=3
User=root


[Install]
WantedBy=multi-user.target

Uruchamianie usługi jako root z katalogu użytkownika może nie jest najelegantsze, ale na potrzeby wystawy spełnia swoje zadanie. Skrypt przetestowałem na chyba najlepszym wejściu – poście jednej z Instagramowych gwiazdeczek które dostają tysiące komentarzy pod każdym nowym postem – w różnych językach i pełnych emotek. 

Działa znakomicie, komentarze sprawdzane są co 30 sekund i po kilku godzinach nie było widać żadnych śladów blokowania ruchu ze strony Instagrama. Czy wszystko gotowe? Prawie. Wśród autorów wystawy pojawił się pomysł podłączenia drugiej tablicy, która miała wyświetlać teksty z innego źródła, np. lokalnie zainstalowanego tabletu.

Wybrałem się więc do sklepu po niestandardowy kabel USB A↔A – dostałem go bez problemów w najbliższym elektronicznym za 10 złotych. Kiedy zacząłem wkładać go do gniazda w drugiej tablicy zdarzyło się coś czego w życiu bym się nie spodziewał – tablica zgasła, po chwili zaczęła wydobywać się z niej strużka dymu i pomieszczenie zaczęło wypełniać się swądem palonej elektroniki.

Tak wygląda złoczyńca

Dlaczego?! Jak duży prąd musiał popłynąć, żeby wywołać dym? I gdzie było spięcie? Druga strona przewodu nie była podłączona! Kable sprawdziłem miernikiem – jedyna różnica w ich budowie była w połączeniu osłony wtyczki, sam kabel nie miał żadnego zwarcia. Tablica na szczęście dalej działała, ale teraz musi być już ciągle połączona kablem (oryginalnym) z komputerem.

Usunąłem metalowe osłony z dwóch stron kabla, ale nie przetestowałem tej modyfikacji – autorzy wystawy nagle zrezygnowali z uruchomienia drugiej tablicy. W wolnej chwili zajrzę do jej wnętrza, ciekawi mnie co spaliłem i jak to naprawić.

Dzięki rezygnacji z drugiej tablicy projekt się uprościł i jednocześnie zakończył. Po zakończeniu wystawy postaram się lepiej przebadać protokół sterowania i udostępnię go szerzej – być może pozwoli to komuś jeszcze wskrzesić do życia jakąś tablicę, do której już dawno zaginęło oryginalne oprogramowanie. A może nawet przyda się, kiedy XYLED przestanie działać z nowszymi wersjami Windows.

Uruchomienie reklamowej tablicy LED, część IV

Co jeszcze zostało do zrobienia? Przede wszystkim rozpracowanie jak wysłać do tablicy długi tekst który będzie na niej przewijany. W tym celu trzeba znaleźć odpowiedzi na dwa pytania:

  • Jak skonfigurować ośmiobitowy blok efektów?
  • Jak poprawnie wysłać do tablicy więcej danych?

Na obydwa z łatwością odpowie Wireshark podsłuchujący komunikację z tablicą na którą wysyłane będą animacje.

XYLED pozwala na ustawienie 60 różnych efektów!

Z racji braku czasu skupiłem się jedynie na drugim efekcie z listy – przewijaniu w lewo. Kilka testów przyniosło odpowiedzi na wszystkie wątpliwości związane z tym efektem.

Blok kontroli efektów z ustawieniami dla przewijania

Bajty oznaczone na szaro nie zmieniały się pomimo żonglowania parametrami animacji – najprawdopodobniej używane są przy innych efektach, lub nie zmodyfikowałem ustawień które by na nie wpływały.

Oznaczony na zielono piąty bajt koduje w swojej drugiej połowie parametr opóźnienia – w XYLED możliwy do ustawienia między 0 a 7. W wypadku efektu przewijania określa on jak długo na tablicy będzie wyświetlała się końcówka napisu, kiedy animacja dojdzie do końca. W moim wypadku to kompletnie nieprzydatny parametr, więc zawsze ustawiam go na 0.

Szósty, żółty bajt na trzech pierwszych (najstarszych) bitach koduje prędkość animacji (0 to najszybsza, 7 to najwolniesza – logiczne, prawda?). Reszta bitów prawdopodobnie ma związek z numerem animacji (dla przewijania w lewo musi być ustawiona na 00001).

Ostatni bajt koduje długość danych – do 0x80 należy dodać liczbę „stron” jakie zajmuje tekst lub animacja. Co się stanie jeśli oszukamy sterownik i wyślemy tylko jedną stronę i licznik ustawimy np. na 40? Tablica wyświetli na samym początku nasz krótki tekst a potem odczyta pozostałości starych ustawień z pamięci i wyświetli 40 stron z nimi. Daje to bardzo ciekawy efekt – można podejrzeć dawną zawartość tablicy.

Zaktualizowany schemat transmisji

Jak natomiast wysłać więcej danych? Okazało się to bardzo proste – wystarczy zwyczajnie wysłać więcej bloków transmisyjnych, pamiętając tylko o tym żeby dostarczyć dane dla liczby segmentów będącą wielokrotnością 3 (tyle segmentów ma moja tablica).

Można to połączyć z funkcją generowaniem bitmapy, która wygląda teraz następująco:

def render_text(text, font_size=21, tile_height=32, page_width=32*3):

    image = Image.new('1', (page_width, tile_height), 0)

    font = ImageFont.truetype("arial.ttf", font_size)
    draw = ImageDraw.Draw(image)
    
    size = draw.textsize(text, font=font)

    if size[0] > page_width:
        image = image.resize((((size[0]/page_width)+1)*page_width, tile_height))
        draw = ImageDraw.Draw(image)

    #Wysrodkowanie w pionie
    y = (tile_height-size[1])/2

    draw.text((0,y), text, font=font, fill=1)

    return image

Długo wyczekiwany wynik jej pracy wygląda następująco:

Ale z generowaniem grafiki jest jeszcze jeden problem – tablica ma wyświetlać polskojęzyczne komentarze z mediów społecznościowych. Powinna więc móc wyświetlić nie tylko polskie znaki, ale także emotikony Unicode i przydałoby się wsparcie dla znaków grupy CJK (niektóre japońskie znaki używane są w miejsce emotikon, np. ツ). Niestety Pillow nie jest na tyle mądre żeby samo znaleźć czcionki zastępcze, więc wszystkie te znaki muszą znaleźć się w jednej. Bardzo trudno znaleźć też czcionkę która ma te wszystkie znaki i jednocześnie dobrze wygląda na tablicy.

FontForge

Rozwiązanie tego problemu zajęło raptem kilka minut – pamiętałem że kilkanaście lat temu edytowałem czcionki, dodając do nich polskie znaki i używałem do tego bardzo rozbudowanego programu FontForge. I tym razem to narzędzie mnie nie zawiodło – połączyć czcionki można w nim jednym kliknięciem, a kolejne dwa wystarczają żeby zapisać wynik prac.

Wszystko to razem prezentuje się na tablicy całkiem ładnie i okazuje się że rozdzielczość jest na tyle duża, że nawet chińskie znaki wydają się czytelne!

Wszystko gotowe? Też tak myślałem, ale na ostatniej prostej wystąpiło tyle zupełnie nieoczekiwanych problemów, że wystarczy ich na osobny, podsumowujący całą zabawę wpis!