I have a node-red subflow I wanted to share, in case others had need for it.
DESCRIPTION
The subflow should be used on the output of a hubitat command node. It will check the end device and verify that the command worked/had its intended function. If not, the subflow will attempt to re-send the command up to 3x.
NOTE - the subflow only support specific commands/attributes! Since the subflow needs to know exactly what attribute to look at when a command is done, they all have to be hard coded in! See below for some additional information on adding your own command/attribute pairs.
Currently the subflow supports the commands:
- on, off, setLevel, lock, unlock, arrived, departed, and the custom commands from the GE/Jasco Motion Dimmer/Switch devices - Manual, Occupancy, Vacancy, setDefaultDimmerLevel, and setLightTimeout.
PREREQUISITES
- Hubitat Nodes for Node-RED v1.7 or later
- Hubitat Nodes for Node-RED setup and fully working
IMPLEMENTATION
- IMPORT subflow code below to node-red
- Edit subflow
- Edit the hubitat "device" node and change the "server" setting to the one configured for your hub. Click Done.
- Edit the hubitat "command" node and change the "server" setting to the one configured for your hub. Click Done.
- Deploy changed flows/subflows
- Use the subflow after a hubitat command node in a flow. Similar to this:
FEATURES
- Subflow will show Failure, Success, or the current delay in the status area.
- Failure - device status did not match what was expected from the command.
- Success - device status did match what was expected from the command.
- Delay... - The subflow is waiting for the command to finish before checking the status. After the delay it will proceed to Failure or Success
- The delay to wait for the command to finish is adjustable as a subflow property. By default it is 1000ms. Some devices (like GE dimmers that dim down to off, for example) sometimes need longer delays before the attributes update. It can be adjust on a node by node basis.
- Subflow automatically adjusts the delay before checking if you pass it a setLevel command that has a duration specified in the arguments.
ADDING NEW COMMANDS/ATTRIBUTES:
To add support for new commands/attributes, the code in two function nodes in the subflow needs to be updated:
In the 1st one, the new command needs to be added to the "switch" statement, defining which attribute needs to be checked.
In the "Compare" function node, the "switch" statement also needs to be updated, defining what actually needs to be compared between the command and attribute.
SUBFLOW CODE
CODE
[
{
"id": "d7dbc436.02cd78",
"type": "subflow",
"name": "Hubitat Command Check",
"info": "### Inputs\r\nThis node must be placed immediately after a Hubitat Command node, as it uses values from the response of the command node.\r\n\r\n### Outputs\r\nThe output message is sanitized of Hubitat specific values, so that commands can be daisy chained with no fear of unintended interaction from the previous Hubitat commands.",
"category": "",
"in": [
{
"x": 60,
"y": 100,
"wires": [
{
"id": "ecc481b9.7d3d1"
}
]
}
],
"out": [
{
"x": 940,
"y": 180,
"wires": [
{
"id": "410f04c8906919e7",
"port": 0
}
]
}
],
"env": [
{
"name": "delayInMs",
"type": "num",
"value": "1000"
}
],
"meta": {},
"color": "#DDAA99",
"status": {
"x": 1220,
"y": 260,
"wires": [
{
"id": "28dba094.bdd1",
"port": 0
},
{
"id": "198288c1.729167",
"port": 0
},
{
"id": "31773f7a.abb72",
"port": 0
},
{
"id": "1f33a4d2.e91c0b",
"port": 0
}
]
}
},
{
"id": "f9c75c9c.41425",
"type": "do-return",
"z": "d7dbc436.02cd78",
"name": "",
"mode": "done",
"x": 1870,
"y": 80,
"wires": []
},
{
"id": "2e641855.b3ccd8",
"type": "hubitat device",
"z": "d7dbc436.02cd78",
"deviceLabel": "",
"name": "",
"server": "4f6ecd8f2eafd25d",
"deviceId": "",
"attribute": "",
"sendEvent": false,
"x": 1370,
"y": 80,
"wires": [
[
"3b6f2d01.7301e2"
]
]
},
{
"id": "162dd42f.0b3e0c",
"type": "function",
"z": "d7dbc436.02cd78",
"name": "Create device node params",
"func": "msg.deviceId = msg.response.id;\n\nswitch(msg.requestCommand){\n // switch\n case \"on\":\n case \"off\":\n msg.attribute = \"switch\";\n break;\n // level\n case \"setLevel\":\n if (msg.requestArguments.split(',')[0] == 0) {\n msg.attribute = \"switch\";\n } else\n {\n msg.attribute = \"level\";\n }\n break;\n // lock\n case \"lock\":\n case \"unlock\":\n msg.attribute = \"lock\";\n break;\n // presence\n case \"arrived\":\n case \"departed\":\n msg.attribute = \"presence\";\n break;\n // Jasco Motion Dimmer/Switch Specific Commands \n case \"Manual\":\n case \"Occupancy\":\n case \"Vacancy\":\n msg.attribute = \"operatingMode\";\n break;\n case \"setDefaultDimmerLevel\":\n msg.attribute = \"defaultDimmerLevel\";\n break;\n case \"setLightTimeout\":\n msg.attribute = \"lightTimeout\";\n break;\n}\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1180,
"y": 80,
"wires": [
[
"2e641855.b3ccd8"
]
]
},
{
"id": "e560d1ba.6ca2f",
"type": "delay",
"z": "d7dbc436.02cd78",
"name": "",
"pauseType": "delayv",
"timeout": "500",
"timeoutUnits": "milliseconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"outputs": 1,
"x": 960,
"y": 80,
"wires": [
[
"162dd42f.0b3e0c"
]
]
},
{
"id": "96b990cc.531ff",
"type": "function",
"z": "d7dbc436.02cd78",
"name": "Calculate Delay",
"func": "msg.delay = env.get(\"delayInMs\");\n\nif (msg.requestCommand==\"setLevel\") {\n if (msg.requestArguments.split(',')[1]) {\n msg.delay = msg.delay + (msg.requestArguments.split(',')[1] * 1000);\n }\n}\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 780,
"y": 80,
"wires": [
[
"e560d1ba.6ca2f",
"198288c1.729167"
]
]
},
{
"id": "ecc481b9.7d3d1",
"type": "switch",
"z": "d7dbc436.02cd78",
"name": "Has command?",
"property": "requestCommand",
"propertyType": "msg",
"rules": [
{
"t": "nnull"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 200,
"y": 100,
"wires": [
[
"b0f7cbc8.06f378"
],
[
"31773f7a.abb72"
]
]
},
{
"id": "b0f7cbc8.06f378",
"type": "switch",
"z": "d7dbc436.02cd78",
"name": "Has response.id?",
"property": "response.id",
"propertyType": "msg",
"rules": [
{
"t": "nnull"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 410,
"y": 80,
"wires": [
[
"de092a82.b50988"
],
[
"1f33a4d2.e91c0b"
]
]
},
{
"id": "3b6f2d01.7301e2",
"type": "function",
"z": "d7dbc436.02cd78",
"name": "Compare",
"func": "var itMatches = false;\n\nswitch(msg.requestCommand){\n // Any item you can compare command and attribute value directly\n case \"on\":\n case \"off\":\n case \"Manual\":\n case \"Vacancy\":\n if (msg.requestCommand == msg.payload.value) {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n }\n break;\n // Any item you can compare arguments and attribute value directly \n case \"setDefaultDimmerLevel\":\n case \"setLightTimeout\":\n if (msg.requestArguments == msg.payload.value) {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n msg.arguments = msg.requestArguments; \n }\n break;\n //\n // Special cases below here\n //\n // Level has to take into account multiple arguments like duration\n case \"setLevel\":\n if (msg.requestArguments.split(',')[0] == 0) {\n if (msg.payload.value == \"off\") {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n msg.arguments = msg.requestArguments; \n }\n } else {\n if (msg.payload.value == msg.requestArguments.split(',')[0]) {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n msg.arguments = msg.requestArguments; \n }\n }\n break;\n // Presence has to be massaged\n case \"arrived\":\n if (msg.payload.value == \"present\") {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n }\n break;\n case \"departed\":\n if (msg.payload.value == \"not present\") {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n }\n break;\n // Jasco Motion dimmer/switch driver added the wording (default) to Occupancy. Oops. \n case \"Occupancy\":\n if ((msg.requestCommand + \" (default)\") == msg.payload.value) {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n }\n break;\n // Locks\n case \"lock\":\n if (msg.payload.value == \"locked\") {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n msg.arguments = msg.requestArguments;\n }\n break;\n case \"unlock\":\n if (msg.payload.value == \"unlocked\") {\n itMatches = true;\n } else {\n itMatches = false;\n msg.command = msg.requestCommand;\n msg.arguments = msg.requestArguments;\n }\n break;\n}\n\nmsg.itMatches = itMatches;\n\nif (itMatches) {\n return [msg, null]; \n} else {\n return [null, msg];\n}\n",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1520,
"y": 80,
"wires": [
[
"43a3afe6.34d54"
],
[
"9bde0df0.121cc"
]
]
},
{
"id": "43a3afe6.34d54",
"type": "do-return",
"z": "d7dbc436.02cd78",
"name": "",
"mode": "abort",
"x": 1870,
"y": 20,
"wires": []
},
{
"id": "28dba094.bdd1",
"type": "change",
"z": "d7dbc436.02cd78",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "itMatches ? \"Success\" : \"Failure\"",
"tot": "jsonata"
},
{
"t": "delete",
"p": "command",
"pt": "msg"
},
{
"t": "delete",
"p": "arguments",
"pt": "msg"
},
{
"t": "delete",
"p": "deviceId",
"pt": "msg"
},
{
"t": "delete",
"p": "attribute",
"pt": "msg"
},
{
"t": "delete",
"p": "itMatches",
"pt": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 780,
"y": 120,
"wires": [
[
"410f04c8906919e7"
]
]
},
{
"id": "9bde0df0.121cc",
"type": "hubitat command",
"z": "d7dbc436.02cd78",
"deviceLabel": "",
"name": "",
"server": "4f6ecd8f2eafd25d",
"deviceId": "",
"command": "",
"commandArgs": "",
"x": 1740,
"y": 80,
"wires": [
[
"f9c75c9c.41425"
]
]
},
{
"id": "de092a82.b50988",
"type": "do",
"z": "d7dbc436.02cd78",
"name": "",
"tasks": [
"Task 1",
"Task 2",
"Task 3"
],
"outputs": 4,
"x": 610,
"y": 80,
"wires": [
[
"96b990cc.531ff"
],
[
"96b990cc.531ff"
],
[
"96b990cc.531ff"
],
[
"28dba094.bdd1"
]
]
},
{
"id": "198288c1.729167",
"type": "change",
"z": "d7dbc436.02cd78",
"name": "Update status",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "(delay / 1000) & \"s delay...\"",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1020,
"y": 120,
"wires": [
[]
]
},
{
"id": "31773f7a.abb72",
"type": "change",
"z": "d7dbc436.02cd78",
"name": "Update status",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "Failed command check",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 660,
"y": 260,
"wires": [
[]
]
},
{
"id": "1f33a4d2.e91c0b",
"type": "change",
"z": "d7dbc436.02cd78",
"name": "Update status",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "Failed response.id check",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 660,
"y": 220,
"wires": [
[]
]
},
{
"id": "ddf24629e572aea4",
"type": "comment",
"z": "d7dbc436.02cd78",
"name": "Documentation",
"info": "DESCRIPTION\nThe subflow should be used on the output of a hubitat command node. It will check the end device and verify that the command worked/had its intended function. If not, the subflow will attempt to re-send the command up to 3x.\n\nNOTE - the subflow only support specific commands/attributes! Since the subflow needs to know exactly what attribute to look at when a command is done, they all have to be hard coded in! See below for some additional information on adding your own command/attribute pairs.\n\nCurrently the subflow supports the commands:\non, off, setLevel, arrived, departed, lock, unlock, and the custom commands from the GE/Jasco Motion Dimmer/Switch devices - Manual, Occupancy, Vacancy, setDefaultDimmerLevel, and setLightTimeout.\n\n\nFEATURES\nSubflow will show Failure, Success, or the current delay in the status area.\nFailure - device status did not match what was expected from the command.\nSuccess - device status did match what was expected from the command.\nDelay... - The subflow is waiting for the command to finish before checking the status. After the delay it will proceed to Failure or Success\n\nThe delay to wait for the command to finish is adjustable as a subflow property. By default it is 1000ms. Some devices (like GE dimmers that dim down to off, for example) sometimes need longer delays before the attributes update. It can be adjust on a node by node basis.\n\nThe subflow automatically adjusts the delay before checking if you pass it a setLevel command that has a duration specified in the arguments.\n\nADDING NEW COMMANDS/ATTRIBUTES:\nTo add support for new commands/attributes, the code in two function nodes in the subflow needs to be updated: \"Create device node params\" and \"Compare\"\n\nIn the \"Create device node params\" function node, the new command needs to be added to the switch statement, defining which attribute needs to be checked.\n\nIn the \"Compare\" function node, the switch statement also needs to be updated defining what actually needs to be compared between the command and attribute.",
"x": 420,
"y": 320,
"wires": []
},
{
"id": "410f04c8906919e7",
"type": "delay",
"z": "d7dbc436.02cd78",
"name": "",
"pauseType": "delay",
"timeout": "50",
"timeoutUnits": "milliseconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 830,
"y": 180,
"wires": [
[]
]
}
]