Docs
Examples

Temperature alert

Warn when a temperature sensor goes above or below a limit, once per excursion, with a margin before the alert resets.

A freezer, a server cupboard, a greenhouse. Numeric sensors are different from switches and doors: their state is a number, so you subscribe to every change and compare in the handler. The margin (hysteresis) stops a reading hovering around the limit from sending a notification every minute.

What it shows

  • subscribe(sensor, {}, handler) for a numeric domain, and turning evt.newState.state into a number safely. See Sensors report values, not enums.
  • Hysteresis with two booleans in state.
  • Labelling values with getUnitPreferences().temperatureScale (readings already arrive in the home's units). See Units.
temperature-alert.js
const definition = {
  name: 'Temperature Alert',
  namespace: 'com.example',
  description: 'Warns when a temperature sensor goes above or below a limit, once per excursion.',
  category: 'monitoring',
};

const preferences = {
  pages: [{
    id: 'main',
    title: 'Temperature Alert',
    inputs: [
      { key: 'sensor', label: 'Temperature sensor', type: 'entity',
        domainFilter: 'sensor', deviceClassFilter: 'temperature', required: true },
      { key: 'high', label: 'Alert above', type: 'number', description: "In the home's temperature unit. Leave empty for no upper limit." },
      { key: 'low', label: 'Alert below', type: 'number', description: 'Leave empty for no lower limit.' },
      { key: 'margin', label: 'Reset margin', type: 'number', default: 1, minValue: 0,
        description: 'How far back inside the limit the reading must come before the alert resets.' },
      { key: 'recipients', label: 'Notify', type: 'accountUserId', multiple: true },
    ],
  }],
};

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

function initialize() {
  subscribe(sensor, {}, onReading);                  // numeric sensors: every change, compare in the handler
}

function onReading(evt) {
  const value = Number(evt.newState.state);
  if (!Number.isFinite(value)) return;               // 'unavailable' / 'unknown' / null
  const band = typeof margin === 'number' && margin >= 0 ? margin : 1;
  const unit = getUnitPreferences().temperatureScale;

  if (typeof high === 'number') {
    if (value > high && state.highAlerted !== true) {
      state.highAlerted = true;
      notify(`${nameOf()} is ${value}${unit}, above ${high}${unit}.`, true);
    } else if (value <= high - band && state.highAlerted === true) {
      state.highAlerted = false;
      notify(`${nameOf()} is back to ${value}${unit}.`, false);
    }
  }

  if (typeof low === 'number') {
    if (value < low && state.lowAlerted !== true) {
      state.lowAlerted = true;
      notify(`${nameOf()} is ${value}${unit}, below ${low}${unit}.`, true);
    } else if (value >= low + band && state.lowAlerted === true) {
      state.lowAlerted = false;
      notify(`${nameOf()} is back to ${value}${unit}.`, false);
    }
  }
}

function notify(message, critical) {
  sendPushNotification(message, 'Temperature', critical, settings.recipients);
}

function nameOf() {
  try { return String(sensor.getAttribute('friendlyName')); } catch { return sensor.entityId; }
}

How it works

  • {} matches any change. A from / to filter only knows exact tokens, which is useless for 21.4. The empty filter also fires on attribute-only changes, which is why the handler parses and compares rather than assuming the state moved.
  • Number(...) and isFinite cover the numeric string the hub may store as well as the unavailable / unknown tokens every entity can report.
  • Hysteresis. highAlerted flips on when the reading passes high, and only flips back once the reading is margin below it. In between, nothing is sent.
  • No conversion. Readings and limits are both in the location's unit system, so the unit is only used to label the message.

Try changing

  • Alert on a stale sensor: runIn(6 * 3600, noReading) in onReading (replace semantics give you a watchdog for free) and notify in noReading.
  • Watch several sensors: make the input multiple: true and key state.highAlerted by evt.entityId, as Door left open does.

On this page