NetDoc.pl — monitoring i utrzymanie sieci. Piszemy o sprzęcie, który sami uruchamiamy i mierzymy.

Energetyka i automatyka budynku

Node-RED na Venus OS (Cerbo GX): edytor pod HTTPS na porcie 1881

Node-RED na Venus OS: edytor stoi pod HTTPS na porcie 1881, nie 1880. Przełączenie ustawienia nie startuje usługi, a pamięć zmiennych przeżywa wgranie nowej wersji.

Ustawienie przestawione, a usługi dalej nie ma

Przestawiasz Settings/Services/NodeRed z 1 na 2, odświeżasz http://<adres-cerbo>:1880/ i nic się nie dzieje. Nic nie jest zepsute. Przełączenie ustawienia nie uruchamia usługi od razu, a adres, pod którym ona się w końcu pojawi, jest inny: HTTPS i port 1881. Poniżej cała droga — od włączenia usługi, przez sprawdzenie, czy zastany flow w ogóle czymkolwiek steruje, po trzymanie flow w pliku poza urządzeniem.

Wszystko tutaj jest zmierzone na jednej pracującej instalacji, nie przepisane z dokumentacji. Rozpoznanie Node-RED: 30.08.2026, Venus OS v3.79 na Cerbo GX, zastana wersja Node-RED 4.1.1. Instalacja to ESS 3-fazowy (hub4). Pracują w niej: MultiPlus VE.Bus 3-fazowy (vebus), bateria Pylontech BMS-Can 222 Ah = 3 × US3000C (battery/512) i MPPT BlueSolar VE.Can 250/70. Na przyłączu stoi licznik sieci Carlo Gavazzi EM24 (grid/40), a Fronius Symo 3.7-3-S Cerbo widzi jako pvinverter.

Praktyczny przykład sterowania to pętla „nie oddawaj w nocy”, trzymająca saldo licznika przy zerze. Opisaliśmy ją w osobnym artykule tej serii. Tutaj chodzi o samo środowisko: usługę, edytor, flow i sposób jego wgrywania.

Obraz Large Venus OS jest wymagany. W obrazie zwykłym tej usługi po prostu nie ma i żadne ustawienie jej nie doda.

Włączenie usługi: ustawienie to jeszcze nie start

Ustawienie Settings/Services/NodeRed ma trzy wartości. To zapis w konfiguracji, a nie polecenie „uruchom teraz”.

wartośćtrybco to znaczy w praktyce
0wyłączonyusługa nie startuje
1securedwymaga hasła; zastaliśmy tę wartość bez ustawionego hasła — usługa nie odpowiadała pod żadnym portem
2no-authedytor bez logowania: kto ma dostęp do sieci, ten ma dostęp do sterowania

Zmiana wartości nie uruchamia usługi od razu. Sprawdziliśmy na tym urządzeniu i przejście 1 → 2, i objazd 2 → 0 → 2. Za każdym razem to samo: ustawienie się zapisuje, a usługa nadal nie odpowiada. Potrzebny jest restart całego GX. A ponieważ SSH na tym urządzeniu jest zamknięty, nie ma jak zrestartować samej usługi ani zajrzeć w jej log.

  1. Sprawdź obraz. Bez obrazu Large dalsze kroki nie mają sensu.
  2. Ustaw wartość. Zrobisz to z Remote Console albo zapisem po MQTT: W/<portalId>/settings/0/Settings/Services/NodeRed {"value":2}. Wartość 1 wybieraj tylko wtedy, gdy jednocześnie ustawiasz hasło.
  3. Zrestartuj GX. Zrób to z Remote Console albo zapisem W/<portalId>/platform/0/Device/Reboot {"value":1}. Nie pomyl tego z platform/0/Firmware/Backup/Activate — to przełącza wersję firmware pracującej instalacji.
  4. Wejdź pod właściwy adres: https://<adres-cerbo>:1881/.
  5. Potwierdź tryb, nie samą odpowiedź. Po czym go poznasz, piszemy niżej.

Jak rozpoznać, że usługa naprawdę wstała

