diff --git a/backend/src/db.js b/backend/src/db.js index 6223a61..5331d28 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -146,6 +146,7 @@ export async function initDb() { async function seedDefaults() { const defaults = { + automation_enabled: true, // master kill switch — overrides every zone's own auto_mode excluded_site_ids: [], poll_interval_minutes: 10, // matches the old integration's DEFAULT_SCAN_INTERVAL default_arrival_time: '15:00:00', diff --git a/backend/src/lib/scheduler.js b/backend/src/lib/scheduler.js index a9fda90..0a8b5f1 100644 --- a/backend/src/lib/scheduler.js +++ b/backend/src/lib/scheduler.js @@ -148,6 +148,12 @@ async function saveZoneState(zoneId, roomState, bookingStatus) { // One poll cycle: fetch bookings once, walk every active room zone, apply heating // logic only where the room's state has actually changed since the last cycle. export async function pollOnce() { + // Master kill switch — overrides every zone's own auto_mode. Off means a full + // pause: no NewBook fetch, no state tracking, no device commands. Deliberately + // silent (no activity_log entry) since "paused" is a normal standing state, not + // an event — the Settings toggle itself is the record of when it changed. + if (!(await getConfig('automation_enabled', true))) return + const defaults = { default_arrival_time: await getConfig('default_arrival_time', '15:00:00'), default_departure_time: await getConfig('default_departure_time', '10:00:00'), diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 878a652..2399334 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -15,6 +15,8 @@ export default function Settings() { const [syncing, setSyncing] = useState(false) const [config, setConfig] = useState(null) + const [automationEnabled, setAutomationEnabled] = useState(true) + const [automationSaving, setAutomationSaving] = useState(false) const [pollMinutes, setPollMinutes] = useState(10) const [defaultArrival, setDefaultArrival] = useState('15:00:00') const [defaultDeparture, setDefaultDeparture] = useState('10:00:00') @@ -39,6 +41,7 @@ export default function Settings() { loadSites() fetchConfig().then(cfg => { setConfig(cfg) + setAutomationEnabled(cfg.automation_enabled ?? true) setPollMinutes(cfg.poll_interval_minutes ?? 10) setDefaultArrival(cfg.default_arrival_time ?? '15:00:00') setDefaultDeparture(cfg.default_departure_time ?? '10:00:00') @@ -86,6 +89,21 @@ export default function Settings() { } } + async function handleAutomationToggle() { + const next = !automationEnabled + setAutomationSaving(true); setError(''); setMsg('') + try { + await updateConfig('automation_enabled', next) + setAutomationEnabled(next) + setMsg(next ? 'Automation resumed' : 'Automation paused — no setpoints will be sent to any device until resumed') + setTimeout(() => setMsg(''), 4000) + } catch (e) { + setError(e instanceof Error ? e.message : 'Failed to update automation state') + } finally { + setAutomationSaving(false) + } + } + async function saveGeneralConfig() { setConfigSaving(true); setError(''); setMsg('') try { @@ -130,6 +148,26 @@ export default function Settings() { {error &&
{error}
} {msg &&
{msg}
} +
Automation
+
+
+
+ {automationEnabled ? 'Automation is ON' : 'Automation is PAUSED'} +
+
+ Master switch for every zone — overrides each zone's own auto mode. While paused, the scheduler + skips its poll entirely: no NewBook fetch, no state tracking, no setpoints sent to any device. +
+
+ +
+
MQTT Broker
{mqttConnected ? : } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 420af65..8e8ab50 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -170,6 +170,7 @@ export interface NewbookSite { } export interface AppConfig { + automation_enabled?: boolean poll_interval_minutes: number default_arrival_time: string default_departure_time: string