Docs
Examples

Porch light

Lights on a little before sunset, off at a fixed time, every day, and still correct after a reboot.

The classic evening light. Sunset moves every day, so the on-time is anchored to the hub's sun times; the off-time is a plain daily schedule.

What it shows

  • scheduleAtSunset is one-shot and is re-armed inside its own handler and on systemStart. See Daily sun-anchored behaviour.
  • runDaily for the fixed off-time; daily schedules persist on their own.
  • A brightness input that only appears when the picked lights can dim, via requiresEntityFeature. See Gating on the picked device.
porch-light.js
const definition = {
  name: 'Porch Light',
  namespace: 'com.example',
  description: 'Turns lights on before sunset and off at a fixed time every night.',
  category: 'lighting',
};

const preferences = {
  pages: [{
    id: 'main',
    title: 'Porch Light',
    inputs: [
      { key: 'lights', label: 'Lights', type: 'entity', domainFilter: 'light', multiple: true, required: true },
      { key: 'beforeSunsetMinutes', label: 'Turn on this many minutes before sunset',
        type: 'number', default: 20, minValue: 0, maxValue: 120 },
      { key: 'brightness', label: 'Brightness (1–100)', type: 'number', minValue: 1, maxValue: 100,
        requiresEntityFeature: { siblingKey: 'lights', domain: 'light', colorMode: 'brightness' } },
      { key: 'offTime', label: 'Turn off at', type: 'time', default: '23:00', required: true },
    ],
  }],
};

function installed() { initialize(); }
function updated() { unsubscribe(); unschedule(); initialize(); }

function initialize() {
  subscribeToLocationEvent('systemStart', armSunset);   // a reboot may have dropped the pending one-shot
  runDaily(typeof offTime === 'string' ? offTime : '23:00', lightsOff);
  armSunset();
}

function armSunset() {
  const minutes = typeof beforeSunsetMinutes === 'number' ? beforeSunsetMinutes : 20;
  scheduleAtSunset(lightsOn, -minutes);                 // fires once, at the next sunset
}

function lightsOn() {
  const params = typeof brightness === 'number' ? { brightnessPercent: brightness } : {};
  lights.turnOn(params);                                // one light or many: the call fans out
  state.lastOnAt = new Date().toISOString();
  armSunset();                                          // re-arm for tomorrow
}

function lightsOff() {
  lights.turnOff();
}

function run() {                                        // the Run button: "do the evening thing now"
  lightsOn();
}

How it works

  • Sunset is a one-shot. scheduleAtSunset arms a single firing at the next sunset. lightsOn ends by arming the next one, which is what makes it daily.
  • systemStart is the safety net. The hub restores pending timers after a reboot, but re-arming on boot costs nothing and protects against the edge cases (a reboot that spans the sunset, a hub that only got its location after install).
  • runDaily needs no re-arming. Cron and daily schedules are persistent; they are torn down in updated so a changed off-time does not leave the old one firing.
  • Before the hub has a location the sun times are unavailable and scheduleAtSunset cannot fire. Set the monitoring address in the Novon app; getSunTimes().available tells you if it is set.

Try changing

  • Turn the lights off at sunrise instead of a fixed time: replace runDaily with a scheduleAtSunrise(lightsOff) that re-arms itself the same way.
  • Only run when nobody is home: wrap lights.turnOn in if (new EntityWrapper('system.group_presence').is('Not Present')). See System entities.

On this page