Nie po tym, że ustawienie ma wartość 2 — ta wartość mówi tylko, co ma się stać przy najbliższym starcie. Rozstrzyga to, co urządzenie odpowie pod HTTPS na porcie 1881.

  • Wyszło: pod https://<adres-cerbo>:1881/ otwiera się edytor, a GET /flows z nagłówkiem Node-RED-API-Version: v2 zwraca odpowiedź z polem rev. Sama strona edytora wygląda podobnie przy kilku różnych awariach. Dopiero rev dowodzi, że działa ta część, przez którą wgrywa się flow, a nie sam serwer WWW.
  • Tryb się zgadza: adres /settings pokazuje użytkownika anonimowego z uprawnieniami *. Tak wygląda tryb no-auth i tak go potwierdzasz.
  • Nie wyszło: pod portem 1881 nie odpowiada nic, choć ustawienie ma wartość 2. Usługa nie wstała — wracasz do restartu GX.
  • Wynik mylący: ostrzeżenie przeglądarki o certyfikacie albo zerwane połączenie w curl bez -k. Jedno i drugie mówi o Twoim narzędziu — usługa właśnie odpowiedziała.

Zmierzone: tryb anonimowy przeżył restart — wartość 2 utrzymała się, a usługa wstała sama, bez ponownego dotykania ustawienia.

Edytor stoi pod HTTPS na porcie 1881, nie pod 1880

Zdanie „Node-RED nie działa” w większości przypadków znaczy „pukam pod zły adres”. Domyślny port Node-RED z dokumentacji projektu to 1880 po HTTP. Na Venus OS jest inaczej.

adresco dostajesz
http://<adres-cerbo>:1880/nic — pod tym portem usługa nie stoi
https://<adres-cerbo>:1881/edytor
https://<adres-cerbo>:1881/flowspełny eksport flow, razem z polem rev

Dwie rzeczy udają tu awarię usługi. Pierwsza: certyfikat, którego przeglądarka nie zna. Dostajesz ostrzeżenie bezpieczeństwa zamiast strony, a w skrypcie curl bez -k przerwie połączenie. Druga: protokółhttp:// pod portem 1881 nie zwróci edytora. W obu przypadkach komunikat mówi coś o Twoim narzędziu, a nie o stanie usługi.

Jeśli pilnujesz tego monitoringiem, sprawdzaj treść odpowiedzi, a nie samo „port otwarty”. Otwarty port 1881 zobaczysz również wtedy, gdy Node-RED wstał, ale wszystkie flow są wyłączone — a to zupełnie inny stan instalacji.

Flow, który wygląda na automatykę, a nią nie jest

Na tym urządzeniu zastaliśmy jeden flow („Flow 1”) z 26 klockami — tak nazywamy dalej pojedyncze prostokąty, które widzisz w oknie edytora. Były wśród nich klocki wyjściowe, czyli takie, które zapisują wartości po dbus. Wyglądało to na działające sterowanie ESS i przez jakiś czas trzymaliśmy się zasady „nie dotykać, tam działa automatyka”. Eksport JSON sprawdzony 30.08.2026 pokazał co innego. Klocki leżały obok siebie, ale nikt ich ze sobą nie połączył: pole wires, w którym flow trzyma połączenia, było puste przy wszystkich poza jednym. To jedno połączenie prowadziło do klocka, z którego już nic dalej nie wychodziło. Żaden odczyt nie docierał do żadnego wyjścia. To była tablica z podglądem, a nie sterowanie.

Po lewej klocki, których nikt nie połączył: żaden odczyt nie dociera do wyjścia. Po prawej ten sam zestaw klocków połączony w łańcuch: odczyt, decyzja, zapis. tablica z podglądemsterowanie nic nie dochodzi do wyjściaodczyt, decyzja, zapis po dbus odczytodczytodczyt donikądwyjście odczytodczytodczyt decyzjawyjście

Liczy się to, co jest połączone, a nie to, ile jest klocków. W zastanym flow były klocki typu victron-output-vebus, victron-output-relay czy victron-output-custom — czyli dokładnie to, co na pierwszy rzut oka mówi „ten flow steruje instalacją”. Puste wires przy samym klocku wyjściowym są zresztą normalne: z wyjścia nic dalej nie wychodzi i wychodzić nie musi. Pytanie brzmi odwrotnie: czy cokolwiek do niego dochodzi.

Jak to sprawdzić, zanim cokolwiek skasujesz

