|
[{"id":"23d70618471de7f9","type":"group","z":"b47ba089725ec70c","name":"Auto power cycle modem","style":{"label":true},"nodes":["7cf2da6f6cc4ad32","5661f6970db0df66","268001e2046e8b04","dae9c2ab26ac60a3","3b7b6fb56f174063"],"x":48,"y":433,"w":2504,"h":1034},{"id":"7cf2da6f6cc4ad32","type":"group","z":"b47ba089725ec70c","g":"23d70618471de7f9","name":"Internet out for more than X minutes","style":{"label":true},"nodes":["216c4c4b31b7c4b2","2a5ad0b04cd24f6a","7f656cbc9b04b6df","d8733d0984a6870a","d0dd9272018c9949","06ec5f4ea951b3a9","7f125919f0066c1f"],"x":74,"y":639,"w":1242,"h":182},{"id":"216c4c4b31b7c4b2","type":"server-state-changed","z":"b47ba089725ec70c","g":"7cf2da6f6cc4ad32","name":"","server":"233a9c63.e2baf4","version":6,"outputs":2,"exposeAsEntityConfig":"","entities":{"entity":["binary_sensor.internet_status"],"substring":[],"regex":[]},"outputInitially":false,"stateType":"str","ifState":"off","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"2","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":270,"y":720,"wires":[["2a5ad0b04cd24f6a"],[]]},{"id":"2a5ad0b04cd24f6a","type":"api-current-state","z":"b47ba089725ec70c","g":"7cf2da6f6cc4ad32","name":"Modem Powered on for 3 minutes","server":"233a9c63.e2baf4","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"switch.kauf_plug_two","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"3","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":620,"y":720,"wires":[["d8733d0984a6870a","06ec5f4ea951b3a9"],[]]},{"id":"7f656cbc9b04b6df","type":"change","z":"b47ba089725ec70c","g":"7cf2da6f6cc4ad32","name":"Notification Message","rules":[{"t":"set","p":"payload","pt":"msg","to":"Internet outage detected. Power cycling modem.","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1132,"y":716.0000095367432,"wires":[["7f125919f0066c1f"]]},{"id":"d8733d0984a6870a","type":"ha-switch","z":"b47ba089725ec70c","g":"7cf2da6f6cc4ad32","name":"Auto reboot modem","version":0,"debugenabled":false,"inputs":1,"outputs":2,"entityConfig":"ff28e76e61713996","enableInput":true,"outputOnStateChange":false,"outputProperties":[{"property":"outputType","propertyType":"msg","value":"state change","valueType":"str"},{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":902,"y":716.0000095367432,"wires":[["7f656cbc9b04b6df"],[]]},{"id":"d0dd9272018c9949","type":"inject","z":"b47ba089725ec70c","g":"7cf2da6f6cc4ad32","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":680,"wires":[["2a5ad0b04cd24f6a"]]},{"id":"06ec5f4ea951b3a9","type":"link out","z":"b47ba089725ec70c","g":"7cf2da6f6cc4ad32","name":"link out 2","mode":"link","links":["26e36143526b4543"],"x":835,"y":780,"wires":[]},{"id":"7f125919f0066c1f","type":"link out","z":"b47ba089725ec70c","g":"7cf2da6f6cc4ad32","name":"link out 7","mode":"link","links":["d273041df2cc0c7a"],"x":1275,"y":720,"wires":[]},{"id":"233a9c63.e2baf4","type":"server","name":"Home Assistant","version":6,"addon":false,"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},{"id":"ff28e76e61713996","type":"ha-entity-config","server":"233a9c63.e2baf4","deviceConfig":"c76f3feee4e3dec6","name":"Auto reboot modem","version":6,"entityType":"switch","haConfig":[{"property":"name","value":"Auto reboot modem"},{"property":"icon","value":"mdi:restart"},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":""}],"resend":false,"debugEnabled":false},{"id":"c76f3feee4e3dec6","type":"ha-device-config","name":"Ping Tests","hwVersion":"","manufacturer":"Node-RED","model":"","swVersion":""},{"id":"5661f6970db0df66","type":"group","z":"b47ba089725ec70c","g":"23d70618471de7f9","name":"Internet intermittently out X times in X minutes","style":{"label":true},"nodes":["fe6c091f0cd552ce","1d18761951aeea07","21a2113481e52225","eacd3b9cc6309eb6","74c6cf1c48095b02","824b3f807998037e","a9ea99ae0d1a7ce8","af32694e902f135c"],"x":74,"y":459,"w":1912,"h":142},{"id":"fe6c091f0cd552ce","type":"server-state-changed","z":"b47ba089725ec70c","g":"5661f6970db0df66","name":"","server":"233a9c63.e2baf4","version":6,"outputs":2,"exposeAsEntityConfig":"","entities":{"entity":["binary_sensor.internet_status"],"substring":[],"regex":[]},"outputInitially":false,"stateType":"str","ifState":"off","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":270,"y":500,"wires":[["21a2113481e52225"],[]]},{"id":"1d18761951aeea07","type":"api-get-history","z":"b47ba089725ec70c","g":"5661f6970db0df66","name":"Get internet status history for last 5 mins","server":"233a9c63.e2baf4","version":1,"startDate":"","endDate":"","entityId":"binary_sensor.internet_status","entityIdType":"equals","useRelativeTime":true,"relativeTime":"5 minutes","flatten":true,"outputType":"array","outputLocationType":"msg","outputLocation":"payload","x":1120,"y":500,"wires":[["74c6cf1c48095b02"]]},{"id":"21a2113481e52225","type":"ha-switch","z":"b47ba089725ec70c","g":"5661f6970db0df66","name":"Auto reboot modem","version":0,"debugenabled":false,"inputs":1,"outputs":2,"entityConfig":"ff28e76e61713996","enableInput":true,"outputOnStateChange":false,"outputProperties":[{"property":"outputType","propertyType":"msg","value":"state change","valueType":"str"},{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":550,"y":500,"wires":[["eacd3b9cc6309eb6"],[]]},{"id":"eacd3b9cc6309eb6","type":"api-current-state","z":"b47ba089725ec70c","g":"5661f6970db0df66","name":"Modem power on for >= 5 mins","server":"233a9c63.e2baf4","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"switch.kauf_plug_two","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"5","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":810,"y":500,"wires":[["1d18761951aeea07"],[]]},{"id":"74c6cf1c48095b02","type":"function","z":"b47ba089725ec70c","g":"5661f6970db0df66","name":"Internet intermittently out 3 times in 5 minutes","func":"// Configuration\nconst THRESHOLD = 3; // Number of \"off\" transitions that trigger a restart\nconst TIME_WINDOW_MINUTES = 5; // Time window to analyze\n\n// Get the history array\nconst history = msg.payload;\n\n// Validate input\nif (!Array.isArray(history) || history.length === 0) {\n node.status({\n fill: \"grey\",\n shape: \"ring\",\n text: \"No history data\"\n });\n return null;\n}\n\n// Count transitions to \"off\" state\nlet offCount = 0;\nlet lastState = null;\n\n// Sort by timestamp to ensure chronological order\nconst sortedHistory = history.sort((a, b) => {\n const dateA = new Date(a.last_changed).getTime();\n const dateB = new Date(b.last_changed).getTime();\n return dateA - dateB;\n});\n\n// Iterate through history and count transitions to \"off\"\nfor (let i = 0; i < sortedHistory.length; i++) {\n const currentState = sortedHistory[i].state;\n\n // Count transition to \"off\" state\n if (currentState === \"off\" && lastState !== \"off\") {\n offCount++;\n }\n\n lastState = currentState;\n}\n\n// Check if threshold is exceeded\nconst thresholdExceeded = offCount >= THRESHOLD;\n\n// Update node status\nif (thresholdExceeded) {\n node.status({\n fill: \"red\",\n shape: \"dot\",\n text: `Threshold exceeded: ${offCount} outages in ${TIME_WINDOW_MINUTES} mins`\n });\n} else {\n node.status({\n fill: \"green\",\n shape: \"ring\",\n text: `${offCount} of ${THRESHOLD} outages (OK)`\n });\n}\n\n// Prepare output message with details\nmsg.outageCount = offCount;\nmsg.threshold = THRESHOLD;\nmsg.thresholdExceeded = thresholdExceeded;\nmsg.timeWindow = TIME_WINDOW_MINUTES;\n\n// Only pass message through if threshold is exceeded\nif (thresholdExceeded) {\n return msg;\n} else {\n return null;\n}","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1490,"y":500,"wires":[["824b3f807998037e","a9ea99ae0d1a7ce8"]]},{"id":"824b3f807998037e","type":"change","z":"b47ba089725ec70c","g":"5661f6970db0df66","name":"Notification Message","rules":[{"t":"set","p":"payload","pt":"msg","to":"Intermittent outage detected. Power cycling modem.","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1820,"y":500,"wires":[["af32694e902f135c"]]},{"id":"a9ea99ae0d1a7ce8","type":"link out","z":"b47ba089725ec70c","g":"5661f6970db0df66","name":"link out 3","mode":"link","links":["26e36143526b4543"],"x":1735,"y":560,"wires":[]},{"id":"af32694e902f135c","type":"link out","z":"b47ba089725ec70c","g":"5661f6970db0df66","name":"link out 5","mode":"link","links":["d273041df2cc0c7a"],"x":1945,"y":500,"wires":[]},{"id":"268001e2046e8b04","type":"group","z":"b47ba089725ec70c","g":"23d70618471de7f9","name":"Reboot modem","style":{"label":true},"nodes":["4913a92b1fd8fefd","d9c13f57616eec3b","06fc61e336eac1d5","1a246935b93238b0","d6b46b3d1df26943","335f889da8eb0f1a","0d7bdc4cdb0f49af","59c175617cbbdd45","827fe9ac1834dec9","a706875040421635","e88832b85aafc5d9","7e37c92bf34ac927","85d6fcc378833d4e","0499f9b237823081","26e36143526b4543","6cd7fa99c9faec59","826cfed30520542e"],"x":94,"y":1099,"w":2432,"h":172},{"id":"4913a92b1fd8fefd","type":"api-call-service","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","server":"233a9c63.e2baf4","version":7,"debugenabled":false,"action":"switch.turn_off","floorId":[],"areaId":[],"deviceId":[],"entityId":["switch.kauf_plug_two"],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":true,"domain":"switch","service":"turn_off","x":1000,"y":1200,"wires":[["d9c13f57616eec3b"]]},{"id":"d9c13f57616eec3b","type":"delay","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1160,"y":1200,"wires":[["06fc61e336eac1d5"]]},{"id":"06fc61e336eac1d5","type":"api-call-service","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","server":"233a9c63.e2baf4","version":7,"debugenabled":false,"action":"switch.turn_on","floorId":[],"areaId":[],"deviceId":[],"entityId":["switch.kauf_plug_two"],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":true,"domain":"switch","service":"turn_on","x":1320,"y":1200,"wires":[["0499f9b237823081"]]},{"id":"1a246935b93238b0","type":"change","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"Failure Notification Message","rules":[{"t":"set","p":"payload","pt":"msg","to":"Internet is still out, power cycling modem again.","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2270,"y":1220,"wires":[["826cfed30520542e"]]},{"id":"d6b46b3d1df26943","type":"ha-switch","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"Auto reboot modem","version":0,"debugenabled":false,"inputs":1,"outputs":2,"entityConfig":"ff28e76e61713996","enableInput":true,"outputOnStateChange":false,"outputProperties":[{"property":"outputType","propertyType":"msg","value":"state change","valueType":"str"},{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":442,"y":1216.0000095367432,"wires":[["335f889da8eb0f1a"],["e88832b85aafc5d9"]]},{"id":"335f889da8eb0f1a","type":"change","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","rules":[{"t":"set","p":"modem_restart_flow_running","pt":"flow","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":1196,"wires":[["4913a92b1fd8fefd"]]},{"id":"0d7bdc4cdb0f49af","type":"switch","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","property":"payload","propertyType":"flow","rules":[{"t":"true"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":250,"y":1220,"wires":[[],["d6b46b3d1df26943"]]},{"id":"59c175617cbbdd45","type":"change","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","rules":[{"t":"set","p":"modem_restart_flow_running","pt":"flow","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":2090,"y":1140,"wires":[[]]},{"id":"827fe9ac1834dec9","type":"change","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"Success Notification Message","rules":[{"t":"set","p":"payload","pt":"msg","to":"Internet restored","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2290,"y":1180,"wires":[["826cfed30520542e"]]},{"id":"a706875040421635","type":"ha-wait-until","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","server":"233a9c63.e2baf4","version":3,"outputs":2,"entities":{"entity":["binary_sensor.internet_status"],"substring":[],"regex":[]},"property":"state","comparator":"is","value":"on","valueType":"str","timeout":"2","timeoutType":"num","timeoutUnits":"minutes","checkCurrentState":true,"blockInputOverrides":true,"outputProperties":[],"x":1820,"y":1200,"wires":[["59c175617cbbdd45","7e37c92bf34ac927"],["d6b46b3d1df26943","85d6fcc378833d4e"]]},{"id":"e88832b85aafc5d9","type":"change","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","rules":[{"t":"set","p":"modem_restart_flow_running","pt":"flow","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":1230,"wires":[[]]},{"id":"7e37c92bf34ac927","type":"ha-switch","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"Auto reboot modem","version":0,"debugenabled":false,"inputs":1,"outputs":2,"entityConfig":"ff28e76e61713996","enableInput":true,"outputOnStateChange":false,"outputProperties":[{"property":"outputType","propertyType":"msg","value":"state change","valueType":"str"},{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":2030,"y":1180,"wires":[["827fe9ac1834dec9"],[]]},{"id":"85d6fcc378833d4e","type":"ha-switch","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"Auto reboot modem","version":0,"debugenabled":false,"inputs":1,"outputs":2,"entityConfig":"ff28e76e61713996","enableInput":true,"outputOnStateChange":false,"outputProperties":[{"property":"outputType","propertyType":"msg","value":"state change","valueType":"str"},{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":2030,"y":1220,"wires":[["1a246935b93238b0"],[]]},{"id":"0499f9b237823081","type":"ha-switch","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"Auto reboot modem","version":0,"debugenabled":false,"inputs":1,"outputs":2,"entityConfig":"ff28e76e61713996","enableInput":true,"outputOnStateChange":false,"outputProperties":[{"property":"outputType","propertyType":"msg","value":"state change","valueType":"str"},{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":1510,"y":1200,"wires":[["6cd7fa99c9faec59"],[]]},{"id":"26e36143526b4543","type":"link in","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"link in 2","links":["06ec5f4ea951b3a9","a9ea99ae0d1a7ce8","0b69b8cade99f703"],"x":135,"y":1220,"wires":[["0d7bdc4cdb0f49af"]]},{"id":"6cd7fa99c9faec59","type":"delay","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"","pauseType":"delay","timeout":"20","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1680,"y":1200,"wires":[["a706875040421635"]]},{"id":"826cfed30520542e","type":"link out","z":"b47ba089725ec70c","g":"268001e2046e8b04","name":"link out 8","mode":"link","links":["d273041df2cc0c7a"],"x":2485,"y":1200,"wires":[]},{"id":"dae9c2ab26ac60a3","type":"group","z":"b47ba089725ec70c","g":"23d70618471de7f9","name":"Auto restart modem if packet loss >= 10% for 2 minutes","style":{"label":true},"nodes":["29d6fa3df3efe624","ac1612132924da81","de22234e0eecc2d1","cc48cd3baac974ba","0b69b8cade99f703","3da5e9fa08ac155a","64ce7caafb2ddcd8","978dd20c024e3f4b","b3b630c450ebe17a","b8a118efd3d9bd47"],"x":74,"y":859,"w":1592,"h":202},{"id":"29d6fa3df3efe624","type":"server-state-changed","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"","server":"233a9c63.e2baf4","version":6,"outputs":2,"exposeAsEntityConfig":"","entities":{"entity":["sensor.modem_packet_loss"],"substring":[],"regex":[]},"outputInitially":false,"stateType":"str","ifState":"10","ifStateType":"num","ifStateOperator":"gte","outputOnlyOnStateChange":true,"for":"2","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":270,"y":900,"wires":[["ac1612132924da81"],[]]},{"id":"ac1612132924da81","type":"ha-switch","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"Auto reboot modem","version":0,"debugenabled":false,"inputs":1,"outputs":2,"entityConfig":"ff28e76e61713996","enableInput":true,"outputOnStateChange":false,"outputProperties":[{"property":"outputType","propertyType":"msg","value":"state change","valueType":"str"},{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":630,"y":900,"wires":[["cc48cd3baac974ba"],[]]},{"id":"de22234e0eecc2d1","type":"api-current-state","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"Modem power on for >= 5 mins","server":"233a9c63.e2baf4","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"switch.kauf_plug_two","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"5","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1210,"y":900,"wires":[["0b69b8cade99f703","3da5e9fa08ac155a"],[]]},{"id":"cc48cd3baac974ba","type":"ha-switch","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"Auto reboot modem for packet loss","version":0,"debugenabled":false,"inputs":1,"outputs":2,"entityConfig":"350fca0b1d8c235b","enableInput":true,"outputOnStateChange":false,"outputProperties":[{"property":"outputType","propertyType":"msg","value":"state change","valueType":"str"},{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":900,"y":900,"wires":[["de22234e0eecc2d1"],[]]},{"id":"0b69b8cade99f703","type":"link out","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"link out 4","mode":"link","links":["26e36143526b4543"],"x":1385,"y":940,"wires":[]},{"id":"3da5e9fa08ac155a","type":"change","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"Notification Message","rules":[{"t":"set","p":"payload","pt":"msg","to":"Packet loss detected. Power cycling modem.","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1480,"y":900,"wires":[["64ce7caafb2ddcd8"]]},{"id":"64ce7caafb2ddcd8","type":"link out","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"link out 6","mode":"link","links":["d273041df2cc0c7a"],"x":1625,"y":900,"wires":[]},{"id":"978dd20c024e3f4b","type":"server-state-changed","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"","server":"233a9c63.e2baf4","version":6,"outputs":1,"exposeAsEntityConfig":"","entities":{"entity":["sensor.google_com_packet_loss"],"substring":[],"regex":[]},"outputInitially":true,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"string","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":280,"y":980,"wires":[["b8a118efd3d9bd47"]]},{"id":"b3b630c450ebe17a","type":"server-state-changed","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"","server":"233a9c63.e2baf4","version":6,"outputs":1,"exposeAsEntityConfig":"","entities":{"entity":["sensor.cloudflare_dns_packet_loss"],"substring":[],"regex":[]},"outputInitially":true,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"string","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":290,"y":1020,"wires":[["b8a118efd3d9bd47"]]},{"id":"b8a118efd3d9bd47","type":"function","z":"b47ba089725ec70c","g":"dae9c2ab26ac60a3","name":"Packet Loss Tracker","func":"/**\n * This node tracks packet loss from multiple endpoints and only fires if we have\n * issues from <REQUIRED_FAILED_HOSTS> hosts having >= <THRESHOLD_PERCENT> packet loss for <DURATION_SECONDS> seconds.\n * (example: 2 hosts having >= 10% packet loss for 60 seconds)\n */\n\n// Configuration constants\nconst THRESHOLD_PERCENT = 10; // Packet loss percentage threshold\nconst REQUIRED_FAILED_HOSTS = 2; // Number of hosts that must exceed threshold\nconst DURATION_SECONDS = 60; // How long ALL hosts must be failing\n\n// Initialize context storage if needed\ncontext.failedHosts = context.failedHosts || {};\ncontext.timer = context.timer || null;\n\n// Helper function to check if trigger conditions are met and send message if so\nfunction checkAndTrigger() {\n const now = Date.now();\n const hostFailureDurations = Object.entries(context.failedHosts).map(([id, startTime]) => ({\n entity_id: id,\n duration_ms: now - startTime,\n duration_sec: (now - startTime) / 1000\n }));\n \n const failedHostCount = hostFailureDurations.length;\n \n // Check if we have enough hosts and all have been failing long enough\n if (failedHostCount >= REQUIRED_FAILED_HOSTS) {\n const shortestFailureDuration = Math.min(...hostFailureDurations.map(h => h.duration_ms));\n const shortestFailureSec = shortestFailureDuration / 1000;\n \n if (shortestFailureSec >= DURATION_SECONDS) {\n node.status({fill: \"red\", shape: \"dot\", text: `TRIGGERED: ${failedHostCount} hosts failing for ${Math.round(shortestFailureSec)}s+`});\n \n // Build the trigger message\n const triggerMsg = {\n payload: {\n triggered: true,\n failed_host_count: failedHostCount,\n minimum_duration_seconds: Math.round(shortestFailureSec),\n threshold_percent: THRESHOLD_PERCENT,\n failing_hosts: hostFailureDurations.map(h => ({\n entity_id: h.entity_id,\n failing_for_seconds: Math.round(h.duration_sec)\n }))\n }\n };\n \n // Reset tracking\n context.failedHosts = {};\n context.timer = null;\n \n // Send the message\n node.send(triggerMsg);\n return true;\n }\n }\n return false;\n}\n\n// Extract entity ID and packet loss value\nconst entityId = msg.data.entity_id;\nconst packetLoss = parseFloat(msg.payload);\n\n// Update the state for this host\nif (packetLoss >= THRESHOLD_PERCENT) {\n // Host is failing - record the time it started failing if not already tracked\n if (!context.failedHosts[entityId]) {\n context.failedHosts[entityId] = Date.now();\n }\n} else {\n // Host recovered - remove from failed list\n if (context.failedHosts[entityId]) {\n delete context.failedHosts[entityId];\n }\n}\n\n// Count how many hosts are currently failing\nconst failedHostCount = Object.keys(context.failedHosts).length;\n\n// Clear any existing timer if we don't have enough failed hosts\nif (failedHostCount < REQUIRED_FAILED_HOSTS) {\n if (context.timer !== null) {\n clearTimeout(context.timer);\n context.timer = null;\n node.status({fill: \"green\", shape: \"dot\", text: `${failedHostCount} hosts failing - timer cleared`});\n } else if (failedHostCount > 0) {\n node.status({fill: \"green\", shape: \"dot\", text: `${failedHostCount} hosts failing`});\n } else {\n node.status({fill: \"green\", shape: \"dot\", text: \"All hosts OK\"});\n }\n return null;\n}\n\n// Check if we should trigger immediately\nif (checkAndTrigger()) {\n return null; // Message already sent by checkAndTrigger\n}\n\n// Not all hosts have been failing long enough - set/update timer\nconst now = Date.now();\nconst hostFailureDurations = Object.entries(context.failedHosts).map(([id, startTime]) => \n now - startTime\n);\nconst shortestFailureDuration = Math.min(...hostFailureDurations);\nconst timeUntilTrigger = (DURATION_SECONDS * 1000) - shortestFailureDuration;\n\n// Clear existing timer if there is one\nif (context.timer !== null) {\n clearTimeout(context.timer);\n}\n\n// Set new timer to check again when the shortest-failing host reaches the threshold\ncontext.timer = setTimeout(() => {\n context.timer = null;\n checkAndTrigger();\n}, timeUntilTrigger);\n\nnode.status({\n fill: \"yellow\", \n shape: \"ring\", \n text: `${failedHostCount} hosts failing - triggering in ${Math.round(timeUntilTrigger/1000)}s`\n});\n\nreturn null;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":1000,"wires":[["ac1612132924da81"]]},{"id":"350fca0b1d8c235b","type":"ha-entity-config","server":"233a9c63.e2baf4","deviceConfig":"c76f3feee4e3dec6","name":"Auto reboot modem for packet loss","version":6,"entityType":"switch","haConfig":[{"property":"name","value":"Auto reboot modem for packet loss"},{"property":"icon","value":"mdi:restart"},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":""}],"resend":false,"debugEnabled":false},{"id":"3b7b6fb56f174063","type":"group","z":"b47ba089725ec70c","g":"23d70618471de7f9","name":"Modem Reboot Notifications","style":{"label":true},"nodes":["d273041df2cc0c7a","81f6e03cf2ee8dde"],"x":74,"y":1299,"w":184,"h":142},{"id":"d273041df2cc0c7a","type":"link in","z":"b47ba089725ec70c","g":"3b7b6fb56f174063","name":"link in 5","links":["af32694e902f135c","64ce7caafb2ddcd8","7f125919f0066c1f","826cfed30520542e"],"x":135,"y":1400,"wires":[[]]},{"id":"81f6e03cf2ee8dde","type":"comment","z":"b47ba089725ec70c","g":"3b7b6fb56f174063","name":"Docs","info":"You can use this link in node to send notifications about the current internet situation.\n\nFor example, you could:\n\n- Play the notification over a smart speaker\n- Send the message to a chat room (I self host my own Matrix chat server so it is accessible even if the internet is down)\n- Send a Home Assistant mobile companion app push notification\n- Log to a text file","x":150,"y":1340,"wires":[]},{"id":"01e09bb0309d8149","type":"global-config","env":[],"modules":{"node-red-contrib-home-assistant-websocket":"0.80.3"}}] |