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!

Uruchomienie reklamowej tablicy LED, część III

Najtrudniejsze już zrobione – wiadomo jak zmusić tablicę do wyświetlenia czegokolwiek. Teraz czas na spięcie wszystkiego razem – podczas tego etapu prac okazało się, jak po samym protokole komunikacyjnym można odkryć jak rozwijały się produkty firmy NewWing, jakie obejścia przy ich produkcji stosowano i jaki dług techniczny zaciągnięto. 

Tak wygląda strona internetowa tego potentata

Ostatni odcinek skończył się na udanym wysyłaniu do tablicy zapisanych wcześniej danych. Prace nad pełnym rozgryzieniem protokołu zaczynamy od modyfikacji bajtów w zrzutach i obserwowaniu wyniku – po kilku próbach bez większych problemów rozpracowałem ogólny schemat.

Podział na logiczne bloki

Blok zaznaczony na szaro zawsze pozostawał taki sam – nazwałem go preambułą. Podejrzewam że zawiera podstawowe parametry, takie jak szerokość i wysokość. Niestety brakuje mi możliwości aby to w pełni zweryfikować. 

Blok oznaczony na niebiesko to ustawienia efektów – zmieniając bity w nim sprawiałem że tekst przesuwał się na tablicy w różnych kierunkach lub był animowany w jeden ze sposobów znanych z naszych ulic. Małym smaczkiem było odkrycie parametru mówiącego o długości tekstu – ustawiając go na dużą wartość można przejrzeć historię danych na tablicy.

Blok zielony, żółty i czerwony to dane pojedynczych pikseli. Po ich zmianie zapalają się i gasną pojedyncze diody. Każdy bajt koduje 4 piksele, tylko nieparzyste bity są używane (pozostałe są ignorowane przez tablicę). Możliwe że protokół działa z tablicami dwukolorowymi (parzyste bity sterują pewnie tym drugim kolorem).

Trzy zapalone piksele = A2

Dlaczego więc trzy kolory bloków? To ilustracja bardzo nieeleganckiego rozwiązania problemu z jakim starli się Chińscy inżynierowie, ale żeby go wyjaśnić, przeanalizujmy jak wygląda cała komunikacja z urządzeniem – warto jeszcze przed tym zaznaczyć, jak fizycznie zbudowana jest moja tablica. Ma ona trzy fizyczne segmenty diod o wymiarach 32 ✕ 32 = 1024 diod na segment.

Liczby przed blokami danych (te niepokolorowane) oznaczają ile bajtów w bloku tablica ma przetworzyć. Większość bloków zaczyna się siódemką (nazwijmy je pełnymi), kodują one 28 pikseli. Po nich wysyłany jest jeden blok z czwórką na początku (nazwijmy go uzupełniającym) i tylko cztery bajty z niego tablica przetwarza, pozostałe są ignorowane bez względu na wartość. Daje to razem 16 pikseli. Po wysłaniu takiego bloku uzupełniającego należy odczytać komunikat z tablicy – tak jakby oznaczało to „zatwierdzenie” całego, dużego bloku transmisyjnego.

Mając te dane można policzyć, ile pikseli zawiera jeden blok transmisyjny – drugi i trzeci zawierają po 1024 piksele, czyli tyle co jeden fizyczny segment. Ale przez to że na samym początku został upchnięty jeden bloczek z ustawieniami efektów, bloki transmisyjne nie tylko nie pasują do segmentów tablicy, ale też konieczne staje się wysłanie dodatkowego, małego bloku specjalnego na końcu, z danymi dla tych kilkunastu pozostałych diod.

Być może w pierwszych tablicach, bez żadnych animacji, układ bloków był logiczny, ale kiedy pojawiła się konieczność dodania efektów, dolepiono je na szybko tak, jak było najłatwiej nie przejmując się logiką transmisji.

Tutaj jeszcze tego nie ogarnąłem

Będąc uzbrojonym w te wszystkie informacje można stworzyć skrypt wyświetlający dowolny tekst – ja użyłem do tego Pythona i biblioteki Pillow. Stworzenie w niej monochromatycznego obrazka z tekstem to już kilkulinijkowa bułka z masłem – dużo bardziej złożone było wygenerowanie bloków danych tak, aby wyświetliły się na tablicy zgodnie z założeniem.

No i w końcu działa!

Więcej o Pillow i graficznej części tego zadania w następnej części. Ale to jeszcze nie będzie wszystko – trzeba rozwiązać jeszcze kilka niecodziennych problemów, takich jak znalezienie odpowiednich czcionek. Magiczny dym uciekający z tablicy również się pojawi…