The HC3's API has 359 endpoints. flua implements the slice your QAs actually touch; the rest forwards to a real controller in online mode. But sometimes you want a missing endpoint to exist in the offline sim — a panel the HC3 has no route for, a sprinkler service, the alarm partition logic. Now you can register it yourself, from Lua:
api.registerEndpoint("GET", "/panels/sprinklers/{id}", function(req)
return { id = tonumber(req.params.id), name = "Sprinklers " .. req.params.id }, 200
end)
The handler returns (data, status) like any REST route, and
from then on every QA in the run reaches it with plain
api.get — no special client, no mocking framework. The whole
pattern is two small files:
-- mock-panels.lua — the emulation module (require() it from any QA)
local M = {}
api.registerEndpoint("GET", "/panels/sprinklers/{id}", function(req)
-- {id} params are strings; the query arrives parsed, e.g. {zone={"2"}}
return { id = tonumber(req.params.id), name = "Sprinklers " .. req.params.id }, 200
end)
api.registerEndpoint("POST", "/panels/sprinklers/{id}/actions/run", function(req)
_FLUA.postRefreshEvent{
type = "DevicePropertyUpdatedEvent",
data = { id = 900001, property = "state", newValue = "running" },
}
return { started = true, minutes = req.body and req.body.minutes or 5 }, 200
end)
return M
-- uses-mock-panels.lua — a consumer QA (run: flua --api local examples/uses-mock-panels.lua)
function QuickApp:onInit()
require("mock-panels")
setTimeout(function()
local panel, s = api.get("/panels/sprinklers/7?zone=2")
print("GET", s, json.encode(panel))
local run, s2 = api.post("/panels/sprinklers/7/actions/run", { minutes = 10 })
print("POST", s2, json.encode(run))
local ok = s == 200 and panel.name == "Sprinklers 7" and s2 == 200 and run.started == true
print(ok and "VERDICT: all passed" or "VERDICT: failed")
exit(ok and 0 or 1)
end, 20)
end
GET 200 {"id":7,"name":"Sprinklers 7"}
POST 200 {"minutes":10,"started":true}
VERDICT: all passed
setTimeout for delayed state transitions,
_FLUA.postRefreshEvent for events your subscribers see).req is {method, path, params, query, body} —
{name} segments become params, the query arrives parsed.override=true
inverts that, so you can prototype the real behavior of an existing
endpoint in Lua — arming delays, state machines — before promoting it to
a Python built-in once the contract is understood.The repo ships both flavors as examples:
examples/mock-panels.lua (additive endpoints) and the alarm
loop — examples/alarm-logic.lua overrides the built-in alarm
partition endpoints with exit-delay arming and refreshState events, an
examples/alarm-panel.lua QA carries the arm/disarm UI, and
examples/uses-alarm-panel.lua consumes it.
And the weather simulator from the last post is the same idea one level
up: a QA whose endpoints and events make the whole
/weather surface come alive, so your climate QAs get a
forecast to react to.
Next up: packaging those endpoints as QuickApps — emulation you can distribute like any other QA.