Nie w edytorze. Zwinięty flow z kilkudziesięcioma klockami wygląda na oko jak działający. Pobierz eksport i policz połączenia:

curl -sk -H "Node-RED-API-Version: v2" https://<adres-cerbo>:1881/flows > flows.json

python3 - <<"PY"
import json
d = json.load(open("flows.json"))
f = d["flows"] if isinstance(d, dict) else d      # z urzadzenia: obiekt; z edytora: lista
k = [(w.get("type"), n) for w in f for wy in (w.get("wires") or []) for n in wy]
print("klockow:", len(f), " polaczen:", len(k))
for x in k: print(x)
PY
  1. Ile jest połączeń. Zero albo pojedyncza sztuka przy kilkudziesięciu klockach to sygnał, że patrzysz na tablicę z podglądem.
  2. Dokąd prowadzą. Sprawdź, na czym kończy się każde połączenie. Jeśli kończy się na klocku, który nie jest wyjściem i z którego nic dalej nie wychodzi, to droga donikąd. Dokładnie tak było u nas.
  3. Czy dojdziesz do wyjścia. Zacznij od klocków, które czytają dane, i idź dalej po wires. Dopiero gdy dojdziesz do klocka typu victron-output-…, możesz powiedzieć „to steruje”.
  4. Zapisz stan. Zrób to, zanim usuniesz zastany flow. Surowa odpowiedź GET /flows razem z polem rev ląduje w pliku. To jedyna kopia tego, co zastałeś, i jedyny sposób, żeby wrócić, gdyby się okazało, że flow jednak coś robił. My skasowaliśmy zastany flow dopiero po zrzucie do pliku i po decyzji właściciela instalacji.

Wynik rozstrzygający: po wires da się przejść od klocka, który czyta dane, do klocka victron-output-…. Jeśli takiej drogi nie ma, masz tablicę z podglądem — i dokładnie tak wyszło u nas.

Program pamięta stare zmienne po wgraniu nowej wersji

Wgrywasz nową wersję flow. Dokładasz w funkcji jedną nową zmienną i przy najbliższym cyklu funkcja się wywala — chociaż akurat w tym miejscu „nic nie zmienialiśmy”. Powód: funkcja odkłada swoje zmienne w osobnej pamięci, którą Node-RED nazywa kontekstem. Wgranie nowej wersji (deploy) tej pamięci nie czyści. Zostają w niej wartości z poprzedniej wersji flow.

Nowej zmiennej w tej starej pamięci po prostu nie ma. Jej brak ma w programie postać undefined, a nie null — i to jest różnica, która tu decyduje. null znaczy „ktoś tu wpisał: nic nie ma”, a undefined znaczy „takiego pola nigdy tu nie było”. Kod, który sięga do środka czegoś, co jest undefined, przerywa funkcję błędem. Funkcja nie dochodzi do zapisu. Efekt jest cichy: sterowanie przestaje aktualizować to, co aktualizowało, a w edytorze wszystko wygląda na poprawnie wgrane.

Obrona pierwsza: sam dopisz brakujące wartości

Odruch flow.get("ess") || {} tu nie pomaga. Obiekt istnieje i wygląda poprawnie — brakuje w nim tylko jednego pola. Weź komplet nazw, których funkcja potrzebuje, i dopisz każdą, której w pamięci nie ma. Potem przepuść każdą wartość przez funkcję, która sprowadza brak do jednej postaci:

const KLUCZE = ["siec", "setp", "tryb", "trybPoprz", "cel"];
const S = Object.assign({}, flow.get("ess") || {});
for (let i = 0; i < KLUCZE.length; i++) {
    if (S[KLUCZE[i]] === undefined) { S[KLUCZE[i]] = null; }
}
const liczba = (x) => (typeof x === "number" && isFinite(x)) ? x : null;

Jest tu drugie dno. Takie dopisywanie zrównuje undefined i null wszędzie tam, gdzie wartość przechodzi przez liczba(). Ale w kodzie zostaje czasem porównanie, które te dwa stany odróżnia — u nas było to sprawdzenie „czy tryb zmienił się od poprzedniego cyklu”. W takim miejscu ustaw wartość jawnie, tuż przed użyciem:

