flua is a QuickApp engine: a bundled Lua interpreter with a built-in
emulation of an HC3, so QAs run offline on the laptop. This post is the
checklist of everything flua offers a coding agent — Copilot, Claude,
ChatGPT, DeepSeek, or whatever sits in your editor. Everything below works
from a bare pip install fibaro-flua (v0.1.32+), no repo, no
controller.
flua --tool setup scaffolds a QA project with the agent's
knowledge base: skills covering the full HC3 REST API reference, QA
patterns, testing, and troubleshooting, plus Lua language-server types for
QuickApp, fibaro, api and
json, plus project instructions (AGENTS.md). The agent then
answers from the API reference instead of its training data.
No pytest, no harness. Assert inside the QA with the built-in library, and the exit code is the verdict:
--%%name:lamp
--%%type:com.fibaro.binarySwitch
--%%u:{button="onBtn",text="On",onReleased="turnOn"}
--%%u:{label="state",text="Off"}
-- --------------- EOH ---------------
local Test = require("flua-test")
local t = Test.new()
function QuickApp:onInit()
_FLUA.uiEvent("onBtn", "onReleased") -- press the button, like the HC3 UI
end
function QuickApp:turnOn()
self:updateProperty("value", true)
self:updateView("state", "text", "On")
t:expect(self.properties.value == true, "lamp on")
local view = self:getView()
local comps = view["$jason"].body.sections.items[2].components
t:expect(comps[1].text == "On", "view shows On")
t:finish()
end
flua --api local --instant lamp.lua echo $? # 0 = all assertions held [lamp5000]: PASS: lamp on [lamp5000]: PASS: view shows On [lamp5000]: failures: 0
require("flua-test"): expect,
deep compare, JSON checks, failure counting, exit wiring. No boilerplate,
no files, never travels in a packaged .fqa. Want the asserts to travel?
Declare --%%file:$flua-test.lua,flua_test — the QA body stays
unchanged, and on the HC3 every assertion becomes a no-op.--instant fires timers immediately,
--start '2027/06/21 05:00:00' pins the date,
--speed 60 runs 60× — timer logic is tested without waiting
for real time.flua: QA lamp (5000) exited with code 1.For agents parsing output, --log json emits one JSON object
per line — stdout stays pure JSON, flua's own notices go to stderr:
flua --api local --log json lamp.lua
{"time": "2026-10-13T11:15:36", "level": "DEBUG", "tag": "lamp5000", "qaid": 5000, "msg": "PASS: lamp on"}
--logfile run.log appends a copy of the log (ANSI colors
stripped) for post-mortems.
_FLUA.uiEvent(element, eventType,
value) delivers a UI event exactly as the HC3 UI would (values
arrive as strings, callbacks fire async).self:getView() returns the
current viewLayout, updateView changes included.flua --tool ui qa.lua:QA "lamp" (com.fibaro.binarySwitch) button onBtn "On" -> onReleased=turnOn label state "Off"
flua --check qa.lua catches syntax
errors, unknown --%% directives, deprecated APIs before the
first run.net.HTTPServer,
net.TCPServer, net.UDPServer,
net.WebSocketServer: the agent writes a mock from a service's
protocol and the QA's real client code runs against it.net.HTTPClient and
mqtt.Client make real network calls, so the agent tests
against the actual service API (or a mock) without the controller.require("testlib") are never packaged into the .fqa; only
--%%file files travel.--seed house.json or
an offline db provides rooms/devices/scenes, and
_FLUA.postRefreshEvent(...) injects fake refreshState events
(alarm, weather) to debug reactive QAs.--%%var:pwd=config.secretPwd
reads a .flua.lua config file; the packaging value
(--%%var:pwd=config.secretPwd,"<set to service password>")
scrubs it from distribution .fqa files.--tool downloadQA/updateQA/uploadQA
deploy to the real HC3 when the agent is done; online/proxy modes let the
QA talk to a real controller from flua.A typical agent session: flua --check, edit, run with
--api local --instant, read the JSON log and the exit code,
fix, repeat — and the controller is never touched until you decide the QA
is ready.
flua: https://github.com/jangabrielsson/flua · docs: USAGE.md · tutorial series: see the other threads in this forum section