DOUT - wyjście cyfrowe#

DOUT (Digital Output) to obiekt reprezentujący wyjście cyfrowe - przekaźnik, lampę, zawór. Występuje w trzech wariantach:

  • Lokalny DOUT - wirtualny moduł vCLU, konfigurowany w wizardzie
  • DOUT z OM - obiekt z Object Manager, po sparowaniu z zewnętrznym kontrolerem
  • GPIO_DOUT - obiekt tworzony w user.lua, sterujący pinem GPIO Raspberry Pi

Wszystkie warianty mają ten sam interfejs API (get/set/execute/add_event) i te same stałe.

Lokalny DOUT - moduły wirtualne#

Gdy vCLU działa w trybie CLU (wizardzie), można dodać wirtualne moduły wyjść. Nie sterują prawdziwym sprzętem, ale pojawiają się w OM i mogą być używane do testów, automatyzacji i integracji z Home Assistant / HomeKit.

Konfiguracja w wizardzie#

W kroku 2 wizarda (“Moduły”) można dodać moduły z wyjściami cyfrowymi:

Typ modułuWyjścia DOUTWejścia DINAnalogHW Type
DOUT8T8810x1e
RELAY44-10x15

Każdy moduł dostaje unikalną nazwę (np. DOUT8T_1) i numer seryjny. Obiekty I/O są generowane automatycznie z losowymi identyfikatorami (np. DOU5048, DOU4458).

Jak to działa#

Wizard → .vclu.json → config.txt + CONFIG.JSON → OM → om.lua → runtime
  1. Użytkownik dodaje moduł w wizardzie
  2. vCLU zapisuje konfigurację w .vclu.json (moduły + obiekty I/O)
  3. Generuje config.txt i CONFIG.JSON z wpisami TFBus
  4. Object Manager (OM) odczytuje konfigurację i generuje om.lua
  5. Przy starcie Lua ładuje om.lua - obiekty DOUT są dostępne w runtime

Dostęp do obiektów#

Obiekty lokalne są dostępne przez registry, identycznie jak na prawdziwym CLU:

-- Przez nazwe z OM
local lampa = _:get("CLU.DOU5048")
lampa:execute(DOUT.METHOD_SWITCH_ON)

-- Albo przez nazwe nadana w wizardzie
local lampa = _:get("CLU.DOUT8T_1_DOU1")

.vclu.json#

Moduły i ich I/O są zapisane w konfiguracji:

{
  "device": {
    "modules": [
      {
        "typeId": "dout8t",
        "serial": "330000027",
        "name": "DOUT8T_1",
        "hardwareType": 30
      }
    ],
    "ioObjects": [
      {
        "id": "DOU5048",
        "typeId": "dout",
        "index": 0,
        "name": "DOUT8T_1_DOU1",
        "luaType": 4
      }
    ]
  }
}

Lokalne moduły DOUT nie sterują fizycznym sprzętem - zmiany stanu są tylko w pamięci. Ale obiekty są w pełni funkcjonalne: emitują zdarzenia, działają z expose(), pojawiają się w Home Assistant i HomeKit. Do sterowania prawdziwymi przekaźnikami na Raspberry Pi użyj GPIO_DOUT.

GPIO_DOUT - tworzenie#

local relay = GPIO_DOUT:new(name, pin, opts)
ParametrTypOpis
namestringNazwa globalna (np. "RELAY1")
pinnumberNumer pinu BCM
optstableOpcje (opcjonalne)

Opcje#

KluczTypDomyślnieOpis
activeLowbooleanfalseInwersja logiki (LOW = ON, HIGH = OFF)
namestring-Nazwa wyświetlana
-- Przekaznik z inwersja (typowe dla plyt relay)
local relay = GPIO_DOUT:new("RELAY1", 17, {activeLow = true})

-- LED bez inwersji
local led = GPIO_DOUT:new("LED1", 22)

activeLow#

Większość płyt przekaźnikowych używa logiki odwróconej - LOW na pinie aktywuje przekaźnik, HIGH wyłącza. Opcja activeLow = true automatycznie odwraca logikę:

LogikaactiveLow = falseactiveLow = true
ON (value=1)pin HIGHpin LOW
OFF (value=0)pin LOWpin HIGH
safeState (przy starcie)LOWHIGH (przekaźnik OFF)

Przy activeLow = true pin startuje w stanie HIGH (przekaźnik wyłączony). To zapobiega przypadkowemu włączeniu przekaźnika przy starcie systemu.

DOUT z OM - obiekty z Object Manager#