const trybPoprz = (S.trybPoprz === "noc" || S.trybPoprz === "dzien") ? S.trybPoprz : null;

Bez tej linijki pierwszy cykl po wdrożeniu udaje zmianę stanu, bo undefined !== null jest prawdą. Logika kasuje wtedy korektę, którą właśnie miała utrzymać.

Obrona druga: wyczyść tę pamięć przy każdym wgraniu

Klocek z kodem (w Node-RED nazywa się function) ma zakładkę initialize. Kod wpisany w tej zakładce wykonuje się przy starcie flow, czyli także po każdym wgraniu nowej wersji. Wyzerowanie stanu w tym miejscu (flow.set("ess", null)) sprawia, że każdy start zaczyna się od tego samego. Te dwie obrony nie są alternatywą. Zakładka initialize załatwia wgranie nowej wersji. Dopisywanie brakujących wartości załatwia całą resztę — łącznie z pierwszym cyklem, w którym część odczytów jeszcze nie przyszła.

Ta sama cecha po raz trzeci: ta pamięć nigdy nie wygasa

Kontekst trzyma ostatnią wartość bez końca. Gdy źródło danych ucichnie, zmienna nie robi się null. Zostaje w niej stara liczba, a wygląda dokładnie tak samo jak świeży odczyt. Logika decyduje wtedy na podstawie stanu sprzed godziny i w ogóle o tym nie wie. Obrona: licz wiek odczytu w cyklach zegara. Zeruj licznik przy każdej nowej próbce, podnoś przy każdym tyknięciu, a powyżej progu traktuj wartość jak brak danych.

Pusta lista błędów to nie dowód, że działa. Sprawdź, czy w wystawianym stanie w ogóle istnieje pole, które funkcja zapisuje na samym końcu. Jeśli go nie ma, funkcja przerwała się błędem przed zapisem. Żadna lista błędów tego nie pokaże, dopóki sam nie przechwycisz tego błędu.

Jak zobaczyć błąd bez dostępu do logów

Przy zamkniętym SSH log Node-RED jest poza zasięgiem. Zamiast niego zbuduj sobie własne okno na błędy. Klocek catch odkłada je do kontekstu, a osobny adres HTTP (http infunctionhttp response) oddaje stan i te błędy jako JSON. Kilka klocków roboty, a to jedyne okno na błędy po wgraniu nowej wersji. Kolorowy status pod klockiem z kodem widzi tylko ten, kto akurat siedzi w edytorze.

const b = flow.get("bledy") || [];
b.unshift({ blad:  msg.error && msg.error.message,
            wezel: msg.error && msg.error.source && msg.error.source.name });
flow.set("bledy", b);   // przytnij do kilku ostatnich
return null;

Flow ma mieszkać w pliku u Ciebie, a nie w edytorze

Flow wyklikany w edytorze i tam zostawiony ma jedną jedyną kopię — w urządzeniu, które właśnie zmieniasz. Nie porównasz go z wczorajszą wersją, nie ma daty, nie ma odpowiedzi na pytanie „co dokładnie było wczoraj”. Przy sterowaniu, które pisze po dbus pracującej instalacji, to jest zbyt mało.

Nasz układ jest odwrotny: prawdziwą wersją jest plik w repozytorium — w katalogu z historią zmian, trzymanym poza urządzeniem. Skrypt składa z niego gotowy JSON flow; progi i komentarze siedzą w stałych na górze kodu funkcji. Wgranie idzie przez POST /flows. Gotowego JSON-a nie poprawiaj już ręcznie — inaczej znów masz dwa źródła i żadne nie jest prawdziwe.

# 1. zbuduj JSON ze zrodla w repozytorium
python3 build_flow.py                      # daje flow_nowy.json

# 2. wez aktualne rev prosto z urzadzenia, tuz przed wyslaniem
REV=$(curl -sk -H "Node-RED-API-Version: v2" https://<adres-cerbo>:1881/flows \
      | python3 -c "import json,sys; print(json.load(sys.stdin)['rev'])")

# 3. wgraj CALA tablice
python3 -c "import json; json.dump({'flows': json.load(open('flow_nowy.json')), \
      'rev': '$REV'}, open('deploy.json','w'))"
