Most interesting QAs react to other devices: the lamp watches the
door sensor, the scene waits for the smoke detector. On the HC3 the
mechanism is the refreshStates event feed; flua simulates it
offline, so reactive logic is testable the same way timers are.
The high-level way is the RefreshStateSubscriber class —
subscribe with a predicate and a callback, then run:
local subscriber = RefreshStateSubscriber()
subscriber:subscribe(
function(event) return true end, -- which events to react to
function(event)
print("Event:", json.encode(event))
end)
subscriber:run()
function QuickApp:onInit()
setTimeout(function()
self:updateProperty("value", true) -- the QA changes its own state
end, 100)
end
Event: {"type": "DevicePropertyUpdatedEvent",
"data": {"id": 5000, "property": "value", "oldValue": false, "newValue": true}, ...}
The event carries everything the HC3 delivers: the type, the device id, the changed property, and old/new values. Filter in the predicate — only door sensors, only breaches:
subscriber:subscribe(
function(event)
return event.type == "DevicePropertyUpdatedEvent"
and event.data.property == "value"
end,
function(event) print("door changed to", event.data.newValue) end)
Because events flow through the same pump as timers, the whole chain is
deterministic: open the door QA, the lamp QA reacts, the scene reacts to
the lamp — in one log, tagged per QA, reproducible every run
(flua door.lua lamp.lua — several QAs, one process, as in
post 5). Debugging a reaction chain becomes reading a chat log instead of
staring at a live controller.
The full event feed is also there at the API level — api.get("/refreshStates?last=0") returns the recorded events, and the
HC3's polling contract holds — so QAs written against the raw feed port
over unchanged.
Next post: the devices inside your QA — child devices.
Examples: flua --examples → refreshState.lua,
trigger.lua · flua: https://github.com/jangabrielsson/flua