Alert Sudden Rise in Temperature

I am trying to create a flow that will alert me of a sudden rise in temperature for my temperature sensors around the house.

He is what I have currently and suggestions or advice?

[{"id":"405f829e0a28b82e","type":"api-call-service","z":"892eaa13311e821a","name":"Notify John","server":"c33123cf.c144e","version":5,"debugenabled":false,"domain":"notify","service":"mobile_app_pixel_7_pro","areaId":[],"deviceId":[],"entityId":[],"data":"{ \"message\": payload  }","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"data"}],"queue":"none","x":1230,"y":2400,"wires":[[]]},{"id":"de1b06b9e93f77a9","type":"server-state-changed","z":"892eaa13311e821a","name":"Rapid Temp Increase","server":"c33123cf.c144e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"temperature","entityidfiltertype":"substring","outputinitially":true,"state_type":"num","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":140,"y":2360,"wires":[["5050be2bd911b8eb"]]},{"id":"53898adb1b4cd249","type":"comment","z":"892eaa13311e821a","name":"Rapid Temp Alert for Fire","info":"","x":150,"y":2280,"wires":[]},{"id":"5050be2bd911b8eb","type":"switch","z":"892eaa13311e821a","name":"Allow Numbers Only","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"number","vt":"number"}],"checkall":"true","repair":false,"outputs":1,"x":380,"y":2360,"wires":[["5a7e3e9d47f5fe84","5e22ad2c0d3a084e"]]},{"id":"b801bf0cadd6db71","type":"debug","z":"892eaa13311e821a","name":"debug 24","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":940,"y":2460,"wires":[]},{"id":"5a7e3e9d47f5fe84","type":"smooth","z":"892eaa13311e821a","name":"","property":"payload","action":"mean","count":"20","round":"","mult":"multi","reduce":false,"x":580,"y":2320,"wires":[["7c790a11a863ba6b"]]},{"id":"7c790a11a863ba6b","type":"function","z":"892eaa13311e821a","name":"function 3","func":"var t = msg.topic\nmsg.topic = msg.topic.replace(/[^a-zA-Z0-9]/g, \" \")\nmsg.topic = msg.topic.replace(/(^\\w|\\s\\w)/g, m => m.toUpperCase())\nflow.set(t, msg.payload.toFixed(0))\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":2320,"wires":[[]]},{"id":"5e22ad2c0d3a084e","type":"function","z":"892eaa13311e821a","name":"function 4","func":"var t = msg.topic // set topic variable\nvar avetemp = parseFloat(flow.get(t))// retrieve the value of the flow variable based on topic\nvar possibleTexts = [\"Bedroom\", \"Laundry\", \"Utility\", \"Garage\", \"Office\", \"Family\", \"Hallway\", \"Door\", \"Bathroom\", \"Kitchen\"]; // array of strings to check for in msg.topic\nvar delta = 10 // Different in Temp \nvar deltatemp = Math.round(avetemp+delta) // Calc Delta Temp\nvar curtemp = parseFloat(msg.payload.toFixed(0)) // Get Current Temp from Payload\nvar alert = \"Temperature High\" // Set Alert Text\nmsg.topic = msg.topic.replace(/[^a-zA-Z0-9]/g, \" \") // Change msg.topic replace non alphanumeric with \" \"\nmsg.topic = msg.topic.replace(/(^\\w|\\s\\w)/g, m => m.toUpperCase()) // Change msg.topic to First Letter Upper Case\n\n\n\n//  Check if msg topic includes allowed areas and if current temp exceeds average temp + delta\nif (possibleTexts.some(topic => msg.topic.includes(topic)) && (curtemp > avetemp + delta)) {\n    msg = { topic: msg.topic, payload: \"The \" + msg.topic + \" \" + alert + \" \" + curtemp, avetemp:avetemp, currenttemp: curtemp, deltatemp: deltatemp };\n    node.status({\n        text: msg.topic + \", \" + msg.payload + \" Average Temp: \" + avetemp + ' ' + \"Current Temp: \" + curtemp\n    });\n\n    return msg\n}\nelse\n\n    node.status({\n        text: msg.topic +  \" Normal\"\n    });\n\nflow.set(\"status\", msg)\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":640,"y":2400,"wires":[["b801bf0cadd6db71","405f829e0a28b82e"]]},{"id":"9ad1cd94e76fe6c5","type":"comment","z":"892eaa13311e821a","name":"Averaging Mean Temps by Topic","info":"","x":590,"y":2260,"wires":[]},{"id":"c7fc45f4e2dbe4d8","type":"comment","z":"892eaa13311e821a","name":"Set Flow Variable Average Temp by Topic","info":"","x":920,"y":2260,"wires":[]},{"id":"695b806a96e554a9","type":"comment","z":"892eaa13311e821a","name":"Check if Current Temp rose high then 10 degrees over average","info":"","x":610,"y":2460,"wires":[]},{"id":"c33123cf.c144e","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

Is this a Home Assistant flow??

What would you consider "sudden"?

One approach would be to calculate a simple moving average (SMA) of the input temperatures over a window that is reasonable for your application (10 data points, for example). Any new temperature value over that moving average becomes an alert.

This is a simple moving average function you can implement, where new temperature is carried on msg.payload

var windowSize = 10;  // How many values to include in the moving average


// If the SMA array of temperatures does not exist yet, create it
if (typeof flow.get("SMA") === "undefined"  || flow.get("SMA") === null) {
    flow.set("SMA", []);
}

var SMA = flow.get("SMA");

// If the SMA array of temepratures is less than the window size,
// add the new temperature to the array
if (SMA.length < windowSize) {
    SMA.push(msg.payload);
    

// If the SMA array of temepratures is full (ie equal to the array size),
// we need to shift the array and add the new value to the end
} else {
    SMA = SMA.slice(1, windowSize); // slice from the second value to the end
    SMA.push(msg.payload);  // add the most recent value to the end
}

flow.set("SMA", SMA);
msg.SMA = SMA;
msg.movingAverage = getAvg(SMA);

return msg;

function getAvg(values) {
  const total = values.reduce((acc, c) => acc + c, 0);
  return total / values.length;
}

To see this in an example, try this simple case:

[{"id":"045defddf411d517","type":"tab","label":"Flow 4","disabled":false,"info":"","env":[]},{"id":"b5407a90dfb28991","type":"inject","z":"045defddf411d517","name":"new temperature (5)","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"5","payloadType":"num","x":210,"y":200,"wires":[["e6f623bb0499d6e0"]]},{"id":"e6f623bb0499d6e0","type":"function","z":"045defddf411d517","name":"Calculate moving average","func":"var windowSize = 10;  // How many values to include in the moving average\n\n\n// If the SMA array of temperatures does not exist yet, create it\nif (typeof flow.get(\"SMA\") === \"undefined\"  || flow.get(\"SMA\") === null) {\n    flow.set(\"SMA\", []);\n}\n\nvar SMA = flow.get(\"SMA\");\n\n// If the SMA array of temepratures is less than the window size,\n// add the new temperature to the array\nif (SMA.length < windowSize) {\n    SMA.push(msg.payload);\n    \n\n// If the SMA array of temepratures is full (ie equal to the array size),\n// we need to shift the array and add the new value to the end\n} else {\n    SMA = SMA.slice(1, windowSize); // slice from the second value to the end\n    SMA.push(msg.payload);  // add the most recent value to the end\n}\n\nflow.set(\"SMA\", SMA);\nmsg.SMA = SMA;\nmsg.movingAverage = getAvg(SMA);\n\nreturn msg;\n\nfunction getAvg(values) {\n  const total = values.reduce((acc, c) => acc + c, 0);\n  return total / values.length;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":200,"wires":[["a51b91ccc7338706","9186a52879445e34","214aaa75b89b7d78"]]},{"id":"a51b91ccc7338706","type":"debug","z":"045defddf411d517","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"SMA","targetType":"msg","statusVal":"","statusType":"auto","x":440,"y":120,"wires":[]},{"id":"3791a9a1a579584e","type":"inject","z":"045defddf411d517","name":"new temperature (4)","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"4","payloadType":"num","x":210,"y":240,"wires":[["e6f623bb0499d6e0"]]},{"id":"5393a07bd24d56d3","type":"inject","z":"045defddf411d517","name":"new temperature (3)","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"3","payloadType":"num","x":210,"y":280,"wires":[["e6f623bb0499d6e0"]]},{"id":"033b3e7803b5d7d0","type":"inject","z":"045defddf411d517","name":"new temperature (50)","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":200,"y":320,"wires":[["e6f623bb0499d6e0"]]},{"id":"9186a52879445e34","type":"debug","z":"045defddf411d517","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"movingAverage","targetType":"msg","statusVal":"","statusType":"auto","x":480,"y":160,"wires":[]},{"id":"214aaa75b89b7d78","type":"function","z":"045defddf411d517","name":"Notify if over threshold","func":"// An alert is triggered if we are so many degrees over the moving average\n\nvar threshold = 5;\nvar delta = msg.payload - msg.movingAverage;\n\nif (delta > threshold) {\n    return msg;\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":820,"y":200,"wires":[["57e518c035641989"]]},{"id":"57e518c035641989","type":"template","z":"045defddf411d517","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Warning!!","output":"str","x":1040,"y":200,"wires":[["97ba9c6bc2b22fc6"]]},{"id":"97ba9c6bc2b22fc6","type":"debug","z":"045defddf411d517","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1210,"y":200,"wires":[]}]

Yes, Node red Home Assistant

I would probably make sure the high readings do not get sent to smooth node. I would also put a trigger node on the alert with a reset if temp falls back with in x seconds.
e.g.

[{"id":"688d7d9f0dc577b4","type":"inject","z":"65617ffeb779f51c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"utility","payload":"$round(($random()*10)+10, 2)","payloadType":"jsonata","x":110,"y":2020,"wires":[["5050be2bd911b8eb"]]},{"id":"5050be2bd911b8eb","type":"switch","z":"65617ffeb779f51c","name":"Allow Numbers Only","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"number","vt":"number"}],"checkall":"true","repair":false,"outputs":1,"x":140,"y":2100,"wires":[["5e22ad2c0d3a084e"]]},{"id":"5fff03092525e600","type":"inject","z":"65617ffeb779f51c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"bedroom","payload":"$round(($random()*10)+10, 2)","payloadType":"jsonata","x":130,"y":2180,"wires":[["5050be2bd911b8eb"]]},{"id":"ea2dcac5e3a4c953","type":"inject","z":"65617ffeb779f51c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"bedroom","payload":"30","payloadType":"num","x":130,"y":2220,"wires":[["5050be2bd911b8eb"]]},{"id":"846c7c996ffe47e7","type":"inject","z":"65617ffeb779f51c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"utility","payload":"30","payloadType":"num","x":100,"y":2060,"wires":[["5050be2bd911b8eb"]]},{"id":"5e22ad2c0d3a084e","type":"function","z":"65617ffeb779f51c","name":"function 4","func":"const possible_topics = [\"Bedroom\", \"Laundry\", \"Utility\", \"Garage\", \"Office\", \"Family\", \"Hallway\", \"Door\", \"Bathroom\", \"Kitchen\"]; // array of strings to check for in msg.topic\nconst output = [null,null];\nmsg.topic = msg.topic.replace(/[^a-zA-Z0-9]/g, \" \") // Change msg.topic replace non alphanumeric with \" \"\nmsg.topic = msg.topic.replace(/(^\\w|\\s\\w)/g, m => m.toUpperCase()) // Change msg.topic to First Letter Upper Case\n\nif(possible_topics.includes(msg.topic)){\n    const path = \"sensor_temps.\" + msg.topic;\n    let avetemp = parseFloat(flow.get(path))// retrieve the value of the flow variable based on topic\n    let delta = 10 // Different in Temp \n    let deltatemp = Math.round(avetemp+delta) // Calc Delta Temp\n    let curtemp = parseFloat(msg.payload.toFixed(0)) // Get Current Temp from Payload\n    let alert = \"Temperature High\" // Set Alert Text\n\n    //  Check if msg topic includes allowed areas and if current temp exceeds average temp + delta\n    if(curtemp > avetemp + delta) {\n        msg = { topic: msg.topic, payload: \"The \" + msg.topic + \" \" + alert + \" \" + curtemp, avetemp:avetemp, currenttemp: curtemp, deltatemp: deltatemp };\n        node.status({\n            text: msg.topic + \", \" + msg.payload + \" Average Temp: \" + avetemp + ' ' + \"Current Temp: \" + curtemp\n        });\n        output[1] = msg;\n    }else{\n        node.status({\n            text: msg.topic +  \" Normal\"\n        });\n        output[0] = msg;\n    }\n    flow.set(\"status\", msg)\n}\nreturn output;\n","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":2100,"wires":[["5a7e3e9d47f5fe84"],["9670aba09026a741"]]},{"id":"5a7e3e9d47f5fe84","type":"smooth","z":"65617ffeb779f51c","name":"","property":"payload","action":"mean","count":"20","round":"","mult":"multi","reduce":false,"x":520,"y":2080,"wires":[["39429f2becf83487","7841bfe8fd6a4e21"]]},{"id":"9670aba09026a741","type":"trigger","z":"65617ffeb779f51c","name":"","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"10","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"topic","topic":"topic","outputs":1,"x":490,"y":2200,"wires":[["b801bf0cadd6db71"]]},{"id":"39429f2becf83487","type":"debug","z":"65617ffeb779f51c","name":"debug 237","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":730,"y":2040,"wires":[]},{"id":"7841bfe8fd6a4e21","type":"change","z":"65617ffeb779f51c","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"sensor_temps[msg.topic]","tot":"flow"},{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":2080,"wires":[["9670aba09026a741"]]},{"id":"b801bf0cadd6db71","type":"debug","z":"65617ffeb779f51c","name":"debug 24","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":680,"y":2200,"wires":[]}]

I would also store the average temps in a single object, as in the example.

Yes, Node red Home Assistant.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.