Obiekty DOUT z OM są tworzone automatycznie po sparowaniu z zewnętrznym kontrolerem. Dostęp przez registry:

local lampa = _:get("CLU.DOUT1")
lampa:execute(DOUT.METHOD_SWITCH_ON)

Porównanie wariantów#

Lokalny DOUTDOUT z OMGPIO_DOUT
ŹródłoWizard (vCLU)Object Manager (OM)user.lua
SprzętBrak (w pamięci)Zewnętrzny kontrolerPin GPIO Raspberry Pi
Konfiguracja.vclu.jsonom.luaOM generuje om.luaGPIO_DOUT:new() w kodzie
ZastosowanieTestowanie, emulacjaProdukcja z prawdziwym CLUProdukcja na Raspberry Pi
APIget/set/execute/add_eventget/set/execute/add_eventget/set/execute/add_event
expose()taktaktak
exposeRegistry()tak (automatycznie)tak (automatycznie)tak (po registerObject)

Stałe#

Cechy (Features)#

StałaWartośćOpis
DOUT.FEATURE_VALUE0Stan wyjścia: 1=ON, 0=OFF
DOUT.FEATURE_VOLTAGE_TYPE3Typ napięcia: 0=AC, 1=DC, 2=Signal
DOUT.FEATURE_VOLTAGE_VALUE4Wartość napięcia
DOUT.FEATURE_POWER5Moc chwilowa (tylko odczyt)
DOUT.FEATURE_OVERLOAD7Próg przeciążenia

Metody#

StałaWartośćOpis
DOUT.METHOD_SWITCH0Przełącz (toggle)
DOUT.METHOD_SWITCH_ON1Włącz
DOUT.METHOD_SWITCH_OFF2Wyłącz

Zdarzenia#

StałaWartośćOpis
DOUT.EVENT_ON_VALUE_CHANGE0Zmiana wartości
DOUT.EVENT_ON_SWITCH_ON1Włączenie
DOUT.EVENT_ON_SWITCH_OFF2Wyłączenie
DOUT.EVENT_ON_OVERLOAD3Przeciążenie

Wartości#

StałaWartość
DOUT.VALUE_ON / DOUT.STATE_ON1
DOUT.VALUE_OFF / DOUT.STATE_OFF0

API#

execute(method, time)#

Wykonuje metodę na obiekcie. Opcjonalny parametr time (ms) powoduje automatyczne cofnięcie po zadanym czasie.

-- Wlacz
relay:execute(DOUT.METHOD_SWITCH_ON)

-- Wylacz
relay:execute(DOUT.METHOD_SWITCH_OFF)

-- Przelacz (toggle)
relay:execute(DOUT.METHOD_SWITCH)

-- Wlacz na 5 sekund, potem wylacz
relay:execute(DOUT.METHOD_SWITCH_ON, 5000)

-- Wylacz na 3 sekundy, potem wlacz
relay:execute(DOUT.METHOD_SWITCH_OFF, 3000)

get(feature)#

Odczytuje wartość cechy.

local stan = relay:get(DOUT.FEATURE_VALUE)  -- 0 lub 1

set(feature, value)#

Ustawia wartość cechy.

relay:set(DOUT.FEATURE_VALUE, 1)  -- wlacz
relay:set(DOUT.FEATURE_VALUE, 0)  -- wylacz

add_event(event, callback)#

Rejestruje callback na zdarzenie.

relay:add_event(DOUT.EVENT_ON_SWITCH_ON, function()
    log.info("Przekaznik wlaczony!")
end)

relay:add_event(DOUT.EVENT_ON_SWITCH_OFF, function()
    log.info("Przekaznik wylaczony!")
end)

relay:add_event(DOUT.EVENT_ON_VALUE_CHANGE, function()
    local v = relay:get(DOUT.FEATURE_VALUE)
    log.info("Nowa wartosc: " .. v)
end)

onChange(callback)#

Rejestruje callback na zmianę wartości. Zwraca funkcję unsubscribe. Używany przez system expose() do automatycznej aktualizacji stanów w Home Assistant i HomeKit.

local unsub = relay:onChange(function(newValue, oldValue)
    log.info("Zmiana: " .. oldValue .. " → " .. newValue)
end)

-- Pozniej: wyrejestruj callback
unsub()

Przykłady#

Przekaźnik GPIO z eksponowaniem#

local relay = GPIO_DOUT:new("RELAY1", 17, {activeLow = true})
expose(relay, "switch", {name = "Lampa salon"})

Toggle na przycisk#

local relay = GPIO_DOUT:new("RELAY1", 17, {activeLow = true})
local btn = GPIO_DIN:new("BTN1", 27)

