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 turningevt.newState.stateinto 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.
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. Afrom/tofilter only knows exact tokens, which is useless for21.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(...)andisFinitecover the numeric string the hub may store as well as theunavailable/unknowntokens every entity can report.- Hysteresis.
highAlertedflips on when the reading passeshigh, and only flips back once the reading ismarginbelow 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)inonReading(replace semantics give you a watchdog for free) and notify innoReading. - Watch several sensors: make the input
multiple: trueand keystate.highAlertedbyevt.entityId, as Door left open does.