Using Date and Time entities to trigger flows

There is now a time node that simplifies the example flows below into one node.

Using the time node

  • Home Assistant Companion app next alarm sensor

    [{"id":"f8329c.6b39dd68","type":"ha-time","z":"93fe35bb.4fb688","name":"","server":"","version":0,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityId":"sensor.pixel_next_alarm","property":"state","offset":0,"offsetType":"num","offsetUnits":"minutes","randomOffset":false,"repeatDaily":false,"payload":"$entity().state","payloadType":"jsonata","debugenabled":true,"x":262,"y":864,"wires":[["513ebdd1.58be64"]]},{"id":"513ebdd1.58be64","type":"debug","z":"93fe35bb.4fb688","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":492,"y":864,"wires":[]}]
    
  • Adding an offset and on/off switch by exposing the node the Home Assistant using the custom componentopen in new window.

    [{"id":"ff4c2fea.d7d15","type":"ha-time","z":"93fe35bb.4fb688","name":"","server":"","version":0,"exposeToHomeAssistant":true,"haConfig":[{"property":"name","value":"Enable next alarm"},{"property":"icon","value":""}],"entityId":"sensor.pixel_next_alarm","property":"state","offset":"$entities(\"input_number.offset\").state","offsetType":"jsonata","offsetUnits":"minutes","randomOffset":false,"repeatDaily":true,"payload":"$entity().state","payloadType":"jsonata","debugenabled":false,"x":216,"y":1184,"wires":[["fceff1c.3c74f1"]]},{"id":"fceff1c.3c74f1","type":"debug","z":"93fe35bb.4fb688","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":444,"y":1184,"wires":[]}]
    
  • Daily alarm using Datetime Entity

    [{"id":"68a7a591.c05a1c","type":"ha-time","z":"93fe35bb.4fb688","name":"","server":"","version":0,"exposeToHomeAssistant":true,"haConfig":[{"property":"name","value":"Alarm enabled"},{"property":"icon","value":""}],"entityId":"input_datetime.alarm","property":"state","offset":0,"offsetType":"num","offsetUnits":"minutes","randomOffset":false,"repeatDaily":true,"payload":"$entity().state","payloadType":"jsonata","debugenabled":false,"x":238,"y":1488,"wires":[["96f4596a.01bed8"]]},{"id":"96f4596a.01bed8","type":"debug","z":"93fe35bb.4fb688","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":444,"y":1488,"wires":[]}]
    

Home Assistant Companion app next alarm sensor

WARNING

Home Assistant Companion app sets the entity's name based on its selected language. Eg. next_alarm must be changed in prossimo_allarme if the app is used in Italian (and vice versa)

Here's a basic example of using the Home Assistant Companion app next alarm sensor.

screenshot

[{"id":"71576a68.4c7494","type":"server-state-changed","z":"56b1c979.b2c618","name":"Next Alarm","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.pixel_next_alarm","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"unavailable","halt_if_type":"str","halt_if_compare":"is_not","outputs":2,"output_only_on_state_change":true,"x":236,"y":1456,"wires":[["17ff4332.e9872d"],["ffa8ad2e.133d4"]]},{"id":"ffa8ad2e.133d4","type":"change","z":"56b1c979.b2c618","name":"reset","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":386,"y":1504,"wires":[["664bcf1c.d25e8"]]},{"id":"17ff4332.e9872d","type":"function","z":"56b1c979.b2c618","name":"time difference","func":"const now = Date.now();\nconst alarm = new Date(msg.payload);\n\nconst timeDifference = alarm - now;\n\nmsg.delay = timeDifference;\n\n// Reset the delay node before setting the new delay\nreturn [[{reset: true},msg]];","outputs":1,"noerr":0,"initialize":"","finalize":"","x":416,"y":1456,"wires":[["664bcf1c.d25e8"]]},{"id":"664bcf1c.d25e8","type":"delay","z":"56b1c979.b2c618","name":"wait until time","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":608,"y":1456,"wires":[["42742068.3eb4f"]]},{"id":"42742068.3eb4f","type":"debug","z":"56b1c979.b2c618","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1456,"wires":[]}]

Adding an offset and on/off switch

Using the Helpers section under Configuration in Home Assistant add an input_boolean and input_number with a min of -90 and a max of 90.

In this example they are input_number.offset and input_boolean.next_alarm_enabled. The offset will be plus or minus minutes to the alarm. The delay node will get updated when either the alarm sensor gets updated, the input boolean gets toggled, or the offset changes.

If the Node-RED custom componentopen in new window is installed in Home Assistant there is no need for the input_boolean as the event state node can be exposed to Home Assistant as the toggle switch.

screenshot

[{"id":"8a005b30.a70028","type":"change","z":"56b1c979.b2c618","name":"reset","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":1616,"wires":[["7443b388.3997bc"]]},{"id":"7443b388.3997bc","type":"delay","z":"56b1c979.b2c618","name":"wait until time","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1008,"y":1568,"wires":[["944beed4.7ca6c"]]},{"id":"944beed4.7ca6c","type":"debug","z":"56b1c979.b2c618","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1180,"y":1568,"wires":[]},{"id":"902c18e0.b83f08","type":"api-current-state","z":"56b1c979.b2c618","name":"enabled?","version":1,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_boolean.next_alarm_enabled","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","blockInputOverrides":false,"x":300,"y":1568,"wires":[["819e7961.c0c5b8"],["8a005b30.a70028"]]},{"id":"88557318.dcb9","type":"function","z":"56b1c979.b2c618","name":"add offset","func":"const now = Date.now();\nconst alarm = new Date(msg.alarm);\nconst offset = msg.offset * 60000;\nconst timeDifference = alarm.getTime() + offset;\nconst delay = timeDifference - now;\n\nif(delay < 0) {\n    node.status({fill: 'red', text: 'Alarm in the past'});\n    node.error(\"Alarm in the past.\");\n    return {reset: true};\n}\n\nnode.status({});\nmsg.delay = delay\n\n// Reset the delay node before setting the new delay\nreturn [[{reset: true},msg]];","outputs":1,"noerr":0,"initialize":"","finalize":"","x":828,"y":1568,"wires":[["7443b388.3997bc"]]},{"id":"5f5d428e.e8ed8c","type":"api-current-state","z":"56b1c979.b2c618","name":"get offset","version":1,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_number.offset","state_type":"str","state_location":"offset","override_payload":"msg","entity_location":"","override_data":"none","blockInputOverrides":false,"x":684,"y":1568,"wires":[["88557318.dcb9"]]},{"id":"6e621173.6634d","type":"server-state-changed","z":"56b1c979.b2c618","name":"Update Alarm","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.offset, input_boolean.next_alarm_enabled, sensor.pixel_next_alarm","entityidfiltertype":"substring","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"x":150,"y":1568,"wires":[["902c18e0.b83f08"]]},{"id":"819e7961.c0c5b8","type":"api-current-state","z":"56b1c979.b2c618","name":"get alarm time","version":1,"outputs":2,"halt_if":"unavailable","halt_if_type":"str","halt_if_compare":"is_not","override_topic":true,"entity_id":"sensor.pixel_next_alarm","state_type":"str","state_location":"alarm","override_payload":"msg","entity_location":"","override_data":"none","blockInputOverrides":false,"x":480,"y":1568,"wires":[["5f5d428e.e8ed8c"],["8a005b30.a70028"]]}]

WARNING

Delay nodes can have a max timeout of around 24.8 days greater than that and weird things will happen.

Managing alarm from multiple phones

If more than one phone in the house has the app installed and you want it to be triggered from any of the alarms this is the way to go and it's working perfectly even with just one phone.

It needs the same couple of helpers for each phone and the same consideration made above for the input_boolean is still valid.

This flow was developed and tested with a Pixel 2 XL and the companion app in Italian so you'll have to change all the references to the phone and the entities.

screenshot

[{"id":"c68594981cf4df93","type":"server-state-changed","z":"e1f44cc40f36d3a0","name":"Update Alarm","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"_prossimo_allarme_offset, _prossimo_allarme_enabled, _prossimo_allarme","entityidfiltertype":"substring","outputinitially":true,"state_type":"str","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":130,"y":1000,"wires":[["bd2261de4c7422c2"]]},{"id":"bd2261de4c7422c2","type":"function","z":"e1f44cc40f36d3a0","name":"Save values","func":"const [, phone] = msg.topic.match(/\\.(.+)_prossimo_allarme/);\nconst key = `alarm['${phone}']`;\nconst fullKey = `alarm['${phone}']['${msg.topic}']`\nflow.set(fullKey, msg.payload);\nconst alarmPhone = flow.get(key);\nif (Object.keys(alarmPhone).length === 3) {\n    const alarmRaw = alarmPhone[`sensor.${phone}_prossimo_allarme`];\n    const enabled = alarmPhone[`input_boolean.${phone}_prossimo_allarme_enabled`] === 'on';\n    if ( enabled && alarmRaw !== \"unavailable\") {\n        const now = Date.now();\n        const alarm = new Date(alarmRaw);\n        const offset = alarmPhone[`input_number.${phone}_prossimo_allarme_offset`] * 60000;\n        const timeDifference = alarm.getTime() + offset;\n        const delay = timeDifference - now;\n\n        if (delay < 0) {\n            node.status({ fill: 'red', text: 'Offset put alarm in the past' });\n            node.warn(\"Offset put alarm in the past.\");\n            return { reset: true };\n        }\n\n        node.status({});\n        msg = {\n            topic: phone,\n            delay,\n            payload: alarm\n        };\n\n        // Reset the delay node before setting the new delay\n        return [[{ reset: true }, msg]];\n    } else {\n        return { reset: true };\n    }\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":1000,"wires":[["31a1b9a97061185e"]]},{"id":"31a1b9a97061185e","type":"delay","z":"e1f44cc40f36d3a0","name":"wait until time","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":520,"y":1000,"wires":[["35675e2e29e54de6"]]},{"id":"764b91777c83fd57","type":"debug","z":"e1f44cc40f36d3a0","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":320,"y":1080,"wires":[]},{"id":"35675e2e29e54de6","type":"switch","z":"e1f44cc40f36d3a0","name":"Phone","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"pixel_2_xl","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":690,"y":1000,"wires":[["b2992c1a93db3836"]]},{"id":"b2992c1a93db3836","type":"link out","z":"e1f44cc40f36d3a0","name":"Pixel 2 XL","mode":"link","links":["2a280002119b6339"],"x":795,"y":1000,"wires":[]},{"id":"2a280002119b6339","type":"link in","z":"e1f44cc40f36d3a0","name":"Alarm Pixel 2 XL","links":["b2992c1a93db3836"],"x":175,"y":1080,"wires":[["764b91777c83fd57"]]}]

Daily alarm using Datetime Entity

screenshot

[{"id":"2672831b.1236ac","type":"inject","z":"ffbd7f06.4a014","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":342,"y":1808,"wires":[["ef533324.d135d"]]},{"id":"8b6ea5bc.5f2478","type":"api-current-state","z":"ffbd7f06.4a014","name":"alarm","version":1,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_datetime.alarm","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"","override_data":"none","blockInputOverrides":false,"x":628,"y":1808,"wires":[["aedf33ed.0ca28"]]},{"id":"d3ec7afe.0c2968","type":"debug","z":"ffbd7f06.4a014","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":910,"y":1808,"wires":[]},{"id":"aedf33ed.0ca28","type":"function","z":"ffbd7f06.4a014","name":"now?","func":"const now = new Date();\nconst hours = now.getHours().toString().padStart(2,'0');\nconst minutes = now.getMinutes().toString().padStart(2,'0');\nconst time = `${hours}:${minutes}`;\n// Remove seconds\nconst alarm = msg.payload.split(\":\").splice(0,2).join(\":\");\n\nif(time === alarm) {\n    return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":756,"y":1808,"wires":[["d3ec7afe.0c2968"]]},{"id":"ef533324.d135d","type":"api-current-state","z":"ffbd7f06.4a014","name":"enabled?","version":1,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_boolean.alarm_enabled","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","blockInputOverrides":false,"x":492,"y":1808,"wires":[["8b6ea5bc.5f2478"],[]]}]

An offset can also be added as shown in the next alarm sensor example.