Zdalne CLU#

vCLU może sterować obiektami na zdalnych CLU (fizycznych kontrolerach lub innych instancjach vCLU) tak, jakby były lokalne. Komunikacja odbywa się przez szyfrowane pakiety UDP.

Architektura#

sequenceDiagram
    participant local as vCLU (lokalne)
    participant remote as CLU (zdalne)

    Note over local,remote: UDP (AES-128 CBC) port 1234
    local->>remote: req:IP:SESSION:script<br/>dom.LAMPA:on()
    remote-->>local: resp:IP:SESSION:result

    Note over local,remote: port 4344
    remote->>local: clientReport:SID:{values}<br/>push changes
    Note left of local: registry update

Dwa tryby komunikacji:

TrybKierunekOpis
Komendylokalne -> zdalneWysyłanie skryptów Lua, oczekiwanie na wynik
Push reportszdalne -> lokalnePowiadomienia o zmianach stanów (subskrypcje)

Import pliku om.lua#

Aby sterować zdalnym CLU, trzeba zaimportować jego plik om.lua. Plik ten zawiera definicje wszystkich obiektów (moduły, I/O, nazwy).

Skąd wziąć plik om.lua#

  1. Z Object Managera - eksport diagnostyczny (ZIP z plikami om.lua każdego CLU)
  2. Z fizycznego CLU - plik przesyłany przez TFTP podczas konfiguracji OM
  3. Z innego vCLU - plik om.lua z katalogu danych

Upload przez interfejs webowy#

Strona OM Files (/om-files) pozwala zaimportować pliki:

  1. Wejdź na /om-files
  2. Kliknij “Upload” i wybierz plik .lua lub .zip
  3. vCLU automatycznie parsuje plik i generuje proxy

Obsługiwane formaty:

  • Plik .lua - pojedynczy plik om.lua jednego CLU
  • Plik .zip - paczka diagnostyczna OM (zawiera pliki om.lua wielu CLU)

Co się dzieje po imporcie#

graph TD
    upload["Upload om_221000290.lua"]
    parser["Parser<br/><i>Wyodrębnia: serial, IP, moduły,<br/>obiekty, nazwy</i>"]
    gen["Generator proxy<br/><i>Tworzy proxy_221000290.lua<br/>(CLU_REMOTE + RemoteSwitch/...)</i>"]
    load["Lua runtime / LoadProxyFiles<br/><i>Ładuje proxy → obiekty w _registry<br/>dom.LAMPA = RemoteSwitch:new(...)</i>"]

    upload --> parser --> gen --> load

Pliki zapisywane na dysku:

imports/
  om_221000290.lua       ← oryginalny plik OM
  proxy_221000290.lua    ← wygenerowany proxy
  proxy_local.lua        ← rejestracja lokalnych obiektów

Format pliku om.lua#

Plik om.lua generowany przez Object Manager zawiera:

-- FwType 00000003
-- FwVersion 000001f6
CLU221000290 = OBJECT:new(0, 0xC0A80003)
-- NAME_CLU DOM20=CLU221000290

-- Moduly fizyczne
mm_330000027 = OBJECT:new(2, 0x13ab669b, 0x1e, "13ab669b")

-- Obiekty I/O
DOU5048 = OBJECT:new(4, mm_330000027, 0)
-- NAME_IO LAMPA_SALON=DOU5048

DIN1473 = OBJECT:new(3, mm_330000028, 1)
-- NAME_IO CZUJNIK_RUCHU=DIN1473

-- Event handlery
DIN1473:add_event(2, EventsFor_DIN1473_2)

Wygenerowany proxy#

Z powyższego pliku OM, generator tworzy:

-- Proxy generated from CLU221000290 (DOM20)
-- IP: 192.168.0.3

-- Referencja do zdalnego CLU
CLU221000290 = CLU_REMOTE:new(221000290, 0xC0A80003, system:getEventBus())
local _clu = CLU221000290

-- Namespace
dom20 = {}

-- DOUT (1 objects)
dom20.LAMPA_SALON = RemoteSwitch:new(_clu, "DOU5048", "LAMPA_SALON")

-- DIN (1 objects)
dom20.CZUJNIK_RUCHU = RemoteSensor:new(_clu, "DIN1473", "CZUJNIK_RUCHU")

-- Rejestracja w _registry
registerObject("CLU221000290.DOU5048", dom20.LAMPA_SALON)
registerObject("CLU221000290.DIN1473", dom20.CZUJNIK_RUCHU)

Obiekty proxy#

Po imporcie om.lua, obiekty zdalne dostępne są jako zmienne globalne w user.lua:

-- Przez namespace (nazwa CLU)
dom20.LAMPA_SALON:execute(DOUT.METHOD_SWITCH_ON)
dom20.CZUJNIK_RUCHU:get(DIN.FEATURE_VALUE)

-- Przez registry (sciezka CLU.ObjID)
_:get("CLU221000290.DOU5048"):execute(DOUT.METHOD_SWITCH_ON)

