Last active
February 2, 2026 17:31
-
-
Save skylord123/567b466876da8b5ebdbe6ab465380dab to your computer and use it in GitHub Desktop.
Revisions
-
skylord123 revised this gist
Feb 2, 2026 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -7,6 +7,8 @@ This Node-RED flow provides automated modem power cycling when internet connectivity issues are detected. It monitors multiple network health indicators and triggers smart modem reboots based on configurable thresholds, helping maintain reliable internet connectivity without manual intervention. Read the article for this [here](https://skylar.tech/home-assistant-internet-monitoring-packet-loss-latency-and-automatic-modem-reboots/) **Companion Flow:** This flow complements the [Internet Connectivity Monitor](https://gist.github.com/skylord123/438e7bbdb5474ab9b738f1c79284b7d9) by providing automated remediation when issues are detected. ## Features -
skylord123 created this gist
Feb 2, 2026 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,142 @@ # Auto Power Cycle Modem <img width="1770" height="765" alt="image" src="https://gist.github.com/user-attachments/assets/3c9ec173-19b0-4044-acd9-0d896a41f788" /> ## Overview This Node-RED flow provides automated modem power cycling when internet connectivity issues are detected. It monitors multiple network health indicators and triggers smart modem reboots based on configurable thresholds, helping maintain reliable internet connectivity without manual intervention. **Companion Flow:** This flow complements the [Internet Connectivity Monitor](https://gist.github.com/skylord123/438e7bbdb5474ab9b738f1c79284b7d9) by providing automated remediation when issues are detected. ## Features This flow monitors four distinct conditions that can trigger an automatic modem reboot: ### 1. **Sustained Internet Outage** - Triggers when internet is completely down for more than 2 minutes - Ensures the modem has been powered on for at least 3 minutes before rebooting (prevents rapid cycling) ### 2. **Intermittent Connectivity Issues** - Monitors for frequent connection drops over a 5-minute window - Configurable threshold (default: 3 outages in 5 minutes) - Requires modem to be powered on for at least 5 minutes before triggering - Analyzes historical state changes to identify unstable connections ### 3. **Remote Host Packet Loss Detection** - Monitors packet loss across multiple remote hosts (Google, Cloudflare DNS) - **Intelligent multi-host verification** prevents false positives from single-endpoint issues - Configurable parameters in the `Packet Loss Tracker` function node: ```javascript const THRESHOLD_PERCENT = 10; // Packet loss percentage threshold const REQUIRED_FAILED_HOSTS = 2; // Number of hosts that must exceed threshold const DURATION_SECONDS = 60; // How long ALL hosts must be failing ``` - Example: With default settings, requires 2 remote hosts with ≥10% packet loss for 60 seconds - Prevents unnecessary reboots when a single remote service (e.g., Google or Cloudflare) has independent issues ### 4. **Router-to-Modem Packet Loss** - Monitors packet loss on the local link between router and modem using `sensor.modem_packet_loss` - Detects issues with the physical connection or modem performance - Triggers on ≥10% packet loss sustained for 2 minutes - Requires modem powered on for at least 5 minutes ## Control Features ### Auto-Reboot Switch - Global on/off control via Home Assistant entity: `switch.auto_reboot_modem` - When disabled, all automatic reboot triggers are suppressed - Separate switch available for packet loss monitoring: `switch.auto_reboot_modem_for_packet_loss` ### Reboot Flow Protection - Prevents multiple simultaneous reboot attempts using flow context variable - Automatic cooldown period between reboot cycles - Sequential reboot logic: 1. Turn off modem power (smart plug) 2. Wait 5 seconds 3. Turn on modem power 4. Wait 20 seconds for modem to stabilize 5. Monitor for internet restoration (2-minute timeout) 6. Send appropriate notification (success or retry) ### Notification System - Centralized notification hub via link nodes - Customizable notification messages for different trigger scenarios: - "Internet outage detected. Power cycling modem." - "Intermittent outage detected. Power cycling modem." - "Packet loss detected. Power cycling modem." - "Internet restored" (success) - "Internet is still out, power cycling modem again." (retry) - Extensible notification delivery options: - Smart speaker announcements - Mobile app push notifications - Matrix/chat room messages (works during internet outages with self-hosted servers) - Log file recording ## Prerequisites ### Required Home Assistant Entities - `binary_sensor.internet_status` - Binary sensor indicating internet connectivity - `switch.kauf_plug_two` - Smart plug controlling modem power (**replace with your own switch entity**) - `sensor.google_com_packet_loss` - Packet loss percentage to Google - `sensor.cloudflare_dns_packet_loss` - Packet loss percentage to Cloudflare DNS - `sensor.modem_packet_loss` - Packet loss between router and modem ### Required Node-RED Nodes - `node-red-contrib-home-assistant-websocket` (v0.80.3 or compatible) ## Installation 1. Import the flow JSON into Node-RED 2. **Replace all instances of `switch.kauf_plug_two` with your own smart switch/plug entity ID** that controls your modem's power - Use the search function in Node-RED (Ctrl+F / Cmd+F) to find all occurrences - There are multiple nodes that reference this entity 3. Verify all other Home Assistant entities are correctly configured for your setup 4. Enable the `Auto reboot modem` switch in Home Assistant 5. Configure notification endpoints as desired 6. Deploy the flow ## Configuration ### Adjusting Trigger Thresholds **Sustained Outage Duration:** - Modify the "Internet out for more than X minutes" group - Change the `for` parameter in the state-changed node (default: 2 minutes) **Intermittent Outage Detection:** - Edit the `THRESHOLD` variable in the "Internet intermittently out" function node - Adjust `TIME_WINDOW_MINUTES` to change the monitoring period (default: 5 minutes) **Remote Host Packet Loss Parameters:** - Open the `Packet Loss Tracker` function node - Modify configuration constants at the top of the code - Adjust `REQUIRED_FAILED_HOSTS` to require more/fewer failing endpoints - Change `DURATION_SECONDS` to modify how long conditions must persist **Router-to-Modem Packet Loss:** - Modify the threshold percentage in the state-changed node (default: 10%) - Adjust the `for` duration (default: 2 minutes) **Modem Stabilization Times:** - Delay nodes control power-off duration (default: 5 seconds) and post-boot wait time (default: 20 seconds) - Adjust these based on your modem's boot requirements ### Notification Customization Connect your preferred notification method to the "Modem Reboot Notifications" link input node (`link in 5`). The payload contains the notification message string. ## How It Works The flow uses a state machine approach with flow context variables to prevent race conditions. When a trigger condition is met: 1. Checks if `auto_reboot_modem` switch is enabled 2. Verifies modem has been powered on for minimum duration 3. Checks if another reboot flow is already running 4. Sets flow context flag to prevent concurrent reboots 5. Executes power cycle sequence 6. Monitors for internet restoration 7. Sends appropriate notifications 8. Clears flow context flag If internet doesn't restore within the timeout period, the flow recursively triggers another reboot attempt. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ [{"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"}}]