Browse the docs

sensors

Adds sensors: named readings that Switchboard keeps the latest value of, for expressions, conditions and triggers.

Details #

A sensor is a value that streams into Switchboard: a battery level, a temperature, the part of the day. The engine starts every sensor when the extension loads (and again after a crash) and keeps the latest value. Macros can then use it in three ways, with no more code from you. The sensor's name is <extension id>.<sensor id>.

  • Expressions: {{ sensor("com.example.sample-sensors.battery") }} is the latest value, or null before the first one.

  • Conditions: the name is also a condition under Only if. Params above, below, atLeast, atMost and equals compare the value (all that are set must hold). With none, it means "the value is truthy".

  • Triggers: the name is also a trigger under When, which fires when the value changes. Params above and below fire only when the value crosses the line, and equals when it becomes equal. The macro gets value, previous and sensor as inputs.

You declare it under adds.sensors and implement it with sensor(id, hooks). There are two forms.

{
  "adds": {
    "sensors": [
      { "id": "battery", "title": "Battery level", "output": { "type": "integer", "minimum": 0, "maximum": 100 } }
    ]
  },
  "permissions": { "exec": { "commands": ["/usr/bin/pmset"] } }
}
import { defineExtension, sensor } from '@switchboard/sdk';

export default defineExtension({
  sensors: [
    // Polling: read() runs every everyMs and the SDK emits what it returns.
    sensor<number>('battery', {
      everyMs: 60000,
      async read(ctx) {
        const r = await ctx.exec('/usr/bin/pmset', ['-g', 'batt']);
        return Number(/(\d+)%/.exec(r.stdout)?.[1]);
      },
    }),
    // Push: start() gets emit and calls it when the value changes. Return a cleanup.
    sensor<string>('mood', {
      start(ctx, emit) {
        const t = setInterval(() => emit('calm'), 30000);
        return () => clearInterval(t);
      },
    }),
  ],
});

The declaration and the code must match. An extension that declares a sensor it does not implement, or implements one it does not declare, is refused when it loads.

Parameters #

A sensor has no params: it is one reading for the whole Mac. Unknown keys in an entry are refused.

Gotchas #

  • The value must be JSON. Switchboard only treats a different value as a change, so emitting the same value again fires no trigger.

  • Pick a polling interval that suits the thing. Reading the battery once a minute is plenty.

  • start has 10 seconds to return, so start your timer and return.

  • Anything the sensor does outside the sandbox needs a permission in the manifest, for example permissions.exec for the program it runs.

  • The latest values are listed at GET /extensions/sensors on the engine's API.

The two forms #

  • read + everyMs: Switchboard polls. everyMs is at least 1000 and defaults to 5000. A read that is still running is not started again. A read that throws is logged as a warning and the sensor keeps its last value.

  • start(ctx, emit): You push values when you have them, for example from a WebSocket or a timer. start may return a cleanup, and you may add stop. A sensor needs read or start; it is an error to give neither.

Full example #

The sample below has two sensors: the battery level (polled) and the part of the day (pushed). You can download it from the Full example section and edit it into your own.

See also #