With the upgrade to v5.1 I, like I assume most are doing, have been updating my javascript based rules. In doing so I came across 4 rules that I use to monitor appliances, and announce when they are finished. What started out years ago as 2 rules - one for our washer and one for the dryer, has grown to 4 nearly identical rules. I thought that surely, there is a more efficient, and elegant way to do this.
The first thing I noticed is that all have 2 things in common. They all monitor the power usage from a smart plug, and they all announce when the appliance is done, sending text notifications to my wife and I as well as announcing it on our Echo devices.
This is where I asked myself, âwhat would Rich or Justin do?â Aha, openhab-rules-tools. I created 2 helper files and saved them in $[CONF}/automations/js/node_modules/mytools. The first is applianceFinish.js. Here it is:
const { Gatekeeper } = require('openhab_rules_tools');
/**
* Appliance Finish Detector
* Optimized for openHAB 5.1
*/
function applianceFinishDetector({
applianceId,
powerItem,
runningWatts,
idleWatts,
idleDuration = 'PT10M',
onFinished
}) {
const logPrefix = `[ApplianceFinish:${applianceId}]`;
const item = items.getItem(powerItem);
// OH 5.1: Use numericState for cleaner unit handling
const power = item.numericState;
if (power === null) {
console.warn(`${logPrefix} Item ${powerItem} has no numeric state. Skipping.`);
return;
}
const gk = cache.private.get(`${applianceId}_finishGK`, () => {
console.info(`${logPrefix} Initializing Gatekeeper`);
return new Gatekeeper();
});
const wasRunningKey = `${applianceId}_wasRunning`;
const idleArmedKey = `${applianceId}_idleArmed`;
const wasRunning = cache.private.get(wasRunningKey, () => false);
const idleArmed = cache.private.get(idleArmedKey, () => false);
// 1. START/RUNNING DETECTION
if (power >= runningWatts) {
if (!wasRunning) console.info(`${logPrefix} Appliance started (Power: ${power}W)`);
// Clear any pending "Finish" timers because we are actively running
gk.cancelAll();
cache.private.put(wasRunningKey, true);
cache.private.put(idleArmedKey, false);
return;
}
// 2. IDLE DETECTION (Only care if it was previously running)
if (power < idleWatts && wasRunning && !idleArmed) {
console.info(`${logPrefix} Power dropped to ${power}W. Arming finish timer (${idleDuration})`);
cache.private.put(idleArmedKey, true);
gk.addCommand(idleDuration, () => {
// Re-check state after timer expired
const currentPower = items.getItem(powerItem).numericState;
if (currentPower < idleWatts) {
console.info(`${logPrefix} FINISHED: Maintained idle for ${idleDuration}`);
cache.private.put(wasRunningKey, false);
cache.private.put(idleArmedKey, false);
if (typeof onFinished === 'function') onFinished();
} else {
console.info(`${logPrefix} Finish aborted: Power rose to ${currentPower}W`);
cache.private.put(idleArmedKey, false);
}
});
}
}
module.exports = { applianceFinishDetector };
The second is notifications.js, here it is:
function notifyIfHome(recipients, message) {
recipients.forEach(({ presenceItem, email }) => {
if (items.getItem(presenceItem).state === 'ON') {
actions.NotificationAction.sendNotification(email, message);
}
});
}
// Announce a message on multiple Echos, but allows for conditional filtering logic.
function announceOnEchos(echoItems, message) {
// Ensure we are working with an array
const itemsToAnnounce = Array.isArray(echoItems) ? echoItems : [echoItems];
itemsToAnnounce.forEach(itemName => {
// Logic for the Bedroom specific restriction
if (itemName === 'Echo_Bedroom_TTS') {
const hour = time.ZonedDateTime.now().hour();
if (hour < 8 || hour >= 20) {
console.debug(`[Helper] Muting Bedroom Echo due to quiet hours (${hour}:00)`);
return; // Skip this specific item
}
}
const item = items.getItem(itemName);
if (item) {
item.sendCommand(message);
}
});
}
module.exports = {
notifyIfHome,
announceOnEchos
};
Finally, I put the recipients and Echo devices in a globalConfig.js file for central management. Here it is:
// /automation/js/node_modules/mytools/globalConfig.js
const Recipients = [
{ name: 'Chet', presenceItem: 'ChetPresence', email: âchet@email.com' },
{ name: 'Kat', presenceItem: 'KatPresence', email: 'kat@email.com }
];
const Echo_Devices = [
'Echo_Living_Room_TTS',
'Echo_Office_TTS',
'Echo_Bedroom_TTS' // Our helper already handles the quiet hours for this!
];
module.exports = { Recipients, Echo_Devices };
notifyIfHome(Recipients, messageEN);
}
});
With those functions split off into helper scripts in a globally reachable area, my new rules look like this:
const { applianceFinishDetector } = require('mytools/applianceFinish');
const { notifyIfHome, announceOnEchos } = require('mytools/notifications');
const { Recipients, Echo_Devices } = require('mytools/globalConfig');
// Call the helper to detect when the dryer finishes
applianceFinishDetector({
applianceId: 'dryer', // Unique ID for the dryer
powerItem: 'Dryer_Power', // Your actual dryer power item
runningWatts: 200, // Adjust based on dryer idle vs running power
idleWatts: 170, // Power threshold considered "idle"
idleDuration: 'PT10m', // Duration the dryer must stay idle before notifying
onFinished: () => {
// Messages for notification
const messageEN = 'May I have your attention please. All passengers on flight Dryer number 8 can collect their laundry at carousel number 8!';
const messageDE = 'Achtung bitte. Alle Passagiere des Fluges Dryer Nummer 8 können ihre WÀsche am GepÀckband Nummer 8 abholen.!';
announceOnEchos(Echo_Devices, messageDE);
notifyIfHome(Recipients, messageEN);
}
});
The only differences between them are the specific values of the applianceFinishDetector function, and the announcement. Itâs pretty much a cookie cutter operation to add any other appliance rule.
Why do I send the notifications only to people who are home? If Iâm not home I canât act on the notification, so it doesnât make sense to send it.