diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/404.html b/404.html new file mode 100644 index 0000000000..5f2a99baf2 --- /dev/null +++ b/404.html @@ -0,0 +1,43 @@ + + +
+ + + + + +string
websocket|http
websocket
Protocol to use to access Home Assistant API.
string
get|post
Type of method to use to access the HTTP endpoint.
JSON Object to send for WebSocket requests and HTTP posts.
Type: string
Location to save the API results.
All properties need to be under msg.payload
.
string
websocket|http
Overrides or sets the protocol property of the config.
string
get|post
Overrides or sets the method property of the config.
string
Overrides or sets the path property of the config.
Object|string
Overrides or sets the data/params property of the config.
string
Overrides or sets the results property of the config.
string
msg|flow|global
Overrides or sets the results type property of the config.
Value types:
results
: results of the API requestconfig
: config properties of the nodehass-node-red: A custom Home Assistant integration that extends the WebSocket API, allowing Node-RED to create sensors, buttons, and switches in Home Assistant. It is usually installed via HACS but can also be installed manually.
Node-RED Home Assistant Community Add-on: A Home Assistant Add-on that runs Node-RED with Home Assistant nodes pre-installed.
Node-RED: A flow-based development tool for visual programming, originally developed by IBM, used to wire together hardware devices, APIs, and online services as part of the Internet of Things.
Home Assistant: An open-source home automation platform focused on local control and privacy.
This error occurs if your flow runs before Node-RED connects to Home Assistant and retrieves the latest state information, leaving the cache empty.
For Home Assistant Add-on users, there is a 5-second delay between connection attempts due to the supervisor proxy, which can cause high CPU usage. This delay can be turned off in the server config (Issue #76).
Starting with version 0.12.0, individual nodes in the workspace will have a version number associated with them, allowing structural changes without affecting all nodes of that type until edited. Nodes with legacy versions will display yellow font until upgraded.
Yellow text on a node doesn’t mean it needs immediate updating; it will continue functioning as before and will be upgraded the next time you modify it.
',8))])}const m=i(c,[["render",u],["__file","FAQ.html.vue"]]),w=JSON.parse('{"path":"/FAQ.html","title":"FAQ","lang":"en-US","frontmatter":{"sidebar":"auto"},"headers":[{"level":2,"title":"What is What?","slug":"what-is-what","link":"#what-is-what","children":[]},{"level":2,"title":"Entities Not Showing in the Autocomplete Dropdown","slug":"entities-not-showing-in-the-autocomplete-dropdown","link":"#entities-not-showing-in-the-autocomplete-dropdown","children":[]},{"level":2,"title":"Entity Could Not Be Found in Cache for entity_id: ???","slug":"entity-could-not-be-found-in-cache-for-entity-id","link":"#entity-could-not-be-found-in-cache-for-entity-id","children":[]},{"level":2,"title":"Why Do Some of My Nodes Have a Yellow Font?","slug":"why-do-some-of-my-nodes-have-a-yellow-font","link":"#why-do-some-of-my-nodes-have-a-yellow-font","children":[]}],"git":{"updatedTime":1723606857000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":4}]},"filePathRelative":"FAQ.md"}');export{m as comp,w as data}; diff --git a/assets/InfoPanelOnly-uYuedY0y.js b/assets/InfoPanelOnly-uYuedY0y.js new file mode 100644 index 0000000000..0796665f54 --- /dev/null +++ b/assets/InfoPanelOnly-uYuedY0y.js @@ -0,0 +1 @@ +import{_ as n}from"./app-CefLgoES.js";const e={};function _(r,c){return null}const o=n(e,[["render",_],["__file","InfoPanelOnly.vue"]]);export{o as default}; diff --git a/assets/Scrubber-Xf9jN2YZ.js b/assets/Scrubber-Xf9jN2YZ.js new file mode 100644 index 0000000000..e29ca27bc8 --- /dev/null +++ b/assets/Scrubber-Xf9jN2YZ.js @@ -0,0 +1 @@ +import{_ as b,o as n,c as l,a as o,g as d,v as y,d as c,h as u,i as h,b as f,w as v,T as m}from"./app-CefLgoES.js";const w=["ha-api","api-call-service","api-current-state","ha-device","ha-entity","server-events","server-state-changed","ha-fire-event","ha-get-entities","api-get-history","poll-state","api-render-template","ha-select","ha-sentence","trigger-state","ha-tag","ha-time","ha-wait-until","ha-webhook","ha-zone","server","ha-device-config","ha-entity-config","ha-binary-sensor","ha-button","ha-number","ha-sensor","ha-switch","ha-text","ha-time-entity","ha-update-config"],x={data:function(){return{after:"",before:"",replaceServerId:"",serverId:"",showCopied:!1,showError:!1}},methods:{scrub:function(t){let e;try{e=JSON.parse(this.before)}catch{this.showError=!0,setTimeout(()=>{this.showError=!1},1500);return}this.showError=!1;let a=e.length;for(;a--;){const s=e[a].type;if(s==="server"){e.splice(a,1);continue}w.includes(s)&&(e[a].server=this.replaceServerId?this.serverId:""),["lat","lon","latitude","longitude"].forEach(p=>{e[a][p]&&(e[a][p]="")})}this.after=JSON.stringify(e)},copy:function(){this.$refs.copyme.select(),document.execCommand("copy"),this.showCopied=!0,setTimeout(()=>{this.showCopied=!1},1500)}},mounted(){localStorage.serverId&&(this.serverId=localStorage.serverId)},watch:{serverId(t){localStorage.serverId=t}}},g={key:0},I={key:0,class:"popup error"},S={key:0,class:"popup copied"};function C(t,e,a,s,p,i){return n(),l("div",null,[o("p",null,[o("label",null,[d(o("input",{type:"checkbox","onUpdate:modelValue":e[0]||(e[0]=r=>t.replaceServerId=r)},null,512),[[y,t.replaceServerId]]),e[6]||(e[6]=c(" Replace Home Assistant server id "))]),t.replaceServerId?(n(),l("span",g,[e[7]||(e[7]=c(" with ")),d(o("input",{type:"text","onUpdate:modelValue":e[1]||(e[1]=r=>t.serverId=r),placeholder:"xxxxxxxx.xxxxx"},null,512),[[u,t.serverId]])])):h("",!0)]),d(o("textarea",{"onUpdate:modelValue":e[2]||(e[2]=r=>t.before=r),placeholder:"paste exported Node-RED flow here"},null,512),[[u,t.before]]),o("button",{onClick:e[3]||(e[3]=(...r)=>i.scrub&&i.scrub(...r))},"Scrub"),f(m,{name:"fade"},{default:v(()=>[t.showError?(n(),l("span",I,"Invalid JSON")):h("",!0)]),_:1}),d(o("textarea",{"onUpdate:modelValue":e[4]||(e[4]=r=>t.after=r),ref:"copyme"},null,512),[[u,t.after]]),o("button",{onClick:e[5]||(e[5]=(...r)=>i.copy&&i.copy(...r))},"Copy to Clipboard"),f(m,{name:"fade"},{default:v(()=>[t.showCopied?(n(),l("span",S,"Copied")):h("",!0)]),_:1})])}const N=b(x,[["render",C],["__scopeId","data-v-26acb438"],["__file","Scrubber.vue"]]);export{N as default}; diff --git a/assets/action.html-COjyZO0k.js b/assets/action.html-COjyZO0k.js new file mode 100644 index 0000000000..e24c38051a --- /dev/null +++ b/assets/action.html-COjyZO0k.js @@ -0,0 +1,38 @@ +import{_ as e,c as u,e as c,a as n,b as t,w as o,r,o as l,d as p}from"./app-CefLgoES.js";const i="/node-red-contrib-home-assistant-websocket/assets/jsonata_1_1-BOrSTtQg.png",k={};function q(y,s){const a=r("RouteLink");return l(),u("div",null,[s[2]||(s[2]=c('Integrations in Home Assistant provide service calls that can be used, for example, to set target temperature on heaters and air-conditioning units. All service calls can be found and tested within Home Assistant Developer toolbox, and it is also useful to check the integration documentation to identify exactly what is required for a successful service call.
All service calls require:
The data object will vary, depending on the integration and service call, from an empty object {}
to something much more complex. Constructing this data object correctly is key to a successful service call, and JSONata rather than mustache templating should ideally be used to achieve this.
Note: The data field used in a service call must be a valid JSON object.
{{msg.payload}}
. Although acceptable in most JSON input fields, this is invalid syntax in JSONata expressions and must not be used.{}
for JSON - this must be strict JSON formed with all literals and string keys. Mustache templates may work successfully in simple strings. JSONata will not work here. The JSON option cannot accept anything other than literals and Mustache templates.J:
for JSONata (expression) - this must evaluate to strict JSON, but can be formed with expressions in both the object keys (evaluating as strings) and the object values. In JSONata, literals evaluate as themselves, so the JSONata option can accept a JSON literal construct.JSONata in Node-RED nodes accesses the incoming message as a JSON object. Thus msg.payload is referenced just as payload
and is evaluated as the value of that key. The leading 'msg.' is not required, and {{ payload }}
is invalid JSONata syntax.
Node-RED typically uses msg.payload to pass values between nodes, however any field can be used. Most WebSocket nodes have output properties that default to set msg.data to the entity details, and therefore in the flow following (not in) the node, data.state
will typically provide the state value of the entity that was the subject of the preceding WebSocket node.
Within the WebSocket node itself, the entity data is accessed using the special JSONata $entity() function, hence $entity().state
would be used. In a similar manner, the $entities() function can reference any Home Assistant entity, and $entities('sensor.date_time').state
would return the state value of the given date_time sensor.
In JSONata, as well as using message fields or the entity functions, Node-RED provides functions to access environment variables using $env('ENV_NAME')
. Global context can be read using $globalContext(name[, store])
and flow context likewise as $flowContext(name[, store])
.
For Node-RED flows with a Action node there are three locations where the Data object can be defined.
There are advantages and drawbacks to each approach, and personal choice as well as the particular case and need will influence your flow design.
Here are three examples, each using JSONata to set the required Data object, and one example of using JSONata to process service call return data.
[{"id":"0050317002075c30","type":"group","z":"776c027950fc8c3f","name":"Call Service node - build a data object / process call return","style":{"label":true,"color":"#000000"},"nodes":["a67d69634829a418","86cbf80d.5a9cf8","8aad6643.9ee7a8","897a31873370f8cb","3bbb02bc34fb543a","6601ecbcd16c5aa3","fe24231bb58e259d","b0b87dc3a978e67e","8602b67dfe66a5e3","fe9baddadc8fea32","fd65cb1e1c933b3a","0acd153a39f25184","be9ef2598f33324a","8446a03e6d2b76a3","3c75c1ae78ed9894","9fc179c38d22f267","c84c9a405862bb69","f0d2bc4cc11a7608"],"x":34,"y":39,"w":1212,"h":442},{"id":"a67d69634829a418","type":"api-call-service","z":"776c027950fc8c3f","g":"0050317002075c30","name":"","server":"","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1030,"y":220,"wires":[[]]},{"id":"86cbf80d.5a9cf8","type":"api-call-service","z":"776c027950fc8c3f","g":"0050317002075c30","name":"","server":"","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.kitchen"],"data":"{\\"brightness\\": $min([$entities(\\"light.kitchen\\").attributes.brightness + $number($entities(\\"input_number.brightness\\").state), 255])}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"data"}],"queue":"none","output_location":"payload","output_location_type":"msg","x":730,"y":120,"wires":[[]]},{"id":"8aad6643.9ee7a8","type":"server-state-changed","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Remote Button","server":"","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"sensor.button","entityIdType":"exact","outputInitially":false,"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":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":520,"y":120,"wires":[["86cbf80d.5a9cf8"]]},{"id":"897a31873370f8cb","type":"server-state-changed","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Input Number (Temp)","server":"","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"input_number.help_temp_number","entityIdType":"exact","outputInitially":false,"stateType":"num","ifState":"","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":540,"y":220,"wires":[["fe24231bb58e259d"]]},{"id":"3bbb02bc34fb543a","type":"inject","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Manual Test","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":530,"y":80,"wires":[["86cbf80d.5a9cf8"]]},{"id":"6601ecbcd16c5aa3","type":"comment","z":"776c027950fc8c3f","g":"0050317002075c30","name":"1a - JSONata builds object in Call Service node \\\\n Increase light brightness with remote","info":"","x":240,"y":100,"wires":[]},{"id":"fe24231bb58e259d","type":"change","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Service Call Settings","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\\t \\"target\\": {\\"entity_id\\": $globalContext(\\"AClist\\")},\\t \\"data\\": {\\t \\"temperature\\": payload,\\t \\"hvac_mode\\": payload>20 ? \\"heat\\" : \\"cool\\"\\t }\\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":780,"y":220,"wires":[["a67d69634829a418"]]},{"id":"b0b87dc3a978e67e","type":"api-call-service","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Send notification","server":"","version":5,"debugenabled":false,"domain":"notify","service":"mobile_app_geoff_phone","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":790,"y":300,"wires":[[]]},{"id":"8602b67dfe66a5e3","type":"server-state-changed","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Last person to leave","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"person.","entityIdType":"substring","outputInitially":false,"stateType":"str","ifState":"\\"home\\" in $entities().*[$substringBefore(entity_id,\\".\\")=\\"person\\"].state","ifStateType":"jsonata","ifStateOperator":"jsonata","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"{\\t \\"data\\": {\\t \\"message\\": \\"The \\" & $join($entities().*[state = \\"on\\" and entity_id ~> /^light|^switch/].attributes.friendly_name, \\", \\") & \\" are on.\\",\\t \\"title\\": \\"Things Left On \\" & $entity().attributes.friendly_name\\t }\\t}","valueType":"jsonata"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":530,"y":300,"wires":[[],["b0b87dc3a978e67e"]]},{"id":"fe9baddadc8fea32","type":"api-call-service","z":"776c027950fc8c3f","g":"0050317002075c30","name":"","server":"","version":5,"debugenabled":false,"domain":"weather","service":"get_forecasts","areaId":[],"deviceId":[],"entityId":["weather.forecast_home"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"results","propertyType":"msg","value":"","valueType":"results"}],"queue":"none","x":360,"y":440,"wires":[["8446a03e6d2b76a3"]]},{"id":"fd65cb1e1c933b3a","type":"inject","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Set Forecast type","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\\"data\\": {\\"type\\": \\"hourly\\"}}","payloadType":"jsonata","x":160,"y":440,"wires":[["fe9baddadc8fea32"]]},{"id":"0acd153a39f25184","type":"debug","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Start Hour","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"$substringAfter(payload.time,\\"T\\")~>$substringBefore(\\"+\\")","targetType":"jsonata","statusVal":"$substringAfter(payload.time,\\"T\\")~>$substringBefore(\\"+\\")","statusType":"auto","x":1130,"y":440,"wires":[]},{"id":"be9ef2598f33324a","type":"comment","z":"776c027950fc8c3f","g":"0050317002075c30","name":"1d - JSONata extracts details from service call return \\\\n using Inject, Change, and Debug nodes","info":"","x":250,"y":380,"wires":[]},{"id":"8446a03e6d2b76a3","type":"change","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Extract forecast time and arrays of hourly temp, cloud, and rain","rules":[{"t":"set","p":"payload","pt":"msg","to":"results.*.forecast{\\t \\"time\\": $[0].datetime,\\t \\"temp\\": $.temperature,\\t \\"cloud\\": $.cloud_coverage,\\t \\"rain\\": $.precipitation\\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":440,"wires":[["0acd153a39f25184","3c75c1ae78ed9894"]]},{"id":"3c75c1ae78ed9894","type":"debug","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1120,"y":400,"wires":[]},{"id":"9fc179c38d22f267","type":"comment","z":"776c027950fc8c3f","g":"0050317002075c30","name":"1b - JSONata builds object in Change Node \\\\n Set a/c target temperature and mode","info":"","x":230,"y":200,"wires":[]},{"id":"c84c9a405862bb69","type":"comment","z":"776c027950fc8c3f","g":"0050317002075c30","name":"1c - JSONata builds object in Event: State node \\\\n Send notification when leaving home","info":"","x":240,"y":300,"wires":[]},{"id":"f0d2bc4cc11a7608","type":"inject","z":"776c027950fc8c3f","g":"0050317002075c30","name":"Turn Off","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\\t \\"domain\\": \\"homeassistant\\",\\t \\"service\\": \\"turn_off\\",\\t \\"target\\": {\\t \\"area_id\\": [\\"bedroom\\"]\\t },\\t \\"data\\": {} \\t}","payloadType":"jsonata","x":820,"y":180,"wires":[["a67d69634829a418"]]}]
+
Example: A Action node that sets the target temperature of a climate entity to the current temperature of a sensor entity.
{
+ "temperature": $entities("sensor.living_room_temperature").state
+}
+
Here the state of a sensor entity is read, converted to a number, and then increased by 3.
Warning
States from Home Assistant are always strings, and so any calculations must be converted to a number first.
{
+ "temperature": $number($entities("sensor.living_room_temperature").state) + 3
+}
+
Example: A remote with a button that when clicked increases the brightness of a given light by an amount that is set from an input_number inside Home Assistant.
JSONata expression in the Action node UI Data field. Nothing special is required as input to this node as all the settings are completed within the node itself.
{
+ "brightness": $min([
+ $entities("light.kitchen").attributes.brightness +
+ $number($entities("input_number.brightness").state),
+ 255
+ ])
+}
+
Example: Set the target temperature (and mode) for air-conditioning unit from the input msg.payload value.
Here a Change node is used to build the msg.payload object required to set the Action node parameters, including the nested data field. In a Change node, the special WebSocket $entity()
and $entities()
functions are not available, however we can still perform a read from global context using the Node-RED $globalContext()
function. These Node-RED functions can be used to bring in values from context and environment variables.
{
+ "target": {"entity_id": $globalContext("AClist")},
+ "data": {
+ "temperature": payload,
+ "hvac_mode": payload>20 ? "heat" : "cool"
+ }
+}
+
The Action node can accept from one to all of the UI parameters in msg.payload, and where given these each take precedence over any UI settings. Default settings can therefore be set in the UI, and can then be over-riden by the input message, including when using settings from Inject nodes as shown in the example.
Example: Get notified when lights or switches are left on when you leave.
This example uses JSONata in two places in the Events: state node. The state value test is a JSONata expression that must return true for the node to output a message (upper output). This code selects an array of all entity states for entities starting with 'person', and will return true
if this array contains one or more with state "home". The expression will therefore only return false
when no-one is home, and we can use the lower (condition fail) output rather than using the JSONata $not() function to look for 'not home'.
"home" in $entities().*[$substringBefore(entity_id,".")="person"].state
+
JSONata is also then used in the output properties to create the required object for a notification service call. It uses $entities().*
with a filter looking for those with "light" or "switch" in the entity_id and state "on", then selecting the friendly_name, and joining all those found into a string list in the message.
The message title uses $entity().attributes.friendly_name
to add the name of the person who just left home to the message title.
{
+ "data": {
+ "message": "The " &
+ $join($entities().*[state = "on" and entity_id ~>
+ /^light|^switch/].attributes.friendly_name, ", ") &
+ " are on.",
+ "title": "Things Left On " & $entity().attributes.friendly_name
+ }
+}
+
In this example all the parameter setup is performed in the Events: state node, so the Action node can accept all the required parameters just from the input msg.payload object.
Home Assistant service calls can provide data returned as a result of the call, and JSONata is an ideal tool to manage any resulting JSON object and array structures. This example uses JSONata to extract and manipulate weather forecasts, providing a summary in a modified format.
Example: The forecast service call now returns an array of forecasts. JSONata is used to iterate over all forecasts, returning an new object for each, with a time and arrays for temperature, cloud coverage, and precipitation only.
results.*.forecast{
+ "time": $[0].datetime,
+ "temp": $.temperature,
+ "cloud": $.cloud_coverage,
+ "rain": $.precipitation
+}
+
Also see:
`,49)),n("ul",null,[n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[0]||(s[0]=[p("JSONata guide")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/jsonata-primer.html"},{default:o(()=>s[1]||(s[1]=[p("JSONata primer")])),_:1})])])])}const d=e(k,[["render",q],["__file","action.html.vue"]]),m=JSON.parse('{"path":"/cookbook/jsonata/action.html","title":"Action Node JSONata Examples","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Passing variables to WebSocket nodes in summary","slug":"passing-variables-to-websocket-nodes-in-summary","link":"#passing-variables-to-websocket-nodes-in-summary","children":[]},{"level":2,"title":"JSONata and the Action node","slug":"jsonata-and-the-action-node","link":"#jsonata-and-the-action-node","children":[]},{"level":2,"title":"Setting the Data field","slug":"setting-the-data-field","link":"#setting-the-data-field","children":[{"level":3,"title":"Settings a data property from a Home Assistant entity","slug":"settings-a-data-property-from-a-home-assistant-entity","link":"#settings-a-data-property-from-a-home-assistant-entity","children":[]},{"level":3,"title":"Setting a data property from a Home Assistant entity with a calculation","slug":"setting-a-data-property-from-a-home-assistant-entity-with-a-calculation","link":"#setting-a-data-property-from-a-home-assistant-entity-with-a-calculation","children":[]},{"level":3,"title":"Increase lights brightness with remote","slug":"increase-lights-brightness-with-remote","link":"#increase-lights-brightness-with-remote","children":[]},{"level":3,"title":"Setting target temperature on air-conditioning unit","slug":"setting-target-temperature-on-air-conditioning-unit","link":"#setting-target-temperature-on-air-conditioning-unit","children":[]},{"level":3,"title":"Notification of lights left on when leaving home","slug":"notification-of-lights-left-on-when-leaving-home","link":"#notification-of-lights-left-on-when-leaving-home","children":[]}]},{"level":2,"title":"Processing a service call return","slug":"processing-a-service-call-return","link":"#processing-a-service-call-return","children":[]}],"git":{"updatedTime":1723695708000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":1}]},"filePathRelative":"cookbook/jsonata/action.md"}');export{d as comp,m as data}; diff --git a/assets/action.html-CueRyjyR.js b/assets/action.html-CueRyjyR.js new file mode 100644 index 0000000000..0f3fbee329 --- /dev/null +++ b/assets/action.html-CueRyjyR.js @@ -0,0 +1,14 @@ +import{_ as r,c as u,a as t,d as l,b as n,w as a,e as p,r as o,o as h}from"./app-CefLgoES.js";const g={},f={class:"hint-container tip"},c={id:"action-1",tabindex:"-1"},m={class:"header-anchor",href:"#action-1"};function b(v,e){const i=o("RouteLink"),s=o("Badge"),d=o("info-panel-only");return h(),u("div",null,[e[31]||(e[31]=t("h1",{id:"action",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#action"},[t("span",null,"Action")])],-1)),e[32]||(e[32]=t("p",null,[l("This node allows you to send a request to Home Assistant to perform specific actions. These actions could include tasks such as turning on a light ("),t("code",null,"light.turn_on"),l("), selecting an option from a dropdown menu ("),t("code",null,"input_select.select_option"),l("), or any other service call supported by Home Assistant. It serves as a bridge between Node-RED and Home Assistant, enabling automation flows to directly control devices or entities in your smart home setup.")],-1)),t("div",f,[e[1]||(e[1]=t("p",{class:"hint-container-title"},"Helpful Examples",-1)),t("p",null,[n(i,{to:"/guide/action.html"},{default:a(()=>e[0]||(e[0]=[l("Action Tips and Tricks")])),_:1})])]),e[33]||(e[33]=t("h2",{id:"configuration",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#configuration"},[t("span",null,"Configuration")])],-1)),t("h3",c,[t("a",m,[t("span",null,[e[2]||(e[2]=l("Action ")),n(s,{text:"required"})])])]),t("ul",null,[e[5]||(e[5]=t("li",null,[l("Type: "),t("code",null,"string")],-1)),t("li",null,[e[4]||(e[4]=l("Accepts ")),n(i,{to:"/guide/mustache-templates.html"},{default:a(()=>e[3]||(e[3]=[l("Mustache Templates")])),_:1})])]),e[34]||(e[34]=t("p",null,"Action to perform",-1)),e[35]||(e[35]=t("h3",{id:"floor",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#floor"},[t("span",null,"Floor")])],-1)),t("ul",null,[e[9]||(e[9]=t("li",null,[l("Type: "),t("code",null,"an array of floor ids")],-1)),t("li",null,[e[7]||(e[7]=l("Accepts ")),n(i,{to:"/guide/mustache-templates.html"},{default:a(()=>e[6]||(e[6]=[l("Mustache Templates")])),_:1}),e[8]||(e[8]=l(" for ids"))])]),e[36]||(e[36]=t("p",null,"A list of floor ids that will be used as targets for the action",-1)),e[37]||(e[37]=t("h3",{id:"area",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#area"},[t("span",null,"Area")])],-1)),t("ul",null,[e[13]||(e[13]=t("li",null,[l("Type: "),t("code",null,"an array of area ids")],-1)),t("li",null,[e[11]||(e[11]=l("Accepts ")),n(i,{to:"/guide/mustache-templates.html"},{default:a(()=>e[10]||(e[10]=[l("Mustache Templates")])),_:1}),e[12]||(e[12]=l(" for ids"))])]),e[38]||(e[38]=t("p",null,"A list of area ids that will be used as targets for the action",-1)),e[39]||(e[39]=t("p",null,[l("Custom ids can be inserted into the list by adding a "),t("code",null,"#"),l(" at the end of the id")],-1)),e[40]||(e[40]=t("h3",{id:"device",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#device"},[t("span",null,"Device")])],-1)),t("ul",null,[e[17]||(e[17]=t("li",null,[l("Type: "),t("code",null,"an array of device ids")],-1)),t("li",null,[e[15]||(e[15]=l("Accepts ")),n(i,{to:"/guide/mustache-templates.html"},{default:a(()=>e[14]||(e[14]=[l("Mustache Templates")])),_:1}),e[16]||(e[16]=l(" for ids"))])]),e[41]||(e[41]=t("p",null,"A list of device ids that will be used as targets for the action",-1)),e[42]||(e[42]=t("p",null,[l("Custom ids can be inserted into the list by adding a "),t("code",null,"#"),l(" at the end of the id")],-1)),e[43]||(e[43]=t("h3",{id:"entity",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#entity"},[t("span",null,"Entity")])],-1)),t("ul",null,[e[21]||(e[21]=t("li",null,[l("Type: "),t("code",null,"an array of entity ids")],-1)),t("li",null,[e[19]||(e[19]=l("Accepts ")),n(i,{to:"/guide/mustache-templates.html"},{default:a(()=>e[18]||(e[18]=[l("Mustache Templates")])),_:1}),e[20]||(e[20]=l(" for ids"))])]),e[44]||(e[44]=t("p",null,"A list of entity ids that will be used as targets for the action",-1)),e[45]||(e[45]=t("h3",{id:"label",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#label"},[t("span",null,"Label")])],-1)),t("ul",null,[e[25]||(e[25]=t("li",null,[l("Type: "),t("code",null,"an array of label ids")],-1)),t("li",null,[e[23]||(e[23]=l("Accepts ")),n(i,{to:"/guide/mustache-templates.html"},{default:a(()=>e[22]||(e[22]=[l("Mustache Templates")])),_:1}),e[24]||(e[24]=l(" for ids"))])]),e[46]||(e[46]=t("p",null,"A list of label ids that will be used as targets for the action",-1)),e[47]||(e[47]=t("h3",{id:"data",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#data"},[t("span",null,"Data")])],-1)),t("ul",null,[e[29]||(e[29]=t("li",null,[l("Type: "),t("code",null,"JSONata | JSON")],-1)),t("li",null,[e[27]||(e[27]=l("Accepts ")),n(i,{to:"/guide/mustache-templates.html"},{default:a(()=>e[26]||(e[26]=[l("Mustache Templates")])),_:1}),e[28]||(e[28]=l(" when data type is JSON"))])]),e[48]||(e[48]=p(`JSON object to pass along.
string
If defined will attempt to merge the global and flow context variable into the config
boolean
Will change the tags used for the mustache template to <%
and %>
none | first | all | last
Will store the first, last, or all messages received while disconnected from Home Assistant and send them once connected again
boolean
true
Stop msg.payload
values from overriding local config
All properties need to be under msg.payload
.
Sample input
{
+ "action": "homeassistant.turn_on",
+ "target": {
+ "floor_id": ["first_floor"],
+ "area_id": ["kitchen"],
+ "device_id": ["8932894082930482903"],
+ "entity_id": ["light.kitchen", "switch.garage_light"],
+ "label_id": ["outdoor_lights"]
+ }
+ "data": {
+ "brightness_pct": 50
+ }
+}
+
If the incoming message has a payload
property with action
set it will override any config values if set.
If the incoming message has a payload.data
that is an object these properties will be merged with any config values set.
If the node has a property value in its config for Merge Context
then the flow
and global
contexts will be checked for this property which should be an object that will also be merged into the data payload.
As seen above the data
property has a lot going on in the way of data merging, in the end, all of these are optional and the rightmost will win if a property exists in multiple objects
Config Data, Global Data, Flow Data, Payload Data ( payload data property always wins if provided )
string
Action to call
JSON Object
Data to send with the action
JSON Object with floor_id, area_id, device_id, entity_id, and label_id as array properties
Targets of the action
Value types:
config
: config properties of the noderesults
: response from Home Assistantsent data
: data sent to Home AssistantThe homeassistant
domain is versatile, allowing you to control multiple entities across different domains with various services.
For example, you can use the homeassistant
domain to turn off lights, switches, and any other devices in the laundry room with a single action call. This approach reduces the need for multiple action nodes.
Mustache templates can be applied in the domain, service, and entity ID fields, which is especially useful for setting the service based on msg.payload
or other message properties.
For instance, you can use eztimer to set the output of a node to on
or off
, and then use that output in the action node to determine the service to use.
[{"id":"522c54b8de855062","type":"api-call-service","z":"04f15f38f9038e78","name":"","server":"","version":6,"debugenabled":false,"action":"homeassistant.turn_{{payload}}","floorId":[],"areaId":["75a88dfa58844f8aad752dde5bc51fc7"],"deviceId":[],"entityId":["light.front_porch","light.garage","light.kitchen"],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","domain":"homeassistant","service":"turn_{{payload}}","x":670,"y":288,"wires":[[]]},{"id":"cca91c6fe47d7fa0","type":"eztimer","z":"04f15f38f9038e78","name":"","debug":false,"autoname":"sunset - dawn","tag":"eztimer","suspended":false,"sendEventsOnSuspend":false,"latLongSource":"manual","latLongHaZone":"","lat":"","lon":"","timerType":"1","startupMessage":true,"ontype":"1","ontimesun":"sunset","ontimetod":"17:00","onpropertytype":"msg","onproperty":"payload","onvaluetype":"str","onvalue":"on","onoffset":0,"onrandomoffset":0,"onsuppressrepeats":false,"offtype":"1","offtimesun":"dawn","offtimetod":"dusk","offduration":"00:01:00","offpropertytype":"msg","offproperty":"payload","offvaluetype":"str","offvalue":"off","offoffset":0,"offrandomoffset":0,"offsuppressrepeats":false,"resend":false,"resendInterval":"0s","mon":true,"tue":true,"wed":true,"thu":true,"fri":true,"sat":true,"sun":true,"x":400,"y":288,"wires":[["522c54b8de855062"]]}]
+
Mustache templates are also supported in the data field when set to JSON format.
Related Resources:
`,13)),n("ul",null,[n("li",null,[t(a,{to:"/guide/mustache-templates.html"},{default:o(()=>s[0]||(s[0]=[p("Mustache Templates")])),_:1})])]),s[6]||(s[6]=e('Target fields allow you to specify areas, devices, and/or entities, either individually or in combination. Note that not all services accept areas and devices.
Mustache templates and environment variables can be used within each target list.
Tips
For the data field, it’s recommended to use the JSONata expression (J: Expression
), as it offers several benefits over JSON:
{ "message": "The " & data.attributes.friendly_name & " has been opened.", "entity_id": "all" }
+
[{"id":"cd1f9610a73addbc","type":"server-state-changed","z":"c89d915bdff0f798","name":"","server":"","version":6,"outputs":2,"exposeAsEntityConfig":"","entities":{"entity":[],"substring":[],"regex":["sensor\\\\..+_door$"]},"outputInitially":false,"stateType":"str","ifState":"open","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":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":446,"y":976,"wires":[["4e016826f6497b79"],[]]},{"id":"4e016826f6497b79","type":"api-call-service","z":"c89d915bdff0f798","name":"send tts","server":"","version":6,"debugenabled":false,"action":"tts.google_translate_say","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"{\\"message\\": \\"The \\" & data.attributes.friendly_name & \\" has been opened.\\", \\"entity_id\\": \\"all\\"}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","domain":"tts","service":"google_translate_say","output_location":"","output_location_type":"none","x":676,"y":976,"wires":[[]]}]
+
Home Assistant nodes support custom JSONata functions to fetch properties of any HA entity.
$entities()
returns all entities in the cache$entities("entity_id")
returns a single entity from the cache that matches the given entity_id
{ "message": "The " & $entities("binary_sensor.front_door").attributes.friendly_name & " has been opened." }
+
On
State{
+ "message": "The " & $join($entities().*[state = "on" and entity_id ~> /^light|^switch/].attributes.friendly_name, ", ") & " are on.",
+ "title": "Left On"
+}
+
Since Home Assistant states are represented as strings, you need to cast them to numbers in JSONata to perform arithmetic. Use the function $number()
for this purpose. Although most entity attributes are in the correct state, casting them as numbers ensures accuracy.
Example:
$number($entities("sensor.kitchen_lux").state)
+
{ "temperature": $entities("climate.thermostat").attributes.temperature + 3 }
+
You can generate an entity ID list by retrieving entities using the get-entities node and then creating a comma-separated list to turn them off. In this example, the entity ID field is left blank as it is defined in the data field.
{ "entity_id": $join(payload.entity_id, ",") }
+
[{"id":"80c20ba2.f09cd8","type":"ha-get-entities","z":"c89d915bdff0f798","name":"get lights that are on","server":"","version":1,"rules":[{"condition":"state_object","property":"entity_id","logic":"starts_with","value":"light.","valueType":"str"},{"condition":"state_object","property":"state","logic":"is","value":"on","valueType":"str"}],"outputType":"array","outputEmptyResults":false,"outputLocationType":"msg","outputLocation":"payload","outputResultsCount":1,"x":516,"y":1152,"wires":[["cafbf446.080928"]]},{"id":"cafbf446.080928","type":"api-call-service","z":"c89d915bdff0f798","name":"turn them off","server":"","version":6,"debugenabled":false,"action":"light.turn_off","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"{\\"entity_id\\": $join(payload.entity_id,\\",\\")}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","domain":"light","service":"turn_off","output_location":"","output_location_type":"none","x":710,"y":1152,"wires":[[]]},{"id":"39666758.cee048","type":"inject","z":"c89d915bdff0f798","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":332,"y":1152,"wires":[["80c20ba2.f09cd8"]]}]
+
Additional Resources:
`,32)),n("ul",null,[n("li",null,[t(a,{to:"/node/action.html"},{default:o(()=>s[1]||(s[1]=[p("Action Node Documentation")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[2]||(s[2]=[p("JSONata Guide")])),_:1})]),s[3]||(s[3]=n("li",null,[n("a",{href:"https://docs.jsonata.org",target:"_blank",rel:"noopener noreferrer"},"JSONata Documentation")],-1)),s[4]||(s[4]=n("li",null,[n("a",{href:"http://try.jsonata.org",target:"_blank",rel:"noopener noreferrer"},"JSONata Playground")],-1))])])}const j=u(f,[["render",b],["__file","action.html.vue"]]),x=JSON.parse('{"path":"/guide/action.html","title":"Action Node Tips and Tricks","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Home Assistant Domain","slug":"home-assistant-domain","link":"#home-assistant-domain","children":[]},{"level":2,"title":"Using Mustache Templates","slug":"using-mustache-templates","link":"#using-mustache-templates","children":[]},{"level":2,"title":"Targets","slug":"targets","link":"#targets","children":[]},{"level":2,"title":"Data Field","slug":"data-field","link":"#data-field","children":[{"level":3,"title":"Inserting a Message Property into a String","slug":"inserting-a-message-property-into-a-string","link":"#inserting-a-message-property-into-a-string","children":[]},{"level":3,"title":"Retrieving a Property Value of a Home Assistant Entity","slug":"retrieving-a-property-value-of-a-home-assistant-entity","link":"#retrieving-a-property-value-of-a-home-assistant-entity","children":[]},{"level":3,"title":"Performing Arithmetic Operations","slug":"performing-arithmetic-operations","link":"#performing-arithmetic-operations","children":[]},{"level":3,"title":"Creating a Comma-Delimited Entity ID List","slug":"creating-a-comma-delimited-entity-id-list","link":"#creating-a-comma-delimited-entity-id-list","children":[]}]}],"git":{"updatedTime":1724305054000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":2},{"name":"jason","email":"37859597+zachowj@users.noreply.github.com","commits":1}]},"filePathRelative":"guide/action.md"}');export{j as comp,x as data}; diff --git a/assets/action_02-DgYg4NzD.png b/assets/action_02-DgYg4NzD.png new file mode 100644 index 0000000000..3da36dcab1 Binary files /dev/null and b/assets/action_02-DgYg4NzD.png differ diff --git a/assets/action_03-DnNG0qjN.png b/assets/action_03-DnNG0qjN.png new file mode 100644 index 0000000000..692e3360dc Binary files /dev/null and b/assets/action_03-DnNG0qjN.png differ diff --git a/assets/action_05-ClsCr7kS.png b/assets/action_05-ClsCr7kS.png new file mode 100644 index 0000000000..6f9d2de91b Binary files /dev/null and b/assets/action_05-ClsCr7kS.png differ diff --git a/assets/action_06-Dpyp8yfl.png b/assets/action_06-Dpyp8yfl.png new file mode 100644 index 0000000000..73d85dd698 Binary files /dev/null and b/assets/action_06-Dpyp8yfl.png differ diff --git a/assets/action_07-CDDnq17z.png b/assets/action_07-CDDnq17z.png new file mode 100644 index 0000000000..301c7d8ee2 Binary files /dev/null and b/assets/action_07-CDDnq17z.png differ diff --git a/assets/action_08-5Ct0UYzU.png b/assets/action_08-5Ct0UYzU.png new file mode 100644 index 0000000000..880f5f166b Binary files /dev/null and b/assets/action_08-5Ct0UYzU.png differ diff --git a/assets/actionable-notifications-subflow-for-android.html-C2aCe33_.js b/assets/actionable-notifications-subflow-for-android.html-C2aCe33_.js new file mode 100644 index 0000000000..416f58668c --- /dev/null +++ b/assets/actionable-notifications-subflow-for-android.html-C2aCe33_.js @@ -0,0 +1 @@ +import{_ as t,c as p,e as o,a as n,b as e,w as u,r as c,o as r,d as l}from"./app-CefLgoES.js";const k="/node-red-contrib-home-assistant-websocket/assets/actionable-notifications-subflow-for-android_01-Ff8j17af.png",q="/node-red-contrib-home-assistant-websocket/assets/actionable-notifications-subflow-for-android_02-E9GuL3i2.png",i="/node-red-contrib-home-assistant-websocket/assets/actionable-notifications-subflow-for-android_03-CpQYr5Am.png",y="/node-red-contrib-home-assistant-websocket/assets/actionable-notifications-subflow-for-android_04-p0E-PxHx.png",g={};function d(b,s){const a=c("RouteLink");return r(),p("div",null,[s[1]||(s[1]=o('Questions and Discussion
Post questions and follow the discussion about this recipe here
v1.1.0 - Requires version 0.68.0
[{"id":"6dc0247c.d7210c","type":"subflow","name":"Actionable Notification","info":"Android actionable notification v1.0.2\\n\\n[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"HA Actions","in":[{"x":84,"y":80,"wires":[{"id":"9d85d137.fe487"}]}],"out":[{"x":1188,"y":128,"wires":[{"id":"974bd48d.c253e8","port":0}]},{"x":1188,"y":176,"wires":[{"id":"974bd48d.c253e8","port":1}]},{"x":1188,"y":224,"wires":[{"id":"974bd48d.c253e8","port":2}]},{"x":964,"y":240,"wires":[{"id":"5bc7345c.07b1cc","port":1}]}],"env":[{"name":"ha_config","type":"server","value":"","ui":{"label":{"en-US":"HA config"},"type":"conf-types"}},{"name":"service","type":"str","value":"mobile_app_jason","ui":{"label":{"en-US":"Notify Service"},"type":"select","opts":{"opts":[{"l":{"en-US":"Jason"},"v":"mobile_app_jason"}]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"meta":{"module":"Android actionable notification","version":"1.0.2","license":"MIT"},"color":"#46B1EF","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"icon":"font-awesome/fa-mobile-phone","status":{"x":244,"y":272,"wires":[{"id":"204dbcfc.144ae4","port":0}]}},{"id":"f9e57204.71076","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"msg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nconst actions = [1, 2, 3].reduce((acc, i) => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = getActionProperty(i, \\"title\\") ?? env.get(`${name}Title`);\\n const uri = getActionProperty(i, \\"uri\\") ?? env.get(`${name}Uri`);\\n const action = uri.length ? 'URI' : title ? id : undefined;\\n\\n acc.push({ action, title, uri });\\n\\n return acc;\\n}, []);\\n\\nconst tag = flow.get('notificationTag');\\nconst data = mergeDeep({\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n },\\n }, \\n msg.actionable, \\n {data: {actions}},\\n);\\n\\nif(tag !== data?.data?.tag) {\\n flow.set('notificationTag', data?.data?.tag);\\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n action: `notify.${service}`,\\n data,\\n };\\n node.send(msg);\\n});\\n\\nnode.done();\\n\\nfunction getActionProperty(index, prop) {\\n const i = index - 1;\\n\\n return msg?.actionable?.data?.actions?.[i]?.[prop];\\n}\\n\\n/**\\n * Simple object check.\\n * @param item\\n * @returns {boolean}\\n */\\nfunction isObject(item) {\\n return (item && typeof item === 'object' && !Array.isArray(item));\\n}\\n\\n/**\\n * Deep merge two objects.\\n * @param target\\n * @param sources\\n */\\nfunction mergeDeep(target, ...sources) {\\n if (!sources.length) return target;\\n const source = sources.shift();\\n\\n if (isObject(target) && isObject(source)) {\\n for (const key in source) {\\n if (isObject(source[key])) {\\n if (!target[key]) Object.assign(target, { [key]: {} });\\n mergeDeep(target[key], source[key]);\\n } else {\\n Object.assign(target, { [key]: source[key] });\\n }\\n }\\n }\\n\\n return mergeDeep(target, ...sources);\\n}\\n","outputs":1,"timeout":"","noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","libs":[],"x":298,"y":80,"wires":[["368c9723.5876f8"]]},{"id":"974bd48d.c253e8","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"204dbcfc.144ae4","type":"status","z":"6dc0247c.d7210c","name":"","scope":["f9e57204.71076","5bc7345c.07b1cc","a622c92a.2d9898","368c9723.5876f8"],"x":124,"y":272,"wires":[[]]},{"id":"5bc7345c.07b1cc","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\ndelete latestMessage.actionable;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":832,"y":176,"wires":[["974bd48d.c253e8"],[]]},{"id":"8d3bdc0c.37493","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["83ad2004.d04d"]]},{"id":"271e4479.b9249c","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","server":"${ha_config}","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":822,"y":128,"wires":[["5bc7345c.07b1cc"]]},{"id":"3618f055.6909a","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_cleared","server":"${ha_config}","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_cleared","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":224,"wires":[["8d3bdc0c.37493"]]},{"id":"83ad2004.d04d","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["271e4479.b9249c"],["5bc7345c.07b1cc"]]},{"id":"9d85d137.fe487","type":"switch","z":"6dc0247c.d7210c","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["f9e57204.71076"],["a622c92a.2d9898"]],"l":false},{"id":"a622c92a.2d9898","type":"function","z":"6dc0247c.d7210c","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.status({text: \\"cleared\\"});\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":318,"y":128,"wires":[["368c9723.5876f8"]]},{"id":"9bfe567c.3d10c8","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_action","server":"${ha_config}","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_action","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":176,"wires":[["8d3bdc0c.37493"]]},{"id":"368c9723.5876f8","type":"api-call-service","z":"6dc0247c.d7210c","name":"","server":"${ha_config}","version":6,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"all","domain":"notify","x":550,"y":80,"wires":[[]]},{"id":"8a2ecff38edf7efd","type":"subflow:6dc0247c.d7210c","z":"bd764bbe2981bf8f","g":"88a81b994490d984","name":"","env":[{"name":"ha_config","value":"_ADD_","type":"conf-type"},{"name":"group","value":"","type":"str"},{"name":"HA config","value":"","type":"str"}],"x":196,"y":80,"wires":[[],[],[],[]]}]\n
Changes
v1.0.1 - Requires version 0.68.0
[{"id":"6dc0247c.d7210c","type":"subflow","name":"Actionable Notification","info":"Android actionable notification v1.0.1\\n\\n[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"HA Actions","in":[{"x":84,"y":80,"wires":[{"id":"9d85d137.fe487"}]}],"out":[{"x":1188,"y":128,"wires":[{"id":"974bd48d.c253e8","port":0}]},{"x":1188,"y":176,"wires":[{"id":"974bd48d.c253e8","port":1}]},{"x":1188,"y":224,"wires":[{"id":"974bd48d.c253e8","port":2}]},{"x":964,"y":240,"wires":[{"id":"5bc7345c.07b1cc","port":1}]}],"env":[{"name":"service","type":"str","value":"mobile_app_jason","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"meta":{},"color":"#46B1EF","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"icon":"font-awesome/fa-mobile-phone","status":{"x":244,"y":272,"wires":[{"id":"204dbcfc.144ae4","port":0}]}},{"id":"f9e57204.71076","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"msg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nconst actions = [1, 2, 3].reduce((acc, i) => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = getActionProperty(i, \\"title\\") ?? env.get(`${name}Title`);\\n const uri = getActionProperty(i, \\"uri\\") ?? env.get(`${name}Uri`);\\n const action = uri.length ? 'URI' : title ? id : undefined;\\n\\n acc.push({ action, title, uri });\\n\\n return acc;\\n}, []);\\n\\nconst tag = flow.get('notificationTag');\\nconst data = mergeDeep({\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n },\\n }, \\n msg.actionable, \\n {data: {actions}},\\n);\\n\\nif(tag !== data?.data?.tag) {\\n flow.set('notificationTag', data?.data?.tag);\\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n action: `notify.${service}`,\\n data,\\n };\\n node.send(msg);\\n});\\n\\nnode.done();\\n\\nfunction getActionProperty(index, prop) {\\n const i = index - 1;\\n\\n return msg?.actionable?.data?.actions?.[i]?.[prop];\\n}\\n\\n/**\\n * Simple object check.\\n * @param item\\n * @returns {boolean}\\n */\\nfunction isObject(item) {\\n return (item && typeof item === 'object' && !Array.isArray(item));\\n}\\n\\n/**\\n * Deep merge two objects.\\n * @param target\\n * @param sources\\n */\\nfunction mergeDeep(target, ...sources) {\\n if (!sources.length) return target;\\n const source = sources.shift();\\n\\n if (isObject(target) && isObject(source)) {\\n for (const key in source) {\\n if (isObject(source[key])) {\\n if (!target[key]) Object.assign(target, { [key]: {} });\\n mergeDeep(target[key], source[key]);\\n } else {\\n Object.assign(target, { [key]: source[key] });\\n }\\n }\\n }\\n\\n return mergeDeep(target, ...sources);\\n}\\n","outputs":1,"timeout":"","noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","libs":[],"x":298,"y":80,"wires":[["368c9723.5876f8"]]},{"id":"974bd48d.c253e8","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"204dbcfc.144ae4","type":"status","z":"6dc0247c.d7210c","name":"","scope":["f9e57204.71076","5bc7345c.07b1cc","a622c92a.2d9898","368c9723.5876f8"],"x":124,"y":272,"wires":[[]]},{"id":"5bc7345c.07b1cc","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\ndelete latestMessage.actionable;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":832,"y":176,"wires":[["974bd48d.c253e8"],[]]},{"id":"8d3bdc0c.37493","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["83ad2004.d04d"]]},{"id":"271e4479.b9249c","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","server":"","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":822,"y":128,"wires":[["5bc7345c.07b1cc"]]},{"id":"3618f055.6909a","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_cleared","server":"","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_cleared","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":224,"wires":[["8d3bdc0c.37493"]]},{"id":"83ad2004.d04d","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["271e4479.b9249c"],["5bc7345c.07b1cc"]]},{"id":"9d85d137.fe487","type":"switch","z":"6dc0247c.d7210c","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["f9e57204.71076"],["a622c92a.2d9898"]],"l":false},{"id":"a622c92a.2d9898","type":"function","z":"6dc0247c.d7210c","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.status({text: \\"cleared\\"});\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":318,"y":128,"wires":[["368c9723.5876f8"]]},{"id":"9bfe567c.3d10c8","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_action","server":"","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_action","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":176,"wires":[["8d3bdc0c.37493"]]},{"id":"368c9723.5876f8","type":"api-call-service","z":"6dc0247c.d7210c","name":"","server":"","version":6,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"all","domain":"notify","x":550,"y":80,"wires":[[]]},{"id":"8a2ecff38edf7efd","type":"subflow:6dc0247c.d7210c","z":"bd764bbe2981bf8f","name":"","env":[{"name":"group","value":null,"type":"str"}],"x":180,"y":96,"wires":[[],[],[],[]]}]\n
v1.0.0
msg.actionable
to pass a notification object into the node.[{"id":"6dc0247c.d7210c","type":"subflow","name":"Actionable Notification","info":"Android actionable notification v1.0.0\\n\\n[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"HA Actions","in":[{"x":84,"y":80,"wires":[{"id":"9d85d137.fe487"}]}],"out":[{"x":1188,"y":128,"wires":[{"id":"974bd48d.c253e8","port":0}]},{"x":1188,"y":176,"wires":[{"id":"974bd48d.c253e8","port":1}]},{"x":1188,"y":224,"wires":[{"id":"974bd48d.c253e8","port":2}]},{"x":964,"y":240,"wires":[{"id":"5bc7345c.07b1cc","port":1}]}],"env":[{"name":"service","type":"str","value":"mobile_app_jason","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"meta":{},"color":"#46B1EF","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"icon":"font-awesome/fa-mobile-phone","status":{"x":244,"y":272,"wires":[{"id":"204dbcfc.144ae4","port":0}]}},{"id":"f9e57204.71076","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"msg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nconst actions = [1, 2, 3].reduce((acc, i) => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = getActionProperty(i, \\"title\\") ?? env.get(`${name}Title`);\\n const uri = getActionProperty(i, \\"uri\\") ?? env.get(`${name}Uri`);\\n const action = uri.length ? 'URI' : title ? id : undefined;\\n\\n acc.push({ action, title, uri });\\n\\n return acc;\\n}, []);\\n\\nconst tag = flow.get('notificationTag');\\nconst data = mergeDeep({\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n },\\n }, \\n msg.actionable, \\n {data: {actions}},\\n);\\n\\nif(tag !== data?.data?.tag) {\\n flow.set('notificationTag', data?.data?.tag);\\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data,\\n };\\n node.send(msg);\\n});\\n\\nnode.done();\\n\\nfunction getActionProperty(index, prop) {\\n const i = index - 1;\\n\\n return msg?.actionable?.data?.actions?.[i]?.[prop];\\n}\\n\\n/**\\n * Simple object check.\\n * @param item\\n * @returns {boolean}\\n */\\nfunction isObject(item) {\\n return (item && typeof item === 'object' && !Array.isArray(item));\\n}\\n\\n/**\\n * Deep merge two objects.\\n * @param target\\n * @param sources\\n */\\nfunction mergeDeep(target, ...sources) {\\n if (!sources.length) return target;\\n const source = sources.shift();\\n\\n if (isObject(target) && isObject(source)) {\\n for (const key in source) {\\n if (isObject(source[key])) {\\n if (!target[key]) Object.assign(target, { [key]: {} });\\n mergeDeep(target[key], source[key]);\\n } else {\\n Object.assign(target, { [key]: source[key] });\\n }\\n }\\n }\\n\\n return mergeDeep(target, ...sources);\\n}\\n","outputs":1,"noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","libs":[],"x":298,"y":80,"wires":[["368c9723.5876f8"]]},{"id":"974bd48d.c253e8","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"204dbcfc.144ae4","type":"status","z":"6dc0247c.d7210c","name":"","scope":["f9e57204.71076","5bc7345c.07b1cc","a622c92a.2d9898","368c9723.5876f8"],"x":124,"y":272,"wires":[[]]},{"id":"5bc7345c.07b1cc","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\ndelete latestMessage.actionable;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":832,"y":176,"wires":[["974bd48d.c253e8"],[]]},{"id":"8d3bdc0c.37493","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["83ad2004.d04d"]]},{"id":"271e4479.b9249c","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","server":"","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":822,"y":128,"wires":[["5bc7345c.07b1cc"]]},{"id":"3618f055.6909a","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_cleared","server":"","version":1,"event_type":"mobile_app_notification_cleared","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":224,"wires":[["8d3bdc0c.37493"]]},{"id":"83ad2004.d04d","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["271e4479.b9249c"],["5bc7345c.07b1cc"]]},{"id":"9d85d137.fe487","type":"switch","z":"6dc0247c.d7210c","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["f9e57204.71076"],["a622c92a.2d9898"]],"l":false},{"id":"a622c92a.2d9898","type":"function","z":"6dc0247c.d7210c","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.status({text: \\"cleared\\"});\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":318,"y":128,"wires":[["368c9723.5876f8"]]},{"id":"9bfe567c.3d10c8","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_action","server":"","version":1,"event_type":"mobile_app_notification_action","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":176,"wires":[["8d3bdc0c.37493"]]},{"id":"368c9723.5876f8","type":"api-call-service","z":"6dc0247c.d7210c","name":"","server":"","version":4,"debugenabled":false,"domain":"notify","target":{"areaId":[],"deviceId":[],"entityId":[]},"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"all","x":550,"y":80,"wires":[[]]},{"id":"8a2ecff38edf7efd","type":"subflow:6dc0247c.d7210c","z":"a38123c1.2fd3f","name":"","x":516,"y":544,"wires":[[],[],[],[]]}]\n
v0.0.0
[{"id":"6dc0247c.d7210c","type":"subflow","name":"Actionable Notification","info":"[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"","in":[{"x":84,"y":80,"wires":[{"id":"9d85d137.fe487"}]}],"out":[{"x":1172,"y":128,"wires":[{"id":"974bd48d.c253e8","port":0}]},{"x":1172,"y":176,"wires":[{"id":"974bd48d.c253e8","port":1}]},{"x":1172,"y":224,"wires":[{"id":"974bd48d.c253e8","port":2}]},{"x":964,"y":240,"wires":[{"id":"5bc7345c.07b1cc","port":1}]}],"env":[{"name":"service","type":"str","value":"","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"color":"#DDAA99","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"status":{"x":244,"y":272,"wires":[{"id":"204dbcfc.144ae4","port":0}]}},{"id":"f9e57204.71076","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"const actions = [];\\n[1,2,3].forEach(i => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = env.get(`${name}Title`);\\n const uri = env.get(`${name}Uri`);\\n const action = !!uri.length ? 'URI' : title ? flow.get(`${name}Id`) : undefined;\\n \\n actions.push({\\n action,\\n title,\\n uri\\n });\\n});\\n\\nmsg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag: flow.get('notificationTag'),\\n actions,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.done();","outputs":1,"noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","x":298,"y":80,"wires":[["368c9723.5876f8"]]},{"id":"974bd48d.c253e8","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"204dbcfc.144ae4","type":"status","z":"6dc0247c.d7210c","name":"","scope":["f9e57204.71076","5bc7345c.07b1cc","a622c92a.2d9898","368c9723.5876f8"],"x":124,"y":272,"wires":[[]]},{"id":"5bc7345c.07b1cc","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","x":832,"y":176,"wires":[["974bd48d.c253e8"],[]]},{"id":"8d3bdc0c.37493","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["83ad2004.d04d"]]},{"id":"271e4479.b9249c","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","location":"userData","locationType":"msg","responseType":"json","x":822,"y":128,"wires":[["5bc7345c.07b1cc"]]},{"id":"3618f055.6909a","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_cleared","event_type":"mobile_app_notification_cleared","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"x":194,"y":224,"wires":[["8d3bdc0c.37493"]]},{"id":"83ad2004.d04d","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["271e4479.b9249c"],["5bc7345c.07b1cc"]]},{"id":"9d85d137.fe487","type":"switch","z":"6dc0247c.d7210c","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["f9e57204.71076"],["a622c92a.2d9898"]],"l":false},{"id":"a622c92a.2d9898","type":"function","z":"6dc0247c.d7210c","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","x":318,"y":128,"wires":[["368c9723.5876f8"]]},{"id":"9bfe567c.3d10c8","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_action","event_type":"mobile_app_notification_action","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"x":194,"y":176,"wires":[["8d3bdc0c.37493"]]},{"id":"368c9723.5876f8","type":"api-call-service","z":"6dc0247c.d7210c","name":"","version":1,"debugenabled":false,"service_domain":"notify","service":"","entityId":"","data":"","dataType":"json","mergecontext":"callServiceData","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":550,"y":80,"wires":[[]]}]\n
Options | Description | Documentation |
---|---|---|
HA config | Home Assistant configuration node | |
Notify Service | Can take multiple services as a comma delimited list e.g.: mobile_app_username, mobile_app_username2 | |
Title | Top line of text | link |
Message | Second line of text accepts HTML tags | link |
Action Title | The button title | link |
Action URI | lovelace dashboard, https:// or app:// . If URI is defined no action is returned to Node-RED | link |
User Info | Will resolve user info in Node-RED and place it in msg.userData | |
Sticky | Set whether to dismiss the notification upon selecting it or not | link |
Group | string | link |
Color | Color name or the hex code | link |
Timeout | How long a notification will be shown on a users device before being removed automatically | link |
Icon | Path to icon | link |
Any setting can be set or overwritten using msg.actionable
to pass a notification object into the node.
[{"id":"a54a9c807b16be0f","type":"group","z":"bd764bbe2981bf8f","name":"demo.json","style":{"label":true},"nodes":["aa8e9e73.4957f","d2b46240.6de89","a63fb7bc.64af88","2f4904b8.568d3c","75334c15.956694","81797230.4299e","7b116fa205bb7db2"],"x":50,"y":199,"w":692,"h":178},{"id":"6dc0247c.d7210c","type":"subflow","name":"Actionable Notification","info":"Android actionable notification v1.0.1\\n\\n[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"HA Actions","in":[{"x":84,"y":80,"wires":[{"id":"9d85d137.fe487"}]}],"out":[{"x":1188,"y":128,"wires":[{"id":"974bd48d.c253e8","port":0}]},{"x":1188,"y":176,"wires":[{"id":"974bd48d.c253e8","port":1}]},{"x":1188,"y":224,"wires":[{"id":"974bd48d.c253e8","port":2}]},{"x":964,"y":240,"wires":[{"id":"5bc7345c.07b1cc","port":1}]}],"env":[{"name":"service","type":"str","value":"mobile_app_jason","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"meta":{},"color":"#46B1EF","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"icon":"font-awesome/fa-mobile-phone","status":{"x":244,"y":272,"wires":[{"id":"204dbcfc.144ae4","port":0}]}},{"id":"f9e57204.71076","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"msg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nconst actions = [1, 2, 3].reduce((acc, i) => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = getActionProperty(i, \\"title\\") ?? env.get(`${name}Title`);\\n const uri = getActionProperty(i, \\"uri\\") ?? env.get(`${name}Uri`);\\n const action = uri.length ? 'URI' : title ? id : undefined;\\n\\n acc.push({ action, title, uri });\\n\\n return acc;\\n}, []);\\n\\nconst tag = flow.get('notificationTag');\\nconst data = mergeDeep({\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n },\\n }, \\n msg.actionable, \\n {data: {actions}},\\n);\\n\\nif(tag !== data?.data?.tag) {\\n flow.set('notificationTag', data?.data?.tag);\\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n action: `notify.${service}`,\\n data,\\n };\\n node.send(msg);\\n});\\n\\nnode.done();\\n\\nfunction getActionProperty(index, prop) {\\n const i = index - 1;\\n\\n return msg?.actionable?.data?.actions?.[i]?.[prop];\\n}\\n\\n/**\\n * Simple object check.\\n * @param item\\n * @returns {boolean}\\n */\\nfunction isObject(item) {\\n return (item && typeof item === 'object' && !Array.isArray(item));\\n}\\n\\n/**\\n * Deep merge two objects.\\n * @param target\\n * @param sources\\n */\\nfunction mergeDeep(target, ...sources) {\\n if (!sources.length) return target;\\n const source = sources.shift();\\n\\n if (isObject(target) && isObject(source)) {\\n for (const key in source) {\\n if (isObject(source[key])) {\\n if (!target[key]) Object.assign(target, { [key]: {} });\\n mergeDeep(target[key], source[key]);\\n } else {\\n Object.assign(target, { [key]: source[key] });\\n }\\n }\\n }\\n\\n return mergeDeep(target, ...sources);\\n}\\n","outputs":1,"timeout":"","noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","libs":[],"x":298,"y":80,"wires":[["368c9723.5876f8"]]},{"id":"974bd48d.c253e8","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"204dbcfc.144ae4","type":"status","z":"6dc0247c.d7210c","name":"","scope":["f9e57204.71076","5bc7345c.07b1cc","a622c92a.2d9898","368c9723.5876f8"],"x":124,"y":272,"wires":[[]]},{"id":"5bc7345c.07b1cc","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\ndelete latestMessage.actionable;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":832,"y":176,"wires":[["974bd48d.c253e8"],[]]},{"id":"8d3bdc0c.37493","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["83ad2004.d04d"]]},{"id":"271e4479.b9249c","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","server":"","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":822,"y":128,"wires":[["5bc7345c.07b1cc"]]},{"id":"3618f055.6909a","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_cleared","server":"","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_cleared","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":224,"wires":[["8d3bdc0c.37493"]]},{"id":"83ad2004.d04d","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["271e4479.b9249c"],["5bc7345c.07b1cc"]]},{"id":"9d85d137.fe487","type":"switch","z":"6dc0247c.d7210c","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["f9e57204.71076"],["a622c92a.2d9898"]],"l":false},{"id":"a622c92a.2d9898","type":"function","z":"6dc0247c.d7210c","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.status({text: \\"cleared\\"});\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":318,"y":128,"wires":[["368c9723.5876f8"]]},{"id":"9bfe567c.3d10c8","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_action","server":"","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_action","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":176,"wires":[["8d3bdc0c.37493"]]},{"id":"368c9723.5876f8","type":"api-call-service","z":"6dc0247c.d7210c","name":"","server":"","version":6,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"all","domain":"notify","x":550,"y":80,"wires":[[]]},{"id":"aa8e9e73.4957f","type":"inject","z":"bd764bbe2981bf8f","g":"a54a9c807b16be0f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"abc","payload":"default payload","payloadType":"str","x":186,"y":288,"wires":[["7b116fa205bb7db2"]]},{"id":"d2b46240.6de89","type":"debug","z":"bd764bbe2981bf8f","g":"a54a9c807b16be0f","name":"action1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":636,"y":240,"wires":[]},{"id":"a63fb7bc.64af88","type":"debug","z":"bd764bbe2981bf8f","g":"a54a9c807b16be0f","name":"action2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":636,"y":272,"wires":[]},{"id":"2f4904b8.568d3c","type":"debug","z":"bd764bbe2981bf8f","g":"a54a9c807b16be0f","name":"action3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":636,"y":304,"wires":[]},{"id":"75334c15.956694","type":"debug","z":"bd764bbe2981bf8f","g":"a54a9c807b16be0f","name":"cleared","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":636,"y":336,"wires":[]},{"id":"81797230.4299e","type":"inject","z":"bd764bbe2981bf8f","g":"a54a9c807b16be0f","name":"clear_notification","props":[{"p":"clear_notification","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"str","x":192,"y":336,"wires":[["7b116fa205bb7db2"]]},{"id":"7b116fa205bb7db2","type":"subflow:6dc0247c.d7210c","z":"bd764bbe2981bf8f","g":"a54a9c807b16be0f","name":"","env":[{"name":"group","value":null,"type":"str"}],"x":428,"y":288,"wires":[["d2b46240.6de89"],["a63fb7bc.64af88"],["2f4904b8.568d3c"],["75334c15.956694"]]}]\n
[{"id":"10f78dfbdda44e3b","type":"group","z":"bd764bbe2981bf8f","name":"use-case-01","style":{"label":true},"nodes":["bec57b08.0a8f08","31d5412.74a31be","587bab85.857fe4","e85a9609.74ddd8","1d0b3f5b.37aab1","8c90dfc.28f2f2","df484d95.35a35","6566fa4.a832004","88fd9ca4.3ea69","9e5e4c22.4d925","13c8aaf9.466e05","8f5a5cc3.fb235","5485b497.b4d90c","1b96dc9e.4f1fe3","5b765a47a35c3f91"],"x":22,"y":439,"w":1368,"h":210},{"id":"6dc0247c.d7210c","type":"subflow","name":"Actionable Notification","info":"Android actionable notification v1.0.1\\n\\n[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"HA Actions","in":[{"x":84,"y":80,"wires":[{"id":"9d85d137.fe487"}]}],"out":[{"x":1188,"y":128,"wires":[{"id":"974bd48d.c253e8","port":0}]},{"x":1188,"y":176,"wires":[{"id":"974bd48d.c253e8","port":1}]},{"x":1188,"y":224,"wires":[{"id":"974bd48d.c253e8","port":2}]},{"x":964,"y":240,"wires":[{"id":"5bc7345c.07b1cc","port":1}]}],"env":[{"name":"service","type":"str","value":"mobile_app_jason","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"meta":{},"color":"#46B1EF","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"icon":"font-awesome/fa-mobile-phone","status":{"x":244,"y":272,"wires":[{"id":"204dbcfc.144ae4","port":0}]}},{"id":"f9e57204.71076","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"msg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nconst actions = [1, 2, 3].reduce((acc, i) => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = getActionProperty(i, \\"title\\") ?? env.get(`${name}Title`);\\n const uri = getActionProperty(i, \\"uri\\") ?? env.get(`${name}Uri`);\\n const action = uri.length ? 'URI' : title ? id : undefined;\\n\\n acc.push({ action, title, uri });\\n\\n return acc;\\n}, []);\\n\\nconst tag = flow.get('notificationTag');\\nconst data = mergeDeep({\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n },\\n }, \\n msg.actionable, \\n {data: {actions}},\\n);\\n\\nif(tag !== data?.data?.tag) {\\n flow.set('notificationTag', data?.data?.tag);\\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n action: `notify.${service}`,\\n data,\\n };\\n node.send(msg);\\n});\\n\\nnode.done();\\n\\nfunction getActionProperty(index, prop) {\\n const i = index - 1;\\n\\n return msg?.actionable?.data?.actions?.[i]?.[prop];\\n}\\n\\n/**\\n * Simple object check.\\n * @param item\\n * @returns {boolean}\\n */\\nfunction isObject(item) {\\n return (item && typeof item === 'object' && !Array.isArray(item));\\n}\\n\\n/**\\n * Deep merge two objects.\\n * @param target\\n * @param sources\\n */\\nfunction mergeDeep(target, ...sources) {\\n if (!sources.length) return target;\\n const source = sources.shift();\\n\\n if (isObject(target) && isObject(source)) {\\n for (const key in source) {\\n if (isObject(source[key])) {\\n if (!target[key]) Object.assign(target, { [key]: {} });\\n mergeDeep(target[key], source[key]);\\n } else {\\n Object.assign(target, { [key]: source[key] });\\n }\\n }\\n }\\n\\n return mergeDeep(target, ...sources);\\n}\\n","outputs":1,"timeout":"","noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","libs":[],"x":298,"y":80,"wires":[["368c9723.5876f8"]]},{"id":"974bd48d.c253e8","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"204dbcfc.144ae4","type":"status","z":"6dc0247c.d7210c","name":"","scope":["f9e57204.71076","5bc7345c.07b1cc","a622c92a.2d9898","368c9723.5876f8"],"x":124,"y":272,"wires":[[]]},{"id":"5bc7345c.07b1cc","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\ndelete latestMessage.actionable;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":832,"y":176,"wires":[["974bd48d.c253e8"],[]]},{"id":"8d3bdc0c.37493","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["83ad2004.d04d"]]},{"id":"271e4479.b9249c","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","server":"","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":822,"y":128,"wires":[["5bc7345c.07b1cc"]]},{"id":"3618f055.6909a","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_cleared","server":"","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_cleared","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":224,"wires":[["8d3bdc0c.37493"]]},{"id":"83ad2004.d04d","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["271e4479.b9249c"],["5bc7345c.07b1cc"]]},{"id":"9d85d137.fe487","type":"switch","z":"6dc0247c.d7210c","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["f9e57204.71076"],["a622c92a.2d9898"]],"l":false},{"id":"a622c92a.2d9898","type":"function","z":"6dc0247c.d7210c","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.status({text: \\"cleared\\"});\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":318,"y":128,"wires":[["368c9723.5876f8"]]},{"id":"9bfe567c.3d10c8","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_action","server":"","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_action","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":176,"wires":[["8d3bdc0c.37493"]]},{"id":"368c9723.5876f8","type":"api-call-service","z":"6dc0247c.d7210c","name":"","server":"","version":6,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"all","domain":"notify","x":550,"y":80,"wires":[[]]},{"id":"bec57b08.0a8f08","type":"time-range-switch","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"","lat":"","lon":"","startTime":"sunsetStart","endTime":"6:00","startOffset":"-15","endOffset":0,"x":490,"y":496,"wires":[["1b96dc9e.4f1fe3"],[]]},{"id":"31d5412.74a31be","type":"poll-state","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"garage door open?","server":"","version":3,"exposeAsEntityConfig":"","updateInterval":"30","updateIntervalType":"num","updateIntervalUnits":"seconds","outputInitially":true,"outputOnChanged":true,"entityId":"cover.garage_door","stateType":"str","ifState":"open","ifStateType":"str","ifStateOperator":"is","outputs":2,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":138,"y":528,"wires":[["6566fa4.a832004"],["5485b497.b4d90c"]]},{"id":"587bab85.857fe4","type":"subflow:6dc0247c.d7210c","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"close garage door?","env":[{"name":"service","value":"mobile_app_jason","type":"str"},{"name":"title","value":"Garage Door","type":"str"},{"name":"message","value":"Garage Door is Open","type":"str"},{"name":"action1Title","value":"Close Door","type":"str"},{"name":"action2Title","value":"Ignore 1 hour","type":"str"},{"name":"action3Title","value":"Ignore 2 hours","type":"str"},{"name":"group","value":"","type":"str"}],"x":1066,"y":544,"wires":[["e85a9609.74ddd8"],["8c90dfc.28f2f2"],["df484d95.35a35"],["13c8aaf9.466e05"]],"icon":"font-awesome/fa-car"},{"id":"e85a9609.74ddd8","type":"api-call-service","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"close garage door","server":"","version":6,"debugenabled":false,"action":"cover.close_cover","floorId":[],"areaId":[],"deviceId":[],"entityId":["cover.garage_door"],"labelId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","domain":"cover","service":"close_cover","output_location":"","output_location_type":"none","x":1274,"y":496,"wires":[[]]},{"id":"1d0b3f5b.37aab1","type":"api-current-state","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"not home?","server":"","version":3,"outputs":2,"halt_if":"home","halt_if_type":"str","halt_if_compare":"is_not","entity_id":"person.jason","state_type":"str","blockInputOverrides":false,"outputProperties":[],"for":0,"forType":"num","forUnits":"minutes","x":678,"y":544,"wires":[["5b765a47a35c3f91"],[]]},{"id":"8c90dfc.28f2f2","type":"change","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"","rules":[{"t":"set","p":"ignore","pt":"msg","to":"60","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":1199,"y":544,"wires":[["88fd9ca4.3ea69"]],"l":false},{"id":"df484d95.35a35","type":"change","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"","rules":[{"t":"set","p":"ignore","pt":"msg","to":"120","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":1199,"y":576,"wires":[["88fd9ca4.3ea69"]],"l":false},{"id":"6566fa4.a832004","type":"function","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"ignore?","func":"if(msg.ignore) {\\n const timeout = Date.now() + msg.ignore * 60000;\\n const d = new Date(timeout);\\n context.set('timeout', timeout)\\n node.status({text: `Ignoring until ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`, fill: \\"red\\"});\\n return;\\n}\\n\\nconst timeout = context.get('timeout') || 0;\\n\\nif(timeout > Date.now()) return;\\n\\nnode.status({});\\n\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":316,"y":528,"wires":[["bec57b08.0a8f08","8f5a5cc3.fb235"]]},{"id":"88fd9ca4.3ea69","type":"link out","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"Ignore","links":["9e5e4c22.4d925"],"x":1314,"y":544,"wires":[],"l":true},{"id":"9e5e4c22.4d925","type":"link in","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"","links":["88fd9ca4.3ea69"],"x":207,"y":480,"wires":[["6566fa4.a832004"]]},{"id":"13c8aaf9.466e05","type":"change","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"","rules":[{"t":"set","p":"ignore","pt":"msg","to":"30","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":1199,"y":608,"wires":[["88fd9ca4.3ea69"]],"l":false},{"id":"8f5a5cc3.fb235","type":"trigger","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"2","extend":false,"overrideDelay":false,"units":"min","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":470,"y":544,"wires":[["1d0b3f5b.37aab1"]]},{"id":"5485b497.b4d90c","type":"change","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"reset","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":306,"y":576,"wires":[["8f5a5cc3.fb235","1b96dc9e.4f1fe3"]]},{"id":"1b96dc9e.4f1fe3","type":"trigger","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"15","extend":false,"overrideDelay":false,"units":"min","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":678,"y":496,"wires":[["5b765a47a35c3f91"]]},{"id":"5b765a47a35c3f91","type":"delay","z":"bd764bbe2981bf8f","g":"10f78dfbdda44e3b","name":"Once / 30 mins","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"30","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"allowrate":false,"outputs":1,"x":864,"y":544,"wires":[["587bab85.857fe4"]]}]\n
Required Nodes
[{"id":"eeda20dfef44e955","type":"group","z":"bd764bbe2981bf8f","name":"use-case-02.json","style":{"label":true},"nodes":["b35b9988.72c6d8","4587eb7f.fb86a4","be568091.56bbb","e5d8423c.2ccfe"],"x":34,"y":695,"w":864,"h":113},{"id":"6dc0247c.d7210c","type":"subflow","name":"Actionable Notification","info":"Android actionable notification v1.0.1\\n\\n[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"HA Actions","in":[{"x":84,"y":80,"wires":[{"id":"9d85d137.fe487"}]}],"out":[{"x":1188,"y":128,"wires":[{"id":"974bd48d.c253e8","port":0}]},{"x":1188,"y":176,"wires":[{"id":"974bd48d.c253e8","port":1}]},{"x":1188,"y":224,"wires":[{"id":"974bd48d.c253e8","port":2}]},{"x":964,"y":240,"wires":[{"id":"5bc7345c.07b1cc","port":1}]}],"env":[{"name":"service","type":"str","value":"mobile_app_jason","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"meta":{},"color":"#46B1EF","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"icon":"font-awesome/fa-mobile-phone","status":{"x":244,"y":272,"wires":[{"id":"204dbcfc.144ae4","port":0}]}},{"id":"f9e57204.71076","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"msg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nconst actions = [1, 2, 3].reduce((acc, i) => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = getActionProperty(i, \\"title\\") ?? env.get(`${name}Title`);\\n const uri = getActionProperty(i, \\"uri\\") ?? env.get(`${name}Uri`);\\n const action = uri.length ? 'URI' : title ? id : undefined;\\n\\n acc.push({ action, title, uri });\\n\\n return acc;\\n}, []);\\n\\nconst tag = flow.get('notificationTag');\\nconst data = mergeDeep({\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n },\\n }, \\n msg.actionable, \\n {data: {actions}},\\n);\\n\\nif(tag !== data?.data?.tag) {\\n flow.set('notificationTag', data?.data?.tag);\\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n action: `notify.${service}`,\\n data,\\n };\\n node.send(msg);\\n});\\n\\nnode.done();\\n\\nfunction getActionProperty(index, prop) {\\n const i = index - 1;\\n\\n return msg?.actionable?.data?.actions?.[i]?.[prop];\\n}\\n\\n/**\\n * Simple object check.\\n * @param item\\n * @returns {boolean}\\n */\\nfunction isObject(item) {\\n return (item && typeof item === 'object' && !Array.isArray(item));\\n}\\n\\n/**\\n * Deep merge two objects.\\n * @param target\\n * @param sources\\n */\\nfunction mergeDeep(target, ...sources) {\\n if (!sources.length) return target;\\n const source = sources.shift();\\n\\n if (isObject(target) && isObject(source)) {\\n for (const key in source) {\\n if (isObject(source[key])) {\\n if (!target[key]) Object.assign(target, { [key]: {} });\\n mergeDeep(target[key], source[key]);\\n } else {\\n Object.assign(target, { [key]: source[key] });\\n }\\n }\\n }\\n\\n return mergeDeep(target, ...sources);\\n}\\n","outputs":1,"timeout":"","noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","libs":[],"x":298,"y":80,"wires":[["368c9723.5876f8"]]},{"id":"974bd48d.c253e8","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"204dbcfc.144ae4","type":"status","z":"6dc0247c.d7210c","name":"","scope":["f9e57204.71076","5bc7345c.07b1cc","a622c92a.2d9898","368c9723.5876f8"],"x":124,"y":272,"wires":[[]]},{"id":"5bc7345c.07b1cc","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\ndelete latestMessage.actionable;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":832,"y":176,"wires":[["974bd48d.c253e8"],[]]},{"id":"8d3bdc0c.37493","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["83ad2004.d04d"]]},{"id":"271e4479.b9249c","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","server":"","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":822,"y":128,"wires":[["5bc7345c.07b1cc"]]},{"id":"3618f055.6909a","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_cleared","server":"","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_cleared","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":224,"wires":[["8d3bdc0c.37493"]]},{"id":"83ad2004.d04d","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["271e4479.b9249c"],["5bc7345c.07b1cc"]]},{"id":"9d85d137.fe487","type":"switch","z":"6dc0247c.d7210c","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["f9e57204.71076"],["a622c92a.2d9898"]],"l":false},{"id":"a622c92a.2d9898","type":"function","z":"6dc0247c.d7210c","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.status({text: \\"cleared\\"});\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":318,"y":128,"wires":[["368c9723.5876f8"]]},{"id":"9bfe567c.3d10c8","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_action","server":"","version":3,"exposeAsEntityConfig":"","eventType":"mobile_app_notification_action","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\\"eventData\\").event_type","valueType":"jsonata"}],"x":194,"y":176,"wires":[["8d3bdc0c.37493"]]},{"id":"368c9723.5876f8","type":"api-call-service","z":"6dc0247c.d7210c","name":"","server":"","version":6,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"all","domain":"notify","x":550,"y":80,"wires":[[]]},{"id":"b35b9988.72c6d8","type":"server-state-changed","z":"bd764bbe2981bf8f","g":"eeda20dfef44e955","name":"Away?","server":"","version":6,"outputs":2,"exposeAsEntityConfig":"","entities":{"entity":["person.jason"],"substring":[],"regex":[]},"outputInitially":true,"stateType":"str","ifState":"home","ifStateType":"str","ifStateOperator":"is_not","outputOnlyOnStateChange":true,"for":"24","forType":"num","forUnits":"hours","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":110,"y":736,"wires":[["4587eb7f.fb86a4"],[]]},{"id":"4587eb7f.fb86a4","type":"api-current-state","z":"bd764bbe2981bf8f","g":"eeda20dfef44e955","name":"Vacation mode off?","server":"","version":3,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.vacation_mode","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","x":278,"y":736,"wires":[["e5d8423c.2ccfe"],[]]},{"id":"be568091.56bbb","type":"api-call-service","z":"bd764bbe2981bf8f","g":"eeda20dfef44e955","name":"Turn On Vacation Mode","server":"","version":6,"debugenabled":false,"action":"input_boolean.turn_on","floorId":[],"areaId":[],"deviceId":[],"entityId":["input_boolean.vacation_mode"],"labelId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","domain":"input_boolean","service":"turn_on","output_location":"","output_location_type":"none","x":762,"y":736,"wires":[[]]},{"id":"e5d8423c.2ccfe","type":"subflow:6dc0247c.d7210c","z":"bd764bbe2981bf8f","g":"eeda20dfef44e955","name":"Turn on Vacation Mode?","env":[{"name":"service","value":"mobile_app_jason","type":"str"},{"name":"title","value":"Vacation Mode","type":"str"},{"name":"message","value":"You've been away for 24 hours.<br>Do you want to turn on vacation mode?","type":"str"},{"name":"action1Title","value":"Yes","type":"str"},{"name":"action2Title","value":"No","type":"str"},{"name":"group","value":"","type":"str"}],"x":506,"y":752,"wires":[["be568091.56bbb"],[],[],[]]}]\n
Also see:
',22)),n("ul",null,[n("li",null,[e(a,{to:"/cookbook/vacation-mode.html"},{default:u(()=>s[0]||(s[0]=[l("Cookbook: Vacation Mode")])),_:1})])])])}const f=t(g,[["render",d],["__file","actionable-notifications-subflow-for-android.html.vue"]]),v=JSON.parse('{"path":"/cookbook/actionable-notifications-subflow-for-android.html","title":"Actionable Notifications Subflow for Android","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Demo flow","slug":"demo-flow","link":"#demo-flow","children":[]},{"level":2,"title":"Use Case 1: Get a notification when garage door is left open with ability to ignore the alert for an amount of time","slug":"use-case-1-get-a-notification-when-garage-door-is-left-open-with-ability-to-ignore-the-alert-for-an-amount-of-time","link":"#use-case-1-get-a-notification-when-garage-door-is-left-open-with-ability-to-ignore-the-alert-for-an-amount-of-time","children":[]},{"level":2,"title":"Use Case 2: Ask if vacation mode should be turned on after being away for 24 hours","slug":"use-case-2-ask-if-vacation-mode-should-be-turned-on-after-being-away-for-24-hours","link":"#use-case-2-ask-if-vacation-mode-should-be-turned-on-after-being-away-for-24-hours","children":[]}],"git":{"updatedTime":1725024932000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":6},{"name":"jason","email":"37859597+zachowj@users.noreply.github.com","commits":3},{"name":"Mauricio Bonani","email":"bonanitech@gmail.com","commits":1}]},"filePathRelative":"cookbook/actionable-notifications-subflow-for-android.md"}');export{f as comp,v as data}; diff --git a/assets/actionable-notifications-subflow-for-android_01-Ff8j17af.png b/assets/actionable-notifications-subflow-for-android_01-Ff8j17af.png new file mode 100644 index 0000000000..36302f4515 Binary files /dev/null and b/assets/actionable-notifications-subflow-for-android_01-Ff8j17af.png differ diff --git a/assets/actionable-notifications-subflow-for-android_02-E9GuL3i2.png b/assets/actionable-notifications-subflow-for-android_02-E9GuL3i2.png new file mode 100644 index 0000000000..4416bd797e Binary files /dev/null and b/assets/actionable-notifications-subflow-for-android_02-E9GuL3i2.png differ diff --git a/assets/actionable-notifications-subflow-for-android_03-CpQYr5Am.png b/assets/actionable-notifications-subflow-for-android_03-CpQYr5Am.png new file mode 100644 index 0000000000..7718831898 Binary files /dev/null and b/assets/actionable-notifications-subflow-for-android_03-CpQYr5Am.png differ diff --git a/assets/actionable-notifications-subflow-for-android_04-p0E-PxHx.png b/assets/actionable-notifications-subflow-for-android_04-p0E-PxHx.png new file mode 100644 index 0000000000..9fe308febc Binary files /dev/null and b/assets/actionable-notifications-subflow-for-android_04-p0E-PxHx.png differ diff --git a/assets/app-CefLgoES.js b/assets/app-CefLgoES.js new file mode 100644 index 0000000000..10bad26d93 --- /dev/null +++ b/assets/app-CefLgoES.js @@ -0,0 +1,28 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/play-jellyfin-show-using-sentence-node.html-CsvMb0ZJ.js","assets/play-jellyfin-show-using-sentence-node_03-B3fhig1f.js","assets/sun-events.html-cUR6Nkho.js","assets/first-automation_05-BSXfHgTV.js","assets/first-automation.html-BYONk5Lb.js","assets/sentence.html-hzBKu6RK.js"])))=>i.map(i=>d[i]); +/** +* @vue/shared v3.5.11 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function Zi(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const _e={},yn=[],yt=()=>{},Ya=()=>!1,sl=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Xi=e=>e.startsWith("onUpdate:"),Re=Object.assign,eo=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Za=Object.prototype.hasOwnProperty,he=(e,t)=>Za.call(e,t),Z=Array.isArray,kn=e=>al(e)==="[object Map]",Yl=e=>al(e)==="[object Set]",Ho=e=>al(e)==="[object Date]",ie=e=>typeof e=="function",Te=e=>typeof e=="string",kt=e=>typeof e=="symbol",ve=e=>e!==null&&typeof e=="object",ts=e=>(ve(e)||ie(e))&&ie(e.then)&&ie(e.catch),ns=Object.prototype.toString,al=e=>ns.call(e),Xa=e=>al(e).slice(8,-1),ls=e=>al(e)==="[object Object]",to=e=>Te(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,En=Zi(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Zl=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},ec=/-(\w)/g,Qe=Zl(e=>e.replace(ec,(t,n)=>n?n.toUpperCase():"")),tc=/\B([A-Z])/g,qt=Zl(e=>e.replace(tc,"-$1").toLowerCase()),cl=Zl(e=>e.charAt(0).toUpperCase()+e.slice(1)),mi=Zl(e=>e?`on${cl(e)}`:""),Bt=(e,t)=>!Object.is(e,t),Il=(e,...t)=>{for(let n=0;nWarning
Needs Custom Integration installed in Home Assistant for this node to function
This node allows you to create a binary sensor entity within Home Assistant that can be controlled directly from Node-RED. A binary sensor is a type of entity that has only two possible states: typically "on" or "off". It can represent various real-world conditions, such as whether a door is open or closed, or if motion is detected.
boolean
The state of the entity should be updated to
To set the Home Assistant state to Unknown
, send a state with a js expression null
.
Object
Key/Value pair of attributes to update. The key should be a string and the value can be a [string | number | boolean | object]
string
accept | merge | block
Determine how input values will be handled. When merge is selected the message object values will override the configuration values.
properties of msg.payload
string | number | boolean
The value of the entity state will be updated.
Object
Key/Value pair of attributes to update. The key should be a string and the value can be a [string | number | boolean | object]
Warning
Needs Custom Integration installed in Home Assistant for this node to function
The Button node creates a button entity within Home Assistant that, when pressed, triggers a flow in Node-RED. This is useful for manual triggers within automations, allowing users to start an automation sequence directly from the Home Assistant interface with a simple click.
Value types:
entity
: complete entity objectentity id
: entity id of the pressed buttonentity state
: entity state of the pressed buttonconfig
: config properties of the nodeUsing JSONata for more complex tasks:
Many automations can be coded using a simple JSONata expression at some convenient point in the flow, often directly within one of the WebSocket nodes. Where the data involved is a more complex structure, then JSONata is a powerful tool for manipulating JSON objects and arrays. Almost all computation can be achieved using JSONata in a Change node in place of using a Function node.
Since Home Assistant stores state history for 10 days by default, it is possible to read historic state records. The Get History node can do this for any given entity, and using relative time it is easy to obtain an array of past state-change events.
There are no opportunities to use JSONata within the Get History node itself, however JSONata can be used both to setup the node parameters and to manipulate the returned array. In this example, JSONata is used extensively to:
This returns a filtered array of entity states, with the time that state period started and the time it ended, and the duration in minutes. The requirement for extensive data processing here is due to the way 'person' sensors report, giving rise to short periods of 'unknown' or 'away' because Wi-Fi signal or a smart phone has gone 'off-line'.
[{"id":"1997b594da7d37b1","type":"group","z":"776c027950fc8c3f","name":"Process history data using JSONata in a change node","style":{"label":true,"color":"#000000"},"nodes":["bd6511844a39ca08","3d87bbb92fa35243","9a3ca494edc54a30","3a4534cdc46ae67d","891ee0da3deec2c1","27f0ecf4ea3dc24f","d5dc0b522463577a"],"x":34,"y":2499,"w":1312,"h":122},{"id":"bd6511844a39ca08","type":"api-current-state","z":"776c027950fc8c3f","g":"1997b594da7d37b1","name":"Get current state","server":"","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"person.george","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"payload ~> |$|{\\"entityId\\": $entity().entity_id}|","valueType":"jsonata"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":350,"y":2540,"wires":[["9a3ca494edc54a30"]]},{"id":"3d87bbb92fa35243","type":"inject","z":"776c027950fc8c3f","g":"1997b594da7d37b1","name":"Set parameters","props":[{"p":"payload"},{"p":"startAt","v":"$fromMillis($millis()-(7*24*60*60000))","vt":"jsonata"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\\"relativeTime\\": \\"1 week\\"\\t}","payloadType":"jsonata","x":160,"y":2540,"wires":[["bd6511844a39ca08"]]},{"id":"9a3ca494edc54a30","type":"api-get-history","z":"776c027950fc8c3f","g":"1997b594da7d37b1","name":"Read history","server":"","version":1,"startDate":"","endDate":"","entityId":"","entityIdType":"equals","useRelativeTime":true,"relativeTime":"","flatten":true,"outputType":"array","outputLocationType":"msg","outputLocation":"payload","x":530,"y":2540,"wires":[["3a4534cdc46ae67d"]]},{"id":"3a4534cdc46ae67d","type":"change","z":"776c027950fc8c3f","g":"1997b594da7d37b1","name":"Get filtered state periods","rules":[{"t":"set","p":"payload","pt":"msg","to":"(\\t/* FILTER parameters */\\t $fMins:=50;\\t\\t/* get current state from entity 'data', and set the 'last_changed' to now */\\t $first:= data~>|$|{\\"last_changed\\": $now()}|;\\t\\t/* add this to far end of history payload array, then sort by reverse time order */\\t $x:=$append(payload, $first)^(>last_changed,>last_updated);\\t\\t/* copy the oldest state value, and add in as the first record at start of history */\\t/* we now have a 'now' and 'start of history' record, even if payload was empty */ \\t $x:=$append($x,{\\"state\\": $x[0].state, \\"last_changed\\": startAt});\\t\\t/* create array of state changes, with how long they have been in that state */\\t/* remove any zero periods and unknown states FILTER OUT AS REQUIRED */\\t $events:=$x#$i.(\\t $prior:= $i>0 ? $x[$i-1] : {\\"first\\": $now()};\\t {\\"index\\": $i,\\t \\"state\\": state,\\t \\"from\\": last_changed,\\t \\"upto\\": $prior.last_changed,\\t \\"dmins\\": ($toMillis($prior.last_changed)-$toMillis(last_changed))/60000~>$round(0)\\t }\\t )[dmins>$fMins and state!=\\"unknown\\"];\\t\\t/* merge consecutive records with the same state into one longer period */\\t/* get each event position as 'start - middle - end' or 'only' */\\t\\t $temp:=$events#$v.(\\t $back:= $v<1 ? false : state = $events[$v-1].state;\\t $next:= state = $events[$v+1].state;\\t $position:=( $back ? ($next ? \\"middle\\" : \\"end\\") : ($next ? \\"start\\" : \\"only\\") );\\t $~>|$|{\\"index\\": $v, \\"position\\": $position}|\\t );\\t\\t/* get start and end indexes, and zip into a sequence array of [start, end] */\\t/* map this array of sequences to an array of objects, one for each sequence */\\t/* where the object is the combination of a run of the same state value */\\t\\t $chain:=$zip($temp[position=\\"start\\"].index, $temp[position=\\"end\\"].index);\\t\\t $array:=$map($chain, function($item) {(\\t $recA:=$events[$item[0]];\\t $recB:=$events[$item[1]];\\t {\\"state\\": $recA.state,\\t \\"from\\": $recB.from,\\t \\"upto\\": $recA.upto,\\t \\"dmins\\": ($toMillis($recA.upto)-$toMillis($recB.from))/60000~>$round(0),\\t \\"position\\": \\"merged\\"}\\t )\\t });\\t\\t/* combine the 'only' single events with the now-merged sequences, and sort by time */\\t $append($temp[position=\\"only\\"], $array)^(>from);\\t\\t)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":2540,"wires":[["891ee0da3deec2c1","27f0ecf4ea3dc24f"]]},{"id":"891ee0da3deec2c1","type":"debug","z":"776c027950fc8c3f","g":"1997b594da7d37b1","name":"State History Events","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1200,"y":2540,"wires":[]},{"id":"27f0ecf4ea3dc24f","type":"change","z":"776c027950fc8c3f","g":"1997b594da7d37b1","name":"When 'not home' during the day","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[state=\\"not_home\\" and ($substringBefore(from,\\"T\\") = $substringBefore(upto,\\"T\\") ) ]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":930,"y":2580,"wires":[["d5dc0b522463577a"]]},{"id":"d5dc0b522463577a","type":"debug","z":"776c027950fc8c3f","g":"1997b594da7d37b1","name":"Out during the day","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1190,"y":2580,"wires":[]}]
+
(
+/* FILTER parameters */
+ $fMins:=50;
+
+/* get current state from entity 'data', and set the 'last_changed' to now */
+ $first:= data~>|$|{"last_changed": $now()}|;
+
+/* add this to far end of history payload array, then sort by reverse time order */
+ $x:=$append(payload, $first)^(>last_changed,>last_updated);
+
+/* copy the oldest state value, and add in as the first record at start of history */
+/* we now have a 'now' and 'start of history' record, even if payload was empty */
+ $x:=$append($x,{"state": $x[0].state, "last_changed": startAt});
+
+/* create array of state changes, with how long they have been in that state */
+/* remove any zero periods and unknown states FILTER OUT AS REQUIRED */
+ $events:=$x#$i.(
+ $prior:= $i>0 ? $x[$i-1] : {"first": $now()};
+ {"index": $i,
+ "state": state,
+ "from": last_changed,
+ "upto": $prior.last_changed,
+ "dmins": ($toMillis($prior.last_changed)-$toMillis(last_changed))/60000~>$round(0)
+ }
+ )[dmins>$fMins and state!="unknown"];
+
+/* merge consecutive records with the same state into one longer period */
+/* get each event position as 'start - middle - end' or 'only' */
+
+ $temp:=$events#$v.(
+ $back:= $v<1 ? false : state = $events[$v-1].state;
+ $next:= state = $events[$v+1].state;
+ $position:=( $back ? ($next ? "middle" : "end") : ($next ? "start" : "only") );
+ $~>|$|{"index": $v, "position": $position}|
+ );
+
+/* get start and end indexes, and zip into a sequence array of [start, end] */
+/* map this array of sequences to an array of objects, one for each sequence */
+/* where the object is the combination of a run of the same state value */
+
+ $chain:=$zip($temp[position="start"].index, $temp[position="end"].index);
+
+ $array:=$map($chain, function($item) {(
+ $recA:=$events[$item[0]];
+ $recB:=$events[$item[1]];
+ {"state": $recA.state,
+ "from": $recB.from,
+ "upto": $recA.upto,
+ "dmins": ($toMillis($recA.upto)-$toMillis($recB.from))/60000~>$round(0),
+ "position": "merged"}
+ )
+ });
+
+/* combine the 'only' single events with the now-merged sequences, and sort by time */
+ $append($temp[position="only"], $array)^(>from);
+
+)
+
Notes:
The Inject node uses JSONata to initially create msg.payload as a data object, setting the relative time parameter for the Get History node, and also setting the timestamp for the effective start of this time period.
The Current State node uses JSONata in the output to retain msg.payload and also merge in the entityId using $entity().entity_id
. This now sets msg.payload with the required input parameters for the Get History node (which therefore requires no UI settings). Full entity details are also captured in msg.data as usual.
Caution
This example code has been tested but person sensors can go 'off line' for long periods and the exact nature of the output data should be checked by experimentation.
Once state history has been manipulated like this into an event-array, it is easy to ask questions, such as "how many times has this person been away this week?"
The question "when was this person away during the day?" for example, can be answered by filtering the state event array, to look for 'not home' and for the event period (both start and end) to be entirely on the same date.
payload[state="not_home" and ($substringBefore(from,"T") = $substringBefore(upto,"T") ) ]
+
Note on history records:
In asking for the history for the past 24 hours, the Get History node will return an array of all state change events that occurred within that time period. If the current state has been unchanged for the entire period requested, then an empty array will be returned.
To address this, the JSONata code here first adds a record for 'now' with the current state. The code then finds the oldest state (which may well be the current 'now' state just added) and adds that as a record for 'the start of the time period requested' at that state. This ensures that the returned history event array is topped and tailed. The minimum state event array will therefore be one entry from start of history request to now, at the current state.
Also see:
`,23)),n("ul",null,[n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[0]||(s[0]=[p("JSONata guide")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/jsonata-primer.html"},{default:o(()=>s[1]||(s[1]=[p("JSONata primer")])),_:1})])])])}const h=e(k,[["render",q],["__file","change-node.html.vue"]]),g=JSON.parse(`{"path":"/cookbook/jsonata/change-node.html","title":"Change node","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Read a person state history for the past week","slug":"read-a-person-state-history-for-the-past-week","link":"#read-a-person-state-history-for-the-past-week","children":[]},{"level":2,"title":"Report only those periods when 'not home' during the day","slug":"report-only-those-periods-when-not-home-during-the-day","link":"#report-only-those-periods-when-not-home-during-the-day","children":[]}],"git":{"updatedTime":1719788083000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":2}]},"filePathRelative":"cookbook/jsonata/change-node.md"}`);export{h as comp,g as data}; diff --git a/assets/check-if-an-entity-was-turned-on-in-the-last-24-hours.html-CcLo08bo.js b/assets/check-if-an-entity-was-turned-on-in-the-last-24-hours.html-CcLo08bo.js new file mode 100644 index 0000000000..cb7d72e4d0 --- /dev/null +++ b/assets/check-if-an-entity-was-turned-on-in-the-last-24-hours.html-CcLo08bo.js @@ -0,0 +1,3 @@ +import{_ as n,c as a,e as t,o as p}from"./app-CefLgoES.js";const o="/node-red-contrib-home-assistant-websocket/assets/check-if-an-entity-was-turned-on-in-the-last-24-hours_01-BvAOESUS.png",e="/node-red-contrib-home-assistant-websocket/assets/check-if-an-entity-was-turned-on-in-the-last-24-hours_02-CCEMNzXI.png",u={};function c(r,s){return p(),a("div",null,s[0]||(s[0]=[t('Use the get-history
to get a list of state changes for the last 24 hours. Filter them using a switch
node looking for a given state and then count the remaining to see if the entity was in that state during the time frame.
[{"id":"3f683250.b26f1e","type":"inject","z":"ffbd7f06.4a014","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":236,"y":1008,"wires":[["e60a02e6.d2afc"]]},{"id":"aa3e26cd.152828","type":"debug","z":"ffbd7f06.4a014","name":"do something","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":976,"y":1008,"wires":[]},{"id":"e60a02e6.d2afc","type":"api-get-history","z":"ffbd7f06.4a014","name":"","startdate":"","enddate":"","entityid":"light.kitchen_light","entityidtype":"is","useRelativeTime":true,"relativeTime":"24 hours","flatten":true,"output_type":"split","output_location_type":"msg","output_location":"payload","x":380,"y":1008,"wires":[["b162effa.fc78f"]]},{"id":"b162effa.fc78f","type":"switch","z":"ffbd7f06.4a014","name":"on?","property":"payload.state","propertyType":"msg","rules":[{"t":"eq","v":"on","vt":"str"}],"checkall":"true","repair":true,"outputs":1,"x":530,"y":1008,"wires":[["e4b98b67.bde238"]]},{"id":"e4b98b67.bde238","type":"join","z":"ffbd7f06.4a014","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":658,"y":1008,"wires":[["fff04d54.5615e"]]},{"id":"fff04d54.5615e","type":"switch","z":"ffbd7f06.4a014","name":"count > 0","property":"payload.length","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"}],"checkall":"true","repair":true,"outputs":1,"x":796,"y":1008,"wires":[["aa3e26cd.152828"]]}]
+
Here's the same example but using a function
node.
[{"id":"f79cd7b0.604f98","type":"inject","z":"ffbd7f06.4a014","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":332,"y":1200,"wires":[["753a1de3.091fa4"]]},{"id":"753a1de3.091fa4","type":"api-get-history","z":"ffbd7f06.4a014","name":"","startdate":"","enddate":"","entityid":"light.kitchen_light","entityidtype":"is","useRelativeTime":true,"relativeTime":"24 hours","flatten":true,"output_type":"array","output_location_type":"msg","output_location":"payload","x":476,"y":1200,"wires":[["389b5575.43217a"]]},{"id":"389b5575.43217a","type":"function","z":"ffbd7f06.4a014","name":"check state","func":"const state = \\"on\\";\\nconst filtered = msg.payload.filter((entity) => entity.state === state);\\n\\nif(filtered.length > 0) {\\n msg.payload = filtered;\\n return msg;\\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":644,"y":1200,"wires":[["6a582450.883bdc"]]},{"id":"6a582450.883bdc","type":"debug","z":"ffbd7f06.4a014","name":"do something","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":832,"y":1200,"wires":[]}]
+
==
, !=
, is
, and is not
These operators check if a value is equal to or different from the expected value using the equality operator. The is
and is not
are aliases for ==
and !=
.
<
, <=
, >
, and >=
These operators compare values to determine if one is less than, less than or equal to, greater than, or greater than or equal to another value.
in
and not in
These operators check if a value is within a specified list of items. The list should be comma-separated.
in: "on,off"
+
includes
and not includes
These operators check if an array includes or does not include a specified value.
is null
and is not null
These operators check if a value is null
or not.
JSONata
starts with
This operator checks if a value begins with the expected string.
in group
This operator checks if a value is present in the attributes.entity_id
property of a given group.
string
, number
, boolean
, regular expression
, and JSONata expression
These types evaluate and return the value as the specified type.
msg
, flow
, and global
These refer to the different contexts within Node-RED.
entity
and prevEntity
These refer to the entities that triggered the node.
entity
refers to the current state entity.prevEntity
refers to the previous state entity.To reference the current state of the entity:
entity.: "state"
+
To reference the brightness attribute of the entity:
entity.: "attributes.brightness"
+
The Server Config node is used for configuring the connection to Home Assistant. This node manages the details of how Node-RED communicates with your Home Assistant instance, including server URL, authentication, and other connection settings.
string
Label for this configuration, see details below for implications
boolean
If you're running Node-RED as a Hass.io Add-on check this. No other information is needed.
string
The base URL and port of the Home Assistant instance can be reached at, for example: http://192.168.0.100:8123
or https://homeassistant.mysite.com
string
Long-lived Access Token used to contact the API
boolean
This will allow you to use self-signed certificates. Only use this if you know what you're doing.
string | delimited
A list of strings, not case sensitive, delimited by vertical pipe, |, that will return true for State Type Boolean.
boolean
Heartbeat will send a ping message using the websocket connection to Home Assistant every X seconds. If a pong response is not received within 5 seconds Node-RED will attempt to reconnect to Home Assistant.
number
The interval at which the ping message is sent to Home Assistant. The minimum value is 10 seconds.
boolean
If enabled, the global context store will be used to store the Home Assistant connection, state, and service information. This allows you to use the information in other nodes using context functions.
Example below
Enables the caching of the JSON autocomplete requests. Enabling or disabling this may require a restart of Node-RED for it to take effect.
Which text to show in the selector after the id has been chosen.
A string that will appear in the status of an event node between the state and date string.
The other options are directly from DateTimeFormat Options.
Every node requires a configuration attached to define how to contact Home Assistant, which is this config node's main purpose.
Each config node will also make some data available in the global context, the Name
value in this node is used as, camelcase, and the namespace for those values
Currently states
, services
, and events
is made available in the global context. states
is always set to all available states at startup and updated whenever state changes occur so it should be always up to date. services
and events
is only updated on initial deploy.
Say we have a config node with the name Home Assistant
, with an entity set up in Home Assistant as switch.my_switch
. This state would be available within function nodes and you could fetch using something like the below code
const haCtx = global.get("homeassistant");
+const configCtx = haCtx.homeAssistant;
+const entityState = configCtx.states["switch.my_switch"];
+return entityState.state === "on" ? true : false;
+
Communication with Home Assistant is accomplished via a combination of WebSocket and the REST API if you are having trouble communicating with home assistant make sure you can access the API outside of node-red, but from the same server node-red is running on, using a REST client, curl, or any number of other methods to validate the connection
The Current State node has several opportunities to use JSONata. This example demonstrates all of these, using a node to fetch the current state of a climate air-conditioning unit.
The default output values for this node include setting msg.payload to the state (which for an A/C unit will be "off", "heat", "cool" or more, depending on the integration) as well as msg.data to the entire entity data object. This holds values for the last changed timestamp as well as attributes such as the friendly name and other integration dependent settings.
Here are three examples, showing how to use JSONata to build output properties, to perform If-State conditional tests, and to provide calculated values for UI settings.
[{"id":"d0f1fad3bf96a090","type":"group","z":"776c027950fc8c3f","name":"Current State node - read the state of an entity","style":{"label":true,"color":"#000000"},"nodes":["6394700dfd39838f","8388eb36d3e25254","852c05cf8c7cae73","a3e69091339a457c","11fc322e343cbd9c","6ac52ef3e9010615","63fa3b7423417fe4","a29810da49f1efd4","4f8197db85d55fb5","fd6d41b2f67b85e0","36912490ae36c0db","f84772ea62e6e7ba","6244f42dd53b7e1f","bc6cd64ba0266d97"],"x":34,"y":499,"w":1012,"h":382},{"id":"6394700dfd39838f","type":"api-current-state","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"","server":"","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"climate.bedroom_aircon","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"(\\t $att:=$entity().attributes;\\t $diff:=$att.temperature-$att.current_temperature;\\t ($diff>0 ? \\"Heating\\" : \\"Cooling\\") & \\" by \\" & $abs($diff) & \\" degrees required\\"\\t)","valueType":"jsonata"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":430,"y":580,"wires":[["852c05cf8c7cae73"]]},{"id":"8388eb36d3e25254","type":"inject","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"Manual trigger","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":580,"wires":[["6394700dfd39838f"]]},{"id":"852c05cf8c7cae73","type":"debug","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"Output Text","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":770,"y":580,"wires":[]},{"id":"a3e69091339a457c","type":"comment","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"2a - JSONata builds output in current state node","info":"","x":240,"y":540,"wires":[]},{"id":"11fc322e343cbd9c","type":"api-current-state","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"","server":"","version":3,"outputs":2,"halt_if":"$entities('sensor.time').state < \\"17:00\\" ? \\"cool\\" : \\"off\\"","halt_if_type":"jsonata","halt_if_compare":"is","entity_id":"climate.bedroom_aircon","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"for":"$entity().state = \\"off\\" ? 0 : 2","forType":"jsonata","forUnits":"hours","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":430,"y":700,"wires":[["63fa3b7423417fe4"],["a29810da49f1efd4"]]},{"id":"6ac52ef3e9010615","type":"inject","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"Manual trigger","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":700,"wires":[["11fc322e343cbd9c"]]},{"id":"63fa3b7423417fe4","type":"debug","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"Cooling for 2+ hours before 17:00, or Off","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":840,"y":680,"wires":[]},{"id":"a29810da49f1efd4","type":"debug","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"otherwise","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":700,"y":740,"wires":[]},{"id":"4f8197db85d55fb5","type":"comment","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"2b - JSONata builds conditional test value, and UI parameter","info":"","x":280,"y":660,"wires":[]},{"id":"fd6d41b2f67b85e0","type":"comment","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"2c - JSONata builds conditional Boolean","info":"","x":220,"y":780,"wires":[]},{"id":"36912490ae36c0db","type":"api-current-state","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"","server":"","version":3,"outputs":2,"halt_if":"(\\t $state:=$entity().state;\\t $time:=$entities('sensor.time').state;\\t ($time<=\\"09:00\\" or $time>=\\"17:00\\") and ($state=\\"heat\\" or $state=\\"cool\\")\\t)","halt_if_type":"jsonata","halt_if_compare":"jsonata","entity_id":"climate.bedroom_aircon","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entity"}],"for":"$substringAfter($now(),\\"T\\")>\\"10:00\\" ? 20 : 10","forType":"jsonata","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":430,"y":820,"wires":[["6244f42dd53b7e1f"],["bc6cd64ba0266d97"]]},{"id":"f84772ea62e6e7ba","type":"inject","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"Manual trigger","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":820,"wires":[["36912490ae36c0db"]]},{"id":"6244f42dd53b7e1f","type":"debug","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"On outside of office hours","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload.state","targetType":"msg","statusVal":"payload.state","statusType":"auto","x":850,"y":800,"wires":[]},{"id":"bc6cd64ba0266d97","type":"debug","z":"776c027950fc8c3f","g":"d0f1fad3bf96a090","name":"otherwise","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload.state","targetType":"msg","statusVal":"payload.state","statusType":"auto","x":680,"y":840,"wires":[]}]
+
Example: Report the difference between room temperature and target temperature for an air-conditioning unit.
In the first example we are looking to retrieve the current state and attributes from an air-conditioning unit, and output a text message describing the difference between current and target temperature. The A/C unit current temperature (read only) and the A/C unit target temperature (can be set with a service call) are located in the entity attributes. These are both numbers, so it is very easy to calculate the difference.
Note: The default value msg.data is set only for the output message, and therefore msg.data.state would be used to obtain the entity state in nodes further along the flow, but $entity().state is used to obtain the entity state in JSONata expressions inside the node. When using JSONata to build an output message property, there is no requirement to retain the default output property settings for msg.data unless this is specifically required later in the flow.
(
+ $att:=$entity().attributes;
+ $diff:=$att.temperature-$att.current_temperature;
+ ($diff>0 ? "Heating" : "Cooling") & " by " & $abs($diff) & " degrees required"
+)
+
Example: Output only where the A/C unit has been cooling for two hours and the local time is before 17:00, or when the unit is off.
The Current State node provides for a state value test opportunity, which if set can be used to direct the message output one of two ways, depending whether the entity state passes or fails the test. When the If State option is chosen with a conditional (not JSONata) the condition expects a value to test against. In this example, using JSONata on the right hand side will return the value of the evaluated expression ("cool" before 17:00 and "off" otherwise) to be tested against the current state value and using the chosen conditional operator.
$entities('sensor.time').state < "17:00" ? "cool" : "off"
+
The Current State node also allows for a For, or duration of state test, and here JSONata is again being used to return either '0' when the state is "off" or '2' (hours) when the state is not "off". This complex arrangement will together test for the A/C unit being either immediately off after 17:00, or in cooling mode for at least two hours before 17:00.
$entity().state = "off" ? 0 : 2
+
Note that here the use of $entity().state
returns the current state of the node's subject entity, and that $entities('sensor.time').state
returns the state of another specified entity. The Time & Date Integration should be added to the configuration file, and then the time sensor will return the current local time. This is useful for local-time condition testing, whereas the time_utc sensor will return UTC-time which is more useful with Home Assistant timestamps.
Example: Report the air-conditioning unit being on outside of office hours.
When the If State option is chosen with JSONata (not a conditional) then the right hand side must be a JSONata expression that returns a Boolean value of either true
or false
. When true the If State test is successful (the message is output from the top exit) when false the test fails (the message is output from the lower exit).
In this example the JSONata expression is a code block, obtaining the node subject entity state and separate entity local time as in the previous example, but now combining both into one predicate expression. If the local time is outside 09:00 to 17:00 and the state is either heat or cool, then the expression will return true
.
(
+ $state:=$entity().state;
+ $time:=$entities('sensor.time').state;
+ ($time<="09:00" or $time>="17:00") and ($state="heat" or $state="cool")
+)
+
JSONata used at this point can be extensive, with a combination of entity states or attributes and Boolean logic as required. The use of OR to combine various states can only be more easily achieved in this way.
Also see:
`,28)),n("ul",null,[n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[0]||(s[0]=[p("JSONata guide")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/jsonata-primer.html"},{default:o(()=>s[1]||(s[1]=[p("JSONata primer")])),_:1})])])])}const m=e(g,[["render",y],["__file","current-state.html.vue"]]),h=JSON.parse('{"path":"/cookbook/jsonata/current-state.html","title":"Current State","lang":"en-US","frontmatter":{},"headers":[{"level":3,"title":"Creating output properties","slug":"creating-output-properties","link":"#creating-output-properties","children":[]},{"level":3,"title":"Providing a conditional test value (JSONata expression as a value)","slug":"providing-a-conditional-test-value-jsonata-expression-as-a-value","link":"#providing-a-conditional-test-value-jsonata-expression-as-a-value","children":[]},{"level":3,"title":"Generating UI field setting values","slug":"generating-ui-field-setting-values","link":"#generating-ui-field-setting-values","children":[]},{"level":3,"title":"Conditional test (JSONata expression as a Boolean result)","slug":"conditional-test-jsonata-expression-as-a-boolean-result","link":"#conditional-test-jsonata-expression-as-a-boolean-result","children":[]}],"git":{"updatedTime":1719788083000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":2}]},"filePathRelative":"cookbook/jsonata/current-state.md"}');export{m as comp,h as data}; diff --git a/assets/current-state.html-iUU17wBN.js b/assets/current-state.html-iUU17wBN.js new file mode 100644 index 0000000000..94da6c5245 --- /dev/null +++ b/assets/current-state.html-iUU17wBN.js @@ -0,0 +1 @@ +import{_ as r,c as d,a as t,d as n,b as i,w as o,e as u,r as s,o as p}from"./app-CefLgoES.js";const c={},h={id:"entity-id",tabindex:"-1"},f={class:"header-anchor",href:"#entity-id"};function g(y,e){const a=s("Badge"),l=s("RouteLink");return p(),d("div",null,[e[5]||(e[5]=t("h1",{id:"current-state",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#current-state"},[t("span",null,"Current State")])],-1)),e[6]||(e[6]=t("p",null,"This node is used to fetch the last known state of any entity within Home Assistant when it receives an input. It’s useful for making decisions based on the current status of entities, such as checking if a light is already on before turning it off, or determining the temperature reading before adjusting the thermostat.",-1)),e[7]||(e[7]=t("h2",{id:"configuration",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#configuration"},[t("span",null,"Configuration")])],-1)),t("h3",h,[t("a",f,[t("span",null,[e[0]||(e[0]=n("Entity ID ")),i(a,{text:"required"})])])]),t("ul",null,[e[3]||(e[3]=t("li",null,[n("Type: "),t("code",null,"string")],-1)),t("li",null,[e[2]||(e[2]=n("Accepts ")),i(l,{to:"/guide/mustache-templates.html"},{default:o(()=>e[1]||(e[1]=[n("Mustache Templates")])),_:1})])]),e[8]||(e[8]=t("p",null,"The id of the entity to return.",-1)),e[9]||(e[9]=t("h3",{id:"if-state",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#if-state"},[t("span",null,"If State")])],-1)),e[10]||(e[10]=t("ul",null,[t("li",null,[n("Type: "),t("code",null,"string")])],-1)),e[11]||(e[11]=t("p",null,"If the conditional statement is evaluated as true send the message to the first output otherwise send it to the second output. If blank there will only be one output.",-1)),e[12]||(e[12]=t("p",null,[t("strong",null,"Also see:")],-1)),t("ul",null,[t("li",null,[i(l,{to:"/guide/conditionals.html"},{default:o(()=>e[4]||(e[4]=[n("Conditionals")])),_:1})])]),e[13]||(e[13]=u('number
The amount of time the entity state needs to have been constant for the "If state" to be true
string
string|number|boolean
string
Convert the state of the entity to the selected type. Boolean will be converted to true based on if the string is equal by default to (y|yes|true|on|home|open
). Original value stored in msg.data.original_state
boolean
false
Stop msg.payload
values from overriding local config
Type : string
Overrides or sets the entity id for which the current state is being fetched
Value types:
entity
: full entity objectentity id
: entity id of the triggered entityentity state
: entity state of the triggered entityconfig
: config properties of the nodeFor detailed contributing guidelines, refer to CONTRIBUTING.md.
Follow these steps to set up your development environment for contributing to this project:
This method sets up a Docker container with all required tools and dependencies.
Fork the Node-RED Home Assistant repository.
Clone your forked repository:
git clone https://github.com/<GITHUB_USER_NAME>/node-red-contrib-home-assistant-websocket
+
Open the project in VS Code.
Install the Remote - Containers extension.
Click the green button in the lower-left corner labeled "Reopen in Container."
Wait for the container to build and start.
Open a terminal in VS Code and run pnpm dev
to start the development server.
Fork the Repository:
Clone Your Forked Repository:
Clone the forked repository to your local machine:
git clone https://github.com/<GITHUB_USER_NAME>/node-red-contrib-home-assistant-websocket
+
Navigate to the Project Directory:
Change to the project’s root directory:
cd node-red-contrib-home-assistant-websocket
+
Setup the Environment:
Option A: Run the Setup Script
Execute the provided setup script to automate the environment configuration:
./scripts/setup.sh
+
Option B: Manual Setup
Alternatively, you can manually create a .node-red
directory and link pnpm
:
mkdir .node-red
+corepack enable && corepack enable pnpm
+pnpm link --dir .node-red
+
Start Node-RED:
Launch Node-RED in development mode:
pnpm dev
+
After running pnpm dev
, Node-RED will be available on ports 1880 and 3000. Access the development server at:
Port 3000 includes browser-sync, which will automatically reload the browser when changes are made to the editor source code.
This project uses ESLint and Prettier for code linting and formatting. Run the following command to lint the code:
pnpm lint
+
To run the tests, use:
pnpm test
+
The Device Config node is used for configuring devices that have been added to Home Assistant. It allows you to set up and manage device-specific settings and options within Node-RED, ensuring that your automations can correctly interact with your devices.
string
The name of the device that will show in Home Assistant
string
The manufacturer of the device
string
The model of the device
string
The software version of the device
string
The hardware version of the device
',18)]))}const c=a(o,[["render",t],["__file","device-config.html.vue"]]),d=JSON.parse('{"path":"/node/device-config.html","title":"Device Config","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Configuration","slug":"configuration","link":"#configuration","children":[{"level":3,"title":"Name","slug":"name","link":"#name","children":[]},{"level":3,"title":"Manufacturer","slug":"manufacturer","link":"#manufacturer","children":[]},{"level":3,"title":"Model","slug":"model","link":"#model","children":[]},{"level":3,"title":"Software Version","slug":"software-version","link":"#software-version","children":[]},{"level":3,"title":"Hardware Version","slug":"hardware-version","link":"#hardware-version","children":[]}]}],"git":{"updatedTime":1723606857000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":4}]},"filePathRelative":"node/device-config.md"}');export{c as comp,d as data}; diff --git a/assets/device.html-BC-eSPOC.js b/assets/device.html-BC-eSPOC.js new file mode 100644 index 0000000000..ba6221ade4 --- /dev/null +++ b/assets/device.html-BC-eSPOC.js @@ -0,0 +1 @@ +import{_ as i,c as t,e as a,o as n}from"./app-CefLgoES.js";const s={};function o(l,e){return n(),t("div",null,e[0]||(e[0]=[a('The Device node enables you to create device automations and call device actions within Home Assistant. This node interacts with devices connected to your Home Assistant instance, allowing you to perform tasks such as turning devices on or off, changing settings, or triggering specific device-related actions.
string
trigger | action
string
Id of the device
object
Home Assistant object of the trigger
object
Home Assistant object of the action
object
string
When an entity is selected a switch entity will be created in Home Assistant. Turning on and off this switch will disable/enable the nodes in Node-RED.
Value types:
config
: config properties of the nodedevice id
: device id that triggered the nodeevent data
: event data received from Home Assistantsent data
: data sent to Home AssistantTo streamline troubleshooting, a diagnostics button has been added to the Node-RED editor. This button runs a diagnostics flow and copies the output to your clipboard for easy pasting into a GitHub issue or support forum.
Note
In versions prior to 0.62.0, the diagnostics flow was available as a separate flow that needed to be imported manually. You can still access and use this flow here.
Before version 0.62.0, the diagnostics flow was a separate tool for collecting runtime and system environment data. It can still be imported into Node-RED to gather essential information for troubleshooting.
You can also import this flow directly from the examples section in Node-RED:
@examples/diagnostic.json
+
Import the flow into Node-RED.
Deploy the flow.
Click the Inject
node to execute the flow.
Copy the output from the Debug
tab.
Paste the copied output into a GitHub issue or support ticket.
The documentation is built with VuePress v2. The pages are written in Markdown and are located in the docs
directory.
If you want to make small changes to the documentation, you can do so directly in the GitHub web interface. Just navigate to the file you want to change and click the pencil icon in the top right corner.
There is a link on the bottom of each page to edit the file directly in the GitHub web interface.
If you want to make larger changes to the documentation, you can clone the repository.
Install Visual Studio Code.
Install the Remote - Containers extension.
Go to the Node-RED Home Assistant repository and fork it.
Clone your forked repository to your local machine.
git clone https://github.com/<GITHUB_USER_NAME>/node-red-contrib-home-assistant-websocket
+
Open the repository in Visual Studio Code.
Click on the green "Open a Remote Window" button in the bottom left corner and select "Reopen in Container".
Open a terminal in Visual Studio Code and run pnpm docs:dev
to start the development server.
Install Node.js.
Enable pnpm
corepack enable && corepack enable npm
+
Go to the Node-RED Home Assistant repository and fork it.
Clone your forked repository to your local machine.
git clone https://github.com/<GITHUB_USER_NAME>/node-red-contrib-home-assistant-websocket
+
Open the repository in your favorite editor.
Run pnpm install
to install the dependencies.
Run pnpm run docs:dev
to start the development server.
The Entity Config node provides configuration options for the different entity type nodes within Node-RED. It’s used to set up and manage the properties and settings for entities, ensuring they behave as expected within your automations and Home Assistant environment.
string
The name of the entity that will show in Node-RED
string
binary_sensor|button|sensor|switch
boolean
When the entity in Home Assistant is created the state and attributes will also be set. This is only applicable with certain entities nodes.
Configuration options are available for the selected entities
https://developers.home-assistant.io/docs/core/entity
',7))])}const v=l(p,[["render",m],["__file","entity-config.html.vue"]]),b=JSON.parse('{"path":"/node/entity-config.html","title":"Entity Config","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Configuration","slug":"configuration","link":"#configuration","children":[{"level":3,"title":"Name","slug":"name","link":"#name","children":[]},{"level":3,"title":"Device","slug":"device","link":"#device","children":[]},{"level":3,"title":"Type","slug":"type","link":"#type","children":[]},{"level":3,"title":"Resend state and attributes","slug":"resend-state-and-attributes","link":"#resend-state-and-attributes","children":[]},{"level":3,"title":"Other Properties","slug":"other-properties","link":"#other-properties","children":[]}]}],"git":{"updatedTime":1723606857000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":4},{"name":"Joakim Vindgard","email":"joakim.vindgard@gmail.com","commits":1}]},"filePathRelative":"node/entity-config.md"}');export{v as comp,b as data}; diff --git a/assets/entity.html-C290Kyy8.js b/assets/entity.html-C290Kyy8.js new file mode 100644 index 0000000000..b7c8070ce8 --- /dev/null +++ b/assets/entity.html-C290Kyy8.js @@ -0,0 +1 @@ +import{_ as o,c as l,e as n,a as t,d as s,b as i,o as r,r as d}from"./app-CefLgoES.js";const h={},u={id:"type",tabindex:"-1"},c={class:"header-anchor",href:"#type"},p={id:"state",tabindex:"-1"},b={class:"header-anchor",href:"#state"};function g(f,e){const a=d("Badge");return r(),l("div",null,[e[2]||(e[2]=n('DEPRECATED
This node has been deprecated in favor of the individual entity nodes. The individual entity nodes can be used in subflows and added to devices.
Creates an entity in Home Assistant which can be manipulated from this node.
binary sensor
, sensor
, and switch
Warning
Needs Custom Integration installed in Home Assistant for this node to function
string
binary_sensor|sensor|switch
sensor
The state the entity should be updated to
Object
Configuration options available for the selected entity
WARNING
Entity nodes will not work in a subflow due to the way they register themselves with Home Assistant. After a Node-RED restart, a new entity will be created in Home Assistant.
string | number | boolean
The state the entity should be updated to
Object
Key/Value pair of attributes to update. The key should be a string and the value can be a [string | number | boolean | object]
string
accept | merge | block
Determine how input values will be handled. When merge is selected the message object values will override the configuration values.
boolean
When creating the entity in Home Assistant this will also send the last updated state and attributes then node sent to Home Assistant
boolean
When the state of the switch changes it will output to the top if the switch is on or to the bottom if it is in the off position.
str | num | bool | JSONata | timestamp
Customizable output set to msg.payload
if Output on state change
is enabled.
properties of msg.payload
string | number | boolean
The state the entity should be updated to
Object
Key/Value pair of attributes to update. The key should be a string and the value can be a [string | number | boolean | object]
boolean
Set to true
to turn on the switch and false
to turn off. If the message has a property enable
set to the type boolean
the node will not have any output.
Status Color
This node listens for all types of events from Home Assistant, with the ability to filter by event type. It’s a powerful tool for triggering automations based on specific events occurring within Home Assistant, such as state changes, sensor readings, or user interactions.
string
the name of the node
string
filter by event type or leave blank for all events
Caution
Leaving this empty will listen for all events from Home Assistant which may overload the WebSocket message queue.
json
A JSON object that will be compared to the event data. If this JSON is a subset of the event data object, the event will be emitted.
boolean
What until Home Assistant has reported its state as running
before outputting events. Client events will always output.
entity config
Creates a switch within Home Assistant to enable/disable this node. This feature requires Node-RED custom integration to be installed in Home Assistant
Value types:
event data
: event received from Home Assistantconfig
: config properties of the nodeUse home_assistant_client
as the event type to receive events from the Websocket client.
Events sent from the client:
Here's a flow inspired by a feature request that listens for state_changed
events with a specific label. This is great for filtering by domain or capturing all events linked to a particular label.
💡 What it does:
state_changed
events in Home Assistant.[{"id":"56092aa4390d93bf","type":"ha-api","z":"3aa045026af17229","name":"entities","server":"","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/entity_registry/list\\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":540,"y":304,"wires":[["9227dd2dc5056d39"]]},{"id":"9227dd2dc5056d39","type":"function","z":"3aa045026af17229","name":"","func":"const entities = {};\\n\\nmsg.payload.forEach((e) => { \\n entities[e.entity_id] = e.labels;\\n});\\n\\nmsg.payload = entities;\\nmsg.update = true;\\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":724,"y":304,"wires":[["072d3e5fd5e540cb"]]},{"id":"072d3e5fd5e540cb","type":"function","z":"3aa045026af17229","name":"add label","func":"if(msg.update) {\\n node.status({fill:\\"green\\", shape: \\"dot\\", text: \\"data loaded\\", })\\n context.set(\\"data\\", msg.payload);\\n return;\\n}\\nconst data = context.get(\\"data\\");\\n\\nif(!data) {\\n node.status({fill:\\"red\\", shape: \\"ring\\", text: \\"no data\\", })\\n return;\\n}\\n\\nmsg.labels = data[msg.payload.entity_id];\\n\\nreturn msg;\\n\\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":352,"wires":[["666d393a4c0cb248"]]},{"id":"55b25821a65fcdde","type":"server-events","z":"3aa045026af17229","name":"on connect","server":"","version":3,"exposeAsEntityConfig":"","eventType":"home_assistant_client","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"}],"x":172,"y":304,"wires":[["2f3c8ac155fe3383"]]},{"id":"2f3c8ac155fe3383","type":"switch","z":"3aa045026af17229","name":"connected","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"running","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":374,"y":304,"wires":[["56092aa4390d93bf"]]},{"id":"ba967a32b033e303","type":"server-events","z":"3aa045026af17229","name":"entity_registry_updated","server":"","version":3,"exposeAsEntityConfig":"","eventType":"entity_registry_updated","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"}],"x":212,"y":256,"wires":[["56092aa4390d93bf"]]},{"id":"afb909fb7dfdcf47","type":"server-events","z":"3aa045026af17229","name":"","server":"","version":3,"exposeAsEntityConfig":"","eventType":"state_changed","eventData":"","waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"}],"x":212,"y":352,"wires":[["072d3e5fd5e540cb"]]},{"id":"01591113936768d5","type":"inject","z":"3aa045026af17229","name":"manual update","props":[],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":206,"y":208,"wires":[["56092aa4390d93bf"]]},{"id":"666d393a4c0cb248","type":"switch","z":"3aa045026af17229","name":"labels to watch for","property":"labels","propertyType":"msg","rules":[{"t":"cont","v":"lights","vt":"str"},{"t":"cont","v":"random_stuff","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":602,"y":352,"wires":[["972412bcf4727a99"],["87c4e2b7970975bd","7f08bcaf874489bf"]]},{"id":"87c4e2b7970975bd","type":"switch","z":"3aa045026af17229","name":"filter by domain","property":"payload.entity_id","propertyType":"msg","rules":[{"t":"regex","v":"^light.","vt":"str","case":false},{"t":"regex","v":"^switch.","vt":"str","case":false}],"checkall":"true","repair":false,"outputs":2,"x":816,"y":448,"wires":[["b79d2219a9d190c3"],["7fe6ea5d9b84d446"]]},{"id":"972412bcf4727a99","type":"link out","z":"3aa045026af17229","name":"events with \\"lights\\" label","mode":"link","links":["852b8bf86a74dce5"],"x":844,"y":352,"wires":[],"l":true},{"id":"b79d2219a9d190c3","type":"link out","z":"3aa045026af17229","name":"events with \\"random stuff\\" label and light domain","mode":"link","links":["a936f44cd50ff333"],"x":1138,"y":448,"wires":[],"l":true},{"id":"7fe6ea5d9b84d446","type":"link out","z":"3aa045026af17229","name":"events with \\"random stuff\\" label and switch domain","mode":"link","links":["6329fdc276129897"],"x":1138,"y":496,"wires":[],"l":true},{"id":"7f08bcaf874489bf","type":"link out","z":"3aa045026af17229","name":"events with \\"random stuff\\" label","mode":"link","links":["63e253fa6c8e08f1"],"x":866,"y":400,"wires":[],"l":true},{"id":"852b8bf86a74dce5","type":"link in","z":"3aa045026af17229","name":"state_changed event for \\"lights\\" label","links":["972412bcf4727a99"],"x":264,"y":544,"wires":[["aa74be0a11e1d63d"]],"l":true},{"id":"aa74be0a11e1d63d","type":"debug","z":"3aa045026af17229","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":580,"y":544,"wires":[]},{"id":"a936f44cd50ff333","type":"link in","z":"3aa045026af17229","name":"events with \\"random stuff\\" label and light domain","links":["b79d2219a9d190c3"],"x":296,"y":592,"wires":[["b5a888fa9acdabc0"]],"l":true},{"id":"b5a888fa9acdabc0","type":"debug","z":"3aa045026af17229","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":582,"y":592,"wires":[]},{"id":"6329fdc276129897","type":"link in","z":"3aa045026af17229","name":"events with \\"random stuff\\" label and switch domain","links":["7fe6ea5d9b84d446"],"x":306,"y":640,"wires":[["3dfddc440cb1a5aa"]],"l":true},{"id":"3dfddc440cb1a5aa","type":"debug","z":"3aa045026af17229","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":580,"y":640,"wires":[]},{"id":"63e253fa6c8e08f1","type":"link in","z":"3aa045026af17229","name":"events with \\"random stuff\\" label","links":["7f08bcaf874489bf"],"x":246,"y":688,"wires":[["bf5aca1357af9a13"]],"l":true},{"id":"bf5aca1357af9a13","type":"debug","z":"3aa045026af17229","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":582,"y":688,"wires":[]}]
+
boolean
Creates a switch within Home Assistant to enable/disable this node. This feature requires Node-RED custom integration to be installed in Home Assistant
Value types:
calendar item
: the calendar item object as provided by the Home Assistant APIstring
The entity ID is used to listen for state changes. This can be a entity ID, regex, or a substring. If a regex or substring is used, the node will listen for all entities that match.
Example:
light.kitchen
(entity) listens for state changes of the light.kitchen
entity^light.*
(regex) listens for state changes of all entities that start with light.
light
(substring) listens for state changes of all entities that contain light
in the entity IDstring
If the conditional is evaluated as true send the message to the first output otherwise send it to the second output. If blank there will only be one output.
Also see:
',8)),t("ul",null,[t("li",null,[o(l,{to:"/guide/conditionals.html"},{default:c(()=>e[1]||(e[1]=[n("Conditionals")])),_:1})])]),e[6]||(e[6]=i('number
An amount of time an entity's state needs to be in that state before triggering.
Warning
Output on Connect state changes will not start a timer.
string
string|number|boolean
string
Convert the state of the entity to the selected type. Boolean will be converted to true based on if the string is equal by default to (y|yes|true|on|home|open
). Original value stored in msg.data.original_state
boolean
A list of possible states that will be ignored.
boolean
Output once on startup/deploy
boolean
Creates a switch within Home Assistant to enable/disable this node. This feature requires Node-RED custom integration to be installed in Home Assistant
Value types:
event data
: full event objectentity id
: entity id of the triggered entityentity state
: entity state of the triggered entityconfig
: config properties of the nodeThe Events: state node receives state change events for one or more entities, and will output a message in response. The message output can be optionally controlled by a test condition on the state value, and also by a test on the length of time the state remains at that value. Both the state test condition and the state time duration can use JSONata. This node can also provide output message properties, again with the ability to use JSONata.
Here are three examples, showing how to use JSONata to perform If-State conditional tests, and to build output properties.
[{"id":"35b892e153bb5bc0","type":"group","z":"776c027950fc8c3f","name":"Events: state node - respond to state changes","style":{"label":true,"color":"#000000"},"nodes":["445466367c412014","e27b6ee2807af2eb","4583560d5089575d","24ed76ccd4da5975","266eadf86abd6481","27857c0c670e452b","e2ba71699db68b1a","1d9b8adce4f77e4e","a9b23a3911f5ef89"],"x":34,"y":919,"w":592,"h":342},{"id":"445466367c412014","type":"comment","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"3a - JSONata builds conditional test value","info":"","x":220,"y":960,"wires":[]},{"id":"e27b6ee2807af2eb","type":"server-state-changed","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"Switch was on for less than 3 minutes","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"switch\\\\.[a-z][0-9]","entityIdType":"regex","outputInitially":false,"stateType":"str","ifState":"(\\t $new:=$entity();\\t $old:=$prevEntity();\\t $mins:=($toMillis($new.last_changed)-$toMillis($old.last_changed))/60000~>$round(0);\\t $new.state=\\"off\\" and $old.state=\\"on\\" and $mins<3\\t)","ifStateType":"jsonata","ifStateOperator":"jsonata","outputOnlyOnStateChange":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"\\"Device \\" & $entity().attributes.friendly_name & \\" has been on for less than 3 minutes\\"","valueType":"jsonata"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":230,"y":1140,"wires":[["4583560d5089575d"],[]]},{"id":"4583560d5089575d","type":"debug","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"Flow trigger","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"topic","targetType":"msg","statusVal":"payload","statusType":"auto","x":490,"y":1140,"wires":[]},{"id":"24ed76ccd4da5975","type":"server-state-changed","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"Motion between dawn and dusk","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"motion","entityIdType":"substring","outputInitially":false,"stateType":"str","ifState":"(\\t $ismotion:= $entity().state=\\"on\\";\\t\\t $dawn:=$entities('sensor.sun_next_dawn').state;\\t $dusk:=$entities('sensor.sun_next_dusk').state;\\t $date:=$entities('sensor.date_time_utc').state~>$substringBefore(\\",\\");\\t\\t $isdawn:= $date=$substringBefore($dawn,\\"T\\");\\t $isdusk:= $date!=$substringBefore($dusk,\\"T\\");\\t \\t $ismotion and ($isdawn or $isdusk)\\t)","ifStateType":"jsonata","ifStateOperator":"jsonata","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"(\\t $ismotion:= $entity().state=\\"on\\";\\t\\t $dawn:=$entities('sensor.sun_next_dawn').state;\\t $dusk:=$entities('sensor.sun_next_dusk').state;\\t $date:=$entities('sensor.date_time_utc').state~>$substringBefore(\\",\\");\\t\\t $isdawn:= $date=$substringBefore($dawn,\\"T\\");\\t $isdusk:= $date!=$substringBefore($dusk,\\"T\\");\\t \\t $fire:= $ismotion and ($isdawn or $isdusk);\\t\\t {\\"motion\\": $ismotion,\\t \\"dawn\\": $dawn,\\t \\"dusk\\": $dusk,\\t \\"date\\": $date,\\t \\"isdawn\\": $isdawn,\\t \\"isdusk\\": $isdusk,\\t \\"fire\\": $fire\\t }\\t)","valueType":"jsonata"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":210,"y":1220,"wires":[["266eadf86abd6481"],[]]},{"id":"266eadf86abd6481","type":"debug","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"Dawn or Dusk","active":false,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload.isdawn ? \\"Before Dawn\\" : payload.isdusk ? \\"After Dusk\\"","statusType":"jsonata","x":500,"y":1220,"wires":[]},{"id":"27857c0c670e452b","type":"server-state-changed","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"motion","entityIdType":"substring","outputInitially":false,"stateType":"str","ifState":"(\\t $time:=$entities('sensor.time').state;\\t $append([\\"on\\"], $time<\\"08:30\\" or $time>\\"17:30\\" ? [\\"off\\"]);\\t) ","ifStateType":"jsonata","ifStateOperator":"includes","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"seconds","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":240,"y":1020,"wires":[["1d9b8adce4f77e4e"],["e2ba71699db68b1a"]]},{"id":"e2ba71699db68b1a","type":"debug","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"Condition false","active":false,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":500,"y":1040,"wires":[]},{"id":"1d9b8adce4f77e4e","type":"debug","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"Condition true","active":false,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload=\\"on\\" ? \\"Motion at \\" & $now('[H]:[m]') : \\"end\\"","statusType":"jsonata","x":500,"y":980,"wires":[]},{"id":"a9b23a3911f5ef89","type":"comment","z":"776c027950fc8c3f","g":"35b892e153bb5bc0","name":"3b - JSONata builds conditional Boolean","info":"","x":220,"y":1100,"wires":[]}]
+
Example: Respond to motion events for both "on" (start of motion detection) all day, and also "off" (end of motion detection) but only outside of 08:30 to 17:30.
(
+ $time:=$entities('sensor.time').state;
+ $append(["on"], $time<"08:30" or $time>"17:30" ? ["off"]);
+)
+
When the If State option is chosen with a conditional (not JSONata) the condition expects a value to test against. The result of the JSONata expression is the final line in the code block. This creates an array containing just ["on"] or if the time is before 08:30 or after 17:30, an array of ["on", "off"]. The motion sensor entity state will be compared with this array value using the inclusive condition 'in'.
This JSONata uses the Date-Time integration sensor to obtain the local time. The Time & Date Integration should be added to the configuration file, and then the time sensor will return the current local time.
Example: Switch has just been turned off, and was previously on for less than three minutes.
When the If State option is chosen with JSONata (not a conditional) then the right hand side must be a JSONata expression that returns a Boolean value of either true
or false
. When true the "If State" test is successful (the message is output from the top exit) when false the test fails (the message is output from the lower exit).
(
+ $new:=$entity();
+ $old:=$prevEntity();
+ $mins:=($toMillis($new.last_changed)-$toMillis($old.last_changed))/60000~>$round(0);
+ $new.state="off" and $old.state="on" and $mins<3
+)
+
+
In all the WebSocket nodes, the $entity()
function will return an object for the entity that is the subject of the node. For the Event: nodes, the $entity()
return is for the current or new state, and an additional $prevEntity()
function will return a similar object for the previous state. Use of both functions allows JSONata expressions to work with and compare the triggering state change.
Example: Motion has been detected before dawn or after dusk.
Here JSONata is again used to generate a Boolean result for the If State test. The expression return is the final line in the code block, which tests for motion and either before dawn or after dusk. The use of 'or' here is an example of how JSONata can extend logic testing since the Trigger state node conditions can only group conditions as AND logic.
(
+ $ismotion:= $entity().state="on";
+
+ $dawn:=$entities('sensor.sun_next_dawn').state;
+ $dusk:=$entities('sensor.sun_next_dusk').state;
+ $date:=$entities('sensor.date_time_utc').state~>$substringBefore(",");
+
+ $isdawn:= $date=$substringBefore($dawn,"T");
+ $isdusk:= $date!=$substringBefore($dusk,"T");
+
+ $ismotion and ($isdawn or $isdusk)
+)
+
The Sun integration in Home Assistant provides 'nextdawn' and 'next_dusk' sensors, with a date-time string value in UTC. Since the _next dawn will be for today before dawn, but will be for tomorrow after dawn, and similarly the next dusk will be for today before dusk, but for tomorrow after dusk, it is possible to compare just the date parts of the UTC timestamps, so as to determine if the current time is before or after either dawn or dusk.
To extend this last example, JSONata is used to generate an output object. Note that there is no internal connection between the two JSONata expressions, so all the values have to be recalculated as required.
(
+ $ismotion:= $entity().state="on";
+
+ $dawn:=$entities('sensor.sun_next_dawn').state;
+ $dusk:=$entities('sensor.sun_next_dusk').state;
+ $date:=$entities('sensor.date_time_utc').state~>$substringBefore(",");
+
+ $isdawn:= $date=$substringBefore($dawn,"T");
+ $isdusk:= $date!=$substringBefore($dusk,"T");
+
+ $fire:= $ismotion and ($isdawn or $isdusk);
+
+ {"motion": $ismotion,
+ "dawn": $dawn,
+ "dusk": $dusk,
+ "date": $date,
+ "isdawn": $isdawn,
+ "isdusk": $isdusk,
+ "fire": $fire
+ }
+)
+
+
The Trigger: state node is great if you have several conditions you want to check for but it doesn't allow use of OR conditions. Using a JSONata expression with an Event: state node will allow you to fill this gap.
Example: Motion sensor at the front door triggers, and have a text to speech notification be sent if at least one person is home.
$entity().state = "on" and (
+ $entities("person.person1").state = "home" or $entities("person.person2").state = "home"
+)
+
Also see:
`,29)),n("ul",null,[n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[0]||(s[0]=[p("JSONata guide")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/jsonata-primer.html"},{default:o(()=>s[1]||(s[1]=[p("JSONata primer")])),_:1})])])])}const m=e(d,[["render",g],["__file","events-state.html.vue"]]),f=JSON.parse('{"path":"/cookbook/jsonata/events-state.html","title":"Events: state","lang":"en-US","frontmatter":{},"headers":[{"level":3,"title":"Providing a conditional test value (JSONata expression as a value)","slug":"providing-a-conditional-test-value-jsonata-expression-as-a-value","link":"#providing-a-conditional-test-value-jsonata-expression-as-a-value","children":[]},{"level":3,"title":"Conditional test (JSONata expression as a Boolean result)","slug":"conditional-test-jsonata-expression-as-a-boolean-result","link":"#conditional-test-jsonata-expression-as-a-boolean-result","children":[]},{"level":3,"title":"Creating output properties","slug":"creating-output-properties","link":"#creating-output-properties","children":[]},{"level":3,"title":"OR conditional for the Events: state node","slug":"or-conditional-for-the-events-state-node","link":"#or-conditional-for-the-events-state-node","children":[]}],"git":{"updatedTime":1719788083000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":2}]},"filePathRelative":"cookbook/jsonata/events-state.md"}');export{m as comp,f as data}; diff --git a/assets/expiration-date-monitor.html-iEz7TEU6.js b/assets/expiration-date-monitor.html-iEz7TEU6.js new file mode 100644 index 0000000000..77571eb415 --- /dev/null +++ b/assets/expiration-date-monitor.html-iEz7TEU6.js @@ -0,0 +1,20 @@ +import{_ as n,c as a,e as t,o as p}from"./app-CefLgoES.js";const o="/node-red-contrib-home-assistant-websocket/assets/expiration-date-monitor_01-D-HOPyeA.png",e="/node-red-contrib-home-assistant-websocket/assets/expiration-date-monitor_02-CLuUTqyf.png",u="/node-red-contrib-home-assistant-websocket/assets/expiration-date-monitor_03-Cmsu_170.png",c={};function r(l,s){return p(),a("div",null,s[0]||(s[0]=[t(`Originally posted here on the Home Assistant Forums.
Here's a demo showing how to build a system to track the expiration date of items in the pantry. Using the Home Assistant Shopping List as a way to input and update a list of items and their expiration date.
The format of the name in the shopping list would be [item] : [expiration date]
with a colon separating the item name from the expiration date.
First, you would have to activate the shopping list in the config. https://www.home-assistant.io/integrations/shopping_list/
# Example configuration.yaml entry
+shopping_list:
+
There's already a lovelace card for the shopping list. I also added an input_number
to dynamically control the expiration window to check.
# Example configuration.yaml entry
+input_number:
+ pantry_expiration:
+ name: Pantry Expiration Window
+ initial: 90
+ min: 30
+ max: 120
+ step: 1
+ unit_of_measurement: days
+ icon: mdi:calendar-clock
+
Simple Lovelace config
type: vertical-stack
+cards:
+ - title: Pantry Items
+ type: shopping-list
+ - type: entities
+ entities:
+ - input_number.pantry_expiration
+
You can set the inject node to fire at a set time each day or every other day whatever fits your needs.
[{"id":"e78bbe2c.9141","type":"inject","z":"56b1c979.b2c618","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":284,"y":1056,"wires":[["720213f9.9bb8fc"]]},{"id":"adeab5ee.bc4098","type":"ha-api","z":"56b1c979.b2c618","name":"Get Items","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"shopping_list/items\\"}","dataType":"json","location":"payload","locationType":"msg","responseType":"json","x":652,"y":1056,"wires":[["236e3cd6.fab7d4"]]},{"id":"236e3cd6.fab7d4","type":"function","z":"56b1c979.b2c618","name":"do the stuff","func":"const items = msg.payload;\\n\\nif (items.length === 0) return;\\n\\nconst expItems = [];\\n\\n// Current timestamp + expiration days in milliseconds\\nconst expireWindow = Date.now() + msg.expDays * 8.64e7;\\n\\nitems.forEach(i => {\\n // If the name doesn't contain the split character don't process\\n // If complete set to true in the shopping list don't process\\n if (!i.name.includes(\\":\\") || i.complete === true) return;\\n\\n // Split the name and remove white spaces\\n const [name, exp] = i.name.split(\\":\\").map(x => x.trim());\\n\\n // check for valid date\\n const expiredDate = Date.parse(exp);\\n if (isNaN(expiredDate) || expiredDate > expireWindow) return;\\n \\n // Add item to expired list\\n expItems.push({ \\n name, \\n exp, \\n inThePast: expiredDate < Date.now()\\n });\\n});\\n\\n// If array is empty nothing to report\\nif (expItems.length === 0) return;\\n\\nmsg.payload = expItems;\\n\\nreturn msg;\\n","outputs":1,"noerr":0,"x":822,"y":1056,"wires":[["4270d967.43cc08"]]},{"id":"720213f9.9bb8fc","type":"api-current-state","z":"56b1c979.b2c618","name":"Get expiration window","version":1,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_number.pantry_expiration","state_type":"num","state_location":"expDays","override_payload":"msg","entity_location":"","override_data":"none","blockInputOverrides":false,"x":468,"y":1056,"wires":[["adeab5ee.bc4098"]]},{"id":"4270d967.43cc08","type":"split","z":"56b1c979.b2c618","name":"","splt":"\\\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":290,"y":1120,"wires":[["e1f6793d.9be5f8"]]},{"id":"3d3871cd.cb442e","type":"join","z":"56b1c979.b2c618","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":674,"y":1120,"wires":[["ddffd5ea.ebd528"]]},{"id":"e1f6793d.9be5f8","type":"moment","z":"56b1c979.b2c618","name":"pretty","topic":"","input":"payload.exp","inputType":"msg","inTz":"America/Los_Angeles","adjAmount":0,"adjType":"days","adjDir":"add","format":"timeAgo","locale":"en_US","output":"payload.pretty","outputType":"msg","outTz":"America/Los_Angeles","x":418,"y":1120,"wires":[["c96d522b.7cfbb"]]},{"id":"c96d522b.7cfbb","type":"function","z":"56b1c979.b2c618","name":"format","func":"const d = msg.payload;\\nmsg.payload = `${d.name} expire${d.inThePast ? 'd' : 's'} ${d.pretty}`;\\nreturn msg;","outputs":1,"noerr":0,"x":544,"y":1120,"wires":[["3d3871cd.cb442e"]]},{"id":"ddffd5ea.ebd528","type":"api-call-service","z":"56b1c979.b2c618","name":"","version":1,"debugenabled":false,"service_domain":"notify","service":"mobile_app_phone","entityId":"","data":"{\\t \\"title\\": \\"Pantry Items Expiring:\\",\\t \\"message\\": $join(payload, \\"\\\\n\\")\\t}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":882,"y":1120,"wires":[[]]}]\n
There's a lot more polish that could go into this such as being notified if the date entered in the shopping list is invalid or doesn't have a date at all. Sort the expired list so that the closest to expiring is at the top.
',18)]))}const k=n(c,[["render",r],["__file","expiration-date-monitor.html.vue"]]),q=JSON.parse('{"path":"/cookbook/expiration-date-monitor.html","title":"Expiration Date Monitor with notification","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Home Assistant stuff","slug":"home-assistant-stuff","link":"#home-assistant-stuff","children":[]},{"level":2,"title":"Node-RED stuff","slug":"node-red-stuff","link":"#node-red-stuff","children":[]}],"git":{"updatedTime":1647350454000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":2}]},"filePathRelative":"cookbook/expiration-date-monitor.md"}');export{k as comp,q as data}; diff --git a/assets/expiration-date-monitor_01-D-HOPyeA.png b/assets/expiration-date-monitor_01-D-HOPyeA.png new file mode 100644 index 0000000000..1061a3c1c9 Binary files /dev/null and b/assets/expiration-date-monitor_01-D-HOPyeA.png differ diff --git a/assets/expiration-date-monitor_02-CLuUTqyf.png b/assets/expiration-date-monitor_02-CLuUTqyf.png new file mode 100644 index 0000000000..700cd2e102 Binary files /dev/null and b/assets/expiration-date-monitor_02-CLuUTqyf.png differ diff --git a/assets/expiration-date-monitor_03-Cmsu_170.png b/assets/expiration-date-monitor_03-Cmsu_170.png new file mode 100644 index 0000000000..6d4c1c129d Binary files /dev/null and b/assets/expiration-date-monitor_03-Cmsu_170.png differ diff --git a/assets/exposed-nodes.html-YvdovF3I.js b/assets/exposed-nodes.html-YvdovF3I.js new file mode 100644 index 0000000000..62ad10d09e --- /dev/null +++ b/assets/exposed-nodes.html-YvdovF3I.js @@ -0,0 +1 @@ +import{_ as i,c as s,a as t,b as l,w as n,e as a,r,o as u,d}from"./app-CefLgoES.js";const p={};function g(h,e){const o=r("RouteLink");return u(),s("div",null,[e[10]||(e[10]=t("h1",{id:"exposed-nodes",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#exposed-nodes"},[t("span",null,"Exposed Nodes")])],-1)),e[11]||(e[11]=t("h2",{id:"nodes-available-for-exposure",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#nodes-available-for-exposure"},[t("span",null,"Nodes Available for Exposure")])],-1)),e[12]||(e[12]=t("p",null,"The following nodes can be exposed to Home Assistant, appearing as switches that can be toggled to enable or disable them in Node-RED:",-1)),t("ul",null,[t("li",null,[l(o,{to:"/node/device.html"},{default:n(()=>e[0]||(e[0]=[d("Device")])),_:1})]),t("li",null,[l(o,{to:"/node/events-all.html"},{default:n(()=>e[1]||(e[1]=[d("Events: all")])),_:1})]),t("li",null,[l(o,{to:"/node/events-state.html"},{default:n(()=>e[2]||(e[2]=[d("Events: state")])),_:1})]),t("li",null,[l(o,{to:"/node/poll-state.html"},{default:n(()=>e[3]||(e[3]=[d("Poll State")])),_:1})]),t("li",null,[l(o,{to:"/node/sentence.html"},{default:n(()=>e[4]||(e[4]=[d("Sentence")])),_:1})]),t("li",null,[l(o,{to:"/node/tag.html"},{default:n(()=>e[5]||(e[5]=[d("Tag")])),_:1})]),t("li",null,[l(o,{to:"/node/time.html"},{default:n(()=>e[6]||(e[6]=[d("Time")])),_:1})]),t("li",null,[l(o,{to:"/node/trigger-state.html"},{default:n(()=>e[7]||(e[7]=[d("Trigger: state")])),_:1})]),t("li",null,[l(o,{to:"/node/webhook.html"},{default:n(()=>e[8]||(e[8]=[d("Webhook")])),_:1})]),t("li",null,[l(o,{to:"/node/zone.html"},{default:n(()=>e[9]||(e[9]=[d("Zone")])),_:1})])]),e[13]||(e[13]=a('This feature provides a cleaner alternative to creating input_booleans
in Home Assistant for controlling flow activation.
nodered.trigger
Exposed nodes can be activated using the nodered.trigger
service call with the following parameters:
entity_id
entity_id
of the exposed node to be triggered, as seen in Home Assistant. For example, use switch.my_node
if that is the node's entity_id
.output_path
0
0
: Send through all output paths.1
: Send through the first output path.2
: Send through the second output path, and so on.message
{ "payload": "hello world" }
would set msg.payload
to hello world
.string
Event name to fire
Object
JSON object to pass along
If the incoming message has a payload
property with event
set it will override any config values if set. If the incoming message has a payload.data
that is an object or parsable into an object these properties will be merged with any config values set. If the node has a property value in its config for Merge Context
then the flow
and global
contexts will be checked for this property which should be an object that will also be merged into the data payload.
string
Event to fire
Object
Event data to send
string
Event Type that was fired
Object
The event data
sent if one was used
For your first automation, let's keep it simple by setting up a light to turn on when the sun sets and off when it rises.
This example uses the sun.sun
entity in Home Assistant, which has states below_horizon
and above_horizon
, to trigger the light.
sun.sun
. This is the entity that will trigger the automation.The If State condition checks the entity's state when the node is triggered. If the condition is true
, the message will be sent through the top output; if false
, it will be sent through the bottom output. If no condition is set, all messages will pass through a single output.
For this example, set the If State to above_horizon
.
Now, let's set up the actions that will be triggered. Most Home Assistant interactions are done through action calls, which we'll handle with the Action node.
Drag two Action nodes onto the workspace and connect them to the outputs of the Events: state node.
above_horizon
, the top output will turn off the light, and the bottom output will turn it on.light.turn_off
(for the top output) and light.turn_on
(for the bottom output)light.front_porch
[{"id":"b74ada49.d7e408","type":"server-state-changed","z":"ffbd7f06.4a014","name":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sun.sun","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"above_horizon","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"x":244,"y":784,"wires":[["1f467cbb.0c3983"],["da5ff3e0.cbb2a"]]},{"id":"1f467cbb.0c3983","type":"api-call-service","z":"ffbd7f06.4a014","name":"","version":1,"debugenabled":false,"service_domain":"light","service":"turn_off","entityId":"light.front_porch","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":474,"y":784,"wires":[[]]},{"id":"da5ff3e0.cbb2a","type":"api-call-service","z":"ffbd7f06.4a014","name":"","version":1,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"light.front_porch","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":474,"y":832,"wires":[[]]}]
+
Related Resources:
`,23)),n("ul",null,[s[3]||(s[3]=n("li",null,[n("a",{href:"https://nodered.org/docs/user-guide/editor/workspace/import-export",target:"_blank",rel:"noopener noreferrer"},"Importing and Exporting Flows")],-1)),n("li",null,[a(t,{to:"/node/action.html"},{default:o(()=>s[0]||(s[0]=[p("Action Node Documentation")])),_:1})]),n("li",null,[a(t,{to:"/node/events-state.html"},{default:o(()=>s[1]||(s[1]=[p("Events: State Node Documentation")])),_:1})]),n("li",null,[a(t,{to:"/guide/conditionals.html"},{default:o(()=>s[2]||(s[2]=[p("Conditionals in Node-RED")])),_:1})])])])}const A=r(g,[["render",m],["__file","first-automation.html.vue"]]),v=JSON.parse('{"path":"/guide/first-automation.html","title":"First Automation","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Events: State Node","slug":"events-state-node","link":"#events-state-node","children":[]},{"level":2,"title":"Entity ID Configuration","slug":"entity-id-configuration","link":"#entity-id-configuration","children":[]},{"level":2,"title":"If State Condition","slug":"if-state-condition","link":"#if-state-condition","children":[]},{"level":2,"title":"Action Node","slug":"action-node","link":"#action-node","children":[]},{"level":2,"title":"Complete Automation","slug":"complete-automation","link":"#complete-automation","children":[]}],"git":{"updatedTime":1724305054000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":6},{"name":"jason","email":"37859597+zachowj@users.noreply.github.com","commits":1}]},"filePathRelative":"guide/first-automation.md"}');export{A as comp,v as data}; diff --git a/assets/first-automation_02-vMFtAs18.png b/assets/first-automation_02-vMFtAs18.png new file mode 100644 index 0000000000..8e5f1542ce Binary files /dev/null and b/assets/first-automation_02-vMFtAs18.png differ diff --git a/assets/first-automation_03-Cs4xo4aA.png b/assets/first-automation_03-Cs4xo4aA.png new file mode 100644 index 0000000000..bf4ba7daac Binary files /dev/null and b/assets/first-automation_03-Cs4xo4aA.png differ diff --git a/assets/first-automation_04-D1RRhu3H.png b/assets/first-automation_04-D1RRhu3H.png new file mode 100644 index 0000000000..fc13966c09 Binary files /dev/null and b/assets/first-automation_04-D1RRhu3H.png differ diff --git a/assets/first-automation_05-BBzXhkfO.png b/assets/first-automation_05-BBzXhkfO.png new file mode 100644 index 0000000000..104e8bbc4b Binary files /dev/null and b/assets/first-automation_05-BBzXhkfO.png differ diff --git a/assets/first-automation_05-BSXfHgTV.js b/assets/first-automation_05-BSXfHgTV.js new file mode 100644 index 0000000000..8678ae5c72 --- /dev/null +++ b/assets/first-automation_05-BSXfHgTV.js @@ -0,0 +1 @@ +const s="/node-red-contrib-home-assistant-websocket/assets/first-automation_05-BBzXhkfO.png";export{s as _}; diff --git a/assets/functions.html-DkwcU8r9.js b/assets/functions.html-DkwcU8r9.js new file mode 100644 index 0000000000..ddf15d2433 --- /dev/null +++ b/assets/functions.html-DkwcU8r9.js @@ -0,0 +1,8 @@ +import{_ as e,c as u,e as r,a as n,b as t,w as o,r as c,o as l,d as p}from"./app-CefLgoES.js";const i="/node-red-contrib-home-assistant-websocket/assets/jsonata_8_1-DHI1CMwN.png",k={};function q(y,s){const a=c("RouteLink");return l(),u("div",null,[s[2]||(s[2]=r('Several additional JSONata functions are provided for use within the WebSocket nodes. JSONata expressions are first 'prepared' then 'executed', and the preparation first stage is used to bind the expression to extra Node-RED or other functions. This means that these WebSocket functions can only be used within a WebSocket node.
This example demonstrates the basic use of some of these functions.
[{"id":"c3f7b442994c1a29","type":"group","z":"776c027950fc8c3f","name":"Using WebSocket functions to return entity, device, and area details","style":{"label":true,"color":"#000000"},"nodes":["578b1fe6b17d9053","9e2cc6208465cb53","24a0721c11a9dabf","0e27f1810d1f2726","25aa9320ec1b341f","87097a630683a0f3","2b079469d339ee68","6d7320ce7cf2d71b","4cf613030bdac458","4bba7af21fb76af8","80af7504e9450dd4"],"x":34,"y":2699,"w":732,"h":322},{"id":"578b1fe6b17d9053","type":"inject","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Manual trigger","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":2820,"wires":[["9e2cc6208465cb53"]]},{"id":"9e2cc6208465cb53","type":"ha-get-entities","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Entities with an attribute called \\"array\\"","server":"","version":1,"rules":[{"property":"","logic":"jsonata","value":"$entity().attributes.array!=[]","valueType":"jsonata"}],"outputType":"array","outputEmptyResults":true,"outputLocationType":"msg","outputLocation":"payload","outputResultsCount":1,"x":410,"y":2820,"wires":[["6d7320ce7cf2d71b"]]},{"id":"24a0721c11a9dabf","type":"api-current-state","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Entity attributes called \\"array\\"","server":"","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.date_time","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"$entities().*.attributes[$exists(array)]","valueType":"jsonata"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":380,"y":2900,"wires":[["4cf613030bdac458"]]},{"id":"0e27f1810d1f2726","type":"api-current-state","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Get Areas with Device and Entity Counts","server":"","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.time","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"$areas()^(name).{name: \\" Devices: \\" & $count($areaDevices(area_id)) & \\" Entities: \\" & $count($areaEntities(area_id))}~>$merge()","valueType":"jsonata"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":420,"y":2980,"wires":[["87097a630683a0f3"]]},{"id":"25aa9320ec1b341f","type":"inject","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Manual trigger","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":2980,"wires":[["0e27f1810d1f2726"]]},{"id":"87097a630683a0f3","type":"debug","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":2980,"wires":[]},{"id":"2b079469d339ee68","type":"inject","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Manual trigger","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":2900,"wires":[["24a0721c11a9dabf"]]},{"id":"6d7320ce7cf2d71b","type":"debug","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":2820,"wires":[]},{"id":"4cf613030bdac458","type":"debug","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":2900,"wires":[]},{"id":"4bba7af21fb76af8","type":"server-state-changed","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Using $entity() and $prevEntity()","server":"","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"sensor.time","entityIdType":"exact","outputInitially":false,"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":"{\\"old\\": $prevEntity(), \\"new\\": $entity()}","valueType":"jsonata"}],"x":390,"y":2740,"wires":[["80af7504e9450dd4"]]},{"id":"80af7504e9450dd4","type":"debug","z":"776c027950fc8c3f","g":"c3f7b442994c1a29","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":2740,"wires":[]}]
+
In nodes with output properties, a number of properties are typically set by default, or can be optionally set as:
This provides useful message values in the flow following the node, however these values can be obtained within the node itself using the appropriate functions.
The function $entity()
returns an object containing the entity details, relating to the entity that is the subject of the node. This means that the output property selection options are equivalent to JSONata expressions:
For the Events: nodes (Events: all and Events: state) the node triggers from an entity-change event, with $entity() returning the current (new) entity details, and the function $prevEntity()
returning the previous (old) entity details.
{"old": $prevEntity(), "new": $entity()}
+
The first example uses an Events: state node, with a basic sensor.time
entity to facilitate triggering the node every minute. The Time & Date Integration should be added to the configuration file, and then the given time sensor will return the current local time. The entity state (as time) is "hh:mm" and therefore changes every 60 seconds.
The output msg.payload is set to a new JSONata expression, which creates a new object with the old and new entity details, just as the output property 'event data' would do, but permitting a more detailed JSONata expression to be executed if required.
The next two examples explore ways to obtain specific entities, and this code looks for all Home Assistant entities that have an attribute field called 'array'.
$entity().attributes.array!=[]
+
In the first flow, a Get Entities node is used, with a JSONata expression used to filter the result, based on each entity having an attribute field called 'array' that is a non-empty JSON array. This will return the entire entity, and therefore the output will be an array of entities that have an 'attribute.array' field.
$entities().*.attributes[$exists(array)]
+
In the second flow, a Current State node is used, with a valid subject entity. The return from the given entity itself is not required - the node is just being used as a vehicle in which to execute the $entities() function. When specifying a named entity such as $entities('sun.next_dawn')
only that entity will be returned, otherwise an array of all entities in Home Assistant will be returned. This JSONata expression then matches all entities in the array, filtering out attributes fields where the object key 'array' exists.
In contrast to the preceding flow, this will return just an array of the entity attributes. The JSONata code in this case can also be executed as and when required.
Note: Where a number of entity state values or attributes are required at one point in a flow for collective processing, using JSONata in a Current State node can be highly effective. The alternatives are:
Finally, a more complex JSONata expression that returns details about areas, devices, and entities.
$areas()^(name).{
+ name: " Devices: " & $count($areaDevices(area_id)) & " Entities: " & $count($areaEntities(area_id))
+ } ~> $merge()
+
The JSONata above is just one long expression line (unnecessary new lines have been added just to make the expression easier to read). Evaluated left to right, it starts with the $areas()
function:
Also see:
`,30)),n("ul",null,[n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[0]||(s[0]=[p("JSONata guide")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/jsonata-primer.html"},{default:o(()=>s[1]||(s[1]=[p("JSONata primer")])),_:1})])])])}const d=e(k,[["render",q],["__file","functions.html.vue"]]),m=JSON.parse('{"path":"/cookbook/jsonata/functions.html","title":"Other functions","lang":"en-US","frontmatter":{},"headers":[{"level":3,"title":"Reading entity state and entity attributes with $entity() and $prevEntity()","slug":"reading-entity-state-and-entity-attributes-with-entity-and-preventity","link":"#reading-entity-state-and-entity-attributes-with-entity-and-preventity","children":[]},{"level":3,"title":"Using $entities() to return one or all entities","slug":"using-entities-to-return-one-or-all-entities","link":"#using-entities-to-return-one-or-all-entities","children":[]},{"level":3,"title":"Using $areas(), $areaDevices and $areaEntities()","slug":"using-areas-areadevices-and-areaentities","link":"#using-areas-areadevices-and-areaentities","children":[]}],"git":{"updatedTime":1719788083000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":3}]},"filePathRelative":"cookbook/jsonata/functions.md"}');export{d as comp,m as data}; diff --git a/assets/generate-token_01-DB9TrXpw.png b/assets/generate-token_01-DB9TrXpw.png new file mode 100644 index 0000000000..667fc8b7c0 Binary files /dev/null and b/assets/generate-token_01-DB9TrXpw.png differ diff --git a/assets/generate-token_02-k2eQmSik.png b/assets/generate-token_02-k2eQmSik.png new file mode 100644 index 0000000000..7154870288 Binary files /dev/null and b/assets/generate-token_02-k2eQmSik.png differ diff --git a/assets/get-entities.html-DDxfqZLb.js b/assets/get-entities.html-DDxfqZLb.js new file mode 100644 index 0000000000..b85824b571 --- /dev/null +++ b/assets/get-entities.html-DDxfqZLb.js @@ -0,0 +1,50 @@ +import{_ as r,c as l,e as k,b as t,w as o,a as s,r as p,o as u,d as n}from"./app-CefLgoES.js";const i="/node-red-contrib-home-assistant-websocket/assets/get-entities_03-C5epOs7g.png",y="/node-red-contrib-home-assistant-websocket/assets/get-entities_02-6WjKPZlv.png",g="/node-red-contrib-home-assistant-websocket/assets/get-entities_01-CNrrQzIG.png",d="/node-red-contrib-home-assistant-websocket/assets/get-entities_04-DgqPL72w.png",h={};function b(m,a){const e=p("InfoPanelOnly"),c=p("DocsOnly");return u(),l("div",null,[a[2]||(a[2]=k(`The Get Entities node retrieves entities based on specific search criteria, with three different output options. This node is useful for filtering and finding entities that match certain conditions, such as all lights that are currently on, or sensors reporting a specific range of values.
All specified criteria must be met for an entity to be considered valid.
Autocomplete is available for all properties currently set on loaded entities.
Object
Override the configuration values by passing in a property with a valid value.
rules
(array) condition
: string (e.g., stateObject
, labelRegistry
, areaRegistry
, deviceRegistry
, floorRegistry
)property
: stringlogic
: stringvalue
: stringvalueType
: stringoutputType
: stringoutputEmptyResults
: booleanoutputLocationType
: stringoutputLocation
: stringoutputResultsCount
: numberTo retrieve all entities with the device class battery
on the First floor
that are not labeled not replaceable
, include the following in the payload
property of the message:
{
+ "rules": [
+ {
+ "condition": "stateObject",
+ "property": "attributes.device_class",
+ "logic": "is",
+ "value": "battery",
+ "valueType": "str"
+ },
+ {
+ "condition": "floorRegistry",
+ "property": "name",
+ "logic": "is",
+ "value": "First floor",
+ "valueType": "str"
+ },
+ {
+ "condition": "labelRegistry",
+ "property": "name",
+ "logic": "is_not",
+ "value": "not replaceable",
+ "valueType": "str"
+ }
+ ]
+}
+
Array
Returns an array of state objects based on the search criteria and sends them to the specified output location.
number
Returns the total count of valid entities.
Object | Array
Returns a random object or array from the available state objects. If One Max Results
is selected, it will return a single object; otherwise, it will return an array of state objects.
msg.part
Sends a separate message for each state object, similar to the output of a split node.
Sample output when the output type is an array:
[
+ {
+ "entity_id": "light.kitchen",
+ "state": "on",
+ "attributes": {
+ "brightness": 243,
+ "friendly_name": "Kitchen Light",
+ "supported_features": 33,
+ "icon": "mdi:light-switch"
+ },
+ "last_changed": "2019-12-29T05:38:53.016984+00:00",
+ "last_updated": "2019-12-29T05:38:53.016984+00:00",
+ "context": {
+ "id": "6c16e348494c42fb8c8e8bda92b20fb2",
+ "parent_id": null,
+ "user_id": null
+ },
+ "timeSinceChangedMs": 3466747
+ }
+]
+
string
The entity to which this state belongs.
string
The main attribute state value, such as 'on', 'off', 'home', 'open', 'closed', etc.
Object
Supported attributes of the state as set by Home Assistant.
string
ISO Date string representing the last time the entity state changed.
number
Milliseconds since the last state change of the entity.
string
ISO Date string representing the last time the entity state was updated.
Object
Information about who or what last changed the state of this entity.
The Get History node fetches the history of Home Assistant entities based on input criteria. This can be used to analyze past states or events, such as tracking temperature changes over time or reviewing when a door was last opened. It’s useful for creating more complex automations based on historical data.
string
The entity id to fetch history for. Can be a single entity id or a comma separated list of entity ids.
string
==
| regex
The type of entity id matching to use. ==
will do an exact match, regex
will use a regular expression to match the entity ids.
string
Date to start fetching history from. Will override the configuration if passed in. If relativetime
is used this will be ignored.
Also See:
string
The end date to fetch history too. Will override the configuration if passed in. If relativetime
is used this will be ignored.
Also See:
boolean
A checkbox to use relative time or not. If checked the startdate
and enddate
will be ignored and the relativetime
will be used instead.
string
A time string that will be parsed the following keywords into time values.
Example: 4h 30m
= The last 4 hours and 30 minutes
ms, milli, millisecond, milliseconds - will parse to milliseconds
+s, sec, secs, second, seconds - will parse to seconds
+m, min, mins, minute, minutes - will parse to minutes
+h, hr, hrs, hour, hours - will parse to hours
+d, day, days - will parse to days
+w, week, weeks - will parse to weeks
+mon, mth, mths, month, months - will parse to months
+y, yr, yrs, year, years - will parse to years
+
Instead of returning the data from home assistant ( array for each entity_id ) return one flattened array of one item per history entry
string
array
| split
array
The type of output to return. array
will return an array of history objects. split
will return an array of history objects for each entity id.
All properties of msg.payload
string
equal
| regex
string
string
string
boolean
array
The history returned by home-assistant, which is an array of arrays where each array entry contains history objects for one particular entity
Example output of msg
:
{
+ "payload": [
+ {
+ "attributes": {
+ "friendly_name": "Kitchen Light",
+ "icon": "mdi:light-switch",
+ },
+ "context": {
+ "id": "850e510e36fb494c9abc79e01e897d54",
+ "parent_id": null,
+ "user_id": null
+ },
+ "entity_id": "light.kitchen_light",
+ "last_changed": "2019-12-28T06:47:28.618000+00:00",
+ "last_updated": "2019-12-28T06:47:28.618000+00:00",
+ "state": "off"
+ },
+ {
+ "attributes": {
+ "brightness": 28,
+ "friendly_name": "Kitchen Light",
+ "icon": "mdi:light-switch",
+ },
+ "context": {
+ "id": "4d4abe29f2bc43dab39101193f1fefe4",
+ "parent_id": null,
+ "user_id": null
+ },
+ "entity_id": "light.kitchen_light",
+ "last_changed": "2019-12-28T07:48:11.514137+00:00",
+ "last_updated": "2019-12-28T07:48:11.514137+00:00",
+ "state": "on"
+ },
+ ...
+ ]
+}
+
state_changed
Events Based on Area[{"id":"c964194d.7ad5e8","type":"ha-api","z":"ffbd7f06.4a014","name":"areas","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/area_registry/list\\"}","dataType":"json","location":"areas","locationType":"msg","responseType":"json","x":514,"y":944,"wires":[["4b011603.4c27f8"]]},{"id":"832ebec4.0d4a4","type":"inject","z":"ffbd7f06.4a014","name":"Manual Update","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":320,"y":944,"wires":[["c964194d.7ad5e8"]]},{"id":"4b011603.4c27f8","type":"ha-api","z":"ffbd7f06.4a014","name":"devices","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/device_registry/list\\"}","dataType":"json","location":"devices","locationType":"msg","responseType":"json","x":654,"y":944,"wires":[["4be8b708.430ec8"]]},{"id":"4be8b708.430ec8","type":"ha-api","z":"ffbd7f06.4a014","name":"entities","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/entity_registry/list\\"}","dataType":"json","location":"entities","locationType":"msg","responseType":"json","x":798,"y":944,"wires":[["59e18ea8.03287"]]},{"id":"2538006.71ef4","type":"debug","z":"ffbd7f06.4a014","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1144,"y":944,"wires":[]},{"id":"81c8e84.471e218","type":"server-events","z":"ffbd7f06.4a014","name":"","event_type":"state_changed","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":308,"y":1040,"wires":[["6e177280.669dec"]]},{"id":"59e18ea8.03287","type":"function","z":"ffbd7f06.4a014","name":"","func":"const entities = {};\\n\\nmsg.entities.forEach(e => {\\n if(!e.device_id) return;\\n \\n const device = msg.devices.find(d => d.id === e.device_id);\\n const area = msg.areas.find(a => a.area_id === device.area_id);\\n if(area) {\\n entities[e.entity_id] = {\\n area_id: area.area_id,\\n name: area.name\\n };\\n }\\n});\\n\\nmsg.payload = entities;\\nmsg.update = true;\\nreturn msg;","outputs":1,"noerr":0,"x":964,"y":944,"wires":[["2538006.71ef4","6e177280.669dec"]]},{"id":"6e177280.669dec","type":"function","z":"ffbd7f06.4a014","name":"set area","func":"if(msg.update) {\\n node.status({fill:\\"green\\", shape: \\"dot\\", text: \\"Area Data Loaded\\", })\\n context.set(\\"data\\", msg.payload);\\n return;\\n}\\nconst data = context.get(\\"data\\");\\n\\nif(!data) {\\n node.status({fill:\\"red\\", shape: \\"ring\\", text: \\"No Area Data\\", })\\n return;\\n}\\n\\nconst area = data[msg.payload.entity_id];\\nif(!area) return;\\n\\nmsg.area = area.name.toLowerCase(); \\nnode.status({text: msg.area});\\n\\nreturn msg;","outputs":1,"noerr":0,"x":524,"y":1040,"wires":[["86100cd0.de8c5"]]},{"id":"582c98e0.5cd328","type":"debug","z":"ffbd7f06.4a014","name":"Kitchen","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":828,"y":1024,"wires":[]},{"id":"b60ead00.b4aaa","type":"debug","z":"ffbd7f06.4a014","name":"Bedroom","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":828,"y":1072,"wires":[]},{"id":"86100cd0.de8c5","type":"switch","z":"ffbd7f06.4a014","name":"","property":"area","propertyType":"msg","rules":[{"t":"eq","v":"kitchen","vt":"str"},{"t":"eq","v":"bedroom","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":656,"y":1040,"wires":[["582c98e0.5cd328"],["b60ead00.b4aaa"]]},{"id":"94d81549.f88148","type":"server-events","z":"ffbd7f06.4a014","name":"On Connect","event_type":"home_assistant_client","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":278,"y":896,"wires":[["72ef46c0.dbbce8"]]},{"id":"72ef46c0.dbbce8","type":"switch","z":"ffbd7f06.4a014","name":"connected","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"connected","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":470,"y":896,"wires":[["c964194d.7ad5e8"]]},{"id":"47aad143.f438c","type":"server-events","z":"ffbd7f06.4a014","name":"entity_registry_updated","event_type":"entity_registry_updated","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":308,"y":752,"wires":[["c7d0582b.715958"]]},{"id":"58d3bcdb.d39014","type":"server-events","z":"ffbd7f06.4a014","name":"device_registry_updated","event_type":"device_registry_updated","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":318,"y":800,"wires":[["c7d0582b.715958"]]},{"id":"8a9f192c.425718","type":"server-events","z":"ffbd7f06.4a014","name":"area_registry_updated","event_type":"area_registry_updated","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":308,"y":848,"wires":[["c7d0582b.715958"]]},{"id":"c7d0582b.715958","type":"trigger","z":"ffbd7f06.4a014","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"10","extend":false,"units":"s","reset":"","bytopic":"all","name":"Update at most every 10 secs","x":610,"y":800,"wires":[["c964194d.7ad5e8"]]}]
+
Questions and Discussion
Post questions and follow the discussion about this recipe here
Here's a flow to control some outdoor house LEDs hooked up to an ESP8266 running WLED.
The holiday lights turn on each day at sunset and then off at midnight. It uses WLED presets based on the day or season. It has a demo mode that will cycle through all the defined presets or all the included effects.
[{"id":"886a465.91a0ab8","type":"switch","z":"894a6b3077af9604","name":"dark?","property":"dark","propertyType":"flow","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":450,"y":240,"wires":[["5e38e1ee8665f5e7"],["90579358.a3dce"]]},{"id":"34cb332b.f8228c","type":"server-state-changed","z":"894a6b3077af9604","name":"Demo Mode","server":"","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_select.holiday_lights_demo_mode","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"off","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"","forType":"num","forUnits":"minutes","ignorePrevStateNull":true,"ignorePrevStateUnknown":true,"ignorePrevStateUnavailable":true,"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":294,"y":384,"wires":[["df0da821.aa74b8"],["91131276.4f322"]]},{"id":"90579358.a3dce","type":"api-call-service","z":"894a6b3077af9604","name":"turn off holiday lights","server":"","version":3,"debugenabled":false,"service_domain":"light","service":"turn_off","entityId":"{{flow.wledHolidayLightsEntityId}}","data":"","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"data"}],"queue":"last","x":692,"y":288,"wires":[[]]},{"id":"dc4b2af1.d366a8","type":"ha-wait-until","z":"894a6b3077af9604","name":"loop","server":"","version":0,"outputs":2,"entityId":"{{flow.wledHolidayLightsEntityId}}","entityIdFilterType":"exact","property":"state","comparator":"is","value":"off","valueType":"str","timeout":"60","timeoutType":"num","timeoutUnits":"minutes","entityLocation":"","entityLocationType":"none","checkCurrentState":false,"blockInputOverrides":false,"x":850,"y":240,"wires":[[],["5e38e1ee8665f5e7"]]},{"id":"59e25c2c.a3a514","type":"eztimer","z":"894a6b3077af9604","name":"","debug":false,"autoname":"sunset - 00:00","tag":"test1","suspended":false,"sendEventsOnSuspend":false,"latLongSource":"manual","latLongHaZone":"","lat":"","lon":"","timerType":"1","startupMessage":true,"ontype":"1","ontimesun":"sunset","ontimetod":"17:00","onpropertytype":"flow","onproperty":"dark","onvaluetype":"bool","onvalue":"true","onoffset":"","onrandomoffset":0,"onsuppressrepeats":false,"offtype":"2","offtimesun":"dusk","offtimetod":"00:00","offduration":0,"offpropertytype":"flow","offproperty":"dark","offvaluetype":"bool","offvalue":"false","offoffset":0,"offrandomoffset":0,"offsuppressrepeats":false,"resend":false,"resendInterval":"1m","mon":true,"tue":true,"wed":true,"thu":true,"fri":true,"sat":true,"sun":true,"x":304,"y":240,"wires":[["886a465.91a0ab8"]]},{"id":"f4ab62e2.10029","type":"api-call-service","z":"894a6b3077af9604","name":"turn on holiday lights","server":"","version":3,"debugenabled":false,"service_domain":"select","service":"select_option","entityId":"{{flow.wledHolidayLightsPresetEntityId}}","data":"{\\"option\\": payload}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"last","x":900,"y":192,"wires":[[]]},{"id":"98efb1f6.c85b8","type":"ha-wait-until","z":"894a6b3077af9604","name":"loop","server":"","version":0,"outputs":2,"entityId":"{{flow.wledHolidayLightsEntityId}}","entityIdFilterType":"exact","property":"state","comparator":"is","value":"off","valueType":"str","timeout":"$number($entities(\\"input_number.holiday_lights_demo_mode_delay\\").state)","timeoutType":"jsonata","timeoutUnits":"seconds","entityLocation":"","entityLocationType":"none","checkCurrentState":false,"blockInputOverrides":true,"x":1058,"y":432,"wires":[[],["91131276.4f322"]]},{"id":"91131276.4f322","type":"switch","z":"894a6b3077af9604","name":"which mode?","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"cycle presets","vt":"str"},{"t":"eq","v":"cycle effects","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":470,"y":432,"wires":[["5adba3095728cdab"],["28fd29dc.96d306"]]},{"id":"333ed476.51a77c","type":"api-call-service","z":"894a6b3077af9604","name":"turn on preset","server":"","version":3,"debugenabled":false,"service_domain":"select","service":"select_option","entityId":"{{flow.wledHolidayLightsPresetEntityId}}","data":"{\\"option\\": name}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":880,"y":432,"wires":[["98efb1f6.c85b8","d0b9f667.79a8c8"]]},{"id":"fcf7dabd.b7a368","type":"api-call-service","z":"894a6b3077af9604","name":"turn on effect","server":"","version":3,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"{{flow.wledHolidayLightsEntityId}}","data":"{\\"effect\\": name}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":870,"y":480,"wires":[["98efb1f6.c85b8","d0b9f667.79a8c8"]]},{"id":"df0da821.aa74b8","type":"change","z":"894a6b3077af9604","name":"reset","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":384,"wires":[["886a465.91a0ab8","98efb1f6.c85b8"]]},{"id":"998c4f75.52cbf","type":"api-call-service","z":"894a6b3077af9604","name":"turn off demo mode","server":"","version":3,"debugenabled":false,"service_domain":"input_select","service":"select_option","entityId":"input_select.holiday_lights_demo_mode","data":"{\\"option\\": \\"off\\"}","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":570,"y":528,"wires":[[]]},{"id":"5caeec35.01a114","type":"server-state-changed","z":"894a6b3077af9604","name":"Holiday Lights turned off?","server":"","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"light.wled","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"off","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"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":332,"y":528,"wires":[["998c4f75.52cbf"],[]]},{"id":"28fd29dc.96d306","type":"function","z":"894a6b3077af9604","name":"get next effect","func":"const states = global.get(\\"homeassistant\\").homeAssistant.states;\\nconst entityId = flow.get(\\"wledHolidayLightsEntityId\\");\\nconst effects = states[entityId].attributes.effect_list;\\nconst activeEffect = states[entityId].attributes.effect;\\nlet index = effects.findIndex(ele => ele === activeEffect);\\nindex = (index === -1 || index >= effects.length - 1) ? 0 : index + 1;\\n\\nnode.status({text: effects[index]});\\n\\nmsg.name = effects[index];\\n\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":672,"y":480,"wires":[["fcf7dabd.b7a368"]]},{"id":"d0b9f667.79a8c8","type":"ha-entity","z":"894a6b3077af9604","name":"current demo","server":"","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"Holiday Lights Demo Current"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"name","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1078,"y":480,"wires":[[]]},{"id":"9168ce00.dc7de","type":"comment","z":"894a6b3077af9604","name":"Scheduler","info":"","x":284,"y":192,"wires":[]},{"id":"241d097a.a3fe76","type":"comment","z":"894a6b3077af9604","name":"Demo Mode","info":"","x":294,"y":336,"wires":[]},{"id":"22f2928a.e2ae9e","type":"inject","z":"894a6b3077af9604","name":"Manual Start","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"dark","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":294,"y":288,"wires":[["5e38e1ee8665f5e7"]]},{"id":"9f8ca90550d41489","type":"inject","z":"894a6b3077af9604","name":"Setup","props":[{"p":"entityId","v":"light.holiday_lights","vt":"str"}],"repeat":"3600","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":284,"y":144,"wires":[["1047f2356f1e5203"]]},{"id":"1047f2356f1e5203","type":"change","z":"894a6b3077af9604","name":"set entity ids here","rules":[{"t":"set","p":"wledHolidayLightsEntityId","pt":"flow","to":"light.wled","tot":"str"},{"t":"set","p":"wledHolidayLightsPresetEntityId","pt":"flow","to":"select.wled_preset","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":144,"wires":[[]]},{"id":"5e38e1ee8665f5e7","type":"function","z":"894a6b3077af9604","name":"date ranges","func":"const now = new Date();\\nconst monthDay = getMonthDay(now);\\nconst states = global.get(\\"homeassistant\\").homeAssistant.states;\\nconst entityId = flow.get(\\"wledHolidayLightsEntityId\\");\\nconst presetId = flow.get(\\"wledHolidayLightsPresetEntityId\\");\\nconst christmasPresets = [\\n \\"Christmas\\",\\n \\"Christmas Candy Cane\\",\\n \\"Christmas Classic\\",\\n \\"Christmas Holly Jolly\\"\\n];\\n\\nlet updateInterval = 0; // in minutes\\nlet preset;\\n\\nswitch(true) {\\n// Winter \\n case isToday(214):\\n preset = \\"Valentine\\";\\n break;\\n// President's Day changes every year, Veteran's Day & Flag Day always the same date\\n case isToday(614): // 6/14 Flag Day\\n case isToday(1111): // 11/11 Veterans Day\\n case isToday(\\"President's Day\\"):\\n case isToday(\\"Memorial Day\\"):\\n preset = \\"Red White Blue\\";\\n break;\\n case isToday(317):\\n preset = \\"St Patty\\";\\n break;\\n case isToday(\\"Easter\\"):\\n preset = \\"Easter\\";\\n break;\\n case isToday(\\"Ash Wednesday\\", \\"Easter\\"):\\n preset = \\"Spring\\";\\n break;\\n// Spring\\n case isToday(504):\\n preset = \\"Star Wars\\";\\n break;\\n case isToday(505):\\n preset = \\"Cinco de Mayo\\";\\n break;\\n// Summer\\n case isToday(704):\\n preset = \\"Fireworks\\";\\n break;\\n// Autumn\\n case isToday(1014, 1031):\\n preset = \\"Halloween\\";\\n break;\\n case isToday(\\"Thanksgiving Day\\"):\\n preset = \\"Autumn\\";\\n break;\\n case isToday(1231):\\n preset = \\"Fireworks White\\";\\n break;\\n case isToday(\\"Thanksgiving Day\\", 1231):\\n case isToday(101, 106):\\n preset = getRandomPreset(christmasPresets);\\n updateInterval = 60;\\n break;\\n default:\\n const seasons = {\\n \\"winter\\": \\"Winter\\",\\n \\"spring\\": \\"Spring\\",\\n \\"summer\\": \\"Summer\\",\\n \\"autumn\\": \\"Autumn\\"\\n }\\n const currentSeason = states[\\"sensor.season\\"].state;\\n preset = seasons[currentSeason];\\n break;\\n}\\n\\nnode.status({ text: preset});\\nmsg.payload = preset;\\nconst secondOutput = updateInterval ? { payload: { timeout: updateInterval } } : { reset: true };\\n\\nreturn [msg, secondOutput];\\n\\nfunction isToday(start, end) {\\n if (typeof start === \\"string\\") {\\n start = getHolidayDate(now, start)\\n }\\n if (typeof end === \\"string\\") {\\n end = getHolidayDate(now, end)\\n }\\n if (end === undefined) return monthDay === start;\\n\\n return monthDay >= start && monthDay <= end;\\n}\\n\\nfunction getHolidayDate(date, holiday) {\\n holiday = holiday.toLowerCase();\\n // dates are formatted as month, week, day of the week\\n // names should be lowercase\\n const holidays = {\\n \\"martin luther king day\\": [0,2,1],\\n \\"president's day\\": [1,2,1],\\n \\"daylight savings time begins\\": [2,1,0],\\n \\"mother's day\\": [4,1,0],\\n \\"memorial day\\": [4,-1,1],\\n \\"father's day\\": [5,2,0],\\n \\"labor day\\": [8,0,1],\\n \\"columbus day\\": [9,1,1],\\n \\"daylight savings time ends\\": [10,0,0],\\n \\"thanksgiving day\\": [10,3,4]\\n };\\n let holidayDate;\\n if(holiday === \\"easter\\") {\\n holidayDate = getEasterDate(date);\\n } else if (holiday === \\"ash wednesday\\") {\\n const easter = getEasterDate(date);\\n holidayDate = new Date(easter.getFullYear(), easter.getMonth(), (easter.getDate()-46));\\n } else {\\n const [month, week, day] = holidays[holiday];\\n holidayDate = getDate(date.getFullYear(), month, week, day);\\n }\\n \\n const monthDay = getMonthDay(holidayDate);\\n \\n return monthDay;\\n}\\n\\nfunction getMonthDay(date) {\\n const monthString = String(date.getMonth() + 1).padStart(2, \\"0\\");\\n const dayString = String(date.getDate()).padStart(2, \\"0\\");\\n const monthDay = Number(`${monthString}${dayString}`);\\n \\n return monthDay;\\n}\\n\\nfunction getDate(year, month, week, day) {\\n let firstDay = 1;\\n if (week < 0) {\\n month++;\\n firstDay--;\\n }\\n const date = new Date(year, month, (week * 7) + firstDay);\\n if (day < date.getDay()) {\\n day += 7;\\n }\\n date.setDate(date.getDate() - date.getDay() + day);\\n return date;\\n}\\n\\n/**\\n * Calculates Easter in the Gregorian/Western (Catholic and Protestant) calendar\\n * based on the algorithm by Oudin (1940) from http://www.tondering.dk/claus/cal/easter.php\\n * @returns {array} [int month, int day]\\n */\\nfunction getEasterDate(date) {\\n const year = date.getFullYear();\\n const f = Math.floor,\\n // Golden Number - 1\\n G = year % 19,\\n C = f(year / 100),\\n // related to Epact\\n H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30,\\n // number of days from 21 March to the Paschal full moon\\n I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)),\\n // weekday for the Paschal full moon\\n J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7,\\n // number of days from 21 March to the Sunday on or before the Paschal full moon\\n L = I - J,\\n month = 3 + f((L + 40) / 44),\\n day = L + 28 - 31 * f(month / 4);\\n\\n return new Date(`${month}/${day}/${year}`);\\n}\\n\\nfunction getRandomPreset(list) {\\n const activePreset = states[presetId].state;\\n const filteredList = list.filter((e) => e !== activePreset);\\n \\n return filteredList[Math.floor(Math.random() * filteredList.length)];\\n}\\n","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":662,"y":240,"wires":[["f4ab62e2.10029"],["dc4b2af1.d366a8"]]},{"id":"e01d1bd31bd9e634","type":"comment","z":"894a6b3077af9604","name":"Change holiday dates in the \\"date ranges\\" node","info":"","x":404,"y":96,"wires":[]},{"id":"5adba3095728cdab","type":"function","z":"894a6b3077af9604","name":"get next preset","func":"const states = global.get(\\"homeassistant\\").homeAssistant.states;\\nconst presetId = flow.get(\\"wledHolidayLightsPresetEntityId\\");\\nconst presets = states[presetId].attributes.options;\\nconst activePreset = states[presetId].state;\\nlet index = presets.findIndex(ele => ele === activePreset);\\nindex = index === -1 || index >= presets.length - 1 ? 0 : index +1;\\nconst name = presets[index];\\n\\nnode.status({text: name });\\n\\nmsg.name = name;\\n\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":672,"y":432,"wires":[["333ed476.51a77c"]]}]\n
[{"id":"eb756b3f.d770f8","type":"subflow","name":"Create HA Helpers","info":"","category":"","in":[{"x":84,"y":96,"wires":[{"id":"7f1493fc.1300fc"}]}],"out":[],"env":[{"name":"serverName","type":"str","value":"Home Assistant","ui":{"label":{"en-US":"HA Server Name"},"type":"input","opts":{"types":["str"]}}},{"name":"helpers","type":"json","value":"[]","ui":{"label":{"en-US":"Helpers"},"type":"input","opts":{"types":["json"]}}}],"color":"#DDAA99","status":{"x":246,"y":48,"wires":[{"id":"a66d5d93.8a5f","port":0}]}},{"id":"7f1493fc.1300fc","type":"function","z":"eb756b3f.d770f8","name":"process helpers","func":"const serverName = toCamelCase(env.get('serverName'));\\nconst haServer = global.get(\\"homeassistant\\")[serverName];\\nif(!haServer) {\\n node.error(\\"Invalid HA server name\\");\\n return;\\n}\\nconst states = haServer.states;\\nconst helpers = env.get(\\"helpers\\");\\n\\nhelpers.forEach(h => {\\n const entityId = `${h.type}.${h.id}`;\\n if(!states[entityId]) {\\n const {id, type, ...data} = h;\\n const apiData = {\\n entity: h,\\n payload: { \\n data \\n }\\n };\\n apiData.payload.data.type = `${type}/create`;\\n \\n node.send(apiData);\\n node.status({text: `Creating ${entityId}`});\\n }\\n});\\n\\nnode.done();\\n\\nfunction toCamelCase(str) {\\n return str.replace(/(?:^\\\\w|[A-Z]|\\\\b\\\\w|\\\\s+)/g, (match, index) => {\\n if (+match === 0) return '';\\n return index === 0 ? match.toLowerCase() : match.toUpperCase();\\n });\\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":224,"y":96,"wires":[["a4a0abd9.7576d8"]]},{"id":"a4a0abd9.7576d8","type":"ha-api","z":"eb756b3f.d770f8","name":"create helper","server":"","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"","dataType":"json","location":"payload","locationType":"msg","responseType":"json","x":390,"y":96,"wires":[["b76d0c56.d54b9"]]},{"id":"388bb5c7.47346a","type":"ha-api","z":"eb756b3f.d770f8","name":"rename helper entity id","server":"","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\t \\"type\\":\\"config/entity_registry/update\\",\\t \\"entity_id\\": entity.type & \\".\\" & payload.id,\\t \\"new_entity_id\\": entity.type & \\".\\" & entity.id\\t}","dataType":"jsonata","location":"payload","locationType":"msg","responseType":"json","x":788,"y":96,"wires":[[]]},{"id":"b76d0c56.d54b9","type":"switch","z":"eb756b3f.d770f8","name":"need to rename?","property":"payload.id","propertyType":"msg","rules":[{"t":"neq","v":"entity.id","vt":"msg"}],"checkall":"true","repair":false,"outputs":1,"x":570,"y":96,"wires":[["388bb5c7.47346a","3e47440a.eee2cc"]]},{"id":"a66d5d93.8a5f","type":"status","z":"eb756b3f.d770f8","name":"","scope":["7f1493fc.1300fc","858d7015.518f4","3e47440a.eee2cc"],"x":124,"y":48,"wires":[[]]},{"id":"3e47440a.eee2cc","type":"function","z":"eb756b3f.d770f8","name":"rename status","func":"const oldEntityId = `${msg.entity.type}.${msg.payload.id}`;\\nconst newEntityId = `${msg.entity.type}.${msg.entity.id}`;\\n\\nnode.status({text: `Renaming ${oldEntityId} to ${newEntityId}`});","outputs":1,"noerr":0,"initialize":"","finalize":"","x":768,"y":144,"wires":[[]]},{"id":"4762791a.4c5408","type":"function","z":"daf25abf.0203b8","name":"date ranges","func":"const now = new Date();\\nconst monthDay = getMonthDay(now);\\nconst { idLookup, nameLookup } = flow.get(\\"wledHolidayLightsPresets\\");\\nconst christmasPresets = [\\n \\"Christmas\\",\\n \\"Christmas Classic\\",\\n \\"Christmas Holly Jolly\\",\\n \\"Christmas Candy Cane\\"\\n];\\nconst states = global.get(\\"homeassistant\\").homeAssistant.states;\\nconst entityId = flow.get(\\"wledHolidayLightsEntityId\\");\\n\\nlet updateInterval = 0; // in minutes\\nlet preset;\\n\\nswitch(true) {\\n// Winter \\n case isToday(214):\\n preset = \\"Valentine\\";\\n break;\\n// President's Day changes every year, Veteran's Day & Flag Day always the same date\\n case isToday(614): // 6/14 Flag Day\\n case isToday(1111): // 11/11 Veterans Day\\n case isToday(\\"President's Day\\"):\\n case isToday(\\"Memorial Day\\"):\\n preset = \\"Red, White, and Blue\\";\\n break;\\n case isToday(317):\\n preset = \\"St Patty\\";\\n break;\\n case isToday(\\"Easter\\"):\\n preset = \\"Easter\\";\\n break;\\n// Spring\\n case isToday(504):\\n preset = \\"Starwars\\";\\n break;\\n case isToday(505):\\n preset = \\"Cinco de Mayo\\";\\n break;\\n// Summer\\n case isToday(704):\\n preset = \\"Independence Day\\";\\n break;\\n// Autumn\\n case isToday(1017, 1031):\\n preset = \\"Halloween\\";\\n break;\\n case isToday(\\"Thanksgiving Day\\"):\\n preset = \\"Autumn\\";\\n break;\\n case isToday(1231):\\n preset = \\"New Years\\";\\n break;\\n case isToday(\\"Thanksgiving Day\\", 1231):\\n case isToday(101, 106):\\n preset = getRandomPreset(christmasPresets);\\n updateInterval = 60;\\n break;\\n default:\\n const seasons = {\\n \\"winter\\": \\"Winter\\",\\n \\"spring\\": \\"Spring\\",\\n \\"summer\\": \\"Summer\\",\\n \\"autumn\\": \\"Autumn\\"\\n }\\n const currentSeason = states[\\"sensor.season\\"].state;\\n preset = seasons[currentSeason];\\n break;\\n}\\n\\nnode.status({ text: preset});\\nmsg.payload = nameLookup[preset];\\nconst secondOutput = updateInterval ? { payload: { timeout: updateInterval } } : { reset: true };\\n\\nreturn [msg, secondOutput];\\n\\nfunction isToday(start, end) {\\n if (typeof start === \\"string\\") {\\n start = getHolidayDate(now, start)\\n }\\n if (typeof end === \\"string\\") {\\n end = getHolidayDate(now, end)\\n }\\n if (end === undefined) return monthDay === start;\\n\\n return monthDay >= start && monthDay <= end;\\n}\\n\\nfunction getHolidayDate(date, holiday) {\\n holiday = holiday.toLowerCase();\\n // dates are formatted as month, week, day of the week\\n // names should be lowercase\\n const holidays = {\\n \\"martin luther king day\\": [0,2,1],\\n \\"president's day\\": [1,2,1],\\n \\"daylight savings time begins\\": [2,1,0],\\n \\"mother's day\\": [4,1,0],\\n \\"memorial day\\": [4,-1,1],\\n \\"father's day\\": [5,2,0],\\n \\"labor day\\": [8,0,1],\\n \\"columbus day\\": [9,1,1],\\n \\"daylight savings time ends\\": [10,0,0],\\n \\"thanksgiving day\\": [10,3,4]\\n };\\n let holidayDate;\\n if(holiday === \\"easter\\") {\\n holidayDate = getEasterDate(date); \\n } else {\\n const [month, week, day] = holidays[holiday];\\n holidayDate = getDate(date.getFullYear(), month, week, day);\\n }\\n \\n const monthDay = getMonthDay(holidayDate);\\n \\n return monthDay;\\n}\\n\\nfunction getMonthDay(date) {\\n const monthString = String(date.getMonth() + 1).padStart(2, \\"0\\");\\n const dayString = String(date.getDate()).padStart(2, \\"0\\");\\n const monthDay = Number(`${monthString}${dayString}`);\\n \\n return monthDay;\\n}\\n\\nfunction getDate(year, month, week, day) {\\n let firstDay = 1;\\n if (week < 0) {\\n month++;\\n firstDay--;\\n }\\n const date = new Date(year, month, (week * 7) + firstDay);\\n if (day < date.getDay()) {\\n day += 7;\\n }\\n date.setDate(date.getDate() - date.getDay() + day);\\n return date;\\n}\\n\\n/**\\n * Calculates Easter in the Gregorian/Western (Catholic and Protestant) calendar\\n * based on the algorithm by Oudin (1940) from http://www.tondering.dk/claus/cal/easter.php\\n * @returns {array} [int month, int day]\\n */\\nfunction getEasterDate(date) {\\n const year = date.getFullYear();\\n const f = Math.floor,\\n // Golden Number - 1\\n G = year % 19,\\n C = f(year / 100),\\n // related to Epact\\n H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30,\\n // number of days from 21 March to the Paschal full moon\\n I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)),\\n // weekday for the Paschal full moon\\n J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7,\\n // number of days from 21 March to the Sunday on or before the Paschal full moon\\n L = I - J,\\n month = 3 + f((L + 40) / 44),\\n day = L + 28 - 31 * f(month / 4);\\n\\n return new Date(`${month}/${day}/${year}`);\\n}\\n\\nfunction getRandomPreset(list) {\\n const activePreset = states[entityId].attributes.preset;\\n const presetName = idLookup[activePreset];\\n const filteredList = list.filter((e) => e !== presetName);\\n \\n return filteredList[Math.floor(Math.random() * filteredList.length)];\\n}\\n","outputs":2,"noerr":0,"initialize":"","finalize":"","x":470,"y":272,"wires":[["91b62e9f.fe736","dece0bc.3c215f8"],["dc570e50.22a3a"]]},{"id":"c46d93f8.1fe18","type":"switch","z":"daf25abf.0203b8","name":"dark?","property":"dark","propertyType":"flow","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":306,"y":272,"wires":[["4762791a.4c5408"],["73710f8.1582bf"]]},{"id":"fe4f63d8.5312c","type":"server-state-changed","z":"daf25abf.0203b8","name":"Demo Mode","server":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_select.holiday_lights_demo_mode","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"off","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"","forType":"num","forUnits":"minutes","ignorePrevStateNull":true,"ignorePrevStateUnknown":true,"ignorePrevStateUnavailable":true,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"x":102,"y":464,"wires":[["1a546dd7.2d8ef2"],["92c007de.c4a168"]]},{"id":"73710f8.1582bf","type":"api-call-service","z":"daf25abf.0203b8","name":"turn off holiday lights","server":"","version":1,"debugenabled":false,"service_domain":"light","service":"turn_off","entityId":"{{flow.wledHolidayLightsEntityId}}","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":500,"y":320,"wires":[[]]},{"id":"dc570e50.22a3a","type":"ha-wait-until","z":"daf25abf.0203b8","name":"loop","server":"","outputs":2,"entityId":"{{flow.wledHolidayLightsEntityId}}","entityIdFilterType":"exact","property":"state","comparator":"is","value":"off","valueType":"str","timeout":"60","timeoutType":"num","timeoutUnits":"minutes","entityLocation":"","entityLocationType":"none","checkCurrentState":false,"blockInputOverrides":false,"x":658,"y":272,"wires":[[],["4762791a.4c5408"]]},{"id":"f5422b5c.762798","type":"eztimer","z":"daf25abf.0203b8","name":"","debug":false,"autoname":"sunset - 00:00","tag":"test1","suspended":false,"sendEventsOnSuspend":false,"latLongSource":"manual","latLongHaZone":"zone.home","lat":"","lon":"","timerType":"1","startupMessage":true,"ontype":"1","ontimesun":"sunset","ontimetod":"17:00","onpropertytype":"msg","onproperty":"dark","onvaluetype":"bool","onvalue":"true","onoffset":"","onrandomoffset":0,"onsuppressrepeats":false,"offtype":"2","offtimesun":"dusk","offtimetod":"00:00","offduration":0,"offpropertytype":"msg","offproperty":"dark","offvaluetype":"bool","offvalue":"false","offoffset":0,"offrandomoffset":0,"offsuppressrepeats":false,"resend":false,"resendInterval":"1m","mon":true,"tue":true,"wed":true,"thu":true,"fri":true,"sat":true,"sun":true,"x":112,"y":272,"wires":[["c800976d.89af98"]]},{"id":"91b62e9f.fe736","type":"api-call-service","z":"daf25abf.0203b8","name":"turn on holiday lights","server":"","version":1,"debugenabled":false,"service_domain":"wled","service":"preset","entityId":"{{flow.wledHolidayLightsEntityId}}","data":"{\\"preset\\": payload}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":708,"y":224,"wires":[[]]},{"id":"78b7479f.e79dd8","type":"function","z":"daf25abf.0203b8","name":"get preset","func":"// WLED Presets\\nconst { nameLookup } = flow.get(\\"wledHolidayLightsPresets\\");\\n\\nmsg.payload = nameLookup[msg.payload];\\n\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":668,"y":176,"wires":[["cc0fc459.d7f3a8"]]},{"id":"6a3ef206.84c5fc","type":"api-call-service","z":"daf25abf.0203b8","name":"populate HA dropdown with presets","server":"","version":1,"debugenabled":false,"service_domain":"input_select","service":"set_options","entityId":"input_select.holiday_lights_presets","data":"{\\"options\\": payload}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":748,"y":128,"wires":[["d7d3f0e8.9971d"]]},{"id":"a6b79bf.256d268","type":"inject","z":"daf25abf.0203b8","name":"Setup","props":[{"p":"entityId","v":"light.holiday_lights","vt":"str"},{"p":"WLED_IP","v":"","vt":"str"}],"repeat":"3600","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":108,"y":128,"wires":[["4b308915.771578"]]},{"id":"1538c003.e770a","type":"ha-wait-until","z":"daf25abf.0203b8","name":"loop","server":"","outputs":2,"entityId":"{{flow.wledHolidayLightsEntityId}}","entityIdFilterType":"exact","property":"state","comparator":"is","value":"off","valueType":"str","timeout":"$number($entities(\\"input_number.holiday_lights_demo_mode_delay\\").state)","timeoutType":"jsonata","timeoutUnits":"seconds","entityLocation":"","entityLocationType":"none","checkCurrentState":false,"blockInputOverrides":true,"x":866,"y":512,"wires":[[],["92c007de.c4a168"]]},{"id":"92c007de.c4a168","type":"switch","z":"daf25abf.0203b8","name":"which mode?","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"cycle presets","vt":"str"},{"t":"eq","v":"cycle effects","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":278,"y":512,"wires":[["29bbe4ad.4de71c"],["7f2dc75.a04cf38"]]},{"id":"422a9fe2.d974c","type":"api-call-service","z":"daf25abf.0203b8","name":"turn on preset","server":"","version":1,"debugenabled":false,"service_domain":"wled","service":"preset","entityId":"{{flow.wledHolidayLightsEntityId}}","data":"{\\"preset\\": preset}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":688,"y":512,"wires":[["1538c003.e770a","e132c6fa.e3da18"]]},{"id":"9755a99c.54ba18","type":"api-call-service","z":"daf25abf.0203b8","name":"turn on effect","server":"","version":1,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"{{flow.wledHolidayLightsEntityId}}","data":"{\\"effect\\": effect}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":678,"y":560,"wires":[["1538c003.e770a","e132c6fa.e3da18"]]},{"id":"1a546dd7.2d8ef2","type":"change","z":"daf25abf.0203b8","name":"reset","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"},{"t":"set","p":"payload","pt":"msg","to":"info","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":258,"y":464,"wires":[["1538c003.e770a","c46d93f8.1fe18"]]},{"id":"71da85fd.de44fc","type":"api-call-service","z":"daf25abf.0203b8","name":"turn off demo mode","server":"","version":1,"debugenabled":false,"service_domain":"input_select","service":"select_option","entityId":"input_select.holiday_lights_demo_mode","data":"{\\"option\\": \\"off\\"}","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":378,"y":608,"wires":[[]]},{"id":"320d2444.2aa86c","type":"server-state-changed","z":"daf25abf.0203b8","name":"Holiday Lights turned off?","server":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"light.holiday_lights","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"off","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":140,"y":608,"wires":[["71da85fd.de44fc"],[]]},{"id":"7f2dc75.a04cf38","type":"function","z":"daf25abf.0203b8","name":"get next effect","func":"const states = global.get(\\"homeassistant\\").homeAssistant.states;\\nconst entityId = flow.get(\\"wledHolidayLightsEntityId\\");\\nconst effects = states[entityId].attributes.effect_list;\\nconst activeEffect = states[entityId].attributes.effect;\\nlet index = effects.findIndex(ele => ele === activeEffect);\\nindex = (index === -1 || index >= effects.length - 1) ? 0 : index + 1;\\n\\nnode.status({text: effects[index]});\\n\\nmsg.effect = msg.name = effects[index];\\n\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":480,"y":560,"wires":[["9755a99c.54ba18"]]},{"id":"29bbe4ad.4de71c","type":"function","z":"daf25abf.0203b8","name":"get next preset","func":"const states = global.get(\\"homeassistant\\").homeAssistant.states;\\nconst entityId = flow.get(\\"wledHolidayLightsEntityId\\");\\nconst { idLookup, nameLookup } = flow.get(\\"wledHolidayLightsPresets\\");\\nconst keys = Object.keys(idLookup);\\nconst activePreset = states[entityId].attributes.preset;\\nlet index = keys.findIndex(ele => ele == activePreset);\\nindex = index === -1 || index >= keys.length - 1 ? 0 : index +1;\\nconst name = idLookup[keys[index]];\\n\\nnode.status({text: name });\\n\\nmsg.preset = keys[index];\\nmsg.name = name;\\n\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":480,"y":512,"wires":[["422a9fe2.d974c"]]},{"id":"d89db901.9fd588","type":"function","z":"daf25abf.0203b8","name":"process data","func":"if(msg.statusCode !== 200) {\\n node.status({fill: 'red', shape: 'dot', text: `Status Code: ${msg.statusCode}`})\\n return;\\n}\\n\\nconst wledPresets = msg.payload;\\nconst idLookup = {};\\nconst nameLookup = {};\\n\\nfor(const key in wledPresets) {\\n if(Object.keys(wledPresets[key]).length > 0) {\\n const name = wledPresets[key].n;\\n idLookup[key] = name;\\n nameLookup[name] = key;\\n }\\n}\\n\\nconst presets = {\\n idLookup,\\n nameLookup,\\n names: Object.values(idLookup).sort()\\n};\\n\\nflow.set(\\"wledHolidayLightsPresets\\", presets);\\nflow.set(\\"wledHolidayLightsEntityId\\", msg.entityId);\\nnode.status({text: `${presets.names.length} Presets Processed`});\\n\\nmsg.payload = presets.names;\\n\\nreturn [msg, { payload: \\"disable\\" }];","outputs":2,"noerr":0,"initialize":"","finalize":"","x":470,"y":128,"wires":[["6a3ef206.84c5fc"],["539eea44.9b6524"]]},{"id":"d7d3f0e8.9971d","type":"change","z":"daf25abf.0203b8","name":"enable","rules":[{"t":"set","p":"payload","pt":"msg","to":"enable","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":962,"y":128,"wires":[["7c6317af.370198"]]},{"id":"539eea44.9b6524","type":"trigger-state","z":"daf25abf.0203b8","name":"Preset Selector","server":"","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityid":"input_select.holiday_lights_presets","entityidfiltertype":"exact","debugenabled":false,"constraints":[{"targetType":"this_entity","targetValue":"","propertyType":"current_state","comparatorType":"is_not","comparatorValueDatatype":"prevEntity","comparatorValue":"state","propertyValue":"new_state.state"}],"outputs":2,"customoutputs":[],"outputinitially":false,"state_type":"str","x":480,"y":176,"wires":[["78b7479f.e79dd8"],[]]},{"id":"e132c6fa.e3da18","type":"ha-entity","z":"daf25abf.0203b8","name":"current demo","server":"","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"Holiday Lights Demo Current"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"name","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","x":886,"y":560,"wires":[[]]},{"id":"a3f0ee9.6fd551","type":"server-events","z":"daf25abf.0203b8","name":"HA Client Events","server":"","event_type":"home_assistant_client","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"x":112,"y":176,"wires":[["6c13e445.ca929c"]]},{"id":"6c13e445.ca929c","type":"function","z":"daf25abf.0203b8","name":"HA running?","func":"if(msg.payload !== \\"running\\") return;\\n\\nconst { names } = flow.get(\\"wledHolidayLightsPresets\\");\\n\\nmsg.payload = names;\\n\\nreturn [msg, { payload: \\"disable\\" }];","outputs":2,"noerr":0,"initialize":"","finalize":"","x":278,"y":176,"wires":[["6a3ef206.84c5fc"],["539eea44.9b6524"]]},{"id":"afc3f7e9.88ac98","type":"comment","z":"daf25abf.0203b8","name":"Setup","info":"","x":82,"y":32,"wires":[]},{"id":"5b75e78b.d9d608","type":"comment","z":"daf25abf.0203b8","name":"Scheduler","info":"","x":92,"y":224,"wires":[]},{"id":"cc0fc459.d7f3a8","type":"link out","z":"daf25abf.0203b8","name":"turn on holiday lights","links":["d830a00d.1d744"],"x":852,"y":176,"wires":[],"l":true},{"id":"d830a00d.1d744","type":"link in","z":"daf25abf.0203b8","name":"Holiday Lights On","links":["cc0fc459.d7f3a8"],"x":527,"y":224,"wires":[["91b62e9f.fe736"]]},{"id":"215b93f8.a1883c","type":"comment","z":"daf25abf.0203b8","name":"Demo Mode","info":"","x":102,"y":416,"wires":[]},{"id":"4b308915.771578","type":"http request","z":"daf25abf.0203b8","name":"get presets","method":"GET","ret":"obj","paytoqs":"ignore","url":"http://{{WLED_IP}}/presets.json","tls":"","persist":false,"proxy":"","authType":"","x":278,"y":128,"wires":[["d89db901.9fd588"]]},{"id":"7c6317af.370198","type":"delay","z":"daf25abf.0203b8","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1100,"y":128,"wires":[["539eea44.9b6524"]]},{"id":"aea9da7f.af56a8","type":"ha-entity","z":"daf25abf.0203b8","name":"current preset","server":"","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"Holiday Lights Current Preset"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","x":480,"y":368,"wires":[[]]},{"id":"4b02330f.bc08fc","type":"function","z":"daf25abf.0203b8","name":"format","func":"const { idLookup } = flow.get(\\"wledHolidayLightsPresets\\");\\nconst id = msg.data.new_state.attributes.preset || 0;\\nconst presetName = idLookup[id] || \\"none\\";\\n\\nmsg.payload = msg.payload === \\"on\\" ? presetName : \\"none\\";\\n\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":258,"y":368,"wires":[["aea9da7f.af56a8"]]},{"id":"a3f37acd.911a88","type":"server-state-changed","z":"daf25abf.0203b8","name":"Preset Updated","server":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"light.holiday_lights","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":false,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":112,"y":368,"wires":[["4b02330f.bc08fc"]]},{"id":"dece0bc.3c215f8","type":"debug","z":"daf25abf.0203b8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":752,"y":336,"wires":[]},{"id":"c800976d.89af98","type":"change","z":"daf25abf.0203b8","name":"","rules":[{"t":"set","p":"dark","pt":"flow","to":"dark","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":223,"y":272,"wires":[["c46d93f8.1fe18"]],"l":false},{"id":"b1ec00ad.8c8f7","type":"subflow:eb756b3f.d770f8","z":"daf25abf.0203b8","name":"","env":[{"name":"helpers","value":"[{\\"id\\":\\"holiday_lights_demo_mode_delay\\",\\"type\\":\\"input_number\\",\\"name\\":\\"Cycle Delay\\",\\"initial\\":30,\\"min\\":-5,\\"max\\":300,\\"step\\":5,\\"icon\\":\\"mdi:timer-sand\\"},{\\"id\\":\\"holiday_lights_demo_mode\\",\\"type\\":\\"input_select\\",\\"name\\":\\"Mode\\",\\"options\\":[\\"off\\",\\"cycle effects\\",\\"cycle presets\\"],\\"initial\\":\\"off\\",\\"icon\\":\\"mdi:animation\\"},{\\"id\\":\\"holiday_lights_presets\\",\\"type\\":\\"input_select\\",\\"name\\":\\"Presets\\",\\"options\\":[\\"none\\"],\\"icon\\":\\"mdi:animation\\"}]","type":"json"}],"x":346,"y":80,"wires":[]},{"id":"297d2a12.388f86","type":"inject","z":"daf25abf.0203b8","name":"Create HA elements","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":138,"y":80,"wires":[["b1ec00ad.8c8f7"]]}]\n
The season integration is used to determine the current season and set a WLED preset if no other holiday range is defined.
The two entity nodes will require the Node-RED custom integration but the flow will function fine without it. The UI just won't have the updated current preset and demo effect displayed.
The flow has been modified so that it should run pretty much out of the box after changing the entity id for the light and IP for the WLED controller.
Edit the change node, set entity ids here, to edit the Home Assistant entity id of the holiday lights and the select entity for presets. The entity of your WLED light needs to be changed in two places.
Holidays can be either a single day or a range of dates. If no holiday is defined for the current day it will default to the season preset WINTER
, SPRING
, SUMMER
, and AUTUMN
.
In the date ranges
node, a lot is going on but the main thing is the switch statement that determines which preset to use.
The isToday
function can take two arguments but only the first one is required which is the start date of the holiday. The second argument is the end date of the holiday. Both of the arguments can take a number in the format of MONTHDAY or a holiday name defined in the getHolidayDate
function on line #92.
Lines 16 - 72
switch (true) {
+ // Winter
+ case isToday(214):
+ preset = "Valentine";
+ break;
+ // President's Day changes every year, Veteran's Day & Flag Day always the same date
+ case isToday(614): // 6/14 Flag Day
+ case isToday(1111): // 11/11 Veterans Day
+ case isToday("President's Day"):
+ case isToday("Memorial Day"):
+ preset = "Red, White, and Blue";
+ break;
+ case isToday(317):
+ preset = "St Patty";
+ break;
+ case isToday("Easter"):
+ preset = "Easter";
+ break;
+ // Spring
+ case isToday(504):
+ preset = "Starwars";
+ break;
+ case isToday(505):
+ preset = "Cinco de Mayo";
+ break;
+ // Summer
+ case isToday(704):
+ preset = "Independence Day";
+ break;
+ // Autumn
+ case isToday(1017, 1031):
+ preset = "Halloween";
+ break;
+ case isToday("Thanksgiving Day"):
+ preset = "Autumn";
+ break;
+ case isToday(1231):
+ preset = "New Years";
+ break;
+ case isToday("Thanksgiving Day", 1231):
+ case isToday(101, 106):
+ const activePreset = states[entityId].attributes.preset;
+ const presetName = idLookup[activePreset];
+ preset = getRandomPreset(christmasPresets.filter((e) => e !== presetName));
+ updateInterval = 60;
+ break;
+ default:
+ const seasons = {
+ winter: "Winter",
+ spring: "Spring",
+ summer: "Summer",
+ autumn: "Autumn",
+ };
+ const currentSeason = states["sensor.season"].state;
+ preset = seasons[currentSeason];
+ break;
+}
+
Three entities will need to be created in Home Assistant two input_select and an input_number. The helpers configuration, Configuration > Helpers
, can be used or writing them in YAML.
input_number:
+ holiday_lights_demo_mode_delay:
+ name: Cycle Delay
+ initial: 30
+ min: -5
+ max: 300
+ step: 5
+ icon: "mdi:timer-sand"
+
input_select:
+ holiday_lights_demo_mode:
+ name: Mode
+ options:
+ - off
+ - cycle effects
+ - cycle presets
+ initial: off
+ icon: "mdi:animation"
+
title: Holiday Lights
+type: entities
+entities:
+ - entity: light.holiday_lights
+ - entity: select.holiday_lights_presets
+ - type: section
+ label: Demo Mode
+ - type: conditional
+ conditions:
+ - entity: input_select.holiday_lights_demo_mode
+ state_not: "off"
+ row:
+ entity: sensor.holiday_lights_demo_current
+ - entity: input_select.holiday_lights_demo_mode
+ - entity: input_number.holiday_lights_demo_mode_delay
+
The Scrubber tool cleans up your exported flow by removing specific references and properties, making it more shareable and user-friendly.
lat
, lon
, latitude
, and longitude
properties from all nodes.JSONata is a functional declarative language, designed to work with JSON objects. It is built-in within Node-RED and is available in standard nodes where you see the J: expression option, for example in the Inject Node.
payload
will evaluate as the message payload value, and topic
as the topic value.Warning
JSONata is very different to Mustache templates, and the use of {{msg.payload}}
will not work as you might expect.
In the Home Assistant nodes, JSONata can be used to set entity states, set output property values, generate UI parameters, or as conditional tests (both generating the test value, and as an evaluated predicate expression).
There are several additional Home Assistant functions added for use in JSONata expressions, and these can only be used within the Home Assistant nodes.
$entity()
returns the entity that triggered the node
$prevEntity()
returns the previous state entity if the node is an event node
$areaDevices(areaId)
returns all devices associated with a specific area ID.
$areaEntities(areaId)
returns all entities associated with a specific area ID.
$areas(lookup)
returns an area based on a provided lookup value, or all areas if no lookup value is provided. The lookup value can be an area ID, an entity ID, or a device ID.
$deviceEntities(device_id)
returns all entities associated with a specific device.
$device(lookup)
returns a device based on a provided lookup value. The lookup value can be an entity ID or a device name.
$entities()
returns all entities in the cache
$entities(entity_id)
returns a single entity from cache matching the given entity_id
$sampleSize(collection, [n=1])
https://lodash.com/docs/#sampleSize
Gets n random elements at unique keys from collection up to the size of collection.
$randomNumber([lower=0], [upper=1], [floating])
https://lodash.com/docs/#random
Produces a random number between the inclusive lower and upper bounds. If only one argument is provided a number between 0 and the given number is returned. If floating is true, or either lower or upper are floats, a floating-point number is returned instead of an integer.
Also see:
',7))])}const f=l(p,[["render",h],["__file","index.html.vue"]]),g=JSON.parse('{"path":"/guide/jsonata/","title":"JSONata","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Examples of using JSONata in the Home Assistant Nodes:","slug":"examples-of-using-jsonata-in-the-home-assistant-nodes","link":"#examples-of-using-jsonata-in-the-home-assistant-nodes","children":[]},{"level":2,"title":"Home Assistant functions","slug":"home-assistant-functions","link":"#home-assistant-functions","children":[]},{"level":2,"title":"Exposed Lodash functions","slug":"exposed-lodash-functions","link":"#exposed-lodash-functions","children":[]}],"git":{"updatedTime":1724305054000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":1},{"name":"jason","email":"37859597+zachowj@users.noreply.github.com","commits":1}]},"filePathRelative":"guide/jsonata/index.md"}');export{f as comp,g as data}; diff --git a/assets/index.html-DM7m0Nlu.js b/assets/index.html-DM7m0Nlu.js new file mode 100644 index 0000000000..439747e108 --- /dev/null +++ b/assets/index.html-DM7m0Nlu.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as s}from"./app-CefLgoES.js";const i={};function o(a,n){return s(),t("div")}const l=e(i,[["render",o],["__file","index.html.vue"]]),c=JSON.parse(`{"path":"/","title":"","lang":"en-US","frontmatter":{"home":true,"heroImage":null,"heroText":"Home Assistant nodes for Node-RED","tagline":"A suite of nodes that seamlessly integrates Home Assistant with Node-RED.","actions":[{"text":"Get Started","link":"/guide/","type":"primary"},{"text":"Nodes","link":"/node/","type":"secondary"}],"features":[{"title":"Seamless Integration","details":"Easily connect Home Assistant entities and services with Node-RED flows."},{"title":"Real-time Interaction","details":"Trigger Node-RED workflows based on Home Assistant events, states, and services."},{"title":"Custom Automation","details":"Build custom automations using a visual interface, combining Home Assistant's capabilities with Node-RED's powerful logic nodes."}]},"headers":[],"git":{"updatedTime":1723606857000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":1}]},"filePathRelative":"index.md"}`);export{l as comp,c as data}; diff --git a/assets/index.html-DTYPDoQS.js b/assets/index.html-DTYPDoQS.js new file mode 100644 index 0000000000..16742210a6 --- /dev/null +++ b/assets/index.html-DTYPDoQS.js @@ -0,0 +1 @@ +import{_ as t,c as n,e as i,o as a}from"./app-CefLgoES.js";const s="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbYAAABrCAYAAAAb3qc1AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAgGSURBVHhe7d3BixvnGQfg/mGBJIfgSyCX7CGEQHKK8y/s5hD2kIChUHyM91DINZccvOhgl7bUNyPWbA6h7KGHQuoNrkmMsFOr+apPGq1GI438rbXdnXn3eeCFyDOaGfkd3p++sbP+XQKAQAQbAKEINgBCEWwAhCLYAAhFsAEQimADIBTBBkAogg2AUAQbAKEIto777cmT9OL27TT69NP0yxtvKKVU+Bp98kl6/uWXafz999UkPB/B1mH/OTxMv9y4sbbxSil1HerFN99UE7GcYOuo//74Y/r57bfXNloppa5TjYfDajKWEWwd9fMXX6xtsFJKXbf698cfV5OxjGDrqH+9995SY//y4YfpD7u7aX9/XymlwtbvP/88fXvz5tL8e/rmm9VkLCPYOqre1Fz3vvsuPX36tNoKENOzZ8/S0dHRygw8D8HWUc2mPn/+vNoCEF9zBp6HYOuobZoK0HfbzEDB1lHbNBWg77aZgYKto0qbOhqN0snJSRoOh+nhw4dKKdWryrMrz7A8y+pKZ+A6gq2jSpqab4R8U5yenqbxeFz9KkB/5NmVZ1ieZfVwK5mBbQRbR5U0NX/LyTcEQN/lWZZn2lzJDGwj2DqqpKn5G46VGhBBnmV5ps2VzMA2gq2jSpqan08DRFGfaSUzsI1g66iSpgo2IBLBFlxJUwUbEIlgC66kqYINiESwBVfSVMEGRCLYgitpqmADIhFswZU0VbABkVxSsD1Og92dtLOzrg7ScbVX0/GdyfY7bVu38/hwL+3sDiZXNnE6SHs7e2kQ8P9RLmmqYAMiudRgO3hUvewAwbYg2IBIuhNsOVwmQTPIq7S8kpus1FZWbI8OllZ79ePlffcOB+mgZftkj9q2vXRwZ32wra4SZ9e+dzjdc9n0fQdpkENyfuz5MSvT452dd1K1Y8+u+bi2mq1dQ7X/8nkbK9/GudYpaapgAyLpVrA1BvlSyExDrfbYsrHKmoXB4vV0RXa2/yzUzo5dnWvtim3teVoel86PcxZWy+eZXlM9fKpgnv8+NK95Hmhn17l0LasBu/T706KkqYINiORSg+1spXFWi6HeDKpsMbjXB+M0vKrBvjLkN4XVRPujyFk4zc9VP8eKjde8zvKxV/Zduc68f+0zrKzQattblDRVsAGRdGzF1hYSs0BYDcZJVcGwKdiWQmyuHhSNc+dj1VddrdddGGyz1ePimjcG29J1LoKreYxFLZ+/qaSpgg2IpFfBtun9KyGxRbAttuXztjyGzF4RbIswmh9j+XOcO9ian6FASVMFGxBJT4Jt9v76ny81bQq2aWAUP4rMqkA5nLyvfsymgmte/syvH2zrPkOJkqYKNiCSngTbxHSw17cvh93GYGvsO9s22b812KrjvWKVWBJsizCeva4f81zBNn9/c/9XhF1JUwUbEEl/gm2i+edM9RXc5mDLZqul2Xurv6K/IdiKVkivuubp9vk58+dfDqfzBVtW/wy5Xr2CK2mqYAMiuaRg66EcMvXQ6amSpgo2IBLB1iKvpjauMHuipKmCDYhEsDXNHx8GWK1lJU0VbEAkgi24kqYKNiASwRZcSVMFGxCJYAuupKmCDYhEsAVX0lTBBkQi2IIraapgAyIRbMGVNFWwAZEItuBKmjocDtN4PK5eAfRXnmV5ps2VzMA2gq2jSpp6cnKSTk/rP08MoJ/yLMszba5kBrYRbB1V0tTRaDT9hpNvCCs3oI/y7MozLM+yPNPmSmZgG8HWUaVNzTdC/paTb4r8fFoppfpUeXblGVYPtax0Bq4j2Dpqm6YC9N02M1CwddQ2TQXou21moGDrqJWm/vprtQUgvpUZeA6CraN+euutpaa+ePCg2gIQ229PnizNv1znIdg66tH77680VimlrmP948aNajKWEWwd9eevvlrbYKWUum71t88+qyZjGcHWUT/88EN68MEHa5uslFLXpf7+7rvpr/fuVZOxjGDrsLt376Zvb95M/3znnbUNV0qpqJX/nsGfPvoo/fHrr9PLly+rqVhGsHXc0dFRun37dtrf31dKqWtTt27dSvfv3z93qGWCDYBQBBsAoQg2AEIRbD3nhyArpfpcbT8EeRuCrcfyjZBvCv9sDdBXbf9szTYEW4/lbzn5hgDouzzL8ky7CIKtx/I3HCs1III8y/JMuwiCrcfy82mAKC5qpgm2HhNsQCSCDcEGhCLYEGxAKIINwQaEItgQbEAogg3BBoRyScH2OA12d9Le4ePqNV0i2IBIBBuCDQjlCoPtOB3s7KSded05rn49m+2/dtvpIO3tHKTB4d5i++5g8g5el2ADIrmiYNv0ugq1szBrvJ4GW337LCCtBl+fYAMiuZpge3QwWWkdTCJpjXXbpmG2lwb55/TW/7tyfKcedJyXYAMiuZJge5wfI7Y8Ply/La/KBNv/i2ADIhFsCDYglCsJNo8iu0WwAZFcTbCtvK6H02zbIqgarwXbhRNsQCRXFGxZ46/7Lz1+rMJsvq0eWoLtwgk2IJJLCja6TLABkQg2BBsQimBDsAGhCDYEGxCKYEOwAaEINgQbEIpgQ7ABoQg2BBsQimBDsAGhCDbScDhM4/G4egXQX3mW5Zl2EQRbj52cnKTT09rPKAPoqTzL8ky7CIKtx0aj0fQbTr4hrNyAPsqzK8+wPMvyTLsIgq3n8o2Qv+XkmyI/n1ZKqT5Vnl15hl1UqGWCDYBQBBsAoQg2AEIRbACEItgACEWwARCKYAMgFMEGQCAp/Q+4bDo1V84gbAAAAABJRU5ErkJggg==",r={};function o(A,e){return a(),n("div",null,e[0]||(e[0]=[i('To install this custom integration, you can use HACS or follow the manual installation instructions provided in the README.
When configuring an entity in Node-RED, consider filling in the "friendly name" field before your first deployment. This will create the entity with the specified entity_id
. If left blank, it will default to sensor.nodered_<node id>
, where <node id>
is a unique identifier assigned by Node-RED. The entity_id
can be changed later in Home Assistant.
Ensure you have Node-RED installed and running. If not, follow the installation guide.
node-red-contrib-home-assistant-websocket
.$ cd ~/.node-red
+$ npm install node-red-contrib-home-assistant-websocket
+# Restart Node-RED
+
Find the add-on here: Home Assistant Add-on.
Add an Events: all node to your workspace and open its settings.
Click the pencil icon next to the Server field.
Locate your local Home Assistant instance or manually enter the Base URL.
Enter an Access Token in the server configuration, then save the settings and deploy the changes.
After deploying, you should see a green status box below the Events: all node, indicating connection (e.g., "connected" or "state_changed"). If the box is open and shows "connecting" or "disconnected," double-check the Base URL or access token.
To prevent reloading all flows on deployment, change the deploy type to Modified Flows or Modified Nodes. This only reloads the flows you've modified.
If new entities aren't appearing in the autocomplete results, try unchecking Cache Autocomplete Results in the server configuration and restart Node-RED.
WARNING
The user creating the access token must belong to the administrator group for this package to function correctly.
Go to your Home Assistant home page. In the bottom left corner, click on the blue circle with your initial (e.g., "J").
Scroll to the bottom of the page and click Create Token.
Name the token for easy identification.
Copy the generated access token into Node-RED.
This is a simple introduction highlighting the key aspects of this language. Full details can be found at the online JSONata documentation
JSONata code can be written as either a single line expression, or as multiple line expressions in a code block ( )
. Expressions are evaluated against a _JSON object. In Node-RED this object is the node input message.
A single line expression is evaluated left to right by operator, and the result returned is the final evaluation.
payload
will return the value contained in msg.payload
msg.payload.foo.bar
.
is the mapping (iteration) operator[ ]
is the filter (selection) operator{ }
is the reduce (aggregation) operator^( )
is the sorting (ordering) operatorContext: At the start of a line expression, the context is the entire top level message. Each path operator takes input from the current context, with the final result of each operation passed as output context to the next operator. Available context therefore changes as the evaluation proceeds. The current context at a specific point can be referred to using $
. An additional operator $$
can be used to refer to the top level context at any point, and %
can be used to refer to the parent context (back one level) but only where this is possible to determine.
.
Syntax can be considered as <sequence-list> . <expression>
.
The current input-context on the left hand side of the operator is parsed as a sequence or ordered list of items (for an array this is a list of the array elements, for an object or for a primitive this is usually a singleton list). Each item in this list is taken in sequence as the evaluation-context, and evaluated by the expression formed from the right hand side of the operator. The result sequence is then collated into an output array (from an input list) or returned as a singleton (from an input singleton, or where the input sequence reduces to a singleton).
As an example, $type(payload)
returns the type of the message payload, however payload.$type($)
returns an array of the type of each element within payload. For an array of numbers, this would be ["number", "number", "number"]
. The right hand side, $type()
is a JSONata function requiring a parameter. For many functions one or more parameters are optional, and where omitted they take the current (evaluation) context $
instead. In this case, the parameter is not optional, hence the use of $
for the immediate context (each array element in turn).
Index operator #
: Applied with the mapping operator, the #
index operator can be used to capture the iteration index value for use later in the expression. The operator must be followed by a variable, for example $i
, and this variable is made available for the remaining line expression. If payload
is an array of three items, then payload#$i.($i+2)
would return [2, 3, 4]
. The use of ()
here is required since JSONata is left-associative and the parentheses are required to force the correct order of evaluation.
Wildcards and array flattening: Within a path expression, *
can be used to select all fields in an object, and **
is a descendant wildcard for any depth of nesting. Thus payload.**.time
would return an array of the values of all nested time keys.
The path expression may generate an array of arrays for the evaluation sequence list. In this case, the list will always first be flattened to just one array. This flattening also occurs for output when the result is an arrays of arrays.
If payload is an array of objects then payload.$keys()
returns an array, listing the keys names for each array object. For an array of nested objects, payload.**.$keys()
returns a flattened array of all keys. To remove duplicates, the $distinct()
function can be used, thus $distinct(payload.**.$keys())
returns an array of every key in message payload to any depth.
[ ]
The [ ]
operator has several applications.
payload[0]
returns the first item in the array, [-1]
the last item. Note that the index can be an integer, or any number expression that returns or rounds down to an integer.payload[0, -1]
returns the first and last items from payload array.payload[time<"12:00"]
where time
is a key value within payload
object.[a..b]
, such as [0..3]
this syntax generates an array of integers between the two given integer values. Note that, as in all cases in JSONata, the defining integer values can be either literals, or expressions that evaluate to an integer. To generate an array of integers to be used as a filter requires [[a..b]]
.["one", "two", "three"]
is an array. The simple form []
may also be used at any point in a line expression to force singleton output to an array, as in payload.data[]
.JSONata does not inherently regard singleton values as fundamentally different to arrays. Although a singleton is a value without enclosing structure, path expressions that require an array input can accept a singleton, which is treated as an array of one item. In some situations, particularly with path input or output list flattening, it may be necessary to add an additional []
to the expression to ensure correct evaluation, and where an array of arrays is required in the result.
No result means nothing is returned: One of the more unusual aspects of JSONata is the behaviour when an expression evaluates to no discernible result. Simply put, JSONata will neither return a value, nor null
nor an error message where there is no result from an expression. Mistyping paylaod
or requesting an array index payload[100]
for an array of only 25 items will return nothing. This can make writing, testing and debugging JSONata code challenging, but it eliminates the need to code for exception conditions - declare what you want, and nothing else will be returned.
The filter operator binds more strongly than the mapping operator. This means that payload.array[0]
is returned as an array of the first items in each payload.array
and not the first item in the payload.array
array result. Use (payload.array)[0]
to force evaluation in a different order.
{ }
This operator can only be used once in an expression line, and should ideally be at the very end. Note that { }
is a valid empty object, and {"key": "value"}
is a one-field object as expected. Both the key as well as the value can be any expression, thus{last_updated: payload.state}
will generate a valid object as long as last_updated
results in a string value. Naturally values may be an valid JSON type, including objects and arrays.
It is worth noting that in a key-value pair where the value is an expression that returns nothing, the key-value pair will not be returned in the result.
Thus {"key": "value", "result": [1, 2][4]}
will just return {"key": "value"}
^( )
Applies to arrays, and will sort the array by one or more expressions, where the expression results in either a string or an number. Sorting is ascending <
by default, but can be modified to descending >
. Where payload
is an array of objects, then payload^(>field, second)
will sort the array, by the field
value, in descending order, and for equal values, additionally by the second
field in ascending order. To sort an array of primitives such as strings, use the context variable $
. If payload
is an object, $keys(payload)^($)
returns an array of the object keys sorted into alphabetic order.
JSONata is built on JavaScript, so much of the syntax and basic functions will be familiar. The usual mathematical operators apply.
Comparison operators are as expected, and also the in
inclusion operator where "b" in ["a", "b", "c"]
returns true
.
Boolean operators are the usual and
and or
, but not is provided as a function $not()
.
String concatenation operator is &
, which provides the only situation where type is cast. Numbers, Boolean, arrays and objects are all stringified as required. Hence payload & ""
will turn an object to a stringified equivalent.
Conditional testing is a ternary operator, syntax as <test expression> ? <true expression> : <false expression>
which returns the result of either the true or false expression. False expression is optional, and if omitted a false test expression result will return nothing. Nested conditionals evaluate as expected although the use of parentheses can help visual clarity or to change order.
JSONata permits the use of variables, although some care needs to be exercised in their use. Variables are named as $name
and assigned using $name:= <expression>
. Scope is from first declaration when assigned, and lasts for the enclosing line, code block ( )
, or nested blocks.
Variables in JSONata are not values held in memory-allocated space but rather bindings. In effect this means that variables can only be assigned, then referred to. Once assigned, further references to the variable are replaced at evaluation with the originally evaluated value at assignment. In practice, this means that
:=
assignment can only be a variable $name
, and therefore-$a:=3
returns 3Variable use should be avoided in single line expressions and only used in code blocks. Whilst expression lines are evaluated in order of the operators, left to right, JSONata execution is asynchronous and different parts of the expression may be prepared in any order. Any later reference to a variable in an expression may be evaluated prior to an earlier assignment in the same line. It also worth noting that, all variable and object field references are evaluated at the very start of statement execution. Nothing done during the expression execution can reliably update or change these values. For example: [$a:=3 .. $b:=6].{"result": $a:=$a+$b, "next": $a}
may or may not work as expected.
JSONata has a wide range of inbuilt functions (see documentation) for manipulating:
There are also several higher-order functions, such as $map()
and $reduce()
which apply a mapping function over an array, and similarly with result accumulation.
Functions can be nested, so that
$substringAfter($substringBefore("2024-01-10T10:32:14.0324","."),"T")
returns just the time part of the string.
The function chaining operator ~>
allows for greater visual clarity
$substringBefore("2024-01-10T10:32:14.0324",".") ~> $substringAfter("T")
.
This works with the function chain passing the result as context, and subsequent functions permitting an optional parameter to be substituted by this context.
Additional features: Regex is available in JSONata. The usual regex format /regular_expression/flags
applies, and this can be used in the functions $match(), $contains(), $split() and $replace(). In addition, a regex expression can be used as a function.
$join($entities().*[state = "on" and entity_id ~> /^light|^switch/].attributes.friendly_name, ", ")
$entities()
returns an object containing all Home Assistant entities, each key being an entity id, and value being an object with details of that entity.*
maps over the object matching all keys, therefore returning an array of all entities[]
filters out the entities that match the predicate expression, using state = "on"
to match all entities with state 'on'entity_id ~> /^light|^switch/
generates a function from the regex expression "match any string containing 'light' or 'switch'". Each entity_id
is passed to this function, whichattributes.friendly_name
is picked out, resulting in an array of friendly names for all lights / switches left on$join()
function joins each item in the array, using ", " as the separator, and returning a stringNote that the $entities()
function is a special function added just to the WebSocket nodes.
Note that entity id names are made up from a domain (platform / integration) and name, separated by .
. This is potentially confusing as the object key becomes person.george
. Where object keys contain spaces and special characters, the payload.'person.george'
referencing syntax has to be used.
User defined functions: User functions can be easily defined and assigned to a variable name using the syntax
$fname:= function(arg){"Result Is " & arg}
.
Once defined the function can be called using $fname(parmValue)
from later within the enclosing code block.
To breakout from the constraints of just a single line expression, JSONata permits the use of (expression; expression)
code blocks. These start and end with ( )
and contain one or more line expressions terminated by ;
. The return value of the block is the return of the execution result of the very last line.
Using code blocks permits a much richer coding experience, allowing the use of variable and function definitions.
The following expression takes an object in payload and re-sorts the object keys alphabetically.
$keys(payload)^($).{$:$lookup($$.payload,$)}~>$merge()
This works by
$keys()
to obtain an array of key name strings from the object in payload $lookup(object, key)
to find the value of this key, as the new object value $$.payload
refers back to the $$
top level context to reach payload
, and $
the current context (the key in each iteration)$merge()
to merge this array of objects back into one objectThe simplicity comes from the power of JSONata to map, filter, sort and aggregate, however compared to most procedural languages it is a new way of thinking. Since the JSON object and variables cannot be modified, there is the question of how to, for example, change just one element in an array. The expression $array[2]:=10
will return an error since the left hand side of assignment can only be a variable and not an expression. To achieve this requires a declaration of the new array, built as follows.
(
+ $array:=[1, 2, 3, 4, 5, 6];
+ $newval:=10;
+ $index:=2;
+ $append($array[[0..$index-1]], $newval)~>$append($array[[$index+1..$count($array)]])
+ )
+
Here we declare the result to be the first part of the array up to the new value, the new value, then the remainder of the array. You may note that $count($array)
is incorrect being one greater than the end index, however as JSONata does not complain when accessing beyond the array length, this point can be relaxed.
It is worth noting that it is not necessary to refer to the input JSON document. The following expression generates an array of 24 objects. ([1..24])#$pos.{"index": $, "hour": $formatInteger($pos, "09") & ":00"}
Declarative functional programming for objects: Given an object in msg.payload, how then do we change just one field value, since payload.field:="new value"
is not permitted? The answer is that we have to write a "function" that declares - that is, returns - the result we require, with the idea that JSONata expressions are functions applied to the input JSON object.
The $spread()
function takes a JSON object and returns an array of objects, each with a single key:value pair. The $merge()
function reverses this, taking an array of objects and combining back into one object. A particular feature of this function is that, where key values are duplicated, the end result contains only the last such key to be found. Therefore, the approach to take is to spread the object, append the replacement key:value as an object to the end of this array, then to merge the array back together.
(
+ $object:={"first": 1, "second": 2, "third": 3};
+ $replaceKey:= "second";
+ $replaceVal:= 15;
+ $spread($object) ~> $append({$replaceKey: $replaceVal}) ~> $merge()
+ )
+
Deeply nested objects are much more complex, as the entire tree has to be unpicked and rebuilt in order. Fortunately there is a special function that can perform transformation on an object for us. $object ~> | $ | {"second": 15} |
JSONata has minimal error management. Errors occur at compile time when editing due to incorrect syntax, and will show as red line box in the UI input field, as well as a red triangle on the node. Common reasons for this are missing:
&
between string concatenation:
in conditional testing, or in object construction;
line terminatorsErrors at run time are mostly limited to passing nothing or an incorrect type as parameter to a function. The $number()
function accepts a string and returns a JSON number, but only where the input can be correctly parsed. JSON numbers must only contain numbers throughout. Passing "12,34a"
will generate a fatal error, and the entire expression evaluation will be aborted.
Where functions explicitly require an array, passing singletons will generate an error, and thus the use of [ ]
to either construct arrays or cast singletons to an array may be required, for example $append(payload.array, [$additional])
. Where necessary the use of $exits()
to check for object fields, and the use of $type()
can help.
JSONata can be directly entered into any node where the UI field entry type is J: expression. The Node-RED editor permits expansion of the simple box using the '...' field at the end of the line. This editor provides more space, the ability to select and insert JSONata functions from a pick list, a formatting option, and a tab for testing. However, this can be limited and the try JSONata website is easier to use.
If using the JSONata Exerciser website, use a debug node set to output the "complete message", copy this entire object and paste over the left hand side JSON input object (there is a useful formatter option). Test JSONata code can then be written in the top right hand window, and the result appears immediately below. Useful output messages indicate where there is an error in the input JSON, where no result is generated, or where an error has occurred.
Note that there are special Node-RED functions, $env()
is one, that can be tested in the Node-RED editor, but not in try JSONata. The WebSocket functions, such as $entity()
cannot be tested in either editor.
JSONata can almost completely replace JavaScript in function nodes. However, the simplicity and power of the declarative language is at the expense of efficiency. Arrays and files with more than, say, 500 elements or lines will require significant CPU processing as to temporarily halt Node-RED and potentially Home Assistant. The main barrier to use is more likely to be the time and effort required to generate code, since it can be challenging to think in terms of a functional declaration, the outcome of which is the required result, rather than the more usual approach of designing and writing an algorithm to prescribe how to obtain the required result.
Good luck.
`,82)]))}const c=t(r,[["render",i],["__file","jsonata-primer.html.vue"]]),d=JSON.parse('{"path":"/guide/jsonata/jsonata-primer.html","title":"JSONata primer","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"The mapping (iteration) operator .","slug":"the-mapping-iteration-operator","link":"#the-mapping-iteration-operator","children":[]},{"level":2,"title":"The filter (selection) operator [ ]","slug":"the-filter-selection-operator","link":"#the-filter-selection-operator","children":[]},{"level":2,"title":"The reduce (aggregation) operator { }","slug":"the-reduce-aggregation-operator","link":"#the-reduce-aggregation-operator","children":[]},{"level":2,"title":"The order-by (sort) operator ^( )","slug":"the-order-by-sort-operator","link":"#the-order-by-sort-operator","children":[]},{"level":2,"title":"Other Operators","slug":"other-operators","link":"#other-operators","children":[]},{"level":2,"title":"Variables","slug":"variables","link":"#variables","children":[]},{"level":2,"title":"Functions","slug":"functions","link":"#functions","children":[]},{"level":2,"title":"Code blocks","slug":"code-blocks","link":"#code-blocks","children":[]},{"level":2,"title":"How it works in practice","slug":"how-it-works-in-practice","link":"#how-it-works-in-practice","children":[]},{"level":2,"title":"Errors and error handling","slug":"errors-and-error-handling","link":"#errors-and-error-handling","children":[]},{"level":2,"title":"Writing JSONata code","slug":"writing-jsonata-code","link":"#writing-jsonata-code","children":[]},{"level":2,"title":"What JSONata cannot do","slug":"what-jsonata-cannot-do","link":"#what-jsonata-cannot-do","children":[]}],"git":{"updatedTime":1719788083000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":1}]},"filePathRelative":"guide/jsonata/jsonata-primer.md"}');export{c as comp,d as data}; diff --git a/assets/jsonata_1_1-BOrSTtQg.png b/assets/jsonata_1_1-BOrSTtQg.png new file mode 100644 index 0000000000..9aff20120e Binary files /dev/null and b/assets/jsonata_1_1-BOrSTtQg.png differ diff --git a/assets/jsonata_2_1-AVzKzbZv.png b/assets/jsonata_2_1-AVzKzbZv.png new file mode 100644 index 0000000000..40584c6f97 Binary files /dev/null and b/assets/jsonata_2_1-AVzKzbZv.png differ diff --git a/assets/jsonata_3_1-DCTS43SR.png b/assets/jsonata_3_1-DCTS43SR.png new file mode 100644 index 0000000000..9e368550a9 Binary files /dev/null and b/assets/jsonata_3_1-DCTS43SR.png differ diff --git a/assets/jsonata_4_1-CfZXdRrw.png b/assets/jsonata_4_1-CfZXdRrw.png new file mode 100644 index 0000000000..3107378176 Binary files /dev/null and b/assets/jsonata_4_1-CfZXdRrw.png differ diff --git a/assets/jsonata_5_1-DmQtZ7mW.png b/assets/jsonata_5_1-DmQtZ7mW.png new file mode 100644 index 0000000000..b3e7127fa7 Binary files /dev/null and b/assets/jsonata_5_1-DmQtZ7mW.png differ diff --git a/assets/jsonata_6_1-Cc8tbBMr.png b/assets/jsonata_6_1-Cc8tbBMr.png new file mode 100644 index 0000000000..8b5872544b Binary files /dev/null and b/assets/jsonata_6_1-Cc8tbBMr.png differ diff --git a/assets/jsonata_6_2-CnhkdrfZ.png b/assets/jsonata_6_2-CnhkdrfZ.png new file mode 100644 index 0000000000..96a1937221 Binary files /dev/null and b/assets/jsonata_6_2-CnhkdrfZ.png differ diff --git a/assets/jsonata_7_1-BgEt8HOI.png b/assets/jsonata_7_1-BgEt8HOI.png new file mode 100644 index 0000000000..d8e3cff86b Binary files /dev/null and b/assets/jsonata_7_1-BgEt8HOI.png differ diff --git a/assets/jsonata_8_1-DHI1CMwN.png b/assets/jsonata_8_1-DHI1CMwN.png new file mode 100644 index 0000000000..dc5bc144e0 Binary files /dev/null and b/assets/jsonata_8_1-DHI1CMwN.png differ diff --git a/assets/jsonata_boolean-BjpZUFYr.png b/assets/jsonata_boolean-BjpZUFYr.png new file mode 100644 index 0000000000..076e13d06a Binary files /dev/null and b/assets/jsonata_boolean-BjpZUFYr.png differ diff --git a/assets/jsonata_boolean_example-DbjCVol3.png b/assets/jsonata_boolean_example-DbjCVol3.png new file mode 100644 index 0000000000..d4c7d9d317 Binary files /dev/null and b/assets/jsonata_boolean_example-DbjCVol3.png differ diff --git a/assets/jsonata_value_example-DJn_j-gH.png b/assets/jsonata_value_example-DJn_j-gH.png new file mode 100644 index 0000000000..c20d793089 Binary files /dev/null and b/assets/jsonata_value_example-DJn_j-gH.png differ diff --git a/assets/migration.html-D6ccKEm6.js b/assets/migration.html-D6ccKEm6.js new file mode 100644 index 0000000000..0eee745f77 --- /dev/null +++ b/assets/migration.html-D6ccKEm6.js @@ -0,0 +1,33 @@ +import{_ as n,c as a,e as t,o}from"./app-CefLgoES.js";const p={};function e(c,s){return o(),a("div",null,s[0]||(s[0]=[t(`Unchecking 'Override Topic' and 'Override Payload' doesn't work in node-red-contrib-home-assistant v0.3.2. It always overwrites msg.topic
and msg.payload
. If you have current-state nodes where you have unchecked the 'Override Payload' with node-red-contrib-home-assistant-websocket it will stop overriding payload and put the state object into msg.data
msg.payload.data
moved to msg.data
msg.payload
becomes just the state of the state object msg.payload.data.state
=> msg.payload
msg.payload.dateChanged
removed
msg.timeSinceChanged
and msg.timeSinceChangedMs
moved into msg.data
node-red-contrib-home-assistant v0.3.2
{
+ "topic": "sun.sun",
+ "payload": {
+ "timeSinceChanged": "4 hours ago",
+ "timeSinceChangedMs": 15342567,
+ "dateChanged": "2018-10-11T14:21:01.004Z",
+ "data": {
+ "entity_id": "sun.sun",
+ "state": "above_horizon",
+ "attributes": { ... },
+ "last_changed": "2018-10-11T14:21:01.004183+00:00",
+ "last_updated": "2018-10-11T18:36:30.002985+00:00",
+ "context": { ... }
+ }
+ },
+ "_msgid": "345fd403.6e71ec"
+}
+
node-red-contrib-home-assistant-websocket v0.1.1
{
+ "topic": "sun.sun",
+ "payload": "above_horizon",
+ "data": {
+ "entity_id": "sun.sun",
+ "state": "above_horizon",
+ "timeSinceChanged": "5 hours ago",
+ "timeSinceChangedMs": 16236433
+ "attributes": { ... },
+ "last_changed": "2018-10-11T14:21:01.004183+00:00",
+ "last_updated": "2018-10-11T18:51:30.002737+00:00",
+ "context": { ... },
+ },
+ "_msgid": "5071255c.ad802c"
+}
+
A motion sensor that always sends an on
event when any motion is detected and continues sending an update on each detection. This example turns a light on when motion is detected and waits 5 minutes for no motion and turns off the light.
[{"id":"20f6aead.8b5d52","type":"server-state-changed","z":"cf5fa59f.b4f528","name":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.motion","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"x":224,"y":1560,"wires":[["11c7e80d.388958"]]},{"id":"11c7e80d.388958","type":"trigger","z":"cf5fa59f.b4f528","name":"","op1":"{\\"service\\": \\"turn_on\\"}","op2":"{\\"service\\": \\"turn_off\\"}","op1type":"json","op2type":"json","duration":"5","extend":true,"units":"min","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":460,"y":1560,"wires":[["371465f9.46b39a"]]},{"id":"371465f9.46b39a","type":"api-call-service","z":"cf5fa59f.b4f528","name":"Kitchen Light","version":1,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"light.kitchen","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":652,"y":1560,"wires":[[]]}]
+
A motion sensor that only sends an on
event once then an off
event when motion is no longer detected.
[{"id":"9e2a08f5.4634b8","type":"server-state-changed","z":"ffbd7f06.4a014","name":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.motion","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"x":250,"y":1008,"wires":[["6a54b527.806bec"],["c066de8f.8a0e2"]]},{"id":"6a54b527.806bec","type":"api-call-service","z":"ffbd7f06.4a014","name":"Kitchen Light On","version":1,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"light.kitchen","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":666,"y":1008,"wires":[[]]},{"id":"c066de8f.8a0e2","type":"ha-wait-until","z":"ffbd7f06.4a014","name":"","outputs":2,"entityId":"sensor.motion","property":"state","comparator":"is","value":"on","valueType":"str","timeout":"5","timeoutUnits":"minutes","entityLocation":"","entityLocationType":"none","checkCurrentState":true,"blockInputOverrides":true,"x":476,"y":1056,"wires":[[],["28f00104.71e9de"]]},{"id":"28f00104.71e9de","type":"api-call-service","z":"ffbd7f06.4a014","name":"Kitchen Light Off","version":1,"debugenabled":false,"service_domain":"light","service":"turn_off","entityId":"light.kitchen","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":656,"y":1056,"wires":[[]]}]
+
When using templates, the top level refers to a property of the message object. For example, msg.payload
would be accessed as {{payload}}
.
You can also access flow
, global
, and states
contexts with the following syntax:
flow
context: {{flow.foobar}}
global
context: {{global.something}}
states
context: Use {{entity.domain.entity_id}}
to get the state, or drill further down with {{entity.light.kitchen.attributes.friendly_name}}
.Note:
{{entity.light.kitchen}}
and {{entity.light.kitchen.state}}
are equivalent.By default, Mustache will replace certain characters with their HTML escape codes. To prevent this, use triple braces: {{{payload}}}
.
Warning
Mustache templates are ideal for handling strings. However, if you need to insert a JSON object, consider using a JSONata expression or handling it with a function node and passing it as an input.
Warning
Needs Custom Integration installed in Home Assistant for this node to function
The Number node creates a number entity in Home Assistant that can be manipulated from within Node-RED. This entity type is typically used to represent numeric values that can be adjusted, such as a thermostat setting or a brightness level for lights.
number
The value of the entity should be updated to
properties of msg.payload
number
The value of the entity should be updated to
Value types:
value
: The value of the entityprevious value
: The previous value of the entityconfig
: The config properties of the nodeThis example demonstrates how to use the Sentence node with Home Assistant's conversation integration to play a show from Jellyfin. The flow utilizes dynamic responses to respond with the selected episode.
You can trigger the flow with commands like:
The flow leverages these nodes:
[{"id":"13b4618a99045b71","type":"ha-sentence","z":"d728cb91f51ff055","name":"random MASH episode","server":"2dad33ee.42bf5c","version":2,"inputs":0,"outputs":1,"exposeAsEntityConfig":"","mode":"trigger","sentences":["play [a] [random] mash (episode|show)"],"response":"","responseType":"str","triggerResponse":"Something went wrong","triggerResponseType":"dynamic","responseTimeout":"2000","outputProperties":[{"property":"seriesId","propertyType":"msg","value":"3b264a3b9e602ddc9c9a00a343dd5ce2","valueType":"str"},{"property":"deviceId","propertyType":"msg","value":"c76c8695549b5bedfc21f48b0664b8329ed2ebde","valueType":"str"}],"x":124,"y":144,"wires":[["45d8457d15bfa505"]]},{"id":"41a351cf86e6a70e","type":"random-item","z":"d728cb91f51ff055","name":"","input":"mash_episodes","inputType":"flow","output":"payload","outputType":"msg","number":1,"x":502,"y":144,"wires":[["7de8f9a16179ad63"]]},{"id":"04f10897f711e88c","type":"change","z":"d728cb91f51ff055","name":"set item id","rules":[{"t":"set","p":"_itemId","pt":"msg","to":"payload.id","tot":"msg","dc":true}],"action":"","property":"","from":"","to":"","reg":false,"x":850,"y":144,"wires":[["a29f9d7e6a746a9e"]]},{"id":"a29f9d7e6a746a9e","type":"openApi-red","z":"d728cb91f51ff055","name":"","configUrlNode":"9435086ff9117a76","apiTag":"Session","keepAuth":true,"operationData":{"id":"GetSessions","hasOperationId":true,"path":"/Sessions","method":"get"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{"controllableByUserId query":{"name":"controllableByUserId","type":"jsonata","value":"$env(\\"userId\\")","in":"query"},"deviceId query":{"name":"deviceId","isActive":true,"type":"msg","value":"deviceId","in":"query"},"activeWithinSeconds query":{"name":"activeWithinSeconds","in":"query"}},"requestContentType":"application/json","responseContentType":"application/json","outputs":3,"responseOutputLabels":[{"code":"200","text":"List of sessions returned."},{"code":"401","text":"Unauthorized"},{"code":"403","text":"Forbidden"}],"responseAsPayload":false,"debugMode":"","headers":[],"_version":"2.1.0","x":1010,"y":144,"wires":[["13987ef9a8e0286f"],[],[]]},{"id":"13987ef9a8e0286f","type":"openApi-red","z":"d728cb91f51ff055","name":"","configUrlNode":"9435086ff9117a76","apiTag":"Session","keepAuth":true,"operationData":{"id":"Play","hasOperationId":true,"path":"/Sessions/{sessionId}/Playing","method":"post"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{"itemIds query":{"name":"itemIds","isActive":true,"required":true,"type":"msg","value":"_itemId","in":"query"},"playCommand query":{"name":"playCommand","isActive":true,"required":true,"type":"select","value":"PlayNow","in":"query"},"sessionId path":{"name":"sessionId","isActive":true,"required":true,"type":"msg","value":"payload.0.Id","in":"path"},"startPositionTicks query":{"name":"startPositionTicks","in":"query"},"mediaSourceId query":{"name":"mediaSourceId","in":"query"},"audioStreamIndex query":{"name":"audioStreamIndex","in":"query"},"subtitleStreamIndex query":{"name":"subtitleStreamIndex","in":"query"},"startIndex query":{"name":"startIndex","in":"query"}},"requestContentType":"application/json","responseContentType":"","outputs":3,"responseOutputLabels":[{"code":"204","text":"Instruction sent to session."},{"code":"401","text":"Unauthorized"},{"code":"403","text":"Forbidden"}],"responseAsPayload":false,"debugMode":false,"headers":[],"_version":"2.1.0","x":1146,"y":144,"wires":[["e47af5331f32c92d"],[],[]]},{"id":"b907d1659b1fb6cc","type":"openApi-red","z":"d728cb91f51ff055","name":"","configUrlNode":"9435086ff9117a76","apiTag":"Session","keepAuth":true,"operationData":{"id":"GetSessions","hasOperationId":true,"path":"/Sessions","method":"get"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{"controllableByUserId query":{"name":"controllableByUserId","type":"jsonata","value":"$env(\\"userId\\")","in":"query"},"deviceId query":{"name":"deviceId","type":"msg","value":"deviceId","in":"query"},"activeWithinSeconds query":{"name":"activeWithinSeconds","in":"query"}},"requestContentType":"application/json","responseContentType":"application/json","outputs":3,"responseOutputLabels":[{"code":"200","text":"List of sessions returned."},{"code":"401","text":"Unauthorized"},{"code":"403","text":"Forbidden"}],"responseAsPayload":false,"debugMode":"","headers":[],"_version":"2.1.0","x":302,"y":354,"wires":[["21244d4a0ab99de7"],[],[]]},{"id":"47b0ace1ee8cc84e","type":"inject","z":"d728cb91f51ff055","name":"Find device ID","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":115,"y":351,"wires":[["b907d1659b1fb6cc"]]},{"id":"21244d4a0ab99de7","type":"debug","z":"d728cb91f51ff055","name":"devices","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload {DeviceName: $.{\\"client\\": Client, \\"deviceId\\": DeviceId}}","targetType":"jsonata","statusVal":"","statusType":"auto","x":468,"y":354,"wires":[]},{"id":"e47af5331f32c92d","type":"ha-sentence","z":"d728cb91f51ff055","name":"send response back to HA","server":"2dad33ee.42bf5c","version":2,"inputs":1,"outputs":1,"exposeAsEntityConfig":"","mode":"response","sentences":["play [a] random mash episode"],"response":"_response","responseType":"msg","triggerResponse":"Something went wrong","triggerResponseType":"dynamic","responseTimeout":1000,"outputProperties":[],"x":1050,"y":192,"wires":[[]]},{"id":"7de8f9a16179ad63","type":"template","z":"d728cb91f51ff055","name":"format response","field":"_response","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Playing MASH {{payload.seasonName}} Episode {{payload.episodeNumber}}, {{{payload.episodeName}}}","output":"str","x":680,"y":144,"wires":[["04f10897f711e88c"]]},{"id":"d050c8425ac14d8b","type":"openApi-red","z":"d728cb91f51ff055","name":"","configUrlNode":"9435086ff9117a76","apiTag":"Search","keepAuth":true,"operationData":{"id":"GetSearchHints","hasOperationId":true,"path":"/Search/Hints","method":"get"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{"searchTerm query":{"name":"searchTerm","isActive":true,"required":true,"type":"str","value":"M*A*S*H","in":"query"},"startIndex query":{"name":"startIndex","in":"query"},"limit query":{"name":"limit","in":"query"},"userId query":{"name":"userId","in":"query"},"includeItemTypes query":{"name":"includeItemTypes","in":"query"},"excludeItemTypes query":{"name":"excludeItemTypes","in":"query"},"mediaTypes query":{"name":"mediaTypes","in":"query"},"parentId query":{"name":"parentId","in":"query"},"isMovie query":{"name":"isMovie","in":"query"},"isSeries query":{"name":"isSeries","in":"query"},"isNews query":{"name":"isNews","in":"query"},"isKids query":{"name":"isKids","in":"query"},"isSports query":{"name":"isSports","in":"query"},"includePeople query":{"name":"includePeople","in":"query"},"includeMedia query":{"name":"includeMedia","in":"query"},"includeGenres query":{"name":"includeGenres","in":"query"},"includeStudios query":{"name":"includeStudios","in":"query"},"includeArtists query":{"name":"includeArtists","in":"query"}},"requestContentType":"application/json","responseContentType":"application/json","outputs":3,"responseOutputLabels":[{"code":"200","text":"Search hint returned."},{"code":"401","text":"Unauthorized"},{"code":"403","text":"Forbidden"}],"responseAsPayload":false,"debugMode":"","headers":[],"_version":"2.1.0","x":308,"y":451,"wires":[["63e5998dd3c9dba0"],[],[]]},{"id":"87d85ce4c11270d6","type":"inject","z":"d728cb91f51ff055","name":"Find series ID","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":111,"y":448,"wires":[["d050c8425ac14d8b"]]},{"id":"63e5998dd3c9dba0","type":"debug","z":"d728cb91f51ff055","name":"results","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.SearchHints {Name: $.{\\"seriesId\\": Id, \\"series\\": Series, \\"year\\": ProductionYear, \\"type\\": Type}}","targetType":"jsonata","statusVal":"","statusType":"auto","x":466,"y":451,"wires":[]},{"id":"e286325a4e63d372","type":"openApi-red","z":"d728cb91f51ff055","name":"get series episodes","configUrlNode":"9435086ff9117a76","apiTag":"Items","keepAuth":true,"operationData":{"id":"GetItems","hasOperationId":true,"path":"/Items","method":"get"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{"userId query":{"name":"userId","isActive":true,"type":"jsonata","value":"$env(\\"userId\\")","in":"query"},"maxOfficialRating query":{"name":"maxOfficialRating","in":"query"},"hasThemeSong query":{"name":"hasThemeSong","in":"query"},"hasThemeVideo query":{"name":"hasThemeVideo","in":"query"},"hasSubtitles query":{"name":"hasSubtitles","in":"query"},"hasSpecialFeature query":{"name":"hasSpecialFeature","in":"query"},"hasTrailer query":{"name":"hasTrailer","in":"query"},"adjacentTo query":{"name":"adjacentTo","in":"query"},"parentIndexNumber query":{"name":"parentIndexNumber","in":"query"},"hasParentalRating query":{"name":"hasParentalRating","in":"query"},"isHd query":{"name":"isHd","in":"query"},"is4K query":{"name":"is4K","in":"query"},"locationTypes query":{"name":"locationTypes","in":"query"},"excludeLocationTypes query":{"name":"excludeLocationTypes","in":"query"},"isMissing query":{"name":"isMissing","in":"query"},"isUnaired query":{"name":"isUnaired","in":"query"},"minCommunityRating query":{"name":"minCommunityRating","in":"query"},"minCriticRating query":{"name":"minCriticRating","in":"query"},"minPremiereDate query":{"name":"minPremiereDate","in":"query"},"minDateLastSaved query":{"name":"minDateLastSaved","in":"query"},"minDateLastSavedForUser query":{"name":"minDateLastSavedForUser","in":"query"},"maxPremiereDate query":{"name":"maxPremiereDate","in":"query"},"hasOverview query":{"name":"hasOverview","in":"query"},"hasImdbId query":{"name":"hasImdbId","in":"query"},"hasTmdbId query":{"name":"hasTmdbId","in":"query"},"hasTvdbId query":{"name":"hasTvdbId","in":"query"},"isMovie query":{"name":"isMovie","type":"bool","value":"true","in":"query"},"isSeries query":{"name":"isSeries","in":"query"},"isNews query":{"name":"isNews","in":"query"},"isKids query":{"name":"isKids","in":"query"},"isSports query":{"name":"isSports","in":"query"},"excludeItemIds query":{"name":"excludeItemIds","in":"query"},"startIndex query":{"name":"startIndex","in":"query"},"limit query":{"name":"limit","type":"num","value":"1","in":"query"},"recursive query":{"name":"recursive","isActive":true,"type":"bool","value":"true","in":"query"},"searchTerm query":{"name":"searchTerm","in":"query"},"sortOrder query":{"name":"sortOrder","type":"str","collapsed":false,"value":"Descending","in":"query"},"parentId query":{"name":"parentId","isActive":true,"type":"msg","value":"seriesId","in":"query"},"fields query":{"name":"fields","in":"query"},"excludeItemTypes query":{"name":"excludeItemTypes","in":"query"},"includeItemTypes query":{"name":"includeItemTypes","isActive":true,"type":"str","value":"Episode","in":"query"},"filters query":{"name":"filters","in":"query"},"isFavorite query":{"name":"isFavorite","in":"query"},"mediaTypes query":{"name":"mediaTypes","in":"query"},"imageTypes query":{"name":"imageTypes","in":"query"},"sortBy query":{"name":"sortBy","type":"str","value":"DatePlayed","in":"query"},"isPlayed query":{"name":"isPlayed","in":"query"},"genres query":{"name":"genres","in":"query"},"officialRatings query":{"name":"officialRatings","in":"query"},"tags query":{"name":"tags","in":"query"},"years query":{"name":"years","in":"query"},"enableUserData query":{"name":"enableUserData","in":"query"},"imageTypeLimit query":{"name":"imageTypeLimit","in":"query"},"enableImageTypes query":{"name":"enableImageTypes","in":"query"},"person query":{"name":"person","in":"query"},"personIds query":{"name":"personIds","in":"query"},"personTypes query":{"name":"personTypes","in":"query"},"studios query":{"name":"studios","in":"query"},"artists query":{"name":"artists","in":"query"},"excludeArtistIds query":{"name":"excludeArtistIds","in":"query"},"artistIds query":{"name":"artistIds","in":"query"},"albumArtistIds query":{"name":"albumArtistIds","in":"query"},"contributingArtistIds query":{"name":"contributingArtistIds","in":"query"},"albums query":{"name":"albums","in":"query"},"albumIds query":{"name":"albumIds","in":"query"},"ids query":{"name":"ids","in":"query"},"videoTypes query":{"name":"videoTypes","in":"query"},"minOfficialRating query":{"name":"minOfficialRating","in":"query"},"isLocked query":{"name":"isLocked","in":"query"},"isPlaceHolder query":{"name":"isPlaceHolder","in":"query"},"hasOfficialRating query":{"name":"hasOfficialRating","in":"query"},"collapseBoxSetItems query":{"name":"collapseBoxSetItems","in":"query"},"minWidth query":{"name":"minWidth","in":"query"},"minHeight query":{"name":"minHeight","in":"query"},"maxWidth query":{"name":"maxWidth","in":"query"},"maxHeight query":{"name":"maxHeight","in":"query"},"is3D query":{"name":"is3D","in":"query"},"seriesStatus query":{"name":"seriesStatus","in":"query"},"nameStartsWithOrGreater query":{"name":"nameStartsWithOrGreater","in":"query"},"nameStartsWith query":{"name":"nameStartsWith","in":"query"},"nameLessThan query":{"name":"nameLessThan","in":"query"},"studioIds query":{"name":"studioIds","in":"query"},"genreIds query":{"name":"genreIds","in":"query"},"enableTotalRecordCount query":{"name":"enableTotalRecordCount","in":"query"},"enableImages query":{"name":"enableImages","in":"query"}},"requestContentType":"application/json","responseContentType":"application/json","outputs":3,"responseOutputLabels":[{"code":"200","text":"Success"},{"code":"401","text":"Unauthorized"},{"code":"403","text":"Forbidden"}],"responseAsPayload":false,"debugMode":false,"headers":[],"_version":"2.1.0","x":546,"y":290,"wires":[["57dccb79c1e6b527"],[],[]]},{"id":"57dccb79c1e6b527","type":"function","z":"d728cb91f51ff055","name":"store episodes in flow context","func":"const episodes = flow.get(\\"mash_episodes\\") ?? [];\\n// only keep the data we need\\nmsg.payload.Items.forEach((e) => {\\n episodes.push({\\n id: e.Id,\\n episodeNumber: e.IndexNumber,\\n seasonName: e.SeasonName,\\n episodeName: e.Name,\\n }) \\n});\\nflow.set(\\"mash_episodes\\", episodes);\\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":794,"y":290,"wires":[["b195cd17f0d831f6"]]},{"id":"b195cd17f0d831f6","type":"link out","z":"d728cb91f51ff055","name":"","mode":"return","links":[],"x":973,"y":290,"wires":[]},{"id":"956492fb58071a3f","type":"link in","z":"d728cb91f51ff055","name":"get episodes","links":[],"x":94,"y":290,"wires":[["ae47bac31c4abf22"]],"l":true},{"id":"45d8457d15bfa505","type":"link call","z":"d728cb91f51ff055","name":"get episodes","links":["956492fb58071a3f"],"linkType":"static","timeout":"30","x":330,"y":144,"wires":[["41a351cf86e6a70e"]]},{"id":"cf88a7b5365c7c0f","type":"ha-sentence","z":"d728cb91f51ff055","name":"play MASH episode","server":"2dad33ee.42bf5c","version":2,"inputs":0,"outputs":1,"exposeAsEntityConfig":"","mode":"trigger","sentences":["play mash season {season} episode {episode}","play mash episode {episode}"],"response":"","responseType":"str","triggerResponse":"Something went wrong","triggerResponseType":"dynamic","responseTimeout":"2000","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"},{"property":"deviceId","propertyType":"msg","value":"c76c8695549b5bedfc21f48b0664b8329ed2ebde","valueType":"str"},{"property":"seriesId","propertyType":"msg","value":"3b264a3b9e602ddc9c9a00a343dd5ce2","valueType":"str"}],"x":114,"y":192,"wires":[["3936fa1204b55b3f"]]},{"id":"ae47bac31c4abf22","type":"switch","z":"d728cb91f51ff055","name":"have episode list?","property":"mash_episodes","propertyType":"flow","rules":[{"t":"empty"},{"t":"null"},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":326,"y":290,"wires":[["e286325a4e63d372"],["e286325a4e63d372"],["b195cd17f0d831f6"]]},{"id":"a87cff07f2f77174","type":"function","z":"d728cb91f51ff055","name":"find episode","func":"const episodes = flow.get(\\"mash_episodes\\") ?? [];\\nconst episode = convertStringToNumber(msg.payload.entities.episode.value);\\nconst season = convertStringToNumber(msg.payload.entities.season?.value);\\n\\nif (season) {\\n msg.payload = episodes.find(e => e.episodeNumber === episode && e.seasonName === `Season ${season}`);\\n} else { // if season is empty find show by number is aired in series\\n msg.payload = findNthShowAired(episodes, episode);\\n}\\n\\n// episode not found\\nif (!msg.payload) {\\n msg._response = `No episode found for MASH ${season ? `Season ${season}` : ''} Episode ${episode}`;\\n return [null, msg];\\n}\\nreturn msg;\\n\\nfunction findNthShowAired(episodes, nthShow) {\\n if (nthShow <= 0 || nthShow > episodes.length) {\\n return null; // Invalid input or out of range\\n }\\n\\n // Sort episodes by season and episode number to ensure correct airing order\\n const sortedEpisodes = episodes.sort((a, b) => {\\n const seasonA = Number(a.seasonName.replace('Season ', ''));\\n const seasonB = Number(b.seasonName.replace('Season ', ''));\\n\\n if (seasonA === seasonB) {\\n return a.episodeNumber - b.episodeNumber;\\n } else {\\n return seasonA - seasonB;\\n }\\n });\\n\\n return sortedEpisodes[nthShow - 1]; // nthShow is 1-based, array is 0-based\\n}\\n\\nfunction convertStringToNumber(input) {\\n if (input === undefined) return;\\n \\n const numberWords = {\\n 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, \\n 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, \\n 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, \\n 'fourteen': 14, 'fifteen': 15, 'sixteen': 16, 'seventeen': 17, \\n 'eighteen': 18, 'nineteen': 19, 'twenty': 20\\n };\\n\\n // Check if input is already a numeric string\\n if (!isNaN(input)) {\\n return Number(input);\\n }\\n\\n // Check if the input is a number word\\n const lowerInput = input.toLowerCase().trim();\\n if (lowerInput in numberWords) {\\n return numberWords[lowerInput];\\n }\\n}","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":502,"y":192,"wires":[["7de8f9a16179ad63"],["e47af5331f32c92d"]]},{"id":"3936fa1204b55b3f","type":"link call","z":"d728cb91f51ff055","name":"get episodes","links":["956492fb58071a3f"],"linkType":"static","timeout":"30","x":330,"y":192,"wires":[["a87cff07f2f77174"]]},{"id":"31e71416c0d8fcd4","type":"comment","z":"d728cb91f51ff055","name":"The deviceId and seriesId will need to be changed in the output properties of the sentence node","info":"","x":354,"y":96,"wires":[]},{"id":"dcc480460a475cf8","type":"openApi-red","z":"d728cb91f51ff055","name":"","configUrlNode":"9435086ff9117a76","apiTag":"User","keepAuth":true,"operationData":{"id":"GetPublicUsers","hasOperationId":true,"path":"/Users/Public","method":"get"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{},"requestContentType":"application/json","responseContentType":"application/json","outputs":1,"responseOutputLabels":[{"code":"200","text":"Public users returned."}],"responseAsPayload":false,"debugMode":"","headers":[],"_version":"2.1.0","x":312,"y":513,"wires":[["7bfc313b1590bc35"]]},{"id":"3225bc86df87feba","type":"inject","z":"d728cb91f51ff055","name":"Find user ID","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":114,"y":513,"wires":[["dcc480460a475cf8"]]},{"id":"7bfc313b1590bc35","type":"debug","z":"d728cb91f51ff055","name":"results","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload {Name: Id}","targetType":"jsonata","statusVal":"","statusType":"auto","x":470,"y":513,"wires":[]},{"id":"db16886a9eee2c17","type":"comment","z":"d728cb91f51ff055","name":"The user ID will need to be changed in the \\"get series episodes\\"","info":"","x":686,"y":240,"wires":[]},{"id":"35ebe5b08b627b76","type":"comment","z":"d728cb91f51ff055","name":"Edit searchTerm","info":"","x":308,"y":400,"wires":[]}]\n
The flow includes nodes to retrieve the necessary data from Jellyfin. Follow these steps to set it up:
openapi
node: https://api.jellyfin.org/openapi/jellyfin-openapi-stable.json
.X-MediaBrowser-Token
.userId
field with your Jellyfin user ID.This flow can be expanded to include additional functionality, such as:
The entity id of the entity to poll for.
number
The amount of time between checking/sending updates.
string
If the conditional statement is evaluated as true send the message to the first output otherwise send it to the second output. If blank there will only be one output.
Also see:
',8)),t("ul",null,[t("li",null,[n(o,{to:"/guide/conditionals.html"},{default:h(()=>e[1]||(e[1]=[a("Conditionals")])),_:1})])]),e[6]||(e[6]=i('string
Convert the state of the entity to the selected type. Boolean will be converted to true based on if the string is equal by default to (y|yes|true|on|home|open). Original value stored in msg.data.original_state
boolean
Output once on startup/deploy then on each interval
boolean
Creates a switch within Home Assistant to enable/disable this node. This feature requires Node-RED custom integration to be installed in Home Assistant
string
entity_id of changed entity
object
The last known state of the entity
string
Human readable format string of time since last updated, example "1 hour ago"
number
Number of milliseconds since last changed
The Render Template node allows you to render templates based on input data. Templates in Home Assistant are powerful tools for dynamically generating text or values based on the state of entities or other variables. This node sends the template to Home Assistant for rendering and outputs the result.
NOTE:
The node will output any Home Assistant API errors for catching with the 'catch-all' node
string
Jinja template to be rendered, discarded if msg.template
is provided via input msg
Customizable location to output original template
Customizable location to output rendered template
string
Jinja template to be rendered
string
The original template sent to Home Assistant for rendering
string
The rendered template
Home Assistant added the ability to create scenes on the fly. This has reduced the work needed to be done in Node-RED to be able to restore entities to a previous state.
[{"id":"44c8f86e.a64488","type":"trigger-state","z":"ffbd7f06.4a014","name":"Front Door Opened","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityid":"sensor.front_door","entityidfiltertype":"exact","debugenabled":false,"constraints":[{"id":"nqs49205fvp","targetType":"this_entity","targetValue":"","propertyType":"current_state","propertyValue":"new_state.state","comparatorType":"is","comparatorValueDatatype":"str","comparatorValue":"open"},{"id":"b6o1iwadls4","targetType":"this_entity","targetValue":"","propertyType":"previous_state","propertyValue":"old_state.state","comparatorType":"is","comparatorValueDatatype":"str","comparatorValue":"closed"}],"constraintsmustmatch":"all","outputs":2,"customoutputs":[],"outputinitially":false,"state_type":"str","x":202,"y":1056,"wires":[["af046290.fdf2b"],[]]},{"id":"af046290.fdf2b","type":"api-call-service","z":"ffbd7f06.4a014","name":"Snapshot Entities","version":1,"debugenabled":false,"service_domain":"scene","service":"create","entityId":"","data":"{\\"scene_id\\":\\"before\\",\\"snapshot_entities\\":[\\"light.wled_master_bedroom_tv\\"]}","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":410,"y":1056,"wires":[["7af76536.dfa6cc"]]},{"id":"7af76536.dfa6cc","type":"api-call-service","z":"ffbd7f06.4a014","name":"Change entities","version":1,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"light.wled_master_bedroom_tv","data":"{\\"rgb_color\\":[255,0,0],\\"brightness\\":128}","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":608,"y":1056,"wires":[["c6f24589.9beb48"]]},{"id":"c6f24589.9beb48","type":"delay","z":"ffbd7f06.4a014","name":"","pauseType":"delay","timeout":"15","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":780,"y":1056,"wires":[["f762690.97b1198"]]},{"id":"f762690.97b1198","type":"api-call-service","z":"ffbd7f06.4a014","name":"Restore Entities State","version":1,"debugenabled":false,"service_domain":"scene","service":"turn_on","entityId":"scene.before","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":964,"y":1056,"wires":[[]]}]
+
Here are several examples using the get-entities node to get the states of several entities and then how to restore the entity states.
[{"id":"953f4576.faa588","type":"ha-get-entities","z":"6cb9e978.bf4588","server":"8d00ebbc.99a928","name":"Get States","rules":[{"property":"entity_id","logic":"is","value":"^(light|switch|climate)\\\\.+","valueType":"re"}],"output_type":"array","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":294,"y":80,"wires":[["2dab5a86.e54686"]]},{"id":"821d957.d510168","type":"inject","z":"6cb9e978.bf4588","name":"Store states","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":118,"y":80,"wires":[["953f4576.faa588"]]},{"id":"5bc878cb.d80f08","type":"comment","z":"6cb9e978.bf4588","name":"How I would do store states","info":"","x":152,"y":32,"wires":[]},{"id":"2dab5a86.e54686","type":"function","z":"6cb9e978.bf4588","name":"transform data","func":"const entities = msg.payload.map((e) => {\\n const domain = e.entity_id.split('.')[0];\\n const payload = {};\\n\\n switch(domain) {\\n case 'switch':\\n case 'light':\\n payload.domain = domain;\\n payload.service = `turn_${e.state}`;\\n payload.data = { entity_id: e.entity_id }; \\n break;\\n case 'climate':\\n payload.domain = 'climate';\\n payload.service = `set_temperature`;\\n payload.data = {\\n entity_id: e.entity_id,\\n temperature: e.attributes.temperature,\\n target_temp_low: e.attributes.min_temp,\\n target_temp_high: e.attributes.max_temp,\\n operation_mode: e.attributes.operation_mode\\n };\\n break;\\n }\\n return payload;\\n});\\n// Save the states\\nflow.set('savedStates', entities);\\n\\n// create a blank message object with out new payload\\nmsg = { payload: entities };\\nreturn msg;","outputs":1,"noerr":0,"x":480,"y":80,"wires":[["ceaf42e4.e245d"]]},{"id":"ceaf42e4.e245d","type":"debug","z":"6cb9e978.bf4588","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":646,"y":80,"wires":[]}]\n
[{"id":"f14f56e1.4d9a28","type":"inject","z":"6cb9e978.bf4588","name":"Store states","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":118,"y":256,"wires":[["3790fc30.12b354","18d6f55.537770b","94a3c52.9fa9738"]]},{"id":"18d6f55.537770b","type":"ha-get-entities","z":"6cb9e978.bf4588","server":"8d00ebbc.99a928","name":"Get Switches","rules":[{"property":"entity_id","logic":"starts_with","value":"switch","valueType":"str"},{"property":"state","logic":"includes","value":"on,off","valueType":"str"}],"output_type":"split","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":290,"y":256,"wires":[["19e85c0e.f4d1d4"]]},{"id":"94a3c52.9fa9738","type":"ha-get-entities","z":"6cb9e978.bf4588","server":"8d00ebbc.99a928","name":"Get Lights","rules":[{"property":"entity_id","logic":"starts_with","value":"light","valueType":"str"},{"property":"state","logic":"includes","value":"on,off","valueType":"str"}],"output_type":"split","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":290,"y":208,"wires":[["19e85c0e.f4d1d4"]]},{"id":"297ff244.47ca4e","type":"comment","z":"6cb9e978.bf4588","name":"Easier to follow version","info":"","x":132,"y":160,"wires":[]},{"id":"3790fc30.12b354","type":"ha-get-entities","z":"6cb9e978.bf4588","server":"8d00ebbc.99a928","name":"Get Climate","rules":[{"property":"entity_id","logic":"starts_with","value":"climate","valueType":"str"}],"output_type":"split","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":290,"y":304,"wires":[["36705576.17a42a"]]},{"id":"19e85c0e.f4d1d4","type":"function","z":"6cb9e978.bf4588","name":"transform data","func":"const payload = {};\\npayload.domain = msg.payload.entity_id.split('.')[0];\\npayload.service = `turn_${msg.payload.state}`;\\npayload.data = { entity_id: msg.payload.entity_id };\\n\\n// create a blank message object with out new payload\\nmsg = { payload: payload };\\nreturn msg;","outputs":1,"noerr":0,"x":476,"y":256,"wires":[["2b7e0299.1ce7ae"]]},{"id":"36705576.17a42a","type":"function","z":"6cb9e978.bf4588","name":"transform data","func":"const payloads = [];\\nconst payload = {};\\npayload.domain = 'climate';\\npayload.service = `set_temperature`;\\npayload.data = {\\n entity_id: msg.payload.entity_id,\\n temperature: msg.payload.attributes.temperature,\\n target_temp_low: msg.payload.attributes.min_temp,\\n target_temp_high: msg.payload.attributes.max_temp,\\n operation_mode: msg.payload.attributes.operation_mode\\n};\\n\\n// create a blank message object with out new payload\\nmsg = { payload: payload };\\nreturn msg;","outputs":1,"noerr":0,"x":476,"y":304,"wires":[["2b7e0299.1ce7ae"]]},{"id":"e254a879.41d408","type":"comment","z":"6cb9e978.bf4588","name":"function nodes could be replaced with a change nodes","info":"","x":596,"y":208,"wires":[]},{"id":"2b7e0299.1ce7ae","type":"join","z":"6cb9e978.bf4588","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\\\n","joinerType":"str","accumulate":false,"timeout":"5","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":654,"y":256,"wires":[["b3db19bf.61d8e8"]]},{"id":"472ff71c.c342c8","type":"debug","z":"6cb9e978.bf4588","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1026,"y":256,"wires":[]},{"id":"b3db19bf.61d8e8","type":"change","z":"6cb9e978.bf4588","name":"","rules":[{"t":"set","p":"savedStates","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":832,"y":256,"wires":[["472ff71c.c342c8"]]}]\n
[{"id":"ab90e938.1eff18","type":"inject","z":"3c84cc55.8f02f4","name":"Restore states","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":294,"y":656,"wires":[["a5a0930.ed3ae7"]]},{"id":"a5a0930.ed3ae7","type":"change","z":"3c84cc55.8f02f4","name":"Get Saved States","rules":[{"t":"set","p":"payload","pt":"msg","to":"savedStates","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":656,"wires":[["6247c7cf.5cb858"]]},{"id":"8f420c5d.82373","type":"debug","z":"3c84cc55.8f02f4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":998,"y":656,"wires":[]},{"id":"6247c7cf.5cb858","type":"split","z":"3c84cc55.8f02f4","name":"","splt":"\\\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":658,"y":656,"wires":[["719b7b39.cbf0e4"]]},{"id":"719b7b39.cbf0e4","type":"api-call-service","z":"3c84cc55.8f02f4","name":"Restore State","version":"1","debugenabled":false,"service_domain":"","service":"","entityId":"","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":816,"y":656,"wires":[["8f420c5d.82373"]]}]
+
As you can see in the above examples the switch
and light
domains are just saving and restoring the state, on/off. But what if some users wanted to also save and restore the brightness level or not. The climate
domain is saving and restoring more than just the state.
Also see:
`,15)),n("ul",null,[s[1]||(s[1]=n("li",null,[n("a",{href:"https://www.home-assistant.io/integrations/scene/#creating-scenes-on-the-fly",target:"_blank",rel:"noopener noreferrer"},"Scenes in Home Assistant docs")],-1)),n("li",null,[e(a,{to:"/node/get-entities.html"},{default:u(()=>s[0]||(s[0]=[l("get-entities node")])),_:1})])])])}const f=t(g,[["render",d],["__file","saving-and-restoring-states.html.vue"]]),_=JSON.parse('{"path":"/cookbook/saving-and-restoring-states.html","title":"Saving and Restoring States","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Using Home Assistant scene creation","slug":"using-home-assistant-scene-creation","link":"#using-home-assistant-scene-creation","children":[]},{"level":2,"title":"Using the get entities node","slug":"using-the-get-entities-node","link":"#using-the-get-entities-node","children":[]}],"git":{"updatedTime":1647350454000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":5}]},"filePathRelative":"cookbook/saving-and-restoring-states.md"}');export{f as comp,_ as data}; diff --git a/assets/saving-and-restoring-states_01-C9piafaU.png b/assets/saving-and-restoring-states_01-C9piafaU.png new file mode 100644 index 0000000000..5d9c6369d6 Binary files /dev/null and b/assets/saving-and-restoring-states_01-C9piafaU.png differ diff --git a/assets/saving-and-restoring-states_02-D1d1KEfC.png b/assets/saving-and-restoring-states_02-D1d1KEfC.png new file mode 100644 index 0000000000..0764a95344 Binary files /dev/null and b/assets/saving-and-restoring-states_02-D1d1KEfC.png differ diff --git a/assets/saving-and-restoring-states_03-BUjd6VDW.png b/assets/saving-and-restoring-states_03-BUjd6VDW.png new file mode 100644 index 0000000000..e1f0b89436 Binary files /dev/null and b/assets/saving-and-restoring-states_03-BUjd6VDW.png differ diff --git a/assets/saving-and-restoring-states_04-RzhwF69v.png b/assets/saving-and-restoring-states_04-RzhwF69v.png new file mode 100644 index 0000000000..4648020836 Binary files /dev/null and b/assets/saving-and-restoring-states_04-RzhwF69v.png differ diff --git a/assets/select.html-lQV-KKW4.js b/assets/select.html-lQV-KKW4.js new file mode 100644 index 0000000000..ba43fc4f0a --- /dev/null +++ b/assets/select.html-lQV-KKW4.js @@ -0,0 +1,2 @@ +import{_ as k,c as u,e,a as s,d as t,b as a,w as c,r as o,o as i}from"./app-CefLgoES.js";const y="/node-red-contrib-home-assistant-websocket/assets/select_01-CmUrvZyA.png",g={},d={id:"mode",tabindex:"-1"},b={class:"header-anchor",href:"#mode"},m={id:"value",tabindex:"-1"},f={class:"header-anchor",href:"#value"};function v(h,n){const p=o("Badge"),r=o("InfoPanelOnly"),l=o("DocsOnly");return i(),u("div",null,[n[4]||(n[4]=e('Warning
Needs Custom Integration installed in Home Assistant for this node to function
The Select node creates a select entity in Home Assistant that can be manipulated from Node-RED. This type of entity typically represents a dropdown menu or a list of options from which the user can choose, and it can be used to control or adjust settings within your automations.
string
A string to the set current value of the select entity
properties of msg.payload
string
The string of the text entity should be updated to
Value types:
value
: The text string of the entityprevious value
: The previous text string of the entityconfig
: The config properties of the nodeUsing the WebSocket nodes provides many ways to read events and entity states from Home Assistant into Node-RED. Sending data the other way requires the use of the Entity nodes.
There are several, but they all work in much the same way, and this example will use the Sensor node to create an entity sensor in Home Asisstant, and then to update the sensor state value as well as add an attribute value.
Here is an example, showing how to use JSONata to set state value, and attribute value settings for an entity-sensor node.
[{"id":"c14460fdc14a24a0","type":"group","z":"1cc7d2e94a4815fe","name":"Sensor node - set state and attribute values","style":{"label":true,"color":"#000000"},"nodes":["ba71e67aa0d06f02","4c7a952705afe4a0","e06208fe5410cfb5","23dfef724e706db3","a6d8e76c9ca8af97","fbbda709b5a50be7"],"x":214,"y":219,"w":912,"h":222},{"id":"ba71e67aa0d06f02","type":"inject","z":"1cc7d2e94a4815fe","g":"c14460fdc14a24a0","name":"Manual Trigger","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":340,"y":260,"wires":[["4c7a952705afe4a0"]]},{"id":"4c7a952705afe4a0","type":"ha-sensor","z":"1cc7d2e94a4815fe","g":"c14460fdc14a24a0","name":"How many lights are on","entityConfig":"02814df043c7111b","version":0,"state":"$entities().*[state = \\"on\\" and entity_id ~> /^light|^switch/] ~> $count()","stateType":"jsonata","attributes":[{"property":"lights_table","value":"$entities().*[state = \\"on\\" and entity_id ~> /^light|^switch/].{\\"name\\": attributes.friendly_name, \\"lastChange\\": last_changed}","valueType":"jsonata"},{"property":"time","value":"","valueType":"date"}],"inputOverride":"allow","outputProperties":[],"x":610,"y":260,"wires":[[]],"server":""},{"id":"e06208fe5410cfb5","type":"server-state-changed","z":"1cc7d2e94a4815fe","g":"c14460fdc14a24a0","name":"","server":"","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"sensor.lights_on_count","entityIdType":"exact","outputInitially":false,"stateType":"num","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":"","valueType":"entityState"},{"property":"table","propertyType":"msg","value":"$entity().attributes.lights_table","valueType":"jsonata"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"changes","propertyType":"msg","value":"(\\t $old:=$prevEntity().attributes.lights_table;\\t $new:=$entity().attributes.lights_table;\\t {\\"on\\": $new[$not(name in $old.name)],\\t \\"off\\": $old[$not(name in $new.name)]}\\t)","valueType":"jsonata"}],"x":630,"y":340,"wires":[["23dfef724e706db3","a6d8e76c9ca8af97","fbbda709b5a50be7"]]},{"id":"23dfef724e706db3","type":"debug","z":"1cc7d2e94a4815fe","g":"c14460fdc14a24a0","name":"Count of lights on","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":990,"y":280,"wires":[]},{"id":"a6d8e76c9ca8af97","type":"debug","z":"1cc7d2e94a4815fe","g":"c14460fdc14a24a0","name":"Array table","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"table","targetType":"msg","statusVal":"payload","statusType":"auto","x":970,"y":340,"wires":[]},{"id":"fbbda709b5a50be7","type":"debug","z":"1cc7d2e94a4815fe","g":"c14460fdc14a24a0","name":"Changes","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"changes","targetType":"msg","statusVal":"changes","statusType":"auto","x":960,"y":400,"wires":[]},{"id":"02814df043c7111b","type":"ha-entity-config","server":"","deviceConfig":"","name":"SC LS On Count","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"LS On Count"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""}],"resend":false,"debugEnabled":false}]
+
Example: Provide a sensor with the count of lights that are on.
Once a new entity has been registered with Home Assistant, the entity state value can be updated by passing a message with the new value. Commonly msg.payload is used, however it is possible to use a JSONata expression to generate the new state value.
$entities().*[state = "on" and entity_id ~> /^light|^switch/] ~> $count()
+
Here the $entities()
function is used to return all entities, and select those that have "light" or "switch" in the 'entity id', and have an "on" state. This will return an array of entities, which can be counted to obtain the total number of switches or lights that are on.
Example: Provide a list of the lights that are on.
The entity state value can only be a JSON primitive value, such as a number or string, and objects and arrays can only be passed using the entity attributes.
$entities().*[state = "on" and entity_id ~> /^light|^switch/].{
+ "name": attributes.friendly_name,
+ "lastChange": last_changed}
+
The JSONata expression here first returns a similar array of lights and switches, but from this then builds an array of objects. Each object contains the friendly name and last changed timestamp for each light or switch. This array is set into an attribute value of the sensor entity.
Example: Since I last looked, which lights have been turned on, or off?
In addition to the node above, the example also includes an Event: state node which responds to state changes in the above sensor update.
(
+ $old:=$prevEntity().attributes.lights_table;
+ $new:=$entity().attributes.lights_table;
+ {"on": $new[$not(name in $old.name)],
+ "off": $old[$not(name in $new.name)]
+ }
+)
+
In an Event node, the $entity()
function returns the current or new entity details, and the $prevEntity()
function the previous or old entity details. The JSONata here picks out the sensor attribute and compares the array of lights & switches from before and after the state change. From this, an object with an array list of lights or switches that have been turned on, and those turned off, is provided.
Note: In this example the Sensor node is updated on demand. The state value of the associated Home Assistant entity, which is a count of the number of lights or switches that are on, will only be updated when the Sensor node is triggered. The Events: state node is responding to changes in the sensor node state value, which will only happen when the sensor node is updated, and not when a light or switch is turned on or off.
Also see:
`,23)),n("ul",null,[n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[0]||(s[0]=[p("JSONata guide")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/jsonata-primer.html"},{default:o(()=>s[1]||(s[1]=[p("JSONata primer")])),_:1})])])])}const d=e(i,[["render",q],["__file","sensor.html.vue"]]),h=JSON.parse('{"path":"/cookbook/jsonata/sensor.html","title":"Sensor","lang":"en-US","frontmatter":{},"headers":[{"level":3,"title":"Setting the sensor state value","slug":"setting-the-sensor-state-value","link":"#setting-the-sensor-state-value","children":[]},{"level":3,"title":"Setting an attribute value","slug":"setting-an-attribute-value","link":"#setting-an-attribute-value","children":[]},{"level":3,"title":"Responding to state changes using JSONata","slug":"responding-to-state-changes-using-jsonata","link":"#responding-to-state-changes-using-jsonata","children":[]}],"git":{"updatedTime":1719788083000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":2}]},"filePathRelative":"cookbook/jsonata/sensor.md"}');export{d as comp,h as data}; diff --git a/assets/sensor.html-DWyr03gG.js b/assets/sensor.html-DWyr03gG.js new file mode 100644 index 0000000000..48952a2b1a --- /dev/null +++ b/assets/sensor.html-DWyr03gG.js @@ -0,0 +1 @@ +import{_ as n,c as i,e as a,a as t,d as o,b as r,o as l,r as d}from"./app-CefLgoES.js";const u={},c={id:"state",tabindex:"-1"},h={class:"header-anchor",href:"#state"};function p(b,e){const s=d("Badge");return l(),i("div",null,[e[1]||(e[1]=a('Warning
Needs Custom Integration installed in Home Assistant for this node to function
The Sensor node creates a sensor entity within Home Assistant that is controlled from Node-RED. Sensors are entities that report data from various sources, such as temperature, humidity, or motion detection. This node allows you to create and manage such sensors directly from your Node-RED flows.
string | number | boolean
The state the entity should be updated to
To set the Home Assistant state to Unknown
, send a state with a js expression null
.
Object
Key/Value pair of attributes to update. The key should be a string and the value can be a [string | number | boolean | object]
string
accept | merge | block
Determine how input values will be handled. When merge is selected the message object values will override the configuration values.
boolean
When creating the entity in Home Assistant this will also send the last updated state and attributes then node sent to Home Assistant
properties of msg.payload
string | number | boolean
The state the entity should be updated to
Object
Key/Value pair of attributes to update. The key should be a string and the value can be a [string | number | boolean | object]
The Sentence node is triggered when the Home Assistant Assist feature matches a sentence from a voice assistant using the default conversation agent. This node enables voice control integrations, allowing specific voice commands to trigger automations within Node-RED.
Sentences support basic template syntax. Refer to the Home Assistant documentation for more details.
Warning
This node requires the Custom Integration to be installed in Home Assistant.
string
trigger
response
Defines the node’s function.
If set to trigger, the node activates when a sentence is matched.
If set to response, the node sends a dynamic response back to Home Assistant.
string
A list of sentences to match. Supports basic template syntax. For more details, see the Home Assistant documentation.
string
dynamic
fixed
Specifies the type of response sent to Home Assistant:
string
The message sent to Home Assistant when a sentence is matched. This option is used only if the Response Type is set to fixed
.
string
The message sent to Home Assistant when a timeout occurs. This is used when the Response Type is set to dynamic
.
number
Specifies the time in milliseconds to wait for a response before sending the Fallback Response. This option is used only when the Response Type is set to dynamic
.
string
Select an entity to create a switch in Home Assistant. Turning the switch on or off will enable or disable this node in Node-RED.
All properties must be provided in msg.payload
.
Example input:
{
+ "response": "The light is now on"
+}
+
string
The response sent to Home Assistant when a sentence is matched. This is used only when the Response Type is set to dynamic
.
Value types:
trigger id
: The sentence that triggered the flow.config
: The node's configuration properties.Use a Home Assistant automation to set an input boolean to on
then from Node-RED check if that input boolean is on
after a successful connection or at any time it changes to on
.
[{"id":"fb5296f3c4de2a23","type":"subflow","name":"Create HA Helpers","info":"","category":"HA Actions","in":[{"x":84,"y":96,"wires":[{"id":"fc18bdbb57e2fd63"}]}],"out":[],"env":[{"name":"serverName","type":"str","value":"Home Assistant","ui":{"label":{"en-US":"HA Server Name"},"type":"input","opts":{"types":["str"]}}},{"name":"helpers","type":"json","value":"[]","ui":{"label":{"en-US":"Helpers"},"type":"input","opts":{"types":["json"]}}}],"meta":{},"color":"#46B1EF","status":{"x":246,"y":48,"wires":[{"id":"ea6dc8ad6d9ece17","port":0}]}},{"id":"fc18bdbb57e2fd63","type":"function","z":"fb5296f3c4de2a23","name":"process helpers","func":"const serverName = toCamelCase(env.get('serverName'));\\nconst haServer = global.get(\\"homeassistant\\")[serverName];\\nif(!haServer) {\\n node.error(\\"Invalid HA server name\\");\\n return;\\n}\\nconst states = haServer.states;\\nconst helpers = env.get(\\"helpers\\");\\n\\nhelpers.forEach(h => {\\n const entityId = `${h.type}.${h.id}`;\\n if(!states[entityId]) {\\n const {id, type, ...data} = h;\\n const apiData = {\\n entity: h,\\n payload: { \\n data \\n }\\n };\\n apiData.payload.data.type = `${type}/create`;\\n \\n node.send(apiData);\\n node.status({text: `Creating ${entityId}`});\\n }\\n});\\n\\nnode.done();\\n\\nfunction toCamelCase(str) {\\n return str.replace(/(?:^\\\\w|[A-Z]|\\\\b\\\\w|\\\\s+)/g, (match, index) => {\\n if (+match === 0) return '';\\n return index === 0 ? match.toLowerCase() : match.toUpperCase();\\n });\\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":224,"y":96,"wires":[["2b8e82c6d93d38e5"]]},{"id":"2b8e82c6d93d38e5","type":"ha-api","z":"fb5296f3c4de2a23","name":"create helper","server":"","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"","dataType":"json","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":390,"y":96,"wires":[["767624d22ee0c819"]]},{"id":"372232586f5e138f","type":"ha-api","z":"fb5296f3c4de2a23","name":"rename helper entity id","server":"","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\t \\"type\\":\\"config/entity_registry/update\\",\\t \\"entity_id\\": entity.type & \\".\\" & payload.id,\\t \\"new_entity_id\\": entity.type & \\".\\" & entity.id\\t}","dataType":"jsonata","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":788,"y":96,"wires":[[]]},{"id":"767624d22ee0c819","type":"switch","z":"fb5296f3c4de2a23","name":"need to rename?","property":"payload.id","propertyType":"msg","rules":[{"t":"neq","v":"entity.id","vt":"msg"}],"checkall":"true","repair":false,"outputs":1,"x":570,"y":96,"wires":[["372232586f5e138f","815fe31e9d239e53"]]},{"id":"ea6dc8ad6d9ece17","type":"status","z":"fb5296f3c4de2a23","name":"","scope":["fc18bdbb57e2fd63","815fe31e9d239e53"],"x":124,"y":48,"wires":[[]]},{"id":"815fe31e9d239e53","type":"function","z":"fb5296f3c4de2a23","name":"rename status","func":"const oldEntityId = `${msg.entity.type}.${msg.payload.id}`;\\nconst newEntityId = `${msg.entity.type}.${msg.entity.id}`;\\n\\nnode.status({text: `Renaming ${oldEntityId} to ${newEntityId}`});","outputs":1,"noerr":0,"initialize":"","finalize":"","x":768,"y":144,"wires":[[]]},{"id":"4a91394f75614da1","type":"server-events","z":"8e0ea5b84a1a9a3a","name":"","server":"","version":1,"event_type":"home_assistant_client","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"}],"x":200,"y":320,"wires":[["95e9d47ed2be5007"]]},{"id":"95e9d47ed2be5007","type":"switch","z":"8e0ea5b84a1a9a3a","name":"running?","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"running","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":412,"y":320,"wires":[["4b7a32290de02cf7"]]},{"id":"648dd9ac108befdc","type":"api-call-service","z":"8e0ea5b84a1a9a3a","name":"","server":"","version":3,"debugenabled":false,"service_domain":"input_boolean","service":"turn_off","entityId":"input_boolean.home_assistant_restarted","data":"","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":808,"y":320,"wires":[["3754cbf709193071"]]},{"id":"4b7a32290de02cf7","type":"api-current-state","z":"8e0ea5b84a1a9a3a","name":"has restarted?","server":"","version":2,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.home_assistant_restarted","state_type":"str","blockInputOverrides":false,"outputProperties":[],"x":576,"y":320,"wires":[["648dd9ac108befdc"],[]]},{"id":"f89ae68a99bb4e65","type":"server-state-changed","z":"8e0ea5b84a1a9a3a","name":"restarted?","server":"","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_boolean.home_assistant_restarted","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[],"x":588,"y":368,"wires":[["648dd9ac108befdc"],[]]},{"id":"b5993f580d9eb5b8","type":"comment","z":"8e0ea5b84a1a9a3a","name":"Home Assistant Restarted","info":"","x":190,"y":224,"wires":[]},{"id":"3754cbf709193071","type":"link out","z":"8e0ea5b84a1a9a3a","name":"Home Assistant Restarted","links":["c67631f89e6451f4"],"x":1070,"y":320,"wires":[],"l":true},{"id":"c67631f89e6451f4","type":"link in","z":"8e0ea5b84a1a9a3a","name":"Home Assistant Restarted","links":["3754cbf709193071"],"x":190,"y":416,"wires":[["33ccbe3fca679b80"]],"l":true},{"id":"33ccbe3fca679b80","type":"debug","z":"8e0ea5b84a1a9a3a","name":"do stuff","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":412,"y":416,"wires":[]},{"id":"bcfea616e335904d","type":"inject","z":"8e0ea5b84a1a9a3a","name":"Click to create","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":166,"y":272,"wires":[["447721875af4431a","5a92fd8241edd308"]]},{"id":"447721875af4431a","type":"ha-api","z":"8e0ea5b84a1a9a3a","name":"HA automation","server":"","version":1,"debugenabled":false,"protocol":"http","method":"post","path":"/api/config/automation/config/nrrestartnotification","data":"{\\t \\"alias\\": \\"Home Assistant Restart\\",\\t \\"description\\": \\"\\",\\t \\"trigger\\": [\\t {\\t \\"platform\\": \\"homeassistant\\",\\t \\"event\\": \\"start\\" \\t } \\t ],\\t \\"condition\\": [],\\t \\"action\\": [\\t {\\t \\"service\\": \\"input_boolean.turn_on\\",\\t \\"data\\": {\\t \\"entity_id\\": \\"input_boolean.home_assistant_restarted\\" \\t }\\t } \\t ],\\t \\"mode\\": \\"single\\" \\t}","dataType":"jsonata","responseType":"json","outputProperties":[],"x":432,"y":224,"wires":[[]]},{"id":"5a92fd8241edd308","type":"subflow:fb5296f3c4de2a23","z":"8e0ea5b84a1a9a3a","name":"","env":[{"name":"helpers","value":"[{\\"id\\":\\"home_assistant_restarted\\",\\"type\\":\\"input_boolean\\",\\"name\\":\\"Home Assistant Restarted\\",\\"icon\\":\\"mdi:restart-alert\\"}]","type":"json"}],"x":442,"y":272,"wires":[]}]\n
Tips
The input boolean and automation can be created directly from Node-RED using the inject node found in the exported flow.
input_boolean:
+ home_assistant_restarted:
+ name: Home Assistant Restarted
+
alias: Home Assistant Restart
+description: ""
+trigger:
+ - platform: homeassistant
+ event: start
+condition: []
+action:
+ - service: input_boolean.turn_on
+ data: {}
+ entity_id: input_boolean.home_assistant_restarted
+mode: single
+
[{"id":"b74ada49.d7e408","type":"server-state-changed","z":"ffbd7f06.4a014","name":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sun.sun","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"above_horizon","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"x":244,"y":784,"wires":[["1f467cbb.0c3983"],["da5ff3e0.cbb2a"]]},{"id":"1f467cbb.0c3983","type":"api-call-service","z":"ffbd7f06.4a014","name":"","version":1,"debugenabled":false,"service_domain":"light","service":"turn_off","entityId":"light.front_porch","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":474,"y":784,"wires":[[]]},{"id":"da5ff3e0.cbb2a","type":"api-call-service","z":"ffbd7f06.4a014","name":"","version":1,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"light.front_porch","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":474,"y":832,"wires":[[]]}]
+
[{"id":"74a64f80.d2302","type":"server-state-changed","z":"ffbd7f06.4a014","name":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sun.sun","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"above_horizon","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"x":404,"y":832,"wires":[["ccb2f059.1d384"],["4a84490d.9930a8"]]},{"id":"ccb2f059.1d384","type":"api-call-service","z":"ffbd7f06.4a014","name":"","version":1,"debugenabled":false,"service_domain":"light","service":"turn_off","entityId":"light.front_porch","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":794,"y":832,"wires":[[]]},{"id":"4a84490d.9930a8","type":"change","z":"ffbd7f06.4a014","name":"turn on","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\\"service\\": \\"turn_on\\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":604,"y":880,"wires":[["ccb2f059.1d384"]]}]
+
Required Nodes
[{"id":"ccb2f059.1d384","type":"api-call-service","z":"ffbd7f06.4a014","name":"","version":1,"debugenabled":false,"service_domain":"light","service":"{{payload}}","entityId":"light.front_porch","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":644,"y":832,"wires":[[]]},{"id":"1a6cb307.d3168d","type":"eztimer","z":"ffbd7f06.4a014","name":"","autoname":"sunset - sunrise","tag":"eztimer","suspended":false,"sendEventsOnSuspend":false,"timerType":"1","startupMessage":true,"ontype":"1","ontimesun":"sunset","ontimetod":"17:00","onproperty":"payload","onvaluetype":"str","onvalue":"turn_on","onoffset":0,"onrandomoffset":0,"onsuppressrepeats":false,"offtype":"1","offtimesun":"sunrise","offtimetod":"dusk","offduration":"00:01:00","offproperty":"payload","offvaluetype":"str","offvalue":"turn_off","offoffset":0,"offrandomoffset":0,"offsuppressrepeats":false,"mon":true,"tue":true,"wed":true,"thu":true,"fri":true,"sat":true,"sun":true,"x":288,"y":832,"wires":[["ccb2f059.1d384"]]}]
+
The Switch node and the Change node are standard Node-RED nodes that provide conditional routing (selection) and data manipulation respectively for flow messages. Many of the WebSocket nodes, when using JSONata, can provide some output property data manipulation, and a few nodes also offer simple binary output message routing. However, much more can be achieved using JSONata in Switch and Change nodes in combination with the WebSocket nodes.
This example shows how to use JSONata within the standard Switch node.
[{"id":"fcb0d3b1b8ed3478","type":"group","z":"776c027950fc8c3f","name":"Switch node - selection routing based on entity details","style":{"label":true,"color":"#000000"},"nodes":["7f01d6eef386f743","84b7ac3a7e469780","60331de530222f6f","e253c6a934a17919","76bb91be0ae4c7b4","52713f1b7157f1f0"],"x":34,"y":2159,"w":852,"h":262},{"id":"7f01d6eef386f743","type":"poll-state","z":"776c027950fc8c3f","g":"fcb0d3b1b8ed3478","name":"","server":"","version":3,"exposeAsEntityConfig":"","updateInterval":"10","updateIntervalType":"num","updateIntervalUnits":"seconds","outputInitially":false,"outputOnChanged":true,"entityId":"switch.t1","stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputs":1,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":150,"y":2240,"wires":[["84b7ac3a7e469780"]]},{"id":"84b7ac3a7e469780","type":"switch","z":"776c027950fc8c3f","g":"fcb0d3b1b8ed3478","name":"","property":"$round(data.timeSinceChangedMs/60000,0)","propertyType":"jsonata","rules":[{"t":"btwn","v":"2","vt":"num","v2":"3","v2t":"num"},{"t":"gt","v":"data.state=\\"on\\" ? 4 : 1","vt":"jsonata"},{"t":"jsonata_exp","v":"data.timeSinceChangedMs < 60000","vt":"jsonata"},{"t":"else"}],"checkall":"true","repair":false,"outputs":4,"x":350,"y":2240,"wires":[["60331de530222f6f"],["e253c6a934a17919"],["76bb91be0ae4c7b4"],["52713f1b7157f1f0"]]},{"id":"60331de530222f6f","type":"debug","z":"776c027950fc8c3f","g":"fcb0d3b1b8ed3478","name":"Last changed between 2 and 3 minutes ago","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"\\"Is \\" & payload & \\" for \\" & $round(data.timeSinceChangedMs/60000, 0) & \\" minutes\\"","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":670,"y":2200,"wires":[]},{"id":"e253c6a934a17919","type":"debug","z":"776c027950fc8c3f","g":"fcb0d3b1b8ed3478","name":"If on > 4 mins or if off > 1 mins","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"\\"Is \\" & payload & \\" for \\" & $round(data.timeSinceChangedMs/60000, 0) & \\" minutes\\"","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":630,"y":2260,"wires":[]},{"id":"76bb91be0ae4c7b4","type":"debug","z":"776c027950fc8c3f","g":"fcb0d3b1b8ed3478","name":"Changed state < 1 minute ago","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"\\"Is \\" & payload & \\" for \\" & $round(data.timeSinceChangedMs/60000, 0) & \\" minutes\\"","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":630,"y":2320,"wires":[]},{"id":"52713f1b7157f1f0","type":"debug","z":"776c027950fc8c3f","g":"fcb0d3b1b8ed3478","name":"Otherwise","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"\\"Is \\" & payload & \\" for \\" & $round(data.timeSinceChangedMs/60000, 0) & \\" minutes\\"","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":560,"y":2380,"wires":[]}]
+
A Poll State node has been set up to return the entity state of a light switch every 10 seconds. This will return the entity state ("on" or "off") as the msg.payload value. It will also return the fuller entity state details in msg.data, which includes the 'timeSinceChangedMs' field.
JSONata for the routing property
$round(data.timeSinceChangedMs/60000,0)
+
The routing rules will test against the given property. By default this would be the value held in msg.payload. Using a JSONata expression here allows for a computation on the time since changed field to convert this to minutes. Rounding is also being used, so 31 to 89 seconds would become 1 minute.
Note that the JSONata expression has access to the full incoming message. The value required was output from the Poll State node within the output property msg.data field, and this has to be used here since the $entity()
functions are not available outside of the WebSocket nodes.
JSONata for the rule value
In the first rule, the rule values to test against are given as literals.
In the second rule, the rule test value is provided by a JSONata expression. This will return either 4, if the light state is "on", or 1 if the light state is "off".
data.state="on" ? 4 : 1
+
Note that 'payload' contains the state value and could have been used rather than 'data.state'. Where the output of a WebSocket node is used later in the flow for routing or processing, care should be taken to generate the most appropriate output. For example, rather than generate the Property match value here, this JSONata expression could be used in the Poll State node to provide minutes since last changed in the output msg.payload directly.
JSONata expression for the routing match
true
.data.timeSinceChangedMs < 60000
+
The example is simplistic, however it shows that any incoming message property can be used within the expression.
Note that, since the $entities()
function is not available in a Switch node, to obtain other entity values, JSONata can be used in the preceding Poll State node to generate an output property, for example by setting msg.time to $entities('sensor.time').state
to provide 'time' as an available value for use in the Switch node.
JSONata expression in the Debug node
The Debug nodes also contain JSONata as a further demonstration of how versatile and applicable this language can be.
Also see:
`,25)),n("ul",null,[n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[0]||(s[0]=[p("JSONata guide")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/jsonata-primer.html"},{default:o(()=>s[1]||(s[1]=[p("JSONata primer")])),_:1})])])])}const h=e(q,[["render",d],["__file","switch-node.html.vue"]]),m=JSON.parse('{"path":"/cookbook/jsonata/switch-node.html","title":"Switch node","lang":"en-US","frontmatter":{},"headers":[{"level":3,"title":"Using JSONata in switch routing","slug":"using-jsonata-in-switch-routing","link":"#using-jsonata-in-switch-routing","children":[]}],"git":{"updatedTime":1719788083000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":2}]},"filePathRelative":"cookbook/jsonata/switch-node.md"}');export{h as comp,m as data}; diff --git a/assets/switch.html-Bhvk304d.js b/assets/switch.html-Bhvk304d.js new file mode 100644 index 0000000000..1aaa26918f --- /dev/null +++ b/assets/switch.html-Bhvk304d.js @@ -0,0 +1,2 @@ +import{_ as c,c as r,e as l,b as a,w as t,r as o,o as k,a as s,d as u}from"./app-CefLgoES.js";const i="/node-red-contrib-home-assistant-websocket/assets/switch_usage-BysMPBbE.png",y={};function g(b,n){const p=o("InfoPanelOnly"),e=o("DocsOnly");return k(),r("div",null,[n[2]||(n[2]=l('Warning
Needs Custom Integration installed in Home Assistant for this node to function
The Switch node creates a switch entity within Home Assistant that can be controlled from Node-RED. This node also allows a flow to be started from a service call, making it a versatile tool for both controlling devices and triggering automations.
Input is disabled by default. It can be enabled by checking this option.
boolean
When the state of the switch changes it will output to the top if the switch is on or to the bottom if it is in the off position.
boolean
Set to true
to turn on the switch and false
to turn it off. If the message has a property enable
defined and set to a boolean
the node will not have any output.
Status Color
The Tag node outputs data when Home Assistant receives a tag scanned event for a configured tag ID. This node is useful for creating automations based on NFC tags or other scannable identifiers, allowing specific actions to be triggered when a tag is scanned.
array
An array of tag ids to monitor for tag scanned events.
array
An array of device ids to check when a configured tag is scanned.
string
When an entity is selected a switch entity will be created in Home Assistant. Turning on and off this switch will disable/enable the nodes in Node-RED.
Value types:
event data
: full event objecttag id
: tag id of the tag that was scanned.config
: config properties of the nodeobject
Field | Description |
---|---|
device_id | ID of the device that scanned the tag |
tag_id | ID of the tag that was scanned |
tag_name | Home Assistant name of the tag that was scanned |
user_id | Home Assistant user ID on device that scanned the tag |
Warning
Needs Custom Integration installed in Home Assistant for this node to function
The Text node creates a text entity within Home Assistant that can be manipulated from Node-RED. Text entities are used to store and display text values, and this node allows you to dynamically update and control these values within your automations.
string
The string of the entity should be updated to
properties of msg.payload
string
The string of the text entity should be updated to
Value types:
value
: The text string of the entityprevious value
: The previous text string of the entityconfig
: The config properties of the nodeWarning
Needs Custom Integration installed in Home Assistant for this node to function
The Time Entity node creates a time entity within Home Assistant that can be controlled from Node-RED. This entity type is used to represent time values, such as a specific time of day or a duration, and can be used to trigger automations based on time-related conditions.
string
HH:mm:ss
| HH:mm
The value of the entity should be updated to
properties of msg.payload
string
HH:mm:ss
| HH:mm
The value of the entity should be updated to
Value types:
value
: The value of the entityprevious value
: The previous value of the entityconfig
: The config properties of the nodeThe Time node can be scheduled to trigger at a specific future date and time, based on a Home Assistant entity. This node is useful for creating time-based automations, such as triggering a notification or alarm at a specific hour.
string
A Home Assistant entity to be used when scheduling the node.
string
Which property of the entity object to use to schedule the node.
example properties:
entity_id
state
attributes.date
last_updated
example values for the property:
2020-12-31T02:47:54.837Z
1609382842709
13:40
23:59:02
number
A negative or positive number that will be added to the scheduled time.
boolean
When selected the time to trigger will be random selected from the scheduled time to the +/- offset.
string | number | boolean | object
The payload is fully customizable. The default will be a JSONata expression that outputs the entity state.
boolean
If selected the node will only use the time portion of the date string to schedule the node and will trigger at that time each day. Otherwise, the node will only trigger once at the given day and time.
string
When an entity is selected a switch entity will be created in Home Assistant. Turning on and off this switch will disable/enable the nodes in Node-RED.
string
The entity id in the configuration.
string | number | boolean | object
object
The entity object of the entity in the configuration.
',37)]))}const r=t(o,[["render",n],["__file","time.html.vue"]]),h=JSON.parse('{"path":"/node/time.html","title":"Time","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Configuration","slug":"configuration","link":"#configuration","children":[{"level":3,"title":"Entity Id","slug":"entity-id","link":"#entity-id","children":[]},{"level":3,"title":"Property","slug":"property","link":"#property","children":[]},{"level":3,"title":"Offset","slug":"offset","link":"#offset","children":[]},{"level":3,"title":"Randomize time within the offset","slug":"randomize-time-within-the-offset","link":"#randomize-time-within-the-offset","children":[]},{"level":3,"title":"Payload","slug":"payload","link":"#payload","children":[]},{"level":3,"title":"Repeat Daily","slug":"repeat-daily","link":"#repeat-daily","children":[]},{"level":3,"title":"Expose as","slug":"expose-as","link":"#expose-as","children":[]}]},{"level":2,"title":"Outputs","slug":"outputs","link":"#outputs","children":[{"level":3,"title":"topic","slug":"topic","link":"#topic","children":[]},{"level":3,"title":"payload","slug":"payload-1","link":"#payload-1","children":[]},{"level":3,"title":"data","slug":"data","link":"#data","children":[]}]}],"git":{"updatedTime":1723606857000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":5},{"name":"t1diotac","email":"87152403+t1diotac@users.noreply.github.com","commits":1}]},"filePathRelative":"node/time.md"}');export{r as comp,h as data}; diff --git a/assets/time_entity_01-Brx2iLlv.png b/assets/time_entity_01-Brx2iLlv.png new file mode 100644 index 0000000000..67f7de0e1a Binary files /dev/null and b/assets/time_entity_01-Brx2iLlv.png differ diff --git a/assets/trigger-state.html-CY-rAJPk.js b/assets/trigger-state.html-CY-rAJPk.js new file mode 100644 index 0000000000..4ef4fbfd9e --- /dev/null +++ b/assets/trigger-state.html-CY-rAJPk.js @@ -0,0 +1,10 @@ +import{_ as p,c as d,a as t,d as n,b as a,e as s,w as c,r as o,o as r}from"./app-CefLgoES.js";const u={},h={id:"entity",tabindex:"-1"},g={class:"header-anchor",href:"#entity"};function b(f,e){const i=o("Badge"),l=o("RouteLink");return r(),d("div",null,[e[2]||(e[2]=t("h1",{id:"trigger-state",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#trigger-state"},[t("span",null,"Trigger: state")])],-1)),e[3]||(e[3]=t("p",null,"The Trigger: State node functions similarly to the State Changed Node but provides advanced functionality for common automation use cases. This node allows for more complex conditions and actions based on entity state changes, making it a powerful tool for creating detailed and nuanced automations.",-1)),e[4]||(e[4]=t("h2",{id:"configuration",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#configuration"},[t("span",null,"Configuration")])],-1)),t("h3",h,[t("a",g,[t("span",null,[e[0]||(e[0]=n("Entity ")),a(i,{text:"required"})])])]),e[5]||(e[5]=s('Type: string
The entity ID is used to listen for state changes. This can be a entity ID, regex, or a substring. If a regex or substring is used, the node will listen for all entities that match.
Example:
light.kitchen
(entity) listens for state changes of the light.kitchen
entity^light.*
(regex) listens for state changes of all entities that start with light.
light
(substring) listens for state changes of all entities that contain light
in the entity IDstring
string|number|boolean
string
Convert the state of the entity to the selected type. Boolean will be converted to true based on if the string is equal by default to (y|yes|true|on|home|open
). Original value stored in msg.data.original_state
This node has two default outputs "allowed" and "blocked". If all the conditions are true the default message will be sent to the "allowed" output otherwise, it will be sent to the "blocked" output.
See Also:
',9)),t("ul",null,[t("li",null,[a(l,{to:"/guide/conditionals.html"},{default:c(()=>e[1]||(e[1]=[n("Conditionals")])),_:1})])]),e[6]||(e[6]=s(`All the above conditions need to be true for any custom outputs to be sent, having zero conditions is a valid option. Each custom output can send the default message or a custom message. Also, each one can have its constraint on whether or not to be sent.
boolean
Output once on startup/deployment.
boolean
Enable the input to be used to send a message to the node.
boolean
Output debug information to the debug tab.
boolean
Expose the node to Home Assistant.
Input is disabled by default. It can be enabled using the Enable Input
option. Input can be used to enable/disable the node or for testing.
string
If the incoming payload or message is a string and equal to enable
or disable
then set the node accordingly. Saves over restarts.
string
The entity_id that triggered the node
string
The state as sent by home assistant
object
The original home assistant event containing entity_id
new_state
and old_state
properties
To test automation without having to manually change the state in home assistant send an input payload
as an object which contains entity_id
, new_state
, and old_state
properties. This will trigger the node as if the event came from Home Assistant.
{
+ "entity_id": "test_entity",
+ "old_state": {
+ "state": "on"
+ },
+ "new_state": {
+ "state": "off"
+ }
+}
+
The Trigger: state node is very similar to the Events: state node. It will trigger when a given entity state changes, but unlike the Events: state, the Trigger: state node has the opportunity to add conditions using UI fields to construct complex tests. This removes much of the need to manage conditional tests using JSONata.
JSONata can be used in several places within this node. However real practical use is quite limited. Although the Trigger node has extensive conditional testing included as UI setting, it does not have access to the $entity() or $entities() function in the conditions. The example given here is rather simplistic and contrived to demonstrate that JSONata can be used, rather than focusing on a genuinue or realistic use-case.
Example: Respond every five minutes during the hour before and hour after sunset.
[{"id":"3a2face9747195a7","type":"group","z":"776c027950fc8c3f","name":"Trigger: state node - respond to state changes","style":{"label":true,"color":"#000000"},"nodes":["320b22abb5a9ea96","da88972398c7ed97","c2d9f44ffe436dcc","2144de490d4257d7","94ceadbce4b45cdf","79b056d9779ec8f1","360fe19e1e203539","247cbab5f584ea63","0509fc220ff7735e","8fc293f5015dd3d5"],"x":34,"y":1359,"w":872,"h":302},{"id":"320b22abb5a9ea96","type":"inject","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"Testing","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\\t \\"entity_id\\": \\"sensor.time_utc\\",\\t \\"old_state\\": {\\"state\\": \\"16:49\\"},\\t \\"new_state\\": {\\"state\\": \\"16:50\\"}\\t}","payloadType":"jsonata","x":130,"y":1400,"wires":[["da88972398c7ed97"]]},{"id":"da88972398c7ed97","type":"trigger-state","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"Actions around sunset","server":"","version":4,"inputs":1,"outputs":3,"exposeAsEntityConfig":"","entityId":"sensor.time_utc","entityIdType":"exact","debugEnabled":false,"constraints":[{"targetType":"this_entity","targetValue":"","propertyType":"current_state","propertyValue":"new_state.state","comparatorType":"includes","comparatorValueDatatype":"jsonata","comparatorValue":"(\\t/* UTC sunrise & sunset to 5 mins, month average (London UK) */\\t/* Jan-Dec, Feb-Nov, Mar-Oct, Apr-Sep, May-Aug, Jun-Jly */\\t $times:=[[\\"07:55\\", \\"16:20\\"],\\t [\\"07:15\\", \\"16:45\\"],\\t [\\"06:00\\", \\"17:35\\"],\\t [\\"05:15\\", \\"18:30\\"],\\t [\\"04:30\\", \\"19:30\\"],\\t [\\"04:05\\", \\"20:05\\"]];\\t\\t/* get this month (Jan=0), lookup sun-times table, return sun rise-set for this month */\\t $mtable:=[0..11].$min([$, $abs($-11)]);\\t $mindex:=$now('[M]').$number()-1;\\t\\t/* get sunset and build array of condition test-times around this */\\t $sunset:=($times[$mtable[$mindex]])[1];\\t $h:=$number($substringBefore($sunset, \\":\\"));\\t $m:=$number($substringAfter($sunset, \\":\\"));\\t\\t/* for span period mins, generate array of times and select every nth */\\t $span:=60;\\t $nth:=5;\\t\\t $t:=$h*60+$m-$span;\\t ([$t..$t+$span*2])#$i.(\\t $h:=$floor($/60);\\t $m:=$%60;\\t $i%$nth=0 ? $formatInteger($h,'99') & \\":\\" & $formatInteger($m, '99');\\t )\\t)"}],"customOutputs":[{"messageType":"custom","messageValue":"(\\t $sunset:=$entities('sensor.sun_next_setting').state;\\t $togo:= ($toMillis($sunset)-$millis())/60000~>$floor();\\t $toset:= $togo < 180 ? $togo : $togo-1440;\\t $x:= $toset-25;\\t $pc:= $x<0 and $x>-55 ? $min([$abs($x)*2, 100]) : null;\\t\\t {\\"topic\\": \\"Minutes to Sunset\\",\\t \\"payload\\": $toset,\\t \\"time\\": $entities('sensor.time').state,\\t \\"sunset\\": $sunset,\\t \\"percent\\": $pc\\t }\\t)","messageValueType":"jsonata","comparatorPropertyType":"always","comparatorPropertyValue":"","comparatorType":"is","comparatorValue":"","comparatorValueDataType":"jsonata"}],"outputInitially":false,"stateType":"str","enableInput":true,"x":300,"y":1440,"wires":[["c2d9f44ffe436dcc"],[],["2144de490d4257d7","247cbab5f584ea63"]]},{"id":"c2d9f44ffe436dcc","type":"debug","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"Every 5 mins around sunset","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":580,"y":1400,"wires":[]},{"id":"2144de490d4257d7","type":"switch","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"Sunset Actions","property":"payload","propertyType":"msg","rules":[{"t":"btwn","v":"0","vt":"num","v2":"4","v2t":"num"},{"t":"btwn","v":"10","vt":"num","v2":"14","v2t":"num"},{"t":"btwn","v":"0","vt":"num","v2":"-30","v2t":"num"},{"t":"else"}],"checkall":"false","repair":false,"outputs":4,"x":540,"y":1480,"wires":[["94ceadbce4b45cdf"],["79b056d9779ec8f1"],["360fe19e1e203539"],[]]},{"id":"94ceadbce4b45cdf","type":"debug","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"At Sunset","active":false,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":780,"y":1440,"wires":[]},{"id":"79b056d9779ec8f1","type":"debug","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"Just Before","active":false,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":790,"y":1500,"wires":[]},{"id":"360fe19e1e203539","type":"debug","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"Just After","active":false,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":780,"y":1560,"wires":[]},{"id":"247cbab5f584ea63","type":"change","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"Brightness 0-100% over 50 min","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\\t \\"domain\\": \\"homeassistant\\",\\t \\"service\\": \\"turn_on\\",\\t \\"target\\": {\\t \\"entity_id\\": [\\"light.front_door\\", \\"switch.hall_light\\"]\\t },\\t \\"data\\": {\\t \\"brightness_pct\\": percent\\t }\\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":1620,"wires":[["8fc293f5015dd3d5"]]},{"id":"0509fc220ff7735e","type":"api-call-service","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"","server":"","version":5,"debugenabled":false,"domain":"homeassistant","service":"turn_on","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":760,"y":1620,"wires":[[]]},{"id":"8fc293f5015dd3d5","type":"switch","z":"776c027950fc8c3f","g":"3a2face9747195a7","name":"Not null","property":"payload.data.brightness_pct","propertyType":"msg","rules":[{"t":"nnull"}],"checkall":"true","repair":false,"outputs":1,"x":540,"y":1620,"wires":[["0509fc220ff7735e"]]}]
+
Sunrise and sunset times are often critical to running automations, and Home Assistant has inbuilt entities that provide the next dawn, sunrise, dusk, and sunset times. In order to be able to respond to multiple events during the period both before and after sunset, it is necessary to trigger at a more regular interval, not just on the sun-events, and then to test for required times or periods before and after sunset.
The Trigger: state node can be set to use the sensor.time_utc
entity as the subject of the node. This sensor holds UTC time as "hh:mm", and will change every minute, thus triggering a state change at regular 60 second intervals. We can add conditions to test the time against an expected period around sunset, and to generate a customised output that includes given periods or times before or after sunset. Combined with a Switch node this permits multiple tests and multiple automation events to be actioned.
Here JSONata is used to generate an array of times of interest. When the trigger entity state, UTC time "hh:mm", is included in the calculated array, the condition is met and a message will be output.
Since JSONata does not have access to the $entities() function in this particular UI field, the code here is used to generate a period of interest based on hard-coded sunrise and sunset times according to the current month. This is not ideal, but does demonstrate how JSONata can be used to easily generate and manipulate various JSON structures.
(
+/* UTC sunrise & sunset to 5 mins, month average (London UK) */
+/* Jan-Dec, Feb-Nov, Mar-Oct, Apr-Sep, May-Aug, Jun-Jly */
+ $times:=[["07:55", "16:20"],
+ ["07:15", "16:45"],
+ ["06:00", "17:35"],
+ ["05:15", "18:30"],
+ ["04:30", "19:30"],
+ ["04:05", "20:05"]];
+
+/* get this month (Jan=0), lookup sun-times table, return sun rise-set for this month */
+ $mtable:=[0..11].$min([$, $abs($-11)]);
+ $mindex:=$now('[M]').$number()-1;
+
+/* get sunset and build array of condition test-times around this */
+ $sunset:=($times[$mtable[$mindex]])[1];
+ $h:=$number($substringBefore($sunset, ":"));
+ $m:=$number($substringAfter($sunset, ":"));
+
+/* for span period mins, generate array of times and select every nth */
+ $span:=60;
+ $nth:=5;
+
+ $t:=$h*60+$m-$span;
+ ([$t..$t+$span*2])#$i.(
+ $h:=$floor($/60);
+ $m:=$%60;
+ $i%$nth=0 ? $formatInteger($h,'99') & ":" & $formatInteger($m, '99');
+ )
+)
+
This code uses the current month to lookup a given sunset time, and then to generate an array of times either side of expected sunset, using a given span in minutes, and then picking out every five minutes only.
The figures provided are examples, based on UTC times (DST ignored) for location London, UK and estimate an average sunrise and sunset time for each calendar month. Since sun events can move by up to one hour from start to end of a month, a span of at least 30 minutes is required to successfully include events across the entire month.
The output properties on successful trigger include a default message and a default payload, which includes the basic event details for the trigger entity. It is possible to set the output as a customised payload, which replaces msg.payload with specific output, or as a customised message, which replaces the entire message with a generated object.
(
+ $sunset:=$entities('sensor.sun_next_setting').state;
+ $togo:= ($toMillis($sunset)-$millis())/60000~>$floor();
+ $toset:= $togo < 180 ? $togo : $togo-1440;
+ $x:= $toset-25;
+ $pc:= $x<0 and $x>-55 ? $min([$abs($x)*2, 100]) : null;
+
+ {"topic": "Minutes to Sunset",
+ "payload": $toset,
+ "time": $entities('sensor.time').state,
+ "sunset": $sunset,
+ "percent": $pc
+ }
+)
+
The $entities()
function is available in JSONata in the output properties UI field, which means that both current time and sunset time can be obtained and compared. This allows some simple JSONata code to generate a complete message object, including msg.topic and msg.payload with the exact minutes to the next sunset correctly calculated.
For automation, all that is required is a following Switch node set to route on a message property, for example msg.payload as minutes to next sunset between 0 and 4 (based on a five minute trigger interval) so as to be able to run an automation just as sunset is about to take place (with a five minute discrimination).
As a further example, the JSONata output calculates a percentage value going from 0 to 100 over the period 25 minutes before to 25 minutes after sunset, with the percentage value set to 'null' outside this time. The Change node following also uses JSONata to create the entire Service Call node input settings, to turn on a group of lights in an timed-incremental fashion.
Also see:
`,21)),n("ul",null,[n("li",null,[t(a,{to:"/guide/jsonata/"},{default:o(()=>s[0]||(s[0]=[p("JSONata guide")])),_:1})]),n("li",null,[t(a,{to:"/guide/jsonata/jsonata-primer.html"},{default:o(()=>s[1]||(s[1]=[p("JSONata primer")])),_:1})])])])}const d=e(k,[["render",q],["__file","trigger-state.html.vue"]]),m=JSON.parse('{"path":"/cookbook/jsonata/trigger-state.html","title":"Trigger: state","lang":"en-US","frontmatter":{},"headers":[{"level":3,"title":"Using JSONata to set a condition value","slug":"using-jsonata-to-set-a-condition-value","link":"#using-jsonata-to-set-a-condition-value","children":[]},{"level":3,"title":"Using JSONata to create a custom message","slug":"using-jsonata-to-create-a-custom-message","link":"#using-jsonata-to-create-a-custom-message","children":[]}],"git":{"updatedTime":1719788083000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":2}]},"filePathRelative":"cookbook/jsonata/trigger-state.md"}');export{d as comp,m as data}; diff --git a/assets/update-config.html-D7qJGits.js b/assets/update-config.html-D7qJGits.js new file mode 100644 index 0000000000..995b9cbbed --- /dev/null +++ b/assets/update-config.html-D7qJGits.js @@ -0,0 +1,8 @@ +import{_ as k,c as i,e as c,a as s,d as a,b as t,w as o,r as p,o as u}from"./app-CefLgoES.js";const y="/node-red-contrib-home-assistant-websocket/assets/update_config_use01-BXE5av9O.png",d={};function g(m,n){const e=p("RouteLink"),r=p("InfoPanelOnly"),l=p("DocsOnly");return u(),i("div",null,[n[10]||(n[10]=c('The Update Config node allows you to update the configuration of entities within Home Assistant. This node is used for dynamically adjusting the settings of entities, ensuring they stay in sync with your automation requirements.
string
A name for the node.
string
All properties must be under msg.payload
and will be ignored if not.
Example of the msg.payload
object.
{
+ "id": "d9d27ac6b4ebf78f",
+ "icon": "mdi:lightbulb",
+ "name": "My Light",
+ "entityPicture": "/local/images/my_light.png"
+}
+
string
The node id of an entity config.
string
The icon to use for the entity.
string
The friendly name to use for the entity.
string
The entity picture to use for the entity.
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 component.
[{"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":[]}]
+
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.
[{"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":[]}]
+
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 component 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.
[{"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.
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.
[{"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"]]}]\n
[{"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"],[]]}]\n
An offset can also be added as shown in the next alarm sensor example.
',24))])}const b=p(g,[["render",d],["__file","using-date-and-time-entities-to-trigger-flows.html.vue"]]),h=JSON.parse('{"path":"/cookbook/using-date-and-time-entities-to-trigger-flows.html","title":"Using Date and Time entities to trigger flows","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Using the time node","slug":"using-the-time-node","link":"#using-the-time-node","children":[]},{"level":2,"title":"Home Assistant Companion app next alarm sensor","slug":"home-assistant-companion-app-next-alarm-sensor","link":"#home-assistant-companion-app-next-alarm-sensor","children":[{"level":3,"title":"Adding an offset and on/off switch","slug":"adding-an-offset-and-on-off-switch","link":"#adding-an-offset-and-on-off-switch","children":[]},{"level":3,"title":"Managing alarm from multiple phones","slug":"managing-alarm-from-multiple-phones","link":"#managing-alarm-from-multiple-phones","children":[]}]},{"level":2,"title":"Daily alarm using Datetime Entity","slug":"daily-alarm-using-datetime-entity","link":"#daily-alarm-using-datetime-entity","children":[]}],"git":{"updatedTime":1647350454000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":5},{"name":"Federico Granata","email":"3602209+Edo78@users.noreply.github.com","commits":1},{"name":"Mauricio Bonani","email":"bonanitech@gmail.com","commits":1}]},"filePathRelative":"cookbook/using-date-and-time-entities-to-trigger-flows.md"}');export{b as comp,h as data}; diff --git a/assets/using-date-and-time-entities-to-trigger-flows_01-Bs8pdEv6.png b/assets/using-date-and-time-entities-to-trigger-flows_01-Bs8pdEv6.png new file mode 100644 index 0000000000..f65908a6d0 Binary files /dev/null and b/assets/using-date-and-time-entities-to-trigger-flows_01-Bs8pdEv6.png differ diff --git a/assets/using-date-and-time-entities-to-trigger-flows_02-9ivWXXCe.png b/assets/using-date-and-time-entities-to-trigger-flows_02-9ivWXXCe.png new file mode 100644 index 0000000000..9623eae150 Binary files /dev/null and b/assets/using-date-and-time-entities-to-trigger-flows_02-9ivWXXCe.png differ diff --git a/assets/using-date-and-time-entities-to-trigger-flows_03-DsUrMQlD.png b/assets/using-date-and-time-entities-to-trigger-flows_03-DsUrMQlD.png new file mode 100644 index 0000000000..808d2b141d Binary files /dev/null and b/assets/using-date-and-time-entities-to-trigger-flows_03-DsUrMQlD.png differ diff --git a/assets/using-date-and-time-entities-to-trigger-flows_04-DMd9Dm05.png b/assets/using-date-and-time-entities-to-trigger-flows_04-DMd9Dm05.png new file mode 100644 index 0000000000..28e835a542 Binary files /dev/null and b/assets/using-date-and-time-entities-to-trigger-flows_04-DMd9Dm05.png differ diff --git a/assets/vacation-mode.html-nf33t2Mq.js b/assets/vacation-mode.html-nf33t2Mq.js new file mode 100644 index 0000000000..a24a7b9b00 --- /dev/null +++ b/assets/vacation-mode.html-nf33t2Mq.js @@ -0,0 +1,14 @@ +import{_ as p,c as o,e as n,a,b as e,w as u,r as c,o as r,d as l}from"./app-CefLgoES.js";const k="/node-red-contrib-home-assistant-websocket/assets/vacation-mode_01-3u-aevgN.png",q="/node-red-contrib-home-assistant-websocket/assets/vacation-mode_02-lxgo11VL.png",i="/node-red-contrib-home-assistant-websocket/assets/vacation-mode_03-DVtSIIvI.png",y="/node-red-contrib-home-assistant-websocket/assets/vacation-mode_04-CQ-MFwKC.png",g={};function d(m,s){const t=c("RouteLink");return r(),o("div",null,[s[1]||(s[1]=n('Four steps to adding a vacation mode to your home and having lights turn on and off at random intervals. It also automatically prompts you with a notification about turning on vacation mode if you have been gone longer than 24 hours.
[{"id":"eb756b3f.d770f8","type":"subflow","name":"Create HA Helpers","info":"","category":"","in":[{"x":84,"y":96,"wires":[{"id":"7f1493fc.1300fc"}]}],"out":[],"env":[{"name":"serverName","type":"str","value":"Home Assistant","ui":{"label":{"en-US":"HA Server Name"},"type":"input","opts":{"types":["str"]}}},{"name":"helpers","type":"json","value":"[]","ui":{"label":{"en-US":"Helpers"},"type":"input","opts":{"types":["json"]}}}],"color":"#DDAA99","status":{"x":246,"y":48,"wires":[{"id":"a66d5d93.8a5f","port":0}]}},{"id":"7f1493fc.1300fc","type":"function","z":"eb756b3f.d770f8","name":"process helpers","func":"const serverName = toCamelCase(env.get('serverName'));\\nconst haServer = global.get(\\"homeassistant\\")[serverName];\\nif(!haServer) {\\n node.error(\\"Invalid HA server name\\");\\n return;\\n}\\nconst states = haServer.states;\\nconst helpers = env.get(\\"helpers\\");\\n\\nhelpers.forEach(h => {\\n const entityId = `${h.type}.${h.id}`;\\n if(!states[entityId]) {\\n const {id, type, ...data} = h;\\n const apiData = {\\n entity: h,\\n payload: { \\n data \\n }\\n };\\n apiData.payload.data.type = `${type}/create`;\\n \\n node.send(apiData);\\n node.status({text: `Creating ${entityId}`});\\n }\\n});\\n\\nnode.done();\\n\\nfunction toCamelCase(str) {\\n return str.replace(/(?:^\\\\w|[A-Z]|\\\\b\\\\w|\\\\s+)/g, (match, index) => {\\n if (+match === 0) return '';\\n return index === 0 ? match.toLowerCase() : match.toUpperCase();\\n });\\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":224,"y":96,"wires":[["a4a0abd9.7576d8"]]},{"id":"a4a0abd9.7576d8","type":"ha-api","z":"eb756b3f.d770f8","name":"create helper","server":"","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"","dataType":"json","location":"payload","locationType":"msg","responseType":"json","x":390,"y":96,"wires":[["b76d0c56.d54b9"]]},{"id":"388bb5c7.47346a","type":"ha-api","z":"eb756b3f.d770f8","name":"rename helper entity id","server":"","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\t \\"type\\":\\"config/entity_registry/update\\",\\t \\"entity_id\\": entity.type & \\".\\" & payload.id,\\t \\"new_entity_id\\": entity.type & \\".\\" & entity.id\\t}","dataType":"jsonata","location":"payload","locationType":"msg","responseType":"json","x":788,"y":96,"wires":[[]]},{"id":"b76d0c56.d54b9","type":"switch","z":"eb756b3f.d770f8","name":"need to rename?","property":"payload.id","propertyType":"msg","rules":[{"t":"neq","v":"entity.id","vt":"msg"}],"checkall":"true","repair":false,"outputs":1,"x":570,"y":96,"wires":[["388bb5c7.47346a","3e47440a.eee2cc"]]},{"id":"a66d5d93.8a5f","type":"status","z":"eb756b3f.d770f8","name":"","scope":["7f1493fc.1300fc","3e47440a.eee2cc"],"x":124,"y":48,"wires":[[]]},{"id":"3e47440a.eee2cc","type":"function","z":"eb756b3f.d770f8","name":"rename status","func":"const oldEntityId = `${msg.entity.type}.${msg.payload.id}`;\\nconst newEntityId = `${msg.entity.type}.${msg.entity.id}`;\\n\\nnode.status({text: `Renaming ${oldEntityId} to ${newEntityId}`});","outputs":1,"noerr":0,"initialize":"","finalize":"","x":768,"y":144,"wires":[[]]},{"id":"fda663f6.59a56","type":"subflow","name":"Actionable Notification","info":"[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"","in":[{"x":84,"y":80,"wires":[{"id":"e9b697a.fc03268"}]}],"out":[{"x":1172,"y":128,"wires":[{"id":"57c0c58d.ffa1bc","port":0}]},{"x":1172,"y":176,"wires":[{"id":"57c0c58d.ffa1bc","port":1}]},{"x":1172,"y":224,"wires":[{"id":"57c0c58d.ffa1bc","port":2}]},{"x":964,"y":240,"wires":[{"id":"c3389099.bc489","port":1}]}],"env":[{"name":"service","type":"str","value":"","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"color":"#DDAA99","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"status":{"x":244,"y":272,"wires":[{"id":"d847d277.d448c","port":0}]}},{"id":"b4512e6b.640d9","type":"function","z":"fda663f6.59a56","name":"create service call","func":"const actions = [];\\n[1,2,3].forEach(i => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = env.get(`${name}Title`);\\n const uri = env.get(`${name}Uri`);\\n const action = uri.length ? 'URI' : title ? flow.get(`${name}Id`) : undefined;\\n \\n actions.push({\\n action,\\n title,\\n uri\\n });\\n});\\n\\nmsg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag: flow.get('notificationTag'),\\n actions,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.done();","outputs":1,"noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","x":298,"y":80,"wires":[["7a427ca2.c030e4"]]},{"id":"57c0c58d.ffa1bc","type":"switch","z":"fda663f6.59a56","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"d847d277.d448c","type":"status","z":"fda663f6.59a56","name":"","scope":["b4512e6b.640d9","c3389099.bc489","7955d8e6.b10888","7a427ca2.c030e4"],"x":124,"y":272,"wires":[[]]},{"id":"c3389099.bc489","type":"function","z":"fda663f6.59a56","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","x":832,"y":176,"wires":[["57c0c58d.ffa1bc"],[]]},{"id":"f814cdc6.def07","type":"switch","z":"fda663f6.59a56","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["4db9b8d.0c14a48"]]},{"id":"7596669d.feb668","type":"ha-api","z":"fda663f6.59a56","name":"get user info","server":"","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","location":"userData","locationType":"msg","responseType":"json","x":822,"y":128,"wires":[["c3389099.bc489"]]},{"id":"b745e475.d6cdf8","type":"server-events","z":"fda663f6.59a56","name":"mobile_app_notification_cleared","server":"","event_type":"mobile_app_notification_cleared","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"x":194,"y":224,"wires":[["f814cdc6.def07"]]},{"id":"4db9b8d.0c14a48","type":"switch","z":"fda663f6.59a56","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["7596669d.feb668"],["c3389099.bc489"]]},{"id":"e9b697a.fc03268","type":"switch","z":"fda663f6.59a56","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["b4512e6b.640d9"],["7955d8e6.b10888"]],"l":false},{"id":"7955d8e6.b10888","type":"function","z":"fda663f6.59a56","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","x":318,"y":128,"wires":[["7a427ca2.c030e4"]]},{"id":"520a884c.35f9c8","type":"server-events","z":"fda663f6.59a56","name":"mobile_app_notification_action","server":"","event_type":"mobile_app_notification_action","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"x":194,"y":176,"wires":[["f814cdc6.def07"]]},{"id":"7a427ca2.c030e4","type":"api-call-service","z":"fda663f6.59a56","name":"","server":"","version":1,"debugenabled":false,"service_domain":"notify","service":"","entityId":"","data":"","dataType":"json","mergecontext":"callServiceData","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":550,"y":80,"wires":[[]]},{"id":"aa02fa4a.a35548","type":"inject","z":"9530fd91.600e2","name":"","repeat":"","crontab":"*/6 16-23 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":118,"y":336,"wires":[["7e966ac8.609524"]]},{"id":"cfa1b8e4.8d4a08","type":"time-range-switch","z":"9530fd91.600e2","name":"","startTime":"sunset","endTime":"23:59","startOffset":0,"endOffset":0,"x":496,"y":336,"wires":[["c7efc558.da1bc8"],[]]},{"id":"a41f4d83.d45c4","type":"ha-get-entities","z":"9530fd91.600e2","server":"","name":"","rules":[{"property":"entity_id","logic":"in_group","value":"group.vacation_lights","valueType":"str"},{"property":"state","logic":"is","value":"off","valueType":"str"}],"output_type":"random","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":806,"y":336,"wires":[["f3897d20.fffee"]]},{"id":"c7efc558.da1bc8","type":"function","z":"9530fd91.600e2","name":"25%","func":"const random = Math.round(Math.random() * 100);\\n\\nif(random < 75) {\\n node.status({fill: \\"red\\", text: random});\\n return;\\n}\\n\\nnode.status({fill: \\"green\\", text: random});\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":658,"y":336,"wires":[["a41f4d83.d45c4"]]},{"id":"f3897d20.fffee","type":"api-call-service","z":"9530fd91.600e2","name":"turn on","server":"","version":1,"debugenabled":false,"service_domain":"homeassistant","service":"turn_on","entityId":"{{payload.entity_id}}","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":956,"y":336,"wires":[["20fce293.98ce8e"]]},{"id":"20fce293.98ce8e","type":"delay","z":"9530fd91.600e2","name":"","pauseType":"random","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"5","randomLast":"36","randomUnits":"minutes","drop":false,"x":1100,"y":336,"wires":[["3d851328.fb1b8c"]]},{"id":"3d851328.fb1b8c","type":"api-call-service","z":"9530fd91.600e2","name":"turn off","server":"","version":1,"debugenabled":false,"service_domain":"homeassistant","service":"turn_off","entityId":"{{payload.entity_id}}","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":1234,"y":336,"wires":[[]]},{"id":"7e966ac8.609524","type":"api-current-state","z":"9530fd91.600e2","name":"vacation mode on?","server":"","version":1,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is_not","override_topic":true,"entity_id":"input_boolean.vacation_mode","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":298,"y":336,"wires":[[],["cfa1b8e4.8d4a08"]]},{"id":"22f34ec4.9d3a22","type":"comment","z":"9530fd91.600e2","name":"Vacation Lights","info":"","x":112,"y":288,"wires":[]},{"id":"7003285c.a3a8c8","type":"api-call-service","z":"9530fd91.600e2","name":"turn on vacation mode","server":"","version":1,"debugenabled":false,"service_domain":"input_boolean","service":"turn_on","entityId":"input_boolean.vacation_mode","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":788,"y":192,"wires":[["4971bda0.0758f4"]]},{"id":"4971bda0.0758f4","type":"api-call-service","z":"9530fd91.600e2","name":"notify jason","server":"","version":1,"debugenabled":false,"service_domain":"notify","service":"mobile_app_jason","entityId":"","data":"{\\"title\\":\\"Vacation Mode\\",\\"message\\":\\"Vacation Mode has been enabled.\\"}","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":982,"y":192,"wires":[[]]},{"id":"bf80cebf.58bcc","type":"server-state-changed","z":"9530fd91.600e2","name":"home/away","server":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"person.jason","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"home","halt_if_type":"str","halt_if_compare":"is_not","outputs":2,"output_only_on_state_change":true,"for":"1","forType":"num","forUnits":"days","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":102,"y":192,"wires":[["7d414deb.904f64"],["c81cbf7d.abe22"]]},{"id":"c81cbf7d.abe22","type":"api-call-service","z":"9530fd91.600e2","name":"vacation mode off","server":"","version":1,"debugenabled":false,"service_domain":"input_boolean","service":"turn_off","entityId":"input_boolean.vacation_mode","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":314,"y":240,"wires":[[]]},{"id":"7d414deb.904f64","type":"api-current-state","z":"9530fd91.600e2","name":"vacation mode on?","server":"","version":1,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_boolean.vacation_mode","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":314,"y":192,"wires":[["dca703ed.f3222"],[]]},{"id":"dca703ed.f3222","type":"subflow:fda663f6.59a56","z":"9530fd91.600e2","name":"turn on vacation mode?","env":[{"name":"service","value":"mobile_app_jason","type":"str"},{"name":"title","value":"Vacation Mode","type":"str"},{"name":"message","value":"You've been aways for 24 hours. Do you want to turn on vacation mode?","type":"str"},{"name":"action1Title","value":"Yes","type":"str"},{"name":"action2Title","value":"No","type":"str"},{"name":"group","value":"","type":"str"}],"x":542,"y":208,"wires":[["7003285c.a3a8c8"],[],[],[]]},{"id":"d4a03a04.6c78f8","type":"subflow:eb756b3f.d770f8","z":"9530fd91.600e2","name":"create input_boolean","env":[{"name":"helpers","value":"[{\\"id\\":\\"vacation_mode\\",\\"type\\":\\"input_boolean\\",\\"name\\":\\"Vacation Mode\\",\\"icon\\":\\"mdi:beach\\"}]","type":"json"}],"x":292,"y":96,"wires":[]},{"id":"f3d6f382.70bfe","type":"inject","z":"9530fd91.600e2","name":"Click me to","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":108,"y":96,"wires":[["d4a03a04.6c78f8"]]},{"id":"4a22094b.635368","type":"comment","z":"9530fd91.600e2","name":"Setup","info":"","x":82,"y":48,"wires":[]},{"id":"7ce57cf8.cad134","type":"comment","z":"9530fd91.600e2","name":"Actionable notification to turn on vacation mode","info":"","x":212,"y":144,"wires":[]}]\n
Create an input boolean in Home Assistant that will control if the house is in vacation mode.
or via the helpers menu in the Home Assistant UI or add it manually in YAML
input_boolean:
+ vacation_mode:
+ name: Vacation Mode
+ icon: mdi:beach
+
Create a flow that will automatically change the vacation mode to off if we come home. Secondly will send our phone an actionable notification for android asking if we want to turn on vacation mode if we have been gone longer than 24 hours.
[{"id":"fda663f6.59a56","type":"subflow","name":"Actionable Notification","info":"[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\\n","category":"","in":[{"x":84,"y":80,"wires":[{"id":"e9b697a.fc03268"}]}],"out":[{"x":1172,"y":128,"wires":[{"id":"57c0c58d.ffa1bc","port":0}]},{"x":1172,"y":176,"wires":[{"id":"57c0c58d.ffa1bc","port":1}]},{"x":1172,"y":224,"wires":[{"id":"57c0c58d.ffa1bc","port":2}]},{"x":964,"y":240,"wires":[{"id":"c3389099.bc489","port":1}]}],"env":[{"name":"service","type":"str","value":"","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}}],"color":"#DDAA99","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"status":{"x":244,"y":272,"wires":[{"id":"d847d277.d448c","port":0}]}},{"id":"b4512e6b.640d9","type":"function","z":"fda663f6.59a56","name":"create service call","func":"const actions = [];\\n[1,2,3].forEach(i => {\\n const name = `action${i}`\\n const id = flow.get(`${name}Id`);\\n const title = env.get(`${name}Title`);\\n const uri = env.get(`${name}Uri`);\\n const action = uri.length ? 'URI' : title ? flow.get(`${name}Id`) : undefined;\\n \\n actions.push({\\n action,\\n title,\\n uri\\n });\\n});\\n\\nmsg._originalPayload = msg.payload;\\nflow.set('latestMessage', msg);\\n\\nconst services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n title: env.get('title'),\\n message: env.get('message'),\\n data: {\\n tag: flow.get('notificationTag'),\\n actions,\\n color: env.get(\\"color\\"),\\n group: env.get(\\"group\\"),\\n sticky: env.get(\\"sticky\\"),\\n timeout: env.get(\\"timeout\\"),\\n icon: env.get(\\"icon\\")\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.done();","outputs":1,"noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\\n\\n[1,2,3].forEach(i => {\\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\\n})\\n\\n\\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","x":298,"y":80,"wires":[["7a427ca2.c030e4"]]},{"id":"57c0c58d.ffa1bc","type":"switch","z":"fda663f6.59a56","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"d847d277.d448c","type":"status","z":"fda663f6.59a56","name":"","scope":["b4512e6b.640d9","c3389099.bc489","7955d8e6.b10888","7a427ca2.c030e4"],"x":124,"y":272,"wires":[[]]},{"id":"c3389099.bc489","type":"function","z":"fda663f6.59a56","name":"build message","func":"const latestMessage = flow.get('latestMessage');\\nconst event = msg.payload.event;\\n\\nlatestMessage.eventData = msg.payload;\\nlatestMessage.payload = latestMessage._originalPayload;\\ndelete latestMessage._originalPayload;\\n\\nif(env.get('userInfo')) {\\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\\n latestMessage.userData = userData;\\n}\\n\\nif(msg.event_type === 'mobile_app_notification_cleared') {\\n node.status({\\n text: `cleared at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'blue'\\n });\\n \\n return [null, latestMessage];\\n}\\n\\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\\nnode.status({\\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\\n shape: 'dot',\\n fill: 'green'\\n});\\n\\nreturn latestMessage;\\n\\n\\nfunction getPrettyDate() {\\n return new Date().toLocaleDateString('en-US', {\\n month: 'short',\\n day: 'numeric',\\n hour12: false,\\n hour: 'numeric',\\n minute: 'numeric',\\n });\\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","x":832,"y":176,"wires":[["57c0c58d.ffa1bc"],[]]},{"id":"f814cdc6.def07","type":"switch","z":"fda663f6.59a56","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["4db9b8d.0c14a48"]]},{"id":"7596669d.feb668","type":"ha-api","z":"fda663f6.59a56","name":"get user info","server":"","debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\\"type\\": \\"config/auth/list\\"}","dataType":"json","location":"userData","locationType":"msg","responseType":"json","x":822,"y":128,"wires":[["c3389099.bc489"]]},{"id":"b745e475.d6cdf8","type":"server-events","z":"fda663f6.59a56","name":"mobile_app_notification_cleared","server":"","event_type":"mobile_app_notification_cleared","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"x":194,"y":224,"wires":[["f814cdc6.def07"]]},{"id":"4db9b8d.0c14a48","type":"switch","z":"fda663f6.59a56","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["7596669d.feb668"],["c3389099.bc489"]]},{"id":"e9b697a.fc03268","type":"switch","z":"fda663f6.59a56","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["b4512e6b.640d9"],["7955d8e6.b10888"]],"l":false},{"id":"7955d8e6.b10888","type":"function","z":"fda663f6.59a56","name":"create clear notification","func":"const services = env.get('service');\\nif(!services) {\\n node.status({\\n text: 'no services defined',\\n shape: 'ring',\\n fill: 'red'\\n });\\n return; \\n}\\n\\nservices.trim().split(/,\\\\s*/).forEach(service => {\\n if(!service) return;\\n \\n msg.payload = {\\n service,\\n data: {\\n message: \\"clear_notification\\",\\n data: {\\n tag: flow.get('notificationTag'),\\n }\\n }\\n };\\n node.send(msg);\\n});\\n\\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","x":318,"y":128,"wires":[["7a427ca2.c030e4"]]},{"id":"520a884c.35f9c8","type":"server-events","z":"fda663f6.59a56","name":"mobile_app_notification_action","server":"","event_type":"mobile_app_notification_action","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"x":194,"y":176,"wires":[["f814cdc6.def07"]]},{"id":"7a427ca2.c030e4","type":"api-call-service","z":"fda663f6.59a56","name":"","server":"","version":1,"debugenabled":false,"service_domain":"notify","service":"","entityId":"","data":"","dataType":"json","mergecontext":"callServiceData","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":550,"y":80,"wires":[[]]},{"id":"7003285c.a3a8c8","type":"api-call-service","z":"9530fd91.600e2","name":"turn on vacation mode","server":"","version":1,"debugenabled":false,"service_domain":"input_boolean","service":"turn_on","entityId":"input_boolean.vacation_mode","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":788,"y":192,"wires":[["4971bda0.0758f4"]]},{"id":"4971bda0.0758f4","type":"api-call-service","z":"9530fd91.600e2","name":"notify jason","server":"","version":1,"debugenabled":false,"service_domain":"notify","service":"mobile_app_jason","entityId":"","data":"{\\"title\\":\\"Vacation Mode\\",\\"message\\":\\"Vacation Mode has been enabled.\\"}","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":982,"y":192,"wires":[[]]},{"id":"bf80cebf.58bcc","type":"server-state-changed","z":"9530fd91.600e2","name":"home/away","server":"","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"person.jason","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"home","halt_if_type":"str","halt_if_compare":"is_not","outputs":2,"output_only_on_state_change":true,"for":"1","forType":"num","forUnits":"days","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":102,"y":192,"wires":[["7d414deb.904f64"],["c81cbf7d.abe22"]]},{"id":"c81cbf7d.abe22","type":"api-call-service","z":"9530fd91.600e2","name":"vacation mode off","server":"","version":1,"debugenabled":false,"service_domain":"input_boolean","service":"turn_off","entityId":"input_boolean.vacation_mode","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":314,"y":240,"wires":[[]]},{"id":"7d414deb.904f64","type":"api-current-state","z":"9530fd91.600e2","name":"vacation mode on?","server":"","version":1,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_boolean.vacation_mode","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":314,"y":192,"wires":[["dca703ed.f3222"],[]]},{"id":"dca703ed.f3222","type":"subflow:fda663f6.59a56","z":"9530fd91.600e2","name":"turn on vacation mode?","env":[{"name":"service","value":"mobile_app_jason","type":"str"},{"name":"title","value":"Vacation Mode","type":"str"},{"name":"message","value":"You've been aways for 24 hours. Do you want to turn on vacation mode?","type":"str"},{"name":"action1Title","value":"Yes","type":"str"},{"name":"action2Title","value":"No","type":"str"},{"name":"group","value":"","type":"str"}],"x":542,"y":208,"wires":[["7003285c.a3a8c8"],[],[],[]]},{"id":"7ce57cf8.cad134","type":"comment","z":"9530fd91.600e2","name":"Actionable notification to turn on vacation mode","info":"","x":212,"y":144,"wires":[]}]\n
Also See
',15)),a("ul",null,[a("li",null,[e(t,{to:"/cookbook/actionable-notifications-subflow-for-android.html"},{default:u(()=>s[0]||(s[0]=[l("Actionable notifactions subflow for android")])),_:1})])]),s[2]||(s[2]=n(`Set up a group of lights and switches in Home Assistant that you want to turn on and off while vacation mode is active. This can be done without creating a group in Home Assistant by modifying the get-entities
node in the below flow to entity_id
in
light.night_light,light.kitchen,switch.bedroom_light,switch.laundry_room
.
group:
+ vacation_lights:
+ name: Vacation Lights
+ entities:
+ - light.night_light
+ - light.kitchen
+ - switch.bedroom_light
+ - switch.laundry_room
+
This flow will run between sunset and midnight turning lights on and off at random intervals if vacation mode is enabled.
[{"id":"aa02fa4a.a35548","type":"inject","z":"9530fd91.600e2","name":"","repeat":"","crontab":"*/6 16-23 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":118,"y":336,"wires":[["7e966ac8.609524"]]},{"id":"cfa1b8e4.8d4a08","type":"time-range-switch","z":"9530fd91.600e2","name":"","startTime":"sunset","endTime":"23:59","startOffset":0,"endOffset":0,"x":496,"y":336,"wires":[["c7efc558.da1bc8"],[]]},{"id":"a41f4d83.d45c4","type":"ha-get-entities","z":"9530fd91.600e2","server":"","name":"","rules":[{"property":"entity_id","logic":"in_group","value":"group.vacation_lights","valueType":"str"},{"property":"state","logic":"is","value":"off","valueType":"str"}],"output_type":"random","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":806,"y":336,"wires":[["f3897d20.fffee"]]},{"id":"c7efc558.da1bc8","type":"function","z":"9530fd91.600e2","name":"25%","func":"const random = Math.round(Math.random() * 100);\\n\\nif(random < 75) {\\n node.status({fill: \\"red\\", text: random});\\n return;\\n}\\n\\nnode.status({fill: \\"green\\", text: random});\\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":658,"y":336,"wires":[["a41f4d83.d45c4"]]},{"id":"f3897d20.fffee","type":"api-call-service","z":"9530fd91.600e2","name":"turn on","server":"","version":1,"debugenabled":false,"service_domain":"homeassistant","service":"turn_on","entityId":"{{payload.entity_id}}","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":956,"y":336,"wires":[["20fce293.98ce8e"]]},{"id":"20fce293.98ce8e","type":"delay","z":"9530fd91.600e2","name":"","pauseType":"random","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"5","randomLast":"36","randomUnits":"minutes","drop":false,"x":1100,"y":336,"wires":[["3d851328.fb1b8c"]]},{"id":"3d851328.fb1b8c","type":"api-call-service","z":"9530fd91.600e2","name":"turn off","server":"","version":1,"debugenabled":false,"service_domain":"homeassistant","service":"turn_off","entityId":"{{payload.entity_id}}","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":1234,"y":336,"wires":[[]]},{"id":"7e966ac8.609524","type":"api-current-state","z":"9530fd91.600e2","name":"vacation mode on?","server":"","version":1,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is_not","override_topic":true,"entity_id":"input_boolean.vacation_mode","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":298,"y":336,"wires":[[],["cfa1b8e4.8d4a08"]]},{"id":"22f34ec4.9d3a22","type":"comment","z":"9530fd91.600e2","name":"Vacation Lights","info":"","x":112,"y":288,"wires":[]}]
+
A list of entities to wait for the condition to be met. The node will wait for any of the entities to meet the condition. If the entity is not found it will be ignored.
The types that can be used are: entity id, substring, and regex.
light.living_room
- Will wait for the entity with the id light.living_room
motion
- Will wait for any entity that has motion
in the entity id^light\\..*
- Will wait for any entity that starts with light.
string
The property
field will be checked against the value
field using the comparator.
number
The amount of time to wait for the condition to become true before deactivating the node and passing the message object to the second output. If the timeout is equal to zero the node will wait indefinitely for the condition to be met.
Warning
If using a Node-RED global or flow context variable in the timeout field it will not update the timeout value if the context value changes after the node has been deployed.
boolean
When an input is received it will check the comparator against the current state instead of waiting for a state change.
INFO
This is only available when there is only one entity selected
If the received message has this property set to any value the node will be set to inactive and the timeout cleared.
object
{
+ "entities": {
+ "entity": ["light.living_room", "light.bedroom"],
+ "substring": ["light."],
+ "regex": ["light.*"]
+ },
+ "property": "state",
+ "comparator": "is",
+ "value": "on",
+ "valueType": "str",
+ "timeout": 10,
+ "timeoutUnits": "seconds",
+ "checkCurrentState": false
+}
+
{
+ "entities": "light.living_room"
+}
+
{
+ "entities": ["light.living_room", "light.bedroom"]
+}
+
Value types:
entity
: entity data of the triggered entity. Will be undefined
if multiple entities are selected and the timeout occurs.config
: config properties of the nodeThe Webhook node outputs data received from a created webhook in Home Assistant. Webhooks are a way to receive data or trigger automations from external sources, and this node allows you to integrate those triggers into your Node-RED flows.
Warning
Needs Custom Integration installed in Home Assistant for this node to function
list
A list of allowed methods that Home Assistant will accept for the webhook. At least one method must be selected.
string
When an entity is selected a switch entity will be created in Home Assistant. Turning on and off this switch will disable/enable the nodes in Node-RED.
Value types:
received data
: The parsed body from the webhook requesttrigger id
: webhook idheaders
: entity state of the triggered entityparams
: Query string parametersconfig
: config properties of the nodeThe Zone node outputs data when a configured entity enters or leaves one of the defined zones in Home Assistant. Zones are geographical areas you define in Home Assistant, and this node can trigger automations based on whether entities (like phones or trackers) enter or exit these zones.
array
An array of entity ids to monitor for zone changes.
string
Set when to check an entity. Either entering or leaving a zone.
array
An array of zone ids to check when a configured entity updates.
string
The entity id of the device/person that triggered the update.
string
The state of the device/person entity that triggered the update.
object
The entity object of the device/person that triggered the update.
array
An array of zone entities where the device/person entity entered/left after an update of location.
',25)]))}const d=t(o,[["render",s],["__file","zone.html.vue"]]),h=JSON.parse('{"path":"/node/zone.html","title":"Zone","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Configuration","slug":"configuration","link":"#configuration","children":[{"level":3,"title":"Entities","slug":"entities","link":"#entities","children":[]},{"level":3,"title":"Event","slug":"event","link":"#event","children":[]},{"level":3,"title":"Zones","slug":"zones","link":"#zones","children":[]}]},{"level":2,"title":"Outputs","slug":"outputs","link":"#outputs","children":[{"level":3,"title":"topic","slug":"topic","link":"#topic","children":[]},{"level":3,"title":"payload","slug":"payload","link":"#payload","children":[]},{"level":3,"title":"data","slug":"data","link":"#data","children":[]},{"level":3,"title":"zones","slug":"zones-1","link":"#zones-1","children":[]}]}],"git":{"updatedTime":1723606857000,"contributors":[{"name":"Jason","email":"37859597+zachowj@users.noreply.github.com","commits":4}]},"filePathRelative":"node/zone.md"}');export{d as comp,h as data}; diff --git a/cookbook/actionable-notifications-subflow-for-android.html b/cookbook/actionable-notifications-subflow-for-android.html new file mode 100644 index 0000000000..8ef41b7790 --- /dev/null +++ b/cookbook/actionable-notifications-subflow-for-android.html @@ -0,0 +1,50 @@ + + + + + + + + +