Docs

Getting started

Write, register and run a motion-activated light in about forty lines.

You will build the same app the hub ships as Simple Motion Light: when a motion sensor reports motion, one or more lights turn on; when motion has been clear for a few minutes, they turn off.

Before you start

  • A Novon hub with at least one motion sensor and one light paired to it.
  • Access to the Novon admin console (to register code) and the Novon app (to install it).

The code

Describe the app

definition is what people see in the install picker. The hub identifies the app by namespace + name, so pick a namespace you control.

const definition = {
  name: 'Simple Motion Light',
  namespace: 'com.example',
  description: 'Turns lights on when motion is detected and off after motion stops for a set time.',
  category: 'lighting',
};

Ask for devices and options

preferences describes the install form. Every input's key becomes a variable you can use directly in your code: motionSensor will be a device handle, lights one handle or an array of them, offAfterMinutes a number.

const preferences = {
  pages: [
    {
      id: 'main',
      title: 'Simple Motion Light',
      inputs: [
        {
          key: 'motionSensor',
          label: 'Motion sensor',
          type: 'entity',
          domainFilter: 'binary_sensor',
          deviceClassFilter: 'motion',
          required: true,
        },
        {
          key: 'lights',
          label: 'Lights to control',
          type: 'entity',
          domainFilter: 'light',
          multiple: true,
          required: true,
        },
        {
          key: 'offAfterMinutes',
          label: 'Turn off after no motion for (minutes)',
          type: 'number',
          default: 1,
          minValue: 1,
        },
      ],
    },
  ],
};

Wire up the lifecycle

The hub calls installed once and updated every time someone changes the settings. Both set up subscriptions through a shared initialize; updated tears the old ones down first so a swapped sensor does not leave a stale subscription behind.

function installed() {
  initialize();
}

function updated() {
  unsubscribe();  // drop every subscription this instance made
  unschedule();   // and every pending timer
  initialize();
}

function initialize() {
  subscribe(motionSensor, { to: 'on' }, onMotion);
  subscribe(motionSensor, { to: 'off' }, onMotionCleared);
}

'on' and 'off' are the sensor's raw tokens. You could also write the labels the app shows for a motion sensor, { to: 'Detected' } and { to: 'Clear' }; see Raw tokens and friendly labels.

React to motion

Handlers are plain top-level functions; the hub calls them by name. state is the only memory that survives between calls, so the motion counter lives there.

function onMotion(evt) {
  unschedule(turnLightsOff);                          // motion resumed: cancel a pending off
  state.motionCount = (state.motionCount ?? 0) + 1;
  state.lastMotionAt = new Date().toISOString();
  console.log(`Motion #${state.motionCount} from ${evt.entityId}`);
  lights.turnOn();                                    // one light or many: the call fans out
}

function onMotionCleared() {
  const minutes = typeof offAfterMinutes === 'number' && offAfterMinutes > 0 ? offAfterMinutes : 1;
  runIn(minutes * 60, turnLightsOff);                 // re-arming replaces the previous timer
}

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

Register and install

In the Novon admin console open Smart apps → Register a smart app, paste the file and save. If the hub rejects it, the message names the problem (an unknown subscription filter key, a banned API such as setTimeout, or a file over the size limit). See Validation rules.

In the Novon app open Automations → Smart apps, add Simple Motion Light, and pick your sensor, lights and delay. Saving the form runs installed.

Wave at the sensor. Open the instance to see the log lines from console.log, then stand still and watch the lights go off after the delay.

What just happened

  • Each handler ran in a fresh sandbox. onMotion and turnLightsOff never shared a variable; the counter persisted only because it lives in state. Read How smart apps run.
  • runIn replaced its own timer. Every clear report re-armed the off-timer, so the lights switched off a fixed time after the last motion. See Scheduling.
  • lights.turnOn() worked for one light or five. A multi-select input hydrates to a proxy that fans the call out. See Entities and services.
  • Changing the settings re-ran updated. That is why it tears everything down first. See Lifecycle.

Next

  • Add a brightness option and apply it with lights.turnOn({ brightnessPercent }) — the light capability lists the parameters.
  • Only run at night: compare getSunTimes() with formatDateParts(new Date(), getHomeTimeZone()) inside onMotion. See Scheduling › Time zones.
  • Prefer TypeScript? The same app with types is in TypeScript authoring.

On this page