80 lines
3.5 KiB
Go
80 lines
3.5 KiB
Go
// 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"`
|
|
}
|
|
|
|
// NodeGroup is a cosmetic, editor-side grouping of nodes. The engine ignores it;
|
|
// it is stored and round-tripped so the editor keeps its visual organisation.
|
|
type NodeGroup struct {
|
|
ID string `json:"id"`
|
|
Label string `json:"label,omitempty"`
|
|
Members []string `json:"members"`
|
|
Collapsed bool `json:"collapsed,omitempty"`
|
|
}
|
|
|
|
// Graph is a named, independently-enableable control-logic flow.
|
|
type Graph struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
Version int `json:"version,omitempty"` // git-style revision; bumped on each Save
|
|
Tag string `json:"tag,omitempty"` // optional revision label (e.g. "restored from v3")
|
|
Nodes []Node `json:"nodes"`
|
|
Wires []Wire `json:"wires"`
|
|
Groups []NodeGroup `json:"groups,omitempty"`
|
|
}
|
|
|
|
func (n Node) param(key string) string {
|
|
if n.Params == nil {
|
|
return ""
|
|
}
|
|
return n.Params[key]
|
|
}
|