LoRaGe - Gemini over LoRa
Większość urządzeń szeroko rozumianego IoT łączy się z siecią WiFi i pozwala na sterowanie przez zwykłą stronę WWW. A co jeżeli nie mamy do dyspozycji sieci WiFi, ponieważ urządzenia są rozmieszczone w dużej odległości od siebie? Postanowiłem spróbować rozwiązać ten problem z wykorzystaniem protokołu LoRa.
Kod oraz instrukcje uruchomienia można znaleźć w repozytorium.
Założenia projektu
Celem LoRaGe /lɔ.ʁaʒ/ jest umożliwienie dostępu do m.in. strony statusowej lub panelu urządzenia IoT na duże odległości bez konieczności podłączania go do sieci WiFi lub wyposażania w kartę SIM.
Od czasu projektów związanych z TinyGS zastanawiałem się nad ciekawym użyciem LoRa. W tym przypadku ta technologia pasuje idealnie, ponieważ zapewnia niski pobór mocy i daleki zasięg. Dodatkowo płytki ESP32 ze wsparciem tej technologii są tanie i szeroko dostępne.
Jedyny minus LoRa to bardzo ograniczona szybkość transmisji. Z tego powodu nie zdecydowałem się na klasyczne strony WWW, gdzie oprócz samej struktury zwykle dorzucamy także trochę CSS i skryptów JS. Zamiast tego postawiłem na totalny minimalizm, a mianowicie na projekt Gemini.
Gemini charakteryzuje się prostotą zarówno w warstwie interfejsu użytkownika, jak i od strony developmentu. Podstawowym formatem treści dla Gemini jest gemtext. W dużym uproszczeniu można go traktować jako wariacje Markdown. Jest to oczywiście format o wiele wydajniejszy w transmisji niż trio HTML+CSS+JS.
Połączenie tych dwóch technologii jest dość egzotyczne i nie wiedziałem do końca, jak to wyjdzie w praktyce. Mam natomiast tę przyjemność, że jest to najprawdopodobniej pierwsze takie połączenie na świecie.
Zasada działania
Ogólna idea jest całkiem prosta. Użytkownik łączy się z oddalonymi serwerami za pośrednictwem modułu proxy. Jest to dość popularny zabieg, dzięki któremu warstwę wizualną możemy przenieść na inne, wygodniejsze w obsłudze urządzenia. Płytka ESP32 pracująca jako proxy wystawia Access Point WiFi, do którego łączy się użytkownik np. z poziomu smartphone. Następnie użytkownik za pomocą klienta Gemini wchodzi pod URL:
gemini://lorage.proxy/id:{server_id}/{resource.gmi}
Proxy odczytuje ID i rozpoczyna komunikację z odpowiednim serwerem za pomocą LoRa. Jeżeli serwer jest w zasięgu i odbierze wiadomość, to odczytuje zasób i przesyła go z powrotem do modułu proxy. Proxy odbiera wiadomość, aktualizuje linki, a następnie przesyła ją do klienta Gemini.
Serwer Gemini na ESP32
Początkiem projektu był tak naprawdę znaleziony przypadkowo gist. Zaletą Gemini jest to, że napisanie minimalistycznego serwera jest proste, a ograniczona moc płytek ESP32 nie jest przeszkodą. Mając do dyspozycji przykładowy kod, rozdzieliłem go na dwie części.
Moduł proxy zachowuje się bardzo podobnie do wcześniej podlinkowanego kodu. Proxy nasłuchuje na porcie 1965 i przekazuje odebrane żądania oraz odpowiedzi. Istotną kwestią jest szyfrowanie, które jest wymagane przez specyfikację Gemini. W projekcie LoRaGe szyfrowana jest jedynie komunikacja pomiędzy modułem proxy a klientem. Dla uproszczenia projektu komunikacja przez LoRa do serwera na ten moment jest jawna.
Moduł serwera w związku z pominięciem szyfrowania jest jeszcze prostszy. Na ten moment jest to wyłącznie serwowanie plików statycznych na podstawie nazwy. Plik jest odczytywany, a przed jego zawartość dodawany jest odpowiedni nagłówek. Aktualnie obsługiwany jest wyłącznie gemtext.
Komunikacja przez protokół LoRa
Do obsługi modułu nadawczo-odbiorczego SX1276 użyłem biblioteki o identycznej nazwie SX1276. Jest to świetny kawałek kodu, żeby zrozumieć, w jaki sposób taki moduł funkcjonuje. Znajdziemy w nim przykłady użycia, a sam kod jest bardzo dobrze opisany. Biblioteka ta nie obsługuje jednak fragmentacji wysyłanych wiadomości. W przypadku próby przesłania wiadomości większej niż dopuszczalna długość pojedynczego pakietu zostaniemy zablokowani.
W projekcie LoRaGe jest to kluczowa funkcjonalność, ponieważ prawie każdy pobierany zasób będzie większy. W związku z tym zmodyfikowałem tę bibliotekę przez dodanie do niej nowej metody bulk_send
. Metoda ta dzieli wiadomość na fragmenty, które zmieszczą się w pakiecie.
Dodatkowo pojawiło się nowe pole is_last
w nagłówku, które pozwala określić koniec wiadomości. Wymagało to także napisania nakładki na funkcję odbierającą pakiet, która skleja poszczególne fragmenty i zwraca pełną wiadomość. Dużym uproszczeniem jest fakt, że cała komunikacja działa podobnie do protokołu TCP. Wszystko odbywa się synchronicznie, a kolejny pakiet jest wysyłany dopiero po potwierdzeniu odbioru poprzedniego.
Jedynym minusem omawianej biblioteki jest sposób jej użycia. Dane otrzymane przez LoRa musimy trzymać w zmiennej globalnej, która jest nadpisywana przez bibliotekę. Musimy także często sprawdzać stan dostępności modułu lub zmieniać bezpośrednio tryb jego działania. Podjąłem próbę przepisania jej w taki sposób, żeby operować na callbackach, jednak bez powodzenia.
Atrapa serwera DNS
Bardzo zależało mi, żeby adres URL do modułu proxy był zawsze taki sam. Rozwiązuje to dwa problemy. Pierwszy jest bardzo pragmatyczny. Łatwiej jest wpisywać stały adres niż podawać IP. Druga sprawa to certyfikaty SSL. Protokół Gemini wymaga, żeby komunikacja była szyfrowana, co powoduje konieczność wygenerowania takich certyfikatów (mogą być samopodpisane) dla konkretnej domeny.
Aby to osiągnąć, moduł proxy zawiera atrapę serwera DNS. Całość sprowadza się do utworzenia socketa UDP na port 53 oraz zwracaniu odpowiedzi wskazującej adres IP 192.168.4.1
(jest to stałe IP płytki w trybie Access Point) dla zapytania o lorage.proxy
.
W tym projekcie mogłem sobie pozwolić na takie uproszczenie, ponieważ w tej sieci oprócz ewentualnych innych klientów nie powinno być żadnych innych urządzeń.
Zainteresowanych, w jaki sposób taka odpowiedź serwera DNS powinna wyglądać, odsyłam do tego poradnika. Materiał ma już swoje lata, ale jest naprawdę dobrze opracowany.
Podsumowanie
Muszę przyznać, że tak dobrze bawiłem się z tym projektem, że aż postanowiłem zdać egzamin krótkofalarski. Przygotowałem też dwie anteny J-Pole, tak więc wkrótce możecie spodziewać się testu plenerowego LoRaGe.
Mam także w planach dalszy rozwój projektu. Chciałbym, żeby klient miał informacje, jakie serwery znajdują się w zasięgu. Spróbuję też ponownie usprawnić korzystanie z biblioteki do komunikacji przez LoRa.
Demo
Na koniec krótkie wideo przestawiające LoRaGe w warunkach developerskich. Do pracy nad projektem używam dwóch płytek LilyGo TTGO V2.1_1.6 LoRa32 868 MHz. Można je bez problemu kupić na AliExpress. Przy zakupie upewnij się, że wybrałeś model z modułem pracującym na odpowiedniej częstotliwości.
W Europie dla transmisji LoRa jedynym pasmem nielicencjonowanym jest właśnie pasmo 868-870 MHz. Dodatkowo należy spełnić inne warunki m.in. związane z czasem zajmowania pasma. Wszystko to starałem ująć się w kodzie, natomiast zaznaczam, że transmisje wykonujesz na własną odpowiedzialność.