Modes¶
Orchestration: cross-cutting state machines that flip the whole house in one gesture. This page also covers their two companion entities -- Button Action Bindings (one-tap triggers) and Calendar (scheduled mode changes).
Mode¶
A Mode is a named state (e.g. "Day", "Night", "Away", "Vacances") that, when activated, applies a configured set of impacts across one or more zones.
1 Interfaces¶
interface Mode {
id: string;
name: string;
icon?: string;
description?: string;
active: boolean; // Persisted -- restored on engine restart
createdAt: string;
updatedAt: string;
}
type ZoneModeImpactAction =
| { type: "order"; equipmentId: string; orderAlias: string; value: unknown }
| { type: "recipe_toggle"; instanceId: string; enabled: boolean }
| { type: "recipe_params"; instanceId: string; params: Record<string, unknown> };
interface ZoneModeImpact {
id: string;
modeId: string;
zoneId: string;
actions: ZoneModeImpactAction[]; // Stored as JSON in the row
}
interface ModeWithDetails extends Mode {
impacts: ZoneModeImpact[];
}
2 Behavior¶
- Modes are not exclusive by default -- multiple modes can be active simultaneously. Mutual exclusion (e.g. Day vs Night) is enforced at the policy level (calendar configuration, button bindings).
- On
activateMode(id), the manager iterates allZoneModeImpactrows for the mode and applies each action: dispatching equipment orders, toggling recipe instances, or updating recipe instance params. applyModeToZone(modeId, zoneId)re-runs the impact for a single zone without changing the active flag (useful for "re-apply current mode" on a specific room).- Action errors are logged but never abort the rest of the impact set.
3 SQLite Schema¶
CREATE TABLE modes (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
icon TEXT,
description TEXT,
active INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE zone_mode_impacts (
id TEXT PRIMARY KEY,
mode_id TEXT NOT NULL REFERENCES modes(id) ON DELETE CASCADE,
zone_id TEXT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
actions TEXT NOT NULL -- JSON: ZoneModeImpactAction[]
);
CREATE UNIQUE INDEX idx_zone_mode_impacts_unique ON zone_mode_impacts(mode_id, zone_id);
Button Action Binding¶
A ButtonActionBinding wires a Zigbee button (or any Equipment of type button whose data binding alias is action) to a high-level effect. When the bound action value fires, the engine executes the configured effect.
type ButtonEffectType =
| "mode_activate" // config: { modeId }
| "mode_toggle" // config: { modeOnId, modeOffId } (mutually exclusive pair)
| "equipment_order" // config: { equipmentId, orderAlias, value }
| "recipe_toggle" // config: { instanceId }
| "zone_order"; // config: { zoneId, orderKey, value? }
interface ButtonActionBinding {
id: string;
equipmentId: string; // FK -> Equipment of type "button"
actionValue: string; // Match against the equipment's action alias value
effectType: ButtonEffectType;
config: Record<string, unknown>; // Shape depends on effectType (see above)
createdAt: string;
}
The manager subscribes to equipment.data.changed events with alias === "action" and dispatches the matching binding.
SQLite Schema¶
CREATE TABLE button_action_bindings (
id TEXT PRIMARY KEY,
equipment_id TEXT NOT NULL REFERENCES equipments(id) ON DELETE CASCADE,
action_value TEXT NOT NULL,
effect_type TEXT NOT NULL,
config TEXT NOT NULL DEFAULT '{}', -- JSON
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
Calendar (Mode Scheduling)¶
Calendars schedule mode activations/deactivations on a recurring weekly basis. Two profiles are seeded at first run: Travail and Vacances. Only one profile is active at a time (key calendar.activeProfileId in the settings table).
interface CalendarProfile {
id: string;
name: string;
builtIn: boolean;
createdAt: string;
}
interface CalendarModeAction {
modeId: string;
action: "on" | "off";
}
interface CalendarSlot {
id: string;
profileId: string;
days: number[]; // 0=Sunday..6=Saturday
time: string; // "HH:MM" (local time)
modeActions: CalendarModeAction[]; // Multiple modes can be flipped at the same slot
}
CalendarManager uses croner to schedule every slot of the active profile. On profile change, all crons are torn down and rebuilt. Slot firing calls ModeManager.activateMode / deactivateMode and emits calendar.profile.changed when the active profile itself is changed.
SQLite Schema¶
CREATE TABLE calendar_profiles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
built_in INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Seeded:
-- ('travail', 'Travail', 1)
-- ('vacances', 'Vacances', 1)
CREATE TABLE calendar_slots (
id TEXT PRIMARY KEY,
profile_id TEXT NOT NULL REFERENCES calendar_profiles(id) ON DELETE CASCADE,
days TEXT NOT NULL, -- JSON: number[]
time TEXT NOT NULL, -- "HH:MM"
mode_ids TEXT NOT NULL, -- legacy column, kept for back-compat (JSON)
mode_actions TEXT -- JSON: CalendarModeAction[]
);