Zdalne CLU#

vCLU moze sterowac obiektami na zdalnych CLU (fizycznych Grenton lub innych vCLU) tak, jakby byly lokalne. Komunikacja odbywa sie przez szyfrowane pakiety UDP.

Architektura#

┌─────────────────┐         UDP (AES-128 CBC)         ┌─────────────────┐
│  vCLU (lokalne)  │ ──────────────────────────────── │  CLU (zdalne)    │
│                  │         port 1234                  │                  │
│  dom.LAMPA:on() │ ──req:IP:SESSION:script──────────▶│  LAMPA:on()     │
│                  │ ◀─resp:IP:SESSION:result────────── │                  │
│                  │                                    │                  │
│                  │         port 4344                  │                  │
│  registry update │ ◀─clientReport:SID:{values}─────── │  push changes   │
└─────────────────┘                                    └─────────────────┘

Dwa tryby komunikacji:

TrybKierunekOpis
Komendylokalne -> zdalneWysylanie skryptow Lua, oczekiwanie na wynik
Push reportszdalne -> lokalnePowiadomienia o zmianach stanow (subskrypcje)

Import pliku om.lua#

Aby sterowac zdalnym CLU, trzeba zaimportowac jego plik om.lua. Plik ten zawiera definicje wszystkich obiektow (moduly, I/O, nazwy).

Skad wziac plik om.lua#

  1. Z Object Managera - eksport diagnostyczny (ZIP z plikami om.lua kazdego CLU)
  2. Z fizycznego CLU - plik przesylany 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 zaimportowac pliki:

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

Obslugiwane formaty:

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

Co sie dzieje po imporcie#

Upload om_221000290.lua
        │
        ▼
   ┌──────────┐
   │  Parser   │  ← Wyodrebnia: serial, IP, moduly, obiekty, nazwy
   └────┬─────┘
        │
        ▼
   ┌────────────────┐
   │  Generator      │  ← Tworzy proxy_221000290.lua
   │  proxy          │     (CLU_REMOTE + RemoteSwitch/RemoteSensor/...)
   └────┬───────────┘
        │
        ▼
   ┌────────────────┐
   │  Lua runtime    │  ← Laduje proxy → obiekty w _registry
   │  LoadProxyFiles │     dom.LAMPA = RemoteSwitch:new(...)
   └────────────────┘

Pliki zapisywane na dysku:

imports/
  om_221000290.lua       ← oryginalny plik OM
  proxy_221000290.lua    ← wygenerowany proxy
  proxy_local.lua        ← rejestracja lokalnych obiektow

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 powyzszego 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 dostepne sa 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 obiektow proxy#

Klasa OMProxyOpis
DOUTRemoteSwitchPrzelacznik (on/off/toggle)
DINRemoteSensorCzujnik binarny (odczyt)
DIMMERRemoteLightScialniacz (jasnosc)
LEDRGBRemoteRGBLightLED RGB
ROLLER_SH, ROLLERRemoteCoverRoleta
AnalogINRemoteSensorWejscie analogowe (odczyt)
AnalogOUTRemoteNumberWyjscie analogowe (zapis)

RemoteObject - bazowa klasa#

Wszystkie obiekty proxy dziedzicza z RemoteObject. Kazde wywolanie 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

Protokol komunikacji#

Komendy (request/response)#

Format pakietow UDP (port 1234):

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

Przyklad:

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

Parametry:

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

Subskrypcje (push notifications)#

Zdalne CLU moze powiadamiac o zmianach stanow obiektow. Uzywane przez RemoteSubscription do aktualizacji rejestru bez odpytywania.

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

Protokol:

→ SYSTEM:clientRegister('192.168.0.1', 4344, 123, {{DOU5048, 0}})
← clientReport:123:{1}
ParametrWartosc
Port nasluchiwania4344
Max obiektow per subskrypcja4
TTL subskrypcji60 sekund (auto-refresh)

Wieksze zestawy obiektow sa automatycznie dzielone na partie po 4.

Szyfrowanie#

Oba tryby komunikacji uzywaja tego samego szyfrowania AES-128 CBC:

  1. Klucz generowany z projectKey i IV (XOR pierwszej/drugiej polowy)
  2. Dane szyfrowane przed wyslaniem
  3. Odbiorca deszyfruje odpowiedz tym samym kluczem

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

Synchronizacja stanu#

Zdalne obiekty moga aktualizowac swoj stan w registry vCLU na dwa sposoby:

Push reports (subskrypcje)#

Glowny mechanizm. Po starcie vCLU automatycznie subskrybuje obiekty na zdalnych CLU (patrz sekcja Subskrypcje wyzej). Gdy stan obiektu zmieni sie na zdalnym CLU, przychodzi clientReport i vCLU aktualizuje _lastState obiektu w registry.

sync() - wywolanie z zewnetrznego CLU#

Zdalne CLU moze jawnie zaktualizowac stan obiektu w vCLU. W konfiguracji OM na fizycznym CLU dodaje sie event handler, ktory przy zmianie stanu wywoluje:

-- 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 sciezce lub nazwie)
  2. Aktualizuje _lastState
  3. Emituje state_changed na StateBus
  4. Integracje (MQTT, HomeKit, WebSocket) automatycznie otrzymuja zmiane
-- 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 zaladowaniu proxy zdalnych obiektow, vCLU automatycznie wywoluje exposeRegistry() — wszystkie zaimportowane obiekty sa od razu widoczne w MQTT i HomeKit bez recznej konfiguracji w user.lua.

Przyklady#

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:forEach(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 - wywolanie dowolnej funkcji na zdalnym CLU#

Obiekty proxy (RemoteSwitch, RemoteSensor, …) to wygodne wrappery, ale pod spodem kazde wywolanie to po prostu wyslanie skryptu Lua na zdalne CLU. Mozna to samo zrobic recznie 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, ktory 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 wyrazenia. Skrypty wieloliniowe dzialaja, ale aby zwrocic wartosc trzeba uzyc `return` na poczatku.

Expose zdalnych obiektow 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"})