8.5 KiB
Visual Flow architecture
Flow is an authoring/orchestration layer. It does not replace the thermostat, schedule, group or device engines.
Compilation
weekday + time_range -> zone_thermostatwith schedule-compatible settings compiles to a nativeSchedule.- Richer DAGs compile to native
Automationrows withtrigger_kind = "flow". - Generated rows keep
flow_idandflow_node_id, use stableflow-<stable-unique-id>names, and are read-only in the legacy editors. - The Flow source graph plus all of its generated schedule/automation rows are replaced in one SQLite transaction.
- Flow updates require
expected_revision; stale editor tabs receive HTTP 409. - Work-in-progress graphs may be persisted with
draft=true. Drafts are forced disabled, compile to zero schedules/automations, and can contain unfinished semantic wiring such as a graph without an action. Missing node references and duplicate/empty IDs are still rejected so the editor document remains structurally safe. - Saving a previously executable Flow as a draft atomically deletes its generated outputs, so no stale task can keep running. Completing and saving it normally clears draft status and recompiles it.
Existing control domains remain authoritative
zone_thermostatwrites the existing persistent Zone intent and wakes the normal thermostat cycle.group_actioncalls the existingcontrol_grouppath, including custom group targets.device_actionuses the existingDeviceCommand/ automatic-device path.- Thermostat hysteresis, compressor protection, schedule hand-back, manual/local ownership and Temporary Quick Thermostat keep their existing priority.
- GREE fan/quiet/sleep and dry/fan HVAC modes are suppressed on thermostat-assigned devices when they would fight the regulator. Device-only features such as light/turbo/swing/air/xfan/health remain available through the normal command path.
Concurrency invariants
Structural Flow writes use the global order:
configuration -> automation -> schedule -> thermostat-cycle
Thermostat/device actions then use:
automation -> schedule -> thermostat-cycle -> zone -> device
Group actions use the existing group order:
automation -> house -> thermostat-cycle -> group -> zone(s) -> device
The control loop runs thermostat arbitration and automation arbitration sequentially. Automation execution reloads the row after acquiring the automation lock and skips it when updated_at changed. Same-cycle due automations deterministically claim target devices and Home Assistant entity IDs so two rules cannot issue conflicting commands to the same target in one pass. Polling and physical commands share per-zone/per-device locks.
Holding the automation lock across the physical action is deliberate: Flow cannot be deleted/recompiled while an already-selected action is in flight. Do not shorten this ownership window without adding an explicit execution-generation/token mechanism.
External sensor values can naturally change between condition sampling and action execution. This is normal sampled-control semantics, not a persistent-state race; safety/ownership is rechecked at the action boundary.
Home Assistant safety
ha_state, ha_numeric, ha_attribute and ha_available fail closed. Connection failures, missing attributes and non-numeric numeric states evaluate to false and appear as an error object in dry-run traces. This prevents NOT / neq branches from becoming true merely because Home Assistant is unavailable.
Diagnostics
The Flow editor exposes:
- dry-run at an arbitrary date/time,
- per-source overrides,
- per-node trace,
would_executeand ownership/block reason,- Flow-scoped execution/dry-run logs,
- import/export of versioned
.flow.jsonsource graphs.
Dry-run never mutates thermostats, devices, groups, schedules or automations. For an existing Flow it clones persisted stateful runtime data, so timers, last-observed values, rate-limit history and rolling/oscillation windows are evaluated against the same saved state without writing it back.
Blocks
Condition/source blocks include weekday, time range, date range, optional 5-field CRON triggers, application Night mode, outdoor/device/zone temperature, house mode, device state, thermostat/zone state, group state, Home Assistant state/numeric/attribute/availability, rolling mean/median windows, numeric oscillation detection, a diagnostic constant, and shared_input. Stateful gates include stable_for, bounded state_duration, on_change (condition-result or raw-value edge), rate_limit (maximum successful executions in a rolling period), and delay. Their runtime state, together with rolling statistics and oscillation samples, is persisted in the generated automation payload so restarts do not silently reset timing/history. Editing a stateful block resets only that block's incompatible runtime state. Flow state continues to be observed while the action itself is in cooldown, preventing stale continuity/change state. A rate_limit block must be placed directly before an action and consumes quota only after a successful execution; suppressed or failed actions do not consume it. Shared inputs are configured centrally in Home Assistant / Sensors and referenced by ID. Shared inputs store reusable value sources only; operators and comparison values are configured exclusively in each Flow reference. Logic blocks are AND, OR and NOT.
Actions include thermostat zone, GREE device, climate group, and generic Home Assistant service calls (domain, service, optional entity_id, JSON service data). Direct GREE actions expose power, HVAC mode, target temperature, fan speed, vertical/horizontal swing, quiet, turbo, light, air, xfan, health and sleep.
Built-in presets
The editor ships 37 editable presets grouped into seven categories: Comfort, Energy & cost, Protection & safety, Night, Reliability, Home Assistant / heat sources and Advanced. The library UI also provides search, Favorites, Recent, a mini graph preview and requirement checks before applying a preset. The library includes workday/weekend comfort, weather-aware demand, morning boost, presence and energy-price eco modes, peak-power limiting, open-window/humidity/overheat/frost guards, night profiles, sensor/device resilience, dual heating/cooling thresholds, multi-room group control, nested AND/OR/NOT Home Assistant scenarios, and gas-boiler/external-heat-source coordination including off, reduced-target, boost and fallback heating patterns.
Shared Flow inputs show the Flows that reference them and link directly to those editors. Home Assistant-backed shared inputs can be tested live in the input modal. Shared inputs report the current raw value/attribute. Each Flow reference defines its own comparison without changing the shared source.
Presets are data files, not JavaScript definitions. Every preset lives in presets/<id>.json and contains id, category, localized name, localized description, and the complete flow graph (nodes + edges). build.rs validates and embeds the directory, exposes /presets/index.json plus /presets/<file>.json, and the Flow editor loads that library dynamically. Device/zone/group placeholders such as $zone1, $zone2, $device1 and $group1 are resolved when a preset is applied. Adding a preset therefore does not require adding graph-building code to web/js/flows.js.
The canvas supports multi-selection (Shift/Ctrl/Cmd), Ctrl/Cmd+A, Select all / Clear selection toolbar actions, and dragging the whole selected group while preserving relative positions.
Good next stateful blocks
Stateful timing now has persisted runtime support for stable_for, bounded state duration, change detection, rolling execution limits, delay-before-action, rolling mean/median samples and oscillation detection. Useful next additions on the same runtime-state foundation are:
- debounce with configurable dead time,
- a dedicated external-entity thermostat/hysteresis action (rather than duplicating the built-in HVAC thermostat),
- explicit rising/falling edge modes,
- retry/backoff for external service failures,
- variables/counters,
- reusable subflows/macros,
- explicit priority/mutex groups for actions,
- trace replay from historical sensor samples.
These need durable state and restart semantics before they should be exposed in the editor.
Display name / alias
The Flow name edited in /flows is a presentation alias. Renaming it changes labels shown in the UI (including the dashboard) but does not change the stable Flow ID or generated flow-<unique-id> schedule/automation names.