Typy obiektów proxy#

Klasa OMProxyOpis
DOUTRemoteSwitchPrzełącznik (on/off/toggle)
DINRemoteSensorCzujnik binarny (odczyt)
DIMMERRemoteLightŚciemniacz (jasność)
LEDRGBRemoteRGBLightLED RGB
ROLLER_SH, ROLLERRemoteCoverRoleta
AnalogINRemoteSensorWejście analogowe (odczyt)
AnalogOUTRemoteNumberWyjście analogowe (zapis)

RemoteObject - bazowa klasa#

Wszystkie obiekty proxy dziedziczą z RemoteObject. Każde wywołanie get(), set(), execute() jest przekazywane do zdalnego CLU:

-- Te wywolania:
dom20.LAMPA:get(0)
dom20.LAMPA:set(0, 1)
dom20.LAMPA:execute(1)

-- Generuja skrypty Lua wysylane na zdalne CLU:
-- "DOU5048:get(0)"
-- "DOU5048:set(0, 1)"
-- "DOU5048:execute(1)"

Dodatkowe metody:

-- Tagi
dom20.LAMPA:addTag("lights", "salon")
dom20.LAMPA:hasTag("lights")  -- true
dom20.LAMPA:getTags()          -- {"lights", "salon"}

-- Zdarzenia (lokalne)
dom20.LAMPA:on("OnChange", function(newState, oldState)
    log.info("LAMPA zmienila stan: %s -> %s", oldState, newState)
end)

dom20.LAMPA:onChange(function(newState, oldState)
    -- skrot dla on("OnChange", ...)
end)

-- Informacje
dom20.LAMPA:getInfo()   -- {id, name, type, tags, methods}
dom20.LAMPA:toString()  -- sformatowany string do debugowania

Protokół komunikacji#

Komendy (request/response)#

Format pakietów UDP (port 1234):

Request:  req:<IP>:<SESSION_HEX>:<LUA_SCRIPT>\r\n
Response: resp:<IP>:<SESSION_HEX>:<RESULT>

Przykład:

→ req:192.168.0.1:0000A1B2:DOU5048:get(0)\r\n
← resp:192.168.0.3:0000A1B2:1

Parametry:

ParametrWartość
SzyfrowanieAES-128 CBC (klucz projektu + IV)
Port1234 (CommandPort)
Timeout5 sekund
SesjaLosowy uint32 per request

Subskrypcje (push notifications)#

Zdalne CLU może powiadamiać o zmianach stanów obiektów. Używane przez RemoteSubscription do aktualizacji rejestru bez odpytywania.

-- Auto-subscribe odbywa sie przy starcie vCLU
-- Skanuje _registry, grupuje obiekty po IP, tworzy subskrypcje

Protokół:

→ SYSTEM:clientRegister('192.168.0.1', 4344, 123, {{DOU5048, 0}})
← clientReport:123:{1}
ParametrWartość
Port nasłuchiwania4344
Max obiektów per subskrypcja4
TTL subskrypcji60 sekund (auto-refresh)

Większe zestawy obiektów są automatycznie dzielone na partie po 4.

Szyfrowanie#

Oba tryby komunikacji używają tego samego szyfrowania AES-128 CBC:

  1. Klucz generowany z projectKey i IV (XOR pierwszej/drugiej połowy)
  2. Dane szyfrowane przed wysłaniem
  3. Odbiorca deszyfruje odpowiedź tym samym kluczem

Klucze konfigurowane w wizardzie (krok 3 - Keys & PIN).

Synchronizacja stanu#

Zdalne obiekty mogą aktualizować swój stan w registry vCLU na dwa sposoby:

Push reports (subskrypcje)#

Główny mechanizm. Po starcie vCLU automatycznie subskrybuje obiekty na zdalnych CLU (patrz sekcja Subskrypcje wyżej). Gdy stan obiektu zmieni się na zdalnym CLU, przychodzi clientReport i vCLU aktualizuje _lastState obiektu w registry.

sync() - wywołanie z zewnętrznego CLU#

Zdalne CLU może jawnie zaktualizować stan obiektu w vCLU. W konfiguracji OM na fizycznym CLU dodaje się event handler, który przy zmianie stanu wywołuje:

-- Konfiguracja na fizycznym CLU (w Object Managerze):
-- OnSwitchOn → VCLU:execute("_:sync('CLU221000290.DOU5048', 1)")
-- OnSwitchOff → VCLU:execute("_:sync('CLU221000290.DOU5048', 0)")

_:sync(sciezka, wartosc) na vCLU:

  1. Znajduje obiekt w registry (po ścieżce lub nazwie)
  2. Aktualizuje _lastState
  3. Emituje state_changed na StateBus
  4. Integracje (MQTT, HomeKit, WebSocket) automatycznie otrzymują zmianę
-- sync() akceptuje sciezke lub nazwe
_:sync("CLU221000290.DOU5048", 1)   -- po sciezce
_:sync("LAMPA_SALON", 1)            -- po nazwie