curl -sk -X POST https://<adres-cerbo>:1881/flows \
  -H "Content-Type: application/json" -H "Node-RED-API-Version: v2" \
  -H "Node-RED-Deployment-Type: full" --data-binary @deploy.json

# 4. sprawdz wlasnym adresem diagnostycznym
curl -sk https://<adres-cerbo>:1881/diag-ess

Pole rev opisuje wersję, którą przed chwilą widziałeś. Dlatego czytasz je bezpośrednio przed wysłaniem, a nie raz na początku sesji. Krok 4 nie jest ozdobą. Przy zamkniętym SSH to jedyne okno, w którym zobaczysz, czy nowa wersja naprawdę się wykonuje. Bez niego wiesz tylko tyle, że urządzenie ją przyjęło.

POST /flows wysyła stan docelowy, a nie samą poprawkę. To, co wyślesz, zastępuje na urządzeniu wszystko. Jeśli ktoś obok ma tam swój flow, a Ty wysyłasz tylko swój, jego flow zniknie. Dlatego krok 1 buduje komplet, a nie fragment.

Po czym poznasz, że nowa wersja naprawdę pracuje

Odpowiedź bez błędu na POST /flows znaczy tyle, że urządzenie przyjęło plik. Czy flow pracuje, rozstrzygają trzy rzeczy — wszystkie widać pod własnym adresem diagnostycznym z kroku 4:

  • Pole końcowe jest. W wystawianym stanie istnieje pole, które funkcja zapisuje na samym końcu cyklu. Jeśli go nie ma, funkcja przerwała się błędem przed zapisem — a edytor i tak pokazuje wgraną wersję.
  • Lista błędów pusta — ale liczy się to razem z punktem wyżej. Sama pusta lista nie mówi nic, dopóki nie wiesz, czy funkcja doszła do końca.
  • Wartości się zmieniają z cyklu na cykl. Ta sama liczba przy każdym odpytaniu znaczy, że czytasz starą wartość z kontekstu, a nie świeży odczyt.

Ostatecznie rozstrzyga licznik energii, a nie średnie saldo mocy: „wygląda dobrze na wykresie” to nie to samo co „nie oddaliśmy ani jednej kilowatogodziny”. Tak sprawdziliśmy pętlę nocną — opisaliśmy to w osobnym artykule.

Co daje kopia sprzed zmiany

Przed każdym wdrożeniem zrzut GET /flows ląduje w repozytorium jako plik, razem z polem rev. Kluczowe: ma to być surowa odpowiedź urządzenia, a nie JSON złożony jeszcze raz przez skrypt. Powrót ma przywrócić to, co działało — a nie to, co według naszej pamięci powinno działać. Cofnięcie wygląda wtedy identycznie jak wdrożenie: ten sam POST, tylko plik inny.

Druga rzecz, którą daje trzymanie źródła poza urządzeniem: widać, kiedy repozytorium i urządzenie się rozjeżdżają. U nas repozytorium zawierało kiedyś wersję napisaną i przetestowaną offline, świadomie niewdrożoną. Okno pomiarowe, w którym jej zmiana byłaby widoczna, zdążyło się tego dnia zamknąć. Stąd zasada: przed analizą stanu porównuj z urządzeniem — z odpowiedzi GET /flows, a nie z repozytorium. Notatka „repozytorium zgadza się z urządzeniem” traci ważność przy najbliższym wdrożeniu; u nas wygasła dwa razy tego samego dnia. Drobiazg z tej samej półki: przy zapisie JSON-a wymuś końce linii LF. Inaczej ten sam flow zbudowany na Windows i na Linuksie wygląda przy porównaniu na zmieniony w całości, a prawdziwy rozjazd ginie w tym szumie.

Pomyłka w flow zmienia instalację, a nie wykres

Moduł @victronenergy/node-red-contrib-victron nie jest wtyczką do rysowania. Czyta i zapisuje po całym dbus: setpoint sieci, limity, przekaźniki, ustawienia usług. Pomyłka w flow zmienia zachowanie instalacji, a nie wygląd wykresu.