btn:add_event(DIN.EVENT_ON_CLICK, function()
    relay:execute(DOUT.METHOD_SWITCH)
end)

Włączenie czasowe (np. oświetlenie klatki schodowej)#

local swiatlo = GPIO_DOUT:new("SWIATLO_KLATKA", 17, {activeLow = true})
local przycisk = GPIO_DIN:new("BTN_KLATKA", 27)

przycisk:add_event(DIN.EVENT_ON_CLICK, function()
    swiatlo:execute(DOUT.METHOD_SWITCH_ON, 60000)  -- 60 sekund
end)

Sterowanie obiektem z OM#

-- DOUT z zewnetrznego kontrolera (OM)
local lampa = _:get("CLU.DOUT1")

-- Te same metody co GPIO_DOUT
lampa:execute(DOUT.METHOD_SWITCH_ON)
lampa:add_event(DOUT.EVENT_ON_SWITCH_ON, function()
    log.info("Lampa wlaczona")
end)

expose(lampa, "switch", {name = "Lampa kuchnia"})

Kaskada - włączanie po kolei#

local r1 = GPIO_DOUT:new("R1", 17, {activeLow = true})
local r2 = GPIO_DOUT:new("R2", 22, {activeLow = true})
local r3 = GPIO_DOUT:new("R3", 24, {activeLow = true})

scene("wszystkie_on", function()
    r1:execute(DOUT.METHOD_SWITCH_ON)
    after(500, function() r2:execute(DOUT.METHOD_SWITCH_ON) end)
    after(1000, function() r3:execute(DOUT.METHOD_SWITCH_ON) end)
end)

after() działa asynchronicznie - rejestruje timer i wraca natychmiast. Scena kończy się w ułamku milisekundy, a callbacki odpalają się później z pętli timerowej Go. Nie ma ryzyka zawieszenia CLU.

after(500, ...)   ← NIE czeka 500ms, tylko rejestruje callback i idzie dalej
after(1000, ...)  ← to samo - rejestruje i wraca
-- scena konczy sie tu, natychmiast
-- po 500ms: r2 wlacza sie
-- po 1000ms: r3 wlacza sie

Lua w vCLU jest jednowątkowy - nigdy nie używaj pętli oczekującej (while, repeat, busy-wait). Zablokuje to cały runtime i zatrzyma przetwarzanie zdarzeń, timerów, MQTT i GPIO. Do opóźnień zawsze używaj after() lub every().

-- ZLE - zawiesi CLU!
local start = os.clock()
while os.clock() - start < 0.5 do end  -- busy-wait
r2:execute(DOUT.METHOD_SWITCH_ON)

-- DOBRZE - asynchronicznie
after(500, function()
    r2:execute(DOUT.METHOD_SWITCH_ON)
end)

Przykłady mieszane - GPIO + lokalne moduły#

Można łączyć różne warianty - np. fizyczny przycisk GPIO steruje wirtualnym DOUT, albo Tasmota przez MQTT steruje lokalnym modułem.

GPIO_DIN przełącza lokalny DOUT#

Fizyczny przycisk podłączony do pinu GPIO steruje wirtualnym wyjściem z wizarda:

-- user.lua

-- Przycisk fizyczny na GPIO
local btn = GPIO_DIN:new("BTN1", 27)

-- Lokalny DOUT z wizarda (ID nadany automatycznie)
local lampa = _:get("CLU.DOU5048")

btn:add_event(DIN.EVENT_ON_CLICK, function()
    lampa:execute(DOUT.METHOD_SWITCH)
end)

-- Eksponuj lokalny DOUT do Home Assistant
expose(lampa, "switch", {name = "Lampa salon"})

GPIO_DIN - krótkie/długie naciśnięcie na lokalnych DOUT#

local btn = GPIO_DIN:new("BTN1", 27)

local lampa1 = _:get("CLU.DOU5048")
local lampa2 = _:get("CLU.DOU4458")

-- Krotkie nacisniecie - przelacz lampe 1
btn:add_event(DIN.EVENT_ON_SHORT_PRESS, function()
    lampa1:execute(DOUT.METHOD_SWITCH)
end)

-- Dlugie nacisniecie - wylacz obie lampy
btn:add_event(DIN.EVENT_ON_LONG_PRESS, function()
    lampa1:execute(DOUT.METHOD_SWITCH_OFF)
    lampa2:execute(DOUT.METHOD_SWITCH_OFF)
end)

Tasmota steruje lokalnym DOUT przez MQTT#

