Examples
Scene scheduler
A parent app that hosts any number of child "run this scene at this time" rules, and hears back from them.
Two definitions, registered separately. Scene Scheduler is installed once and shows a list of its children with an Add new button; each Scheduled Scene child runs one scene at one time on chosen days. The children tell the parent when they ran with a location event, so the parent can keep a tally without any shared state.
What it shows
- The
parent/childAppsrelationship and theappinput that renders the children. See Parent and child apps. getChildApps()andrunSmartApp()from the parent;runDailyand anenumwithmultiplein the child.- Location events as the back-channel between instances. See Location events.
Declaring the relationship
The link is metadata on the registered definitions, matched by namespace + name:
Scene Scheduler: childApps: [{ namespace: 'com.example', name: 'Scheduled Scene' }], singleInstance: true
Scheduled Scene: parent: { namespace: 'com.example', name: 'Scene Scheduler' }Register the child first so the parent's childRef resolves when its form is rendered.
The parent
const definition = {
name: 'Scene Scheduler',
namespace: 'com.example',
description: 'Hosts any number of scheduled scenes.',
category: 'automation',
};
const preferences = {
pages: [{
id: 'main',
title: 'Scene Scheduler',
inputs: [
{ key: 'schedules', label: 'Scheduled scenes', type: 'app', multiple: true,
childRef: { namespace: 'com.example', name: 'Scheduled Scene' } },
{ key: 'logging', label: 'Log each run', type: 'boolean', default: false },
],
}],
};
function installed() { initialize(); }
function updated() { unsubscribe(); unschedule(); initialize(); }
function initialize() {
subscribeToLocationEvent('com.example.scheduledScene.ran', onChildRan);
console.log(`${getChildApps().length} scheduled scene(s)`);
}
function onChildRan(evt) {
state.runs = (state.runs ?? 0) + 1;
state.lastRun = evt.value; // { appId, scene, at } from the child
if (logging === true) console.log('Scene ran:', JSON.stringify(evt.value));
}
function run() { // Run on the parent runs every child now
for (const child of getChildApps()) {
const result = runSmartApp(child.appId);
if (!result.ok) console.warn(`Could not run ${child.appName}: ${result.reason}`);
}
}The child
const definition = {
name: 'Scheduled Scene',
namespace: 'com.example',
description: 'Runs one scene at a time of day on chosen days.',
category: 'automation',
};
const preferences = {
pages: [{
id: 'main',
title: 'Scheduled Scene',
inputs: [
{ key: 'scene', label: 'Scene', type: 'entity', domainFilter: 'scene', required: true },
{ key: 'time', label: 'Run at', type: 'time', required: true },
{ key: 'days', label: 'Days', type: 'enum', multiple: true, description: 'Leave empty for every day.',
options: [
{ value: 'Mon', label: 'Monday' }, { value: 'Tue', label: 'Tuesday' }, { value: 'Wed', label: 'Wednesday' },
{ value: 'Thu', label: 'Thursday' }, { value: 'Fri', label: 'Friday' }, { value: 'Sat', label: 'Saturday' },
{ value: 'Sun', label: 'Sunday' },
] },
],
}],
};
function installed() { initialize(); }
function updated() { unsubscribe(); unschedule(); initialize(); }
function initialize() {
if (typeof time !== 'string') return;
runDaily(time, onSchedule); // home time zone; persists across reboots
}
function onSchedule() {
const today = formatDateParts(new Date(), getHomeTimeZone()).weekday; // 'Mon' … 'Sun'
const picked = Array.isArray(days) ? days : typeof days === 'string' && days ? [days] : [];
if (picked.length > 0 && !picked.includes(today)) return;
activate();
}
function run() { // the parent's runSmartApp and this child's own Run button
activate();
}
function activate() {
scene.turnOn();
state.lastRunAt = new Date().toISOString();
const me = getParent(); // null if someone installed the child on its own
sendLocationEvent('com.example.scheduledScene.ran', { appId: me ? me.appId : null, scene: scene.entityId, at: state.lastRunAt });
}How it works
- The parent has almost no behaviour. Its
appinput is a live view of the installed children (not stored in its settings and not hydrated), so the children are the only thing it needs to show. - Each child is an ordinary app with its own settings,
stateand schedule. Installing a new one from the parent's form setsparentAppId; uninstalling the parent uninstalls the children first. - Instances do not share state.
getChildApps()returns identity handles only. The child publishes what the parent wants to know withsendLocationEvent, and the parent subscribes by name. Any other app on the hub could subscribe too; namespace the event name. runis reused.runSmartApp(appId)from the parent fires the child'srun(), the same entry point as the child's own Run button, so both skip the day check deliberately.
Try changing
- Pause a child from the parent (
pauseSmartApp(child.appId, 3600)) for a one-hour snooze. - Give the child a second
sceneto run at a secondtime, or replace the time withscheduleAtSunsetand re-arm it as in Porch light.