Examples
Away setback
Set the thermostat back a while after everyone leaves and restore it when someone comes home.
The hub maintains one presence entity for the whole household, system.group_presence, which is
on while anyone is home. The app watches it, waits a few minutes after the last person leaves
(so a trip to the bins does not count), then sets the thermostat back; the first arrival restores
the home setpoint.
What it shows
- Wrapping a system entity you did not ask for with
new EntityWrapper(id). See Entities you did not ask for. - Presence is a
binary_sensor:'Present'/'Not Present'are the labels foron/off. - Thermostat services and the rule that a thermostat reports
heatorcool, neverauto. See the climate capability. - A delayed action that a quick return cancels.
const definition = {
name: 'Away Setback',
namespace: 'com.example',
description: 'Sets the thermostat back when everyone leaves and restores it when someone comes home.',
category: 'climate',
};
const preferences = {
pages: [{
id: 'main',
title: 'Away Setback',
inputs: [
{ key: 'thermostat', label: 'Thermostat', type: 'entity', domainFilter: 'climate', required: true },
{ key: 'awayAfterMinutes', label: 'Wait after the last person leaves (minutes)',
type: 'number', default: 15, minValue: 0, maxValue: 180 },
{ key: 'awayHeat', label: 'Away heating setpoint', type: 'number', required: true,
description: "In the home's temperature unit." },
{ key: 'awayCool', label: 'Away cooling setpoint', type: 'number', required: true },
{ key: 'homeHeat', label: 'Home heating setpoint', type: 'number', required: true },
{ key: 'homeCool', label: 'Home cooling setpoint', type: 'number', required: true },
],
}],
};
function installed() { initialize(); }
function updated() { unsubscribe(); unschedule(); initialize(); }
function initialize() {
const household = new EntityWrapper('system.group_presence'); // on = someone is home
subscribe(household, { to: 'Not Present' }, onEveryoneLeft);
subscribe(household, { to: 'Present' }, onSomeoneHome);
}
function onEveryoneLeft() {
const minutes = typeof awayAfterMinutes === 'number' && awayAfterMinutes >= 0 ? awayAfterMinutes : 15;
runIn(minutes * 60, applyAway); // a quick return cancels this in onSomeoneHome
}
function onSomeoneHome() {
unschedule(applyAway);
if (state.away === true) applyHome(); // only restore if we actually set back
}
function applyAway() {
state.away = true;
applySetpoint(awayHeat, awayCool);
}
function applyHome() {
state.away = false;
applySetpoint(homeHeat, homeCool);
}
// A thermostat reports the leg it is driving, `heat` or `cool`; pick the matching setpoint.
function applySetpoint(heat, cool) {
const mode = thermostat.getState();
const target = mode === 'cool' ? cool : mode === 'heat' ? heat : null;
if (typeof target !== 'number') {
console.log(`Thermostat is ${mode}; nothing to set`);
return;
}
thermostat.setTemperature({ temperature: target });
const unit = getUnitPreferences().temperatureScale;
console.log(`${state.away ? 'Away' : 'Home'}: ${mode} setpoint set to ${target}${unit}`);
}
function run() { // Run button: flip between the two, handy for testing
if (state.away === true) applyHome();
else applyAway();
}How it works
system.group_presenceis not an input, so the code wraps the well-known id directly. The per-person entities aresystem.person_presence_<userId>if you want to react to one person.- The delay is a plain
runIn. If someone comes back inside the wait,unschedule(applyAway)drops it and nothing happens. BecauserunInreplaces its own timer, repeated leave events do not stack. state.awayrecords that this app changed the setpoint, so a return does not override a thermostat that someone adjusted by hand while the house was empty but the app never set back.setTemperaturetakes the single target for the current mode. If the thermostat isoff(or reportsunavailable) the app logs and does nothing.
Try changing
- Use the location mode instead of presence: subscribe to
new EntityWrapper('system.mode')with{ to: 'AWAY' }/{ from: 'AWAY' }. The modes are listed on the mode capability. - Offer a
modeinput (type: 'mode') and read it fromsettings.mode; it is not hydrated as a global.