Klocek wyjściowy przypina się do konkretnej ścieżki, na przykład com.victronenergy.settings plus /Settings/CGwacs/AcPowerSetPoint. To jest ta sama wartość, którą właściciel widzi w menu ESS. Flow nadpisuje ją bez pytania i nigdzie na ekranie tego nie widać. Trzy rzeczy ustaw raz a dobrze:

  • Zapisuj tylko wtedy, gdy naprawdę jest rozjazd. Porównaj wartość odczytaną z urządzenia z tą, którą chcesz ustawić, i zapisuj wyłącznie wtedy, gdy się różnią. Mniej ruchu na dbus, a flow sam wraca do swojego celu, gdyby ktoś zmienił ustawienie ręcznie.
  • Brak odczytu = nie ruszaj niczego. Sterowanie na podstawie brakującej próbki jest sterowaniem na podstawie zera, którego nikt nie zmierzył.
  • Uważaj na numer, pod którym siedzi urządzenie. Każde urządzenie ma na dbus swój numer — w battery/512 jest to właśnie 512. Ten numer potrafi się zmienić, gdy przepinasz sprzęt. Flow przypięty na sztywno do battery/512 może wtedy czytać nie to, co myślisz. Tam, gdzie tę samą wielkość podaje wspólne podsumowanie systemu com.victronenergy.system/0, czytaj stamtąd.

Zatrzymany Node-RED nie cofa tego, co zapisał. Ostatnia zapisana wartość zostaje na kontrolerze aż do ręcznej zmiany albo ponownego startu flow. Wyłączenie flow nie jest powrotem do stanu sprzed — jest zamrożeniem ostatniej korekty. Sprawdzaj to po każdym restarcie GX i po każdym zatrzymaniu usługi.

Wartość 2 to edytor bez logowania: każdy, kto ma dostęp do tej sieci, może zmienić sterowanie ESS. Nie jest to wyjątek na tle reszty. Na tej instalacji Remote Console działa bez hasła, Modbus TCP przyjmuje zapisy bez logowania, MQTT wpuszcza anonimowych klientów, a tunel VRM jest włączony w obie strony. To są ustawienia domyślne Venus OS i typowej instalacji, a nie czyjeś zaniedbanie: producent projektował je pod sieć, w której właściciel jest jedynym użytkownikiem. Rozróżnienie jest za to twarde. W sieci pokazowej albo we własnym domu to jest akceptowalne ryzyko, świadomie przyjęte. U klienta odpowiedź jest inna. W budynku, do którego sieci wchodzi więcej niż jedna firma, włącz tryb secured z hasłem i ogranicz dostęp do usługi po sieci. O tunelu do chmury niech zdecyduje właściciel. Nasza własna zasada pracy: monitoring wyłącznie do odczytu, żadnego zapisu po Modbus, a jedyny zapis idzie przez jeden autoryzowany flow Node-RED.

Czego nie sprawdziliśmy

Wszystko powyżej pochodzi z jednego urządzenia i jednej pary wersji: Venus OS v3.79, Node-RED 4.1.1, rozpoznanie 30.08.2026. To, czego nie zmierzyliśmy, wypisujemy zamiast zgadywać.

  • Restart samej usługi bez restartu GX — nie wiemy. SSH był zamknięty, nie było czym spróbować; restart GX zadziałał i na tym poprzestaliśmy.
  • Tryb secured (1) z hasłem — nie testowany. Zastaliśmy wartość 1 bez hasła i przeszliśmy na 2.
  • Nieaktualny rev przy POST /flows — nie sprawdzane, bo rev czytamy zawsze bezpośrednio przed wysłaniem.
  • Zainstalowany moduł victron-vrm-api — nie używamy go i nie badaliśmy.
  • Inne wersje — nie wiemy, czy port, protokół i zachowanie ustawienia są takie same na innych wydaniach Venus OS. Jeśli u Ciebie 1881 nie odpowiada, rozstrzygnij to odpowiedzią urządzenia, a nie założeniem.

Metoda jest ważniejsza niż lista: sprawdzaj stan, nie ciszę. Zapisane ustawienie nie znaczy uruchomiona usługa. Otwarty port nie znaczy działający edytor. Obecność klocków wyjściowych nie znaczy sterowanie, a pusta lista błędów nie znaczy, że funkcja doszła do końca. Każdą z tych czterech rzeczy rozstrzygniesz jednym zapytaniem — pod warunkiem że zadasz je urządzeniu, a nie własnym notatkom.