// Package controllogic implements a server-side flow-graph engine. It mirrors // the client-side panel logic engine (web/src/lib/logic.ts) but runs // continuously on the server under the root context, independent of any panel. // // A control-logic graph is a Node-RED-style flow of trigger, gate, control-flow // and action nodes connected by wires. Triggers are flow entry points; when one // activates the engine follows the outgoing wires executing downstream nodes. // // Compared with the panel engine, control logic has no button trigger (buttons // are panel-UI driven) and gains two headless triggers — cron (5-field schedule) // and alarm (signal out of an allowed range) — plus a Lua script action node // with get/set/log host functions. // // Graphs are persisted as JSON in {storageDir}/controllogic.json and CRUD'd via // the REST API; the engine rebuilds itself (Reload) whenever they change. package controllogic // Node kinds. Triggers begin flows; the rest execute downstream. // // trigger.threshold — fires on the rising edge of cmp(signal, value). // trigger.change — fires whenever a watched signal's value changes. // trigger.timer — fires every `interval` ms. // trigger.cron — fires when the wall clock matches a 5-field cron `spec`. // trigger.alarm — fires on the rising edge of signal leaving [min,max]. // gate.and — passes only when all incoming triggers are satisfied. // flow.if — evaluates `cond`, continues on the 'then'/'else' port. // flow.loop — repeats the 'body' port (count / while, capped) then 'done'. // action.write — evaluates `expr`, writes the result to `target`. // action.delay — waits `ms` before continuing. // action.log — logs an expression value to the server log. // action.lua — runs a sandboxed Lua script with get/set/log host funcs. // action.dialog — pushes an info/error/input dialog to connected clients // filtered by user/group; input responses write `target`. // Node is a single node in a control-logic graph. Params are stored as strings // (matching the panel logic model) and parsed per-kind by the engine. type Node struct { ID string `json:"id"` Kind string `json:"kind"` X float64 `json:"x"` Y float64 `json:"y"` Params map[string]string `json:"params,omitempty"` } // Wire connects an output port of one node to the input of another. // FromPort defaults to "out" when empty. type Wire struct { From string `json:"from"` FromPort string `json:"fromPort,omitempty"` To string `json:"to"` } // Graph is a named, independently-enableable control-logic flow. type Graph struct { ID string `json:"id"` Name string `json:"name"` Enabled bool `json:"enabled"` Nodes []Node `json:"nodes"` Wires []Wire `json:"wires"` } func (n Node) param(key string) string { if n.Params == nil { return "" } return n.Params[key] }