-- Stringi liczbowe sa konwertowane automatycznie
_:sync("CLU221000290.DOU5048", "1")  -- wartosc staje sie liczba 1

Autoexpose#

Po załadowaniu proxy zdalnych obiektów, vCLU automatycznie wywołuje exposeRegistry() - wszystkie zaimportowane obiekty są od razu widoczne w MQTT i HomeKit bez ręcznej konfiguracji w user.lua.

Przykłady#

Sterowanie zdalnymi lampami#

-- Wlacz wszystkie lampy w salonie (zdalne CLU "dom20")
dom20.LAMPA_SALON:execute(DOUT.METHOD_SWITCH_ON)
dom20.LAMPA_KUCHNIA:execute(DOUT.METHOD_SWITCH_ON)

-- Odczytaj stan
local stan = dom20.LAMPA_SALON:get(DOUT.FEATURE_VALUE)
log.info("Lampa salon: %d", stan)

Scena z wieloma CLU#

-- Scena "wieczor" - obiekty z roznych CLU
scene("wieczor", function()
    -- Lokalne (ten vCLU)
    _:get("CLU.DOU5048"):execute(DOUT.METHOD_SWITCH_ON)

    -- Zdalne (fizyczny CLU "dom20")
    dom20.LAMPA_SALON:execute(DOUT.METHOD_SWITCH_ON)
    dom20.LAMPA_KORYTARZ:execute(DOUT.METHOD_SWITCH_ON)

    -- Zdalne (drugi vCLU "garage")
    garage.BRAMA:execute(DOUT.METHOD_SWITCH_OFF)
end)

Tagi i grupy mieszane#

-- Otaguj obiekty z roznych CLU
dom20.LAMPA_SALON:addTag("lights")
dom20.LAMPA_KUCHNIA:addTag("lights")
_:get("CLU.DOU5048"):addTag("lights")

-- Steruj wszystkimi naraz
local lights = _:byTag("lights")
lights:each(function(obj)
    obj:execute(DOUT.METHOD_SWITCH_OFF)
end)

Reagowanie na zmiany stanu zdalnego obiektu#

-- Callback gdy zmieni sie stan zdalnej lampy
dom20.LAMPA_SALON:onChange(function(newState, oldState)
    log.info("Lampa salon: %s -> %s", tostring(oldState), tostring(newState))

    -- Synchronizuj z lokalnym obiektem
    if newState == 1 then
        _:get("CLU.DOU5048"):execute(DOUT.METHOD_SWITCH_ON)
    else
        _:get("CLU.DOU5048"):execute(DOUT.METHOD_SWITCH_OFF)
    end
end)

RPC - wywołanie dowolnej funkcji na zdalnym CLU#

Obiekty proxy (RemoteSwitch, RemoteSensor, …) to wygodne wrappery, ale pod spodem każde wywołanie to po prostu wysłanie skryptu Lua na zdalne CLU. Można to samo zrobić ręcznie przez CLU_REMOTE:execute():

-- CLU_REMOTE:execute(0, skrypt_lua) → wysyla skrypt i zwraca wynik

-- Wywolaj funkcje zdefiniowana w user.lua na zdalnym CLU
CLU221000290:execute(0, "mojaFunkcja()")

-- Wywolaj z argumentami
CLU221000290:execute(0, 'wlaczSwiatlo("salon", 80)')

-- Odczytaj wartosc zmiennej
local temp = CLU221000290:execute(0, "return THERM1:get(0)")
log.info("Temperatura: %s", tostring(temp))

-- Dowolny skrypt Lua - jeden wyrazenie
local count = CLU221000290:execute(0, "return #_:list()")

Pierwszy argument to zawsze 0 (indeks metody execute na CLU). Drugi to string ze skryptem Lua, który zostanie wykonany na zdalnym CLU. Wynik jest zwracany jako string.

Przez namespace:

-- Jesli zdalne CLU ma namespace "dom20":
-- CLU221000290 jest dostepny jako zmienna globalna po imporcie om.lua

-- Wywolaj scene na zdalnym CLU
CLU221000290:execute(0, "runScene('wieczor')")

-- Zmien zmienna na zdalnym CLU
CLU221000290:execute(0, "setVar('tryb', 'eco')")

-- Odczytaj stan dowolnego obiektu
local val = CLU221000290:execute(0, "return DOUT3:get(0)")

execute() zwraca wynik tylko z pojedynczego wyrażenia. Skrypty wieloliniowe działają, ale aby zwrócić wartość trzeba użyć return na początku.

Expose zdalnych obiektów do MQTT / HomeKit#

-- Zdalne obiekty mozna eksponowac tak samo jak lokalne
expose(dom20.LAMPA_SALON, "switch", {name = "Lampa Salon"})
expose(dom20.CZUJNIK_RUCHU, "binary_sensor", {name = "Ruch Salon"})