Automatyki w Lua#

W vCLU automatyki, sceny i dashboardy to zwykłe pliki Lua. Ten tutorial pokazuje kilka wzorców na prawdziwym przykładzie: w nocy ruch w wiatrołapie zapala światło na minutę, ale jeśli ktoś zgasi je ręcznie, automatyka milknie na 10 minut.

Zakładamy, że vCLU już działa (z Grentonem albo bez).


1. Gdzie leży kod#

Kod piszesz w edytorze (Editor), w katalogu modules/:

  • każdy plik leżący bezpośrednio w modules/ ładuje się sam (najpierw init.lua, potem reszta alfabetycznie),
  • pliki w podkatalogach trzeba załadować przez require("satel/wiatrolap"),
  • po zmianie: Ctrl+S, potem Reload.

Identyfikatory obiektów (np. CLU220000592.DOU4992 albo satel.zone.2) znajdziesz w Narzędzia → Debug → Registry. W przykładach podmień je na swoje.

2. Reakcja na zdarzenie#

StateBus:watch wywołuje funkcję przy każdej zmianie obiektu. Opcje from i to zawężają wywołanie do konkretnego przejścia:

-- Czujka ruchu przechodzi w 1: zapal światło na minutę
local LIGHT = _:get("CLU220000592.DOU4992")

StateBus:watch("satel.zone.2", function()
    LIGHT:switchOn(60 * 1000)   -- CLU sam je zgasi po 60 s
end, { to = 1 })

_:get(...) zwraca obiekt z rejestru: wyjście Grentona, wejście Satela, obiekt wirtualny albo z pluginu.

3. Kto to zmienił#

Każda zmiana stanu niesie sprawcę (actor): włącznik ścienny, użytkownika panelu, agenta AI, Home Assistant albo Twoją automatykę. Dzięki temu automatyka może odróżnić swoje działanie od ręcznego:

-- modules/wiatrolap.lua
local LIGHT = _:get("CLU220000592.DOU4992")
local ME = "automation:wiatrolap"

-- Stan automatyki, który przetrwa reload i restart
local state = persist("wiatrolap", { pauseUntil = 0 })

StateBus:watch("satel.zone.2", function()
    if not datetime.between("21:00", "06:00") or os.time() < state.pauseUntil then return end
    -- Światło zapalił ktoś inny: nie ruszamy
    local last = LIGHT:lastChange()
    if LIGHT:getValue() == 1 and not (last and last.actor == ME) then return end
    LIGHT:switchOn(60 * 1000)
end, { to = 1, runAs = ME })

-- Zgaszone przez kogokolwiek poza nami: pauza na 10 minut
StateBus:watch("CLU220000592.DOU4992", function(e)
    if e.actor == ME then return end
    state.pauseUntil = os.time() + 600
end, { from = 1, to = 0 })

runAs = ME sprawia, że zmiany zrobione w tej funkcji są zapisane jako zrobione przez automation:wiatrolap. W Narzędzia → Historia i audyt zobaczysz potem przy każdym zapaleniu, kto to zrobił.

4. Stan po restarcie#

Dwa sposoby, żeby coś przetrwało restart:

-- Ustawienie widoczne na zewnątrz (HA, HomeKit, dashboard)
local nightMode = VirtualSwitch:new("night_mode", { initial = 0, persist = true })
expose(nightMode, "switch", { name = "Tryb nocny" })

-- Wewnętrzny stan automatyki: tabela zapisywana automatycznie
local state = persist("podlewanie", { lastRun = 0 })

5. Timery#

local id = after(5 * 60 * 1000, function() print("minęło 5 minut") end)
cancel(id)                                   -- anulowanie

every(60 * 1000, function() print("co minutę") end)

Czas zawsze w milisekundach. Strefę czasową ustaw raz, na początku modules/init.lua: datetime.setTimezone("Europe/Warsaw").

6. Scena#

Scena to obiekt z metodą execute, wystawiony jako scene. Uruchomisz ją z dashboardu, HomeKit, Home Assistant albo przez agenta AI:

local scene = { execute = function(self)
    _:get("CLU220000592.DOU0789"):switchOff()
    _:get("CLU220000592.DOU4992"):switchOff()
end }

expose(scene, "scene", { name = "Wyjście z domu", path = "scene:wyjscie" })

7. Dashboard na tablet#

Dashboard też jest kodem. Ten trafi pod /d/home, z dostępem na PIN:

-- modules/dashboard_home.lua
local r = _registry
Dashboard.new("home")
  :name("Dom"):icon("house-door"):auth("pin"):pin("1234")   -- ustaw własny PIN
  :section("Salon", "lamp")
    :toggle("sofa", "Sofa", "lamp",
      function() return r:get("CLU220000592.DOU0789"):getValue() end,
      function(v) local o = r:get("CLU220000592.DOU0789"); if v then o:switchOn() else o:switchOff() end end)
    :status("temp", "Temperatura", "thermometer-half",
      function() return r:get("CLU220000592.ONE6305"):getValue() end, "°C")
  :build()

Pełny dashboard z alarmem, światłami i temperaturami jest w kroku 16 tutoriala vCLU w instalacji Grenton.


Co dalej?#