Docs
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 for on / off.
  • Thermostat services and the rule that a thermostat reports heat or cool, never auto. See the climate capability.
  • A delayed action that a quick return cancels.
away-setback.js
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_presence is not an input, so the code wraps the well-known id directly. The per-person entities are system.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. Because runIn replaces its own timer, repeated leave events do not stack.
  • state.away records 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.
  • setTemperature takes the single target for the current mode. If the thermostat is off (or reports unavailable) 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 mode input (type: 'mode') and read it from settings.mode; it is not hydrated as a global.

On this page