# Volang i VolangVM: po co inteligentny dom potrzebuje własnego języka automatyki
Każdy inteligentny dom składa się z dwóch warstw. Pierwsza to fizyczne urządzenia: moduły w rozdzielnicy, czujniki, włączniki, silniki rolet, źródła ciepła i chłodu. Druga to logika, czyli reguły, które decydują, co ma się stać, gdy zmienia się stan jakiegoś wejścia. Naciśnięcie przycisku ma włączyć światło. Przekroczenie temperatury ma uruchomić chłodzenie. Wykrycie ruchu o wybranych godzinach ma rozjaśnić korytarz, ale tylko wtedy, kiedy na zewnątrz jest ciemno.
Większość użytkowników widzi system Voldeno od strony pierwszej warstwy: HUB, moduły I/O, RELAY, 1-WIRE i ANALOG INPUT na szynie DIN, wszystko spięte naszą magistralą Voldeno Bus. Druga warstwa, czyli logika, działa pod spodem i to ona odróżnia „zestaw modułów” od „systemu, który robi dokładnie to, co chcesz”.
W tym wpisie tłumaczymy, jak ta druga warstwa jest u nas zorganizowana: czym jest Volang, czym jest VolangVM, dlaczego mamy własny język zamiast korzystać z czegoś popularnego i co z tego wynika dla osób, które będą tej logiki używać. Tekst jest pisany dla czytelnika, który z programowaniem ma niewiele wspólnego, więc kluczowe pojęcia tłumaczymy po drodze.
# Czym właściwie jest „logika” w inteligentnym domu
Logika to po prostu reguły. „Jeśli A, to zrób B”. Czasem jeden warunek, czasem siedem, czasem coś z czasem (po dwóch minutach), z licznikiem, z histerezą albo z zależnością od pory dnia.
Najprostsza logika to dwa zdania po polsku: „kiedy ktoś nacisnie ten przycisk, włącz to światło”. Tę regułę można zapisać w głowie i potem skonfigurować w aplikacji jednym przeciągnięciem. Bardziej złożona logika to kaskada reguł, które wpływają na siebie nawzajem. Temperatura zadana zależy od harmonogramu, harmonogram zależy od trybu domu, tryb domu zależy od obecności, obecność zależy od kilku czujników i ostatniego logowania w aplikacji. Takie rzeczy bardzo szybko stają się trudne do utrzymania, jeśli zapisze się je tylko jako luźną listę „scen” i „automatyzacji”.
Logika musi być gdzieś uruchamiana. W Voldeno robi to Hub, a w wybranych przypadkach również moduły rozszerzeń. Pytanie tylko: w jakim formacie ta logika ma być zapisana, żeby Hub wiedział, co dokładnie ma robić.
# Czy nie można tego po prostu wyklikać?
Można. Dla większości codziennych scenariuszy tak właśnie się robi. W Voldeno Studio całą logikę można złożyć z gotowych klocków. Te klocki nazywamy blokami logicznymi. Są wśród nich przełączniki, harmonogramy, regulator klimatu, sceny, liczniki, mostki do integracji. Łączy się je liniami: wyjście jednego bloku do wejścia drugiego. Cały projekt automatyzacji typowego domu można w ten sposób zbudować bez napisania jednej linii kodu.
Ten sposób ma realne granice. Wbudowane bloki to skończona lista. Robią dokładnie to, do czego zostały zaprojektowane. Jeżeli na przykład chcesz:
- liczyć impulsy z licznika energii i co pełną kilowatogodzinę publikować zdarzenie do swojej chmury,
- zaimplementować nietypową sekwencję bramy garażowej, której producent nie obsługuje natywnie,
- napisać własny algorytm sterowania pompą ciepła z trybami i histerezą dopasowaną do konkretnej instalacji,
- zintegrować się z systemem zewnętrznym po protokole, którego jeszcze nie ma w naszej liście integracji,
to z samych bloków logicznych tego nie złożysz. Trzeba opisać, co dokładnie ma się dziać, w sposób, który komputer wykona dosłownie. Do tego służy język programowania.
# Po co tworzy się „języki” do logiki
Język programowania to bardzo precyzyjny sposób zapisywania reguł. Człowiek pisze tekst, kompilator (program tłumaczący) sprawdza, czy ten tekst trzyma się ustalonych zasad, a potem urządzenie wykonuje opis dosłownie, krok po kroku, miliony razy bez żadnej interpretacji.
Wszystkie poważne systemy automatyki budynkowej mają swój sposób zapisywania logiki. KNX ma ETS i konfigurację adresów grupowych. Sterowniki PLC w przemyśle mają języki standardu IEC 61131-3 (drabinkę, blok funkcyjny, listę instrukcji, tekst strukturalny). Home Assistant używa YAML i Pythona. Im więcej można zrobić z systemu, tym bardziej precyzyjnego „języka” wymaga w środku.
Powód, dla którego nie da się uciec przed jakąś formą języka, jest prosty. Bloki logiczne w GUI też są językiem. Każdy blok to ukryta funkcja. Każde połączenie to ukryta linia kodu. Różnica polega tylko na tym, jak bardzo ten „język” jest widoczny i jak bardzo elastyczny. Wizualne języki są łatwe na początku i męczące, gdy logika rośnie. Tekstowe są trudniejsze na początku i znacznie bardziej wytrzymałe, gdy projekt urośnie.
# Dlaczego nie użyliśmy gotowego języka jak Python czy JavaScript
To pierwsze pytanie, które sami sobie zadaliśmy. Python i JavaScript są wszędzie. Lua jest popularna w urządzeniach wbudowanych. Mamy do tych języków szacunek i używamy ich w innych miejscach naszego ekosystemu (na przykład w narzędziach back-endowych albo w aplikacji desktopowej). Do logiki uruchamianej na module na szynie DIN zdecydowaliśmy się jednak zbudować coś własnego. Powody są następujące.
Rozmiar. Hub ma ograniczoną pamięć i procesor wbudowany, a moduły rozszerzeń jeszcze mniej. Pełen interpreter Pythona zajmuje wiele megabajtów RAM-u i ciągnie za sobą gigantyczny ekosystem zależności. Dla naszych modułów to zaporowo dużo. Volang jest projektowany jako mały, oszczędny i szybki w starcie.
Bezpieczeństwo. Logika napisana przez użytkownika nigdy nie powinna móc zawiesić Huba ani wpływać na inne projekty na tym samym urządzeniu. To wymaga bardzo precyzyjnych ograniczeń, jakie operacje wolno wykonać i jak długo. W ogólnym Pythonie czy JavaScripcie da się to zrobić, ale tylko dużym wysiłkiem i często niepełnie. Własny, wąski język pozwala te ograniczenia wpisać już w sam projekt języka, a nie dokleić je obok.
Przewidywalność czasu wykonania. W systemie sterującym ogrzewaniem, oświetleniem i bramą formuła „prawie zawsze działa szybko” to nie jest dobra formuła. Chcieliśmy języka, w którym wykonanie tego samego kodu zawsze trwa porównywalnie długo i nie wykonuje magii w tle (na przykład samodzielnego zwalniania pamięci w przypadkowych momentach, jak robi to większość ogólnych runtime'ów). Volang nie ma odśmiecacza pamięci typu „garbage collector”, który mógłby wstrzymać wykonanie skryptu w nieoczekiwanej chwili.
Dopasowanie do automatyki. W Volangu od pierwszej linijki ma sens taki kod jak output::set("relay_1", true) albo input::value(). Pojęcia „wejście”, „wyjście”, „kanał”, „blok” są częścią biblioteki standardowej. W Pythonie czy JavaScripcie te warstwy trzeba by najpierw zbudować i utrzymywać, a użytkownik i tak musiałby się ich nauczyć.
Stabilność w czasie. O tym, jak rozwija się składnia Volanga i jego biblioteka standardowa, decydujemy my, a nie zewnętrzna społeczność. W Pythonie czy JavaScripcie kolejne wersje języka regularnie zmieniają stare funkcje albo wycofują je z użycia, przez co projekty co jakiś czas trzeba dostosowywać. W Volangu chcemy, żeby skrypt napisany dziś działał bez zmian również za dwa lata i za dziesięć.
A co z MicroPythonem? To pytanie często wraca, więc krótka odpowiedź. MicroPython i CircuitPython to okrojone wersje Pythona pod mikrokontrolery i są naprawdę dobre w swojej klasie. Dla naszego konkretnego zastosowania powody, dla których ich nie wzięliśmy, to dokładnie te same powody, które wymieniliśmy wyżej, tylko zaostrzone: nadal kilkaset KB pamięci, nadal jest garbage collector zatrzymujący skrypt na nieprzewidywalne milisekundy, a żeby zbudować z tego sensowny sandbox, trzeba odciąć tyle Pythona, że robi się z tego de facto własny, węższy język. „Prawie Python” jest też pułapką, bo użytkownik wpada w nawyki, które nagle nie działają.
To nie jest stanowisko „wszystko po swojemu z zasady”. Rozważamy podpięcie dodatkowych języków w przyszłości tam, gdzie to ma sens. Dla samej warstwy logiki bloków własny, bardzo wąski język daje nam najlepszy kompromis.
# Co to jest VolangVM
VM, czyli maszyna wirtualna, to brzmi groźnie, ale w praktyce jest to po prostu mały, wyspecjalizowany program, który „udaje” prosty komputer i wykonuje na nim instrukcje napisane w Volangu.
Po napisaniu skryptu w Studio dzieje się tak:
- Kod tekstowy w Volangu jest sprawdzany pod kątem składni i typów. To jest etap kompilacji.
- Jeśli wszystko jest w porządku, kompilator zamienia tekst na zwarte, binarne instrukcje, tzw. bytecode. Bytecode wygląda dla człowieka jak ciąg liczb, ale jest dla maszyny szybki do odczytania.
- Bytecode trafia do Huba (a docelowo również do wybranych modułów rozszerzeń) i tam jest wczytywany przez VolangVM.
- VolangVM krok po kroku wykonuje instrukcje. Czyta wejścia, oblicza warunki, ustawia wyjścia, wywołuje funkcje biblioteki standardowej.
Dla porównania: bez VM Hub musiałby za każdym razem od nowa parsować tekst skryptu, sprawdzać go, zamieniać na działania. Z VM ta praca jest wykonana raz, a uruchomienie skryptu na urządzeniu sprowadza się do odczytywania gotowych instrukcji.
# Kompilator działa nie tylko w Studio
Ważna i często pomijana rzecz: ten sam kompilator Volanga jest zarówno w Voldeno Studio, jak i w samym Hubie.
W typowym przepływie pracy instalator otwiera projekt w Studio na komputerze, edytuje skrypt, kompiluje go lokalnie, sprawdza w symulatorze i wgrywa już skompilowany bytecode do Huba. Tak jest najszybciej i najwygodniej, bo Studio od razu pokazuje błędy składni i ostrzeżenia.
Hub jednak nie wymaga, żeby kod zawsze przyszedł skompilowany. Potrafi też przyjąć sam tekst Volanga i samodzielnie go skompilować przed uruchomieniem. Sprawdzenie składni, sprawdzenie typów, kontrola integralności, generacja bytecode'u - wszystko to potrafi wykonać samodzielnie.
Dla nas, jako twórców systemu, jest to fundament architektoniczny. Studio i Hub używają tego samego kodu kompilatora i tej samej VM. Skrypt skompilowany w jednym miejscu zachowa się identycznie w drugim. Nie ma „dialektu Studio” i „dialektu Huba”. To otwiera w przyszłości drogę do scenariuszy, w których logikę da się aktualizować bez Studio (np. zdalnie z Voldeno Cloud albo programowo z narzędzia integratora). Na dziś podstawową ścieżką pracy jest jednak edycja i kompilacja w Studio.
// Krótki przykład - zobacz wejście, które się zmieniło,
// i odpowiednio przełącz wyjście "relay_1".
channel = input::channel()
value = input::value()
if (channel == "input" and value) {
output::toggle("relay_1")
}
To jest kilka lini logiki, które po kompilacji zamieniają się w kilkadziesiąt bajtów bytecode'u i są wykonywane przez VolangVM przy każdej zmianie wejścia bloku.
# Co realnie daje VolangVM
Sandbox, czyli izolacja. Każdy skrypt działa w odgrodzonym środowisku. Błąd wykonania w jednym bloku (na przykład próba dzielenia przez zero) zatrzymuje tylko ten jeden skrypt, a nie cały Hub. Reszta automatyki dalej działa: światła reagują, ogrzewanie się reguluje, harmonogramy się wykonują.
Wydajność na małym sprzęcie. Bytecode jest mały i szybki do uruchomienia. VM mieści się tam, gdzie pełen interpreter ogólnego języka by się nie zmieścił. To otwiera drogę do uruchamiania logiki nie tylko centralnie na Hubie, ale też na samych modułach rozszerzeń.
Identyczna symulacja w Voldeno Studio. To jedna z najważniejszych konsekwencji architektury VM. Ta sama VolangVM, która działa na Hubie, działa też w Studio na komputerze instalatora. Skrypt można uruchomić „na sucho”, podać mu testowe wejścia i zobaczyć, jak się zachowuje, z gwarancją że na realnym urządzeniu będzie działał tak samo. Nie ma sytuacji „u mnie na komputerze działało, u klienta nie”.
Przenośność. Bytecode nie zależy od tego, jaki dokładnie procesor jest w danym module. Logikę można w przyszłości uruchomić na nowym sprzęcie bez żadnych zmian w kodzie projektu, dopóki działa na nim VolangVM.
Rozproszone wykonanie. Wybrane fragmenty logiki mogą działać blisko sprzętu, bezpośrednio na module rozszerzenia, a nie centralnie na Hubie. To zmniejsza opóźnienia i pozwala krytycznym reakcjom (np. „natychmiast wyłącz styk po przekroczeniu prądu”) działać lokalnie nawet wtedy, kiedy magistrala jest chwilowo zajęta.
Aktualizacje, które łatwo wycofać. Skrypty są wersjonowane w projekcie. Wgranie nowej wersji nie zastępuje firmware'u modułu, tylko zawartość VM. Awaryjny powrót do poprzedniej wersji jest tani i szybki.
# Biblioteka standardowa Volanga, czyli co skrypt dostaje od razu
Sam język programowania to dopiero połowa układanki. Druga połowa to biblioteka standardowa, czyli zestaw gotowych funkcji, które każdy skrypt może zawołać od razu, bez instalowania niczego, bez kopiowania kodu skądinąd, bez ściągania pakietów z internetu.
Każdy poważny język programowania ma swoją bibliotekę standardową. W Pythonie to os, math, datetime, json i setki innych. W JavaScripcie to Math, JSON, Date, fetch. Powód jest prosty: bez biblioteki standardowej każdy programista musiałby od zera pisać podstawy. Mnożenie liczb to składnia języka, ale „pierwiastek kwadratowy”, „aktualna godzina”, „zamień tekst na liczbę” albo „zaokrąglij w górę” to już funkcje. Biblioteka standardowa odpowiada na pytanie „jakie podstawowe rzeczy język już potrafi, gdy go uruchomisz”.
W Volangu biblioteka standardowa dzieli się na kilka grup. Wymieniam je, żeby było widać konkretnie, co skrypt ma do dyspozycji:
- Wejścia i wyjścia bloku (
input::channel,input::value,input::get,output::set,output::toggle, ...). To podstawa, bo cały sens Volanga to reakcja na zdarzenie z wejścia i ustawienie wyjścia. Bez tych funkcji skrypt nie ma jak rozmawiać z resztą instalacji. - Stan i konfiguracja bloku (
state::set,state::get,config::get, ...). Dzięki nim blok może coś zapamiętać między zdarzeniami (np. licznik impulsów) i sięgnąć do parametrów ustawionych w Studio. - Czas i harmonogramy (
time::now, callbacki). Bez tego nie da się napisać reguły „włącz na 5 minut, potem wyłącz”. - Praca z tekstem, liczbami, tablicami i mapami (operacje na stringach, math, array, map). Codzienne narzędzia: zliczyć, posortować, podzielić tekst, sprawdzić zakres.
- JSON i Base64. Standardowe formaty, które są potrzebne za każdym razem, gdy logika ma rozmawiać ze światem zewnętrznym.
- HTTP i operacje sieciowe w wybranych, kontrolowanych formach. To jedyna droga, żeby skrypt sięgnął poza urządzenie. Nie ma tu „zaimportuj sobie cokolwiek z internetu”. Jest „użyj tych konkretnych funkcji, które VM zna i rozumie”.
- Kryptografia i operacje pomocnicze. Podpisy, skróty, kodowanie - tyle, ile potrzeba, żeby zrobić bezpieczną integrację (np. JWT do Google Cloud, jak w naszym demie z Google Cloud Next).
- Operacje asynchroniczne. Pozwalają zaplanować akcję na później bez blokowania wykonania.
W Volangu biblioteka standardowa pełni jeszcze jedną rolę, której nie ma w „dużych” językach: jest również granicą bezpieczeństwa. Skoro nie da się doinstalować pakietu ani zaimportować dowolnego modułu, to wszystko, co skrypt potrafi w stosunku do świata zewnętrznego, musi być w stdlib. Każda funkcja, która tam jest, została świadomie zaprojektowana z myślą o tym, że będzie wywoływana z sandboxa. Nic nie wycieknie „bocznymi drzwiami”, bo bocznych drzwi po prostu nie ma.
To zmienia sposób myślenia. Dużego języka uczysz się przez „znajdę bibliotekę, która to robi”. Volanga uczysz się przez „sprawdzę, jaka funkcja w stdlib to robi”. Lista jest skończona, ale za to wszystko, co tam jest, jest stabilne, szybkie i bezpieczne.
Pełną listę funkcji opisuje Biblioteka standardowa Volanga.
# Granice Volanga, czyli czego nie zrobi
Volang jest celowo wąski. Wymienienie tego, czego w nim nie ma, jest tu uczciwsze niż listowanie wszystkiego, co potrafi.
- Brak swobodnego dostępu do internetu. Komunikacja sieciowa odbywa się przez konkretne, przygotowane funkcje biblioteki standardowej (np. wywołania HTTP, klient MQTT, publikacja do Google Pub/Sub). Nie ma czegoś takiego jak
import requestsw środku skryptu. - Brak instalowania zewnętrznych bibliotek. Cała funkcjonalność, jaką możesz wywołać, pochodzi z biblioteki standardowej Volanga albo z bloków logicznych dostarczanych przez Voldeno.
- Brak dostępu do plików. Skrypt nie czyta i nie zapisuje plików na dysku Huba. Stan trzymany jest w zmiennych bloku, w jego wejściach i wyjściach.
- Brak operacji systemowych. Nie da się wywołać programu zewnętrznego, otworzyć socketa „ręcznie”, uruchomić kontenera. To jest celowe i bezpieczeństwo systemu na tym stoi.
- Wąska składnia. Jest tylko pętla
while, nie mafor. Zmienne globalne nie są widoczne wewnątrz funkcji, argumenty trzeba przekazać jawnie. Lista słów kluczowych jest krótka.
Dla kogoś, kto programuje zawodowo, lista ograniczeń jest długa. To jest zgodne z planem. Volang nie ma być „kolejnym językiem ogólnego przeznaczenia”. Ma być najkrótszą drogą od pomysłu na automatyzację do działającego, bezpiecznego i przewidywalnego skryptu wewnątrz Huba.
# Niebezpieczeństwa, o których trzeba wiedzieć
Pisanie własnej logiki to władza, a władza ma konsekwencje. Kilka rzeczy wartych uwagi.
Złe sterowanie sprzętem. Skrypt, który włącza i wyłącza grzałkę kotła co 200 ms, zniszczy stycznik. Skrypt, który próbuje jednocześnie otworzyć i zamknąć rolety, zatrzyma napęd na ograniczniku termicznym. VolangVM nie wie, co fizycznie jest podpięte do styku po drugiej stronie. To leży po stronie projektanta logiki.
Pętle, które się nie kończą. Pętla while z warunkiem, który nigdy nie staje się fałszywy, zablokuje wykonanie skryptu w bloku. Skoro skrypt nie kończy się normalnie, to nie odpowie też na kolejne zdarzenia z wejść i blok przestaje działać. Dlatego pisząc while, warto zadbać o warunek oparty na liczniku albo wartości wejścia, a w środku zawsze umieścić coś, co ten warunek zmienia. Ewentualnie wstawić break powiązany z warunkiem awaryjnym. Volang celowo nie ma pętli for, więc odpowiedzialność za to, żeby pętla się skończyła, leży po stronie autora skryptu.
Logika, która ma sens „na sucho”, a nie z fizyką. Symulacja w Studio sprawdza działanie skryptu, ale nie sprawdza, czy w salonie naprawdę można otworzyć rolety, kiedy o 14:00 świeci pełne słońce, a okno jest uchylone na rozszczelnienie i może zaczepić się o napęd. Realnego testu na działającej instalacji nie da się ominąć.
Złożoność, która rośnie szybciej niż dokumentacja. Łatwo dodać kolejny warunek, kolejną zmienną, kolejną zależność. Trudniej wrócić do tego pliku za rok i zrozumieć, dlaczego tak. Zalecamy dzielenie logiki na małe bloki i komentarze w miejscach, w których powód decyzji nie jest oczywisty z samego kodu.
Aktualizacje, które niezauważalnie zmieniają zachowanie. Volang jako język i biblioteka standardowa są stabilne, ale projekt automatyzacji ewoluuje. Każda zmiana w skrypcie powinna przejść przez Studio z symulacją, zanim trafi do żywego Huba.
Te ryzyka są realne, ale są też normalne dla każdego programowalnego systemu automatyki. Dlatego trzymamy się trzech zasad: skrypt działa w sandboxie, ten sam skrypt można uruchomić w Studio przed wgraniem na Hub, a poprzednia wersja zawsze daje się przywrócić.
# Bloki logiczne, czyli warstwa nad Volangiem
Praktyczna uwaga na koniec. Większość użytkowników i instalatorów nigdy nie napisze ani jednej linii Volanga. Dla nich logika powstaje z gotowych bloków logicznych (przełączniki, regulatory klimatu, harmonogramy, sceny). Studio łączy je wizualnie, a Hub wykonuje. Każdy z tych bloków pod spodem to skompilowany Volang, ale od strony użytkownika wygląda jak klocek z portami wejściowymi i wyjściowymi.
Volang wchodzi do gry wtedy, gdy:
- chcesz dodać własny blok robiący coś, czego nie ma w bibliotece,
- robisz integrację z systemem zewnętrznym,
- piszesz nietypową automatyzację dla konkretnego klienta,
- albo jako instalator/integrator utrzymujesz dla wielu instalacji własny zestaw „swoich” bloków.
Wtedy pisanie w Volangu jest po prostu szybsze i wytrzymalsze w czasie niż próba sklejania wielu wbudowanych bloków logicznych obok siebie.
# Co dalej
- Pełna dokumentacja języka: Volang.
- Funkcje i moduły dostępne w skryptach: Biblioteka standardowa Volang.
- Voldeno Studio jest do pobrania za darmo, razem z narzędziami do edycji bloków, kompilacji i symulacji. Zacznij na stronie pobierania.
- Chcesz pisać własne bloki dla swoich klientów albo zbudować integrację z konkretnym systemem zewnętrznym? Skontaktuj się z nami. Dla integratorów i partnerów: Strefa profesjonalistów.