Gniazdko Tasmota podłączone do wbudowanego brokera MQTT. Stan gniazdka synchronizowany z lokalnym DOUT - dzięki temu obiekt jest widoczny w Home Assistant i HomeKit jak każdy inny DOUT:

-- user.lua

local mqtt = MQTT:new("tasmota")
mqtt:setHost("localhost"):setPort(1883):connect()

local gniazdko = _:get("CLU.DOU5048")

-- Tasmota → lokalny DOUT (synchronizacja stanu)
mqtt:subscribe("stat/gniazdko1/POWER", function(topic, payload)
    if payload == "ON" then
        gniazdko:set(DOUT.FEATURE_VALUE, 1)
    else
        gniazdko:set(DOUT.FEATURE_VALUE, 0)
    end
end)

-- Lokalny DOUT → Tasmota (przekazywanie komend)
gniazdko:add_event(DOUT.EVENT_ON_SWITCH_ON, function()
    mqtt:publish("cmnd/gniazdko1/POWER", "ON")
end)

gniazdko:add_event(DOUT.EVENT_ON_SWITCH_OFF, function()
    mqtt:publish("cmnd/gniazdko1/POWER", "OFF")
end)

-- Eksponuj do HA/HomeKit - sterowanie przez vCLU
expose(gniazdko, "switch", {name = "Gniazdko kuchnia"})

Przepływ:

graph TD
    ha["Home Assistant / HomeKit"]
    dout["Lokalny DOUT"]
    event[EVENT_ON_SWITCH_ON]
    pub["mqtt:publish<br/>('cmnd/gniazdko1/POWER', 'ON')"]
    tasmota["Tasmota włącza gniazdko"]
    stat["Tasmota → 'stat/.../POWER' = 'ON'"]
    sync["gniazdko:set(FEATURE_VALUE, 1)<br/><i>stan zsynchronizowany</i>"]

    ha -->|"komenda ON"| dout
    dout --> event
    event --> pub
    pub --> tasmota
    tasmota --> stat
    stat --> sync

Shelly jako czujnik temperatury + lokalny DOUT#

Shelly H&T publikuje temperaturę na MQTT. Gdy temperatura przekroczy próg - włącz wentylator (lokalny DOUT):

local mqtt = MQTT:new("shelly")
mqtt:setHost("localhost"):setPort(1883):connect()

local wentylator = _:get("CLU.DOU4458")
expose(wentylator, "switch", {name = "Wentylator"})

mqtt:subscribe("shellies/shelly-ht/sensor/temperature", function(topic, payload)
    local temp = tonumber(payload)
    if temp and temp > 25 then
        wentylator:execute(DOUT.METHOD_SWITCH_ON)
    elseif temp and temp < 23 then
        wentylator:execute(DOUT.METHOD_SWITCH_OFF)
    end
end)

Wiele Tasmot - fabryka DOUT#

Jeśli masz kilka gniazdek Tasmota, możesz je podpiąć pod lokalne DOUT za pomocą pętli:

local mqtt = MQTT:new("tasmota")
mqtt:setHost("localhost"):setPort(1883):connect()

-- Mapowanie: nazwa Tasmota → ID lokalnego DOUT
local urzadzenia = {
    {tasmota = "gniazdko1", doutId = "CLU.DOU5048", name = "Gniazdko kuchnia"},
    {tasmota = "gniazdko2", doutId = "CLU.DOU4458", name = "Gniazdko salon"},
    {tasmota = "lampka1",   doutId = "CLU.DOU3327", name = "Lampka biurko"},
}

for _, dev in ipairs(urzadzenia) do
    local obj = _:get(dev.doutId)

    -- Tasmota → DOUT
    mqtt:subscribe("stat/" .. dev.tasmota .. "/POWER", function(topic, payload)
        if payload == "ON" then
            obj:set(DOUT.FEATURE_VALUE, 1)
        else
            obj:set(DOUT.FEATURE_VALUE, 0)
        end
    end)

    -- DOUT → Tasmota
    obj:add_event(DOUT.EVENT_ON_SWITCH_ON, function()
        mqtt:publish("cmnd/" .. dev.tasmota .. "/POWER", "ON")
    end)
    obj:add_event(DOUT.EVENT_ON_SWITCH_OFF, function()
        mqtt:publish("cmnd/" .. dev.tasmota .. "/POWER", "OFF")
    end)

    expose(obj, "switch", {name = dev.name})
end

Podejście “lokalny DOUT + MQTT” pozwala traktować urządzenia Tasmota/Shelly jak natywne obiekty vCLU. Dzięki temu pojawiają się w Home Assistant i HomeKit, mogą być używane w scenach, i mają pełne zdarzenia (add_event).