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łu | Wyjścia DOUT | Wejścia DIN | Analog | HW Type |
|---|---|---|---|---|
| DOUT8T | 8 | 8 | 1 | 0x1e |
| RELAY4 | 4 | - | 1 | 0x15 |
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- Użytkownik dodaje moduł w wizardzie
- vCLU zapisuje konfigurację w
.vclu.json(moduły + obiekty I/O) - Generuje
config.txtiCONFIG.JSONz wpisami TFBus - Object Manager (OM) odczytuje konfigurację i generuje
om.lua - 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żyjGPIO_DOUT.
GPIO_DOUT - tworzenie#
local relay = GPIO_DOUT:new(name, pin, opts)| Parametr | Typ | Opis |
|---|---|---|
name | string | Nazwa globalna (np. "RELAY1") |
pin | number | Numer pinu BCM |
opts | table | Opcje (opcjonalne) |
Opcje#
| Klucz | Typ | Domyślnie | Opis |
|---|---|---|---|
activeLow | boolean | false | Inwersja logiki (LOW = ON, HIGH = OFF) |
name | string | - | 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ę:
| Logika | activeLow = false | activeLow = true |
|---|---|---|
| ON (value=1) | pin HIGH | pin LOW |
| OFF (value=0) | pin LOW | pin HIGH |
| safeState (przy starcie) | LOW | HIGH (przekaźnik OFF) |
Przy
activeLow = truepin 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 DOUT | DOUT z OM | GPIO_DOUT | |
|---|---|---|---|
| Źródło | Wizard (vCLU) | Object Manager (OM) | user.lua |
| Sprzęt | Brak (w pamięci) | Zewnętrzny kontroler | Pin GPIO Raspberry Pi |
| Konfiguracja | .vclu.json → om.lua | OM generuje om.lua | GPIO_DOUT:new() w kodzie |
| Zastosowanie | Testowanie, emulacja | Produkcja z prawdziwym CLU | Produkcja na Raspberry Pi |
| API | get/set/execute/add_event | get/set/execute/add_event | get/set/execute/add_event |
expose() | tak | tak | tak |
exposeRegistry() | tak (automatycznie) | tak (automatycznie) | tak (po registerObject) |
Stałe#
Cechy (Features)#
| Stała | Wartość | Opis |
|---|---|---|
DOUT.FEATURE_VALUE | 0 | Stan wyjścia: 1=ON, 0=OFF |
DOUT.FEATURE_VOLTAGE_TYPE | 3 | Typ napięcia: 0=AC, 1=DC, 2=Signal |
DOUT.FEATURE_VOLTAGE_VALUE | 4 | Wartość napięcia |
DOUT.FEATURE_POWER | 5 | Moc chwilowa (tylko odczyt) |
DOUT.FEATURE_OVERLOAD | 7 | Próg przeciążenia |
Metody#
| Stała | Wartość | Opis |
|---|---|---|
DOUT.METHOD_SWITCH | 0 | Przełącz (toggle) |
DOUT.METHOD_SWITCH_ON | 1 | Włącz |
DOUT.METHOD_SWITCH_OFF | 2 | Wyłącz |
Zdarzenia#
| Stała | Wartość | Opis |
|---|---|---|
DOUT.EVENT_ON_VALUE_CHANGE | 0 | Zmiana wartości |
DOUT.EVENT_ON_SWITCH_ON | 1 | Włączenie |
DOUT.EVENT_ON_SWITCH_OFF | 2 | Wyłączenie |
DOUT.EVENT_ON_OVERLOAD | 3 | Przeciążenie |
Wartości#
| Stała | Wartość |
|---|---|
DOUT.VALUE_ON / DOUT.STATE_ON | 1 |
DOUT.VALUE_OFF / DOUT.STATE_OFF | 0 |
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 1set(feature, value)#
Ustawia wartość cechy.
relay:set(DOUT.FEATURE_VALUE, 1) -- wlacz
relay:set(DOUT.FEATURE_VALUE, 0) -- wylaczadd_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 sieLua 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żywajafter()lubevery().-- 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 --> syncShelly 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})
endPodejś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).