Node-RED nodes for hubitat

I am running version 1.0.4 of Node Red and version 0.0.24 of node-red-contrib-hubitat.

A month or so back, someone posted this fantastic cUrl statement to pull a Hubitat backup and store it. Struggling to turn this into a proper node using "Exec" or HttpRequest, anyone got an idea on getting this to run? I thought it would be a fantastic thing to run in the mornings when my mode manager switches to "Daytime"

curl -s http://192.168.0.100/hub/backupDB?fileName=latest -o "/Volumes/Bacukups/Hubitat//_$(date +%Y-%m-%d-%H%M).lzf"

Thanks in advance.

Side note:
Making fantastic progress on getting my rules moved over from RM to NR, ran across this great step by step on getting Alexa to talk

1 Like

Try adding a sequence to every flow where you query the status of a device upon Node-RED startup. My recollection is that I had the same issue you've encountered. I assumed it went away with an update to node-red-contrib-hubitat. However, I also added sequences that query device status along the way - so perhaps that made the change.

I've attached a sample sequence below that you can import and just change the device.

Query at startup
[
    {
        "id": "3ec77471.1e95d4",
        "type": "inject",
        "z": "1eb1e8e3.1c3ca7",
        "name": "15 secs",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "15",
        "x": 120,
        "y": 460,
        "wires": [
            [
                "e0135fdd.83a79",
                "a5922f41.903af8",
                "59ccae9e.c425b8",
                "99c9b443.79a3a"
            ]
        ]
    },
    {
        "id": "99c9b443.79a3a",
        "type": "function",
        "z": "1eb1e8e3.1c3ca7",
        "name": "Switch Status",
        "func": "msg.attribute = \"switch\"\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 336,
        "y": 374,
        "wires": [
            [
                "bccb6285.6a89"
            ]
        ]
    },
    {
        "id": "bccb6285.6a89",
        "type": "hubitat device",
        "z": "1eb1e8e3.1c3ca7",
        "name": "Kitchen Tube",
        "server": "662851c4.3ccad",
        "deviceId": "7",
        "attribute": "switch",
        "sendEvent": true,
        "x": 550,
        "y": 373,
        "wires": [
            [
                "bc1a9e3d.9154"
            ]
        ]
    },
    {
        "id": "bc1a9e3d.9154",
        "type": "change",
        "z": "1eb1e8e3.1c3ca7",
        "name": "save Kitchen Tube status",
        "rules": [
            {
                "t": "set",
                "p": "kt_status",
                "pt": "flow",
                "to": "payload.currentValue",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 796,
        "y": 374,
        "wires": [
            []
        ]
    },
    {
        "id": "662851c4.3ccad",
        "type": "hubitat config",
        "z": "",
        "name": "HubitatS",
        "usetls": false,
        "host": "192.168.1.36",
        "port": "80",
        "token": "",
        "appId": "4489",
        "nodeRedServer": "http://192.168.1.4:1880",
        "webhookPath": "/hubitat/webhook2"
    }
]

@morningz, below is from a post I shared previously. It was the flow I used to keep a daily backup of both my HE hubs. Hopefully it can give you some ideas on how to get yours configured. Of course the ip's and file paths etc would have to be changed. You will also need to have the following nodes installed (if they aren't already)
node-red-contrib-fs-ops
node-red-contrib-string

It maintains a limit of 15 backups before deleting the oldest log. Please also keep in mind that this flow was written for a Windows based version of NR, so the slashes are reversed for the paths if you are running on a Linux box.

My hub firmware backup flow:

[
    {
        "id": "1a25485a.160c68",
        "type": "tab",
        "label": "Daily Hub F/W Backups (1AM & 1:30AM)",
        "disabled": false,
        "info": ""
    },
    {
        "id": "31f80583.137ffa",
        "type": "file",
        "z": "1a25485a.160c68",
        "name": "Save File",
        "filename": "",
        "appendNewline": false,
        "createDir": true,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 660,
        "y": 40,
        "wires": [
            [
                "fcae276c.cfdc28"
            ]
        ]
    },
    {
        "id": "c916945e.2a3d68",
        "type": "inject",
        "z": "1a25485a.160c68",
        "name": "Dev Hub Daily 4:00AM",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "00 04 * * *",
        "once": false,
        "onceDelay": 0.1,
        "x": 150,
        "y": 40,
        "wires": [
            [
                "67731afb.2979e4"
            ]
        ]
    },
    {
        "id": "67731afb.2979e4",
        "type": "http request",
        "z": "1a25485a.160c68",
        "name": "Get backup",
        "method": "GET",
        "ret": "bin",
        "paytoqs": false,
        "url": "http://192.168.7.221/hub/backupDB?fileName=latest",
        "tls": "",
        "proxy": "",
        "authType": "basic",
        "x": 350,
        "y": 40,
        "wires": [
            [
                "8eadb6f5.01e168"
            ]
        ]
    },
    {
        "id": "835c7307.73939",
        "type": "file",
        "z": "1a25485a.160c68",
        "name": "Save File",
        "filename": "",
        "appendNewline": false,
        "createDir": true,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 660,
        "y": 240,
        "wires": [
            [
                "37201a0d.a27bd6"
            ]
        ]
    },
    {
        "id": "b83036e9.ace978",
        "type": "inject",
        "z": "1a25485a.160c68",
        "name": "Main Hub Daily 4:30AM",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "30 04 * * *",
        "once": false,
        "onceDelay": 0.1,
        "x": 150,
        "y": 240,
        "wires": [
            [
                "ce651073.497f3"
            ]
        ]
    },
    {
        "id": "ce651073.497f3",
        "type": "http request",
        "z": "1a25485a.160c68",
        "name": "Get backup",
        "method": "GET",
        "ret": "bin",
        "paytoqs": false,
        "url": "http://192.168.7.209/hub/backupDB?fileName=latest",
        "tls": "",
        "proxy": "",
        "authType": "basic",
        "x": 350,
        "y": 240,
        "wires": [
            [
                "c4876440.f33c48"
            ]
        ]
    },
    {
        "id": "8eadb6f5.01e168",
        "type": "string",
        "z": "1a25485a.160c68",
        "name": "Get filename",
        "methods": [
            {
                "name": "strip",
                "params": [
                    {
                        "type": "str",
                        "value": "attachment; filename="
                    }
                ]
            },
            {
                "name": "prepend",
                "params": [
                    {
                        "type": "str",
                        "value": "\\\\nodered\\\\backups\\\\dev\\\\"
                    }
                ]
            }
        ],
        "prop": "headers.content-disposition",
        "propout": "filename",
        "object": "msg",
        "objectout": "msg",
        "x": 510,
        "y": 40,
        "wires": [
            [
                "31f80583.137ffa"
            ]
        ]
    },
    {
        "id": "c4876440.f33c48",
        "type": "string",
        "z": "1a25485a.160c68",
        "name": "Get filename",
        "methods": [
            {
                "name": "strip",
                "params": [
                    {
                        "type": "str",
                        "value": "attachment; filename="
                    }
                ]
            },
            {
                "name": "prepend",
                "params": [
                    {
                        "type": "str",
                        "value": "\\\\nodered\\\\backups\\\\main\\\\"
                    }
                ]
            }
        ],
        "prop": "headers.content-disposition",
        "propout": "filename",
        "object": "msg",
        "objectout": "msg",
        "x": 510,
        "y": 240,
        "wires": [
            [
                "835c7307.73939"
            ]
        ]
    },
    {
        "id": "37201a0d.a27bd6",
        "type": "fs-ops-dir",
        "z": "1a25485a.160c68",
        "name": "# of Backups",
        "path": "\\nodered\\backups\\main\\",
        "pathType": "str",
        "filter": "*",
        "filterType": "str",
        "dir": "files",
        "dirType": "msg",
        "x": 130,
        "y": 320,
        "wires": [
            [
                "f2d91ce9.bb0d5"
            ]
        ]
    },
    {
        "id": "b12fb6f7.a44868",
        "type": "fs-ops-delete",
        "z": "1a25485a.160c68",
        "name": "Del Oldest BckUp",
        "path": "\\nodered\\backups\\main\\",
        "pathType": "str",
        "filename": "files[0]",
        "filenameType": "msg",
        "x": 470,
        "y": 320,
        "wires": [
            []
        ]
    },
    {
        "id": "f2d91ce9.bb0d5",
        "type": "switch",
        "z": "1a25485a.160c68",
        "name": "15 File Limit",
        "property": "files.length",
        "propertyType": "msg",
        "rules": [
            {
                "t": "gte",
                "v": "15",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 290,
        "y": 320,
        "wires": [
            [
                "b12fb6f7.a44868"
            ]
        ]
    },
    {
        "id": "fcae276c.cfdc28",
        "type": "fs-ops-dir",
        "z": "1a25485a.160c68",
        "name": "# of Backups",
        "path": "\\nodered\\backups\\dev\\",
        "pathType": "str",
        "filter": "*",
        "filterType": "str",
        "dir": "files",
        "dirType": "msg",
        "x": 130,
        "y": 120,
        "wires": [
            [
                "6a93c3f4.90cd5c"
            ]
        ]
    },
    {
        "id": "6fc01da3.081d04",
        "type": "fs-ops-delete",
        "z": "1a25485a.160c68",
        "name": "Del Oldest BckUp",
        "path": "\\nodered\\backups\\dev\\",
        "pathType": "str",
        "filename": "files[0]",
        "filenameType": "msg",
        "x": 470,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "6a93c3f4.90cd5c",
        "type": "switch",
        "z": "1a25485a.160c68",
        "name": "15 File Limit",
        "property": "files.length",
        "propertyType": "msg",
        "rules": [
            {
                "t": "gte",
                "v": "15",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 290,
        "y": 120,
        "wires": [
            [
                "6fc01da3.081d04"
            ]
        ]
    }
]
1 Like

I was never able to figure this out trying to send text to my Google Home Mini. The Mini was speaking the "%20". I hadn't looked at it lately but want to try again because I figured out a way to send my Caller ID info from my Android phone to a Global Variable connector to use on a Dashboard to tell me who called if I am not near my phone but it also inserts the "%20". Obviously, like you, I can decipher the info but it is definitely a sloppy way to to it and definitely not good enough for WAF.

I created a new feature request towards Hubitat for this, let's see if we can get some traction for supporting this....

4 Likes

Here is a different way of doing it with the "node-red-contrib-cheerio-function" plugin. The difference in this version of the flow is that it doesn't create a new backup, it downloads the last backup that was created by Hubitat.

You can set the destination path in the "Settings" node

Downlaod Backup Flow
[
    {
        "id": "45424774.052448",
        "type": "tab",
        "label": "Download Last Hubitat Backup",
        "disabled": false,
        "info": ""
    },
    {
        "id": "dc14a65c.3701d8",
        "type": "inject",
        "z": "45424774.052448",
        "name": "get data",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "",
        "once": false,
        "x": 111,
        "y": 135.9999976158142,
        "wires": [
            [
                "681a2dc.8dc30d4"
            ]
        ]
    },
    {
        "id": "681a2dc.8dc30d4",
        "type": "http request",
        "z": "45424774.052448",
        "name": "",
        "method": "GET",
        "ret": "txt",
        "paytoqs": false,
        "url": "http://192.168.10.153/hub/backup",
        "tls": "",
        "proxy": "",
        "x": 275.0000114440918,
        "y": 136.00000190734863,
        "wires": [
            [
                "5ffb2d8.1b69dd4"
            ]
        ]
    },
    {
        "id": "5ffb2d8.1b69dd4",
        "type": "cheerio-function",
        "z": "45424774.052448",
        "name": "parse html cheerio",
        "func": "const tableSelector = '#backup-table'\n\n//const cheerio = global.get('cheerio')\n//const $ = cheerio.load(msg.payload)\nconst options = {\n    rowForHeadings: 0,  // extract th cells from this row for column headings (zero-based)\n    ignoreHeadingRow: true, // Don't tread the heading row as data\n    ignoreRows: [],\n}\nconst rowEntries = []\nconst columnHeadings = []\n\n\n$(tableSelector).each(function(i, table) {\n    node.log(\"Something happened\");\n\n    var trs = $(table).find('tr')\n    \n    // Set up the column heading names\n    getColHeadings( $(trs[options.rowForHeadings]) )\n\n    // Process rows for data\n    $(table).find('tr').each(processRow)\n})\n\nif (rowEntries.length === 0)\n    return null;\n\nmsg.payload = {\n    fileName: rowEntries[rowEntries.length-1].Name\n}\n\nreturn msg\n\nfunction getColHeadings(headingRow) {\n    const alreadySeen = {}\n    \n    $(headingRow).find('th').each(function(j, cell) {\n        let tr = $(cell).text().trim()\n        \n        if ( alreadySeen[tr] ) {\n            let suffix = ++alreadySeen[tr]\n            tr = `${tr}_${suffix}`\n        } else {\n            alreadySeen[tr] = 1\n        }\n        \n        columnHeadings.push(tr)\n    })\n}\n\nfunction processRow(i, row) {\n    const rowJson = {}\n    \n    if ( options.ignoreHeadingRow && i === options.rowForHeadings ) return\n    // TODO: Process options.ignoreRows\n    \n    $(row).find('td').each(function(j, cell) {\n        rowJson[ columnHeadings[j] ] = $(cell).text().trim()\n    })\n    \n    // Skip blank rows\n    if (JSON.stringify(rowJson) !== '{}') rowEntries.push(rowJson)\n}\n",
        "outputs": 1,
        "noerr": 0,
        "x": 462.5,
        "y": 137,
        "wires": [
            [
                "9a78be62.d051d"
            ]
        ]
    },
    {
        "id": "9a78be62.d051d",
        "type": "http request",
        "z": "45424774.052448",
        "name": "",
        "method": "GET",
        "ret": "bin",
        "paytoqs": true,
        "url": "http://192.168.10.153/hub/backupDB?",
        "tls": "",
        "proxy": "",
        "authType": "basic",
        "x": 268.5,
        "y": 259,
        "wires": [
            [
                "8e274a2.c00ccb8"
            ]
        ]
    },
    {
        "id": "a81ffa29.327a08",
        "type": "file",
        "z": "45424774.052448",
        "name": "",
        "filename": "",
        "appendNewline": false,
        "createDir": true,
        "overwriteFile": "true",
        "encoding": "binary",
        "x": 571.5,
        "y": 258,
        "wires": [
            []
        ]
    },
    {
        "id": "8e274a2.c00ccb8",
        "type": "string",
        "z": "45424774.052448",
        "name": "Settings",
        "methods": [
            {
                "name": "strip",
                "params": [
                    {
                        "type": "str",
                        "value": "attachment; filename="
                    }
                ]
            },
            {
                "name": "prepend",
                "params": [
                    {
                        "type": "str",
                        "value": "/var/log/"
                    }
                ]
            }
        ],
        "prop": "headers.content-disposition",
        "propout": "filename",
        "object": "msg",
        "objectout": "msg",
        "x": 434.5,
        "y": 257,
        "wires": [
            [
                "a81ffa29.327a08"
            ]
        ]
    }
]
2 Likes

Hi @fblackburn, I've imported the flow into my node-red and I'm starting to get error/warning

not sure what I'm missing. any idea? Cheers

image

You need to install node-red-dashboard.

1 Like

Thanks @aaiyar - I thought that was the first thing I installed.
upon checking, it is installed but it's conflicting with contrib-ui-j.
now, I can't uninstall contrib-ui-j because it's being used.
is there a way to remove it?

I can see that ui_tab is being used. not sure where though :laughing:

EDITED = Sorted it out! had to export all flows, remove palette and import them back in.

thanks @aaiyar

1 Like

Thanks! I will try this solution.

I would still like to know, if anyone knows, the cause of this issue.

Try using node-red-contrib-cast to send messages to Google Home Mini. It works well, but you have to assign the speaker to a fixed IP. Also, you can't choose the voice. But the sound is very clear.

I also use node-red-contrib-pushover for sending messages to my phone.

1 Like

Thanks. I had installed Cast but hadn't tried it yet. I will see if it works tonight.

I send 98% of my pushover messages with that node. Works great.

One thing that drives me nuts about the node is that i seemingly have to copy the user key and token in every copy of the node I make, surely there much be some way to plug those values in from a global.variable i set on startup or something

Don't know about that. But here's @stephack's solution, which works pretty well for me:

That doesn't work here on my install.

Copying and pasting a working Pushover node results in the new node having those values being blank

Edit: this looks like an almost-there method, good enough to just have to quickly put "$(pushkey)" and "$(pushtoken)"

Screenshot 2020-03-10 11.54.02

Screenshot 2020-03-10 11.53.38

I use pushbullets and they've been working fine too.
I'm using the node-red palette of pushbullet

You shouldn't need to have multiple pushover nodes. Create one that has the key etc and then have a change node at the end of every sequence with their own custom message, priority, etc. I no longer use pushover but I remember there were at least 2 nodes that I tried..one was blue and one was green. I believe I stuck with the blue version. Let me know which one you are using and I will try to create a sample sequence to share. Won't be till later tonight though.

Edit:
@morningz, I did a quick npm search and it jogged my memory a little. I believe one of the reasons I picked the blue Pushover (node-red-contrib-pushover) was because it had it's own config node that allowed you to setup the keys once, without the need to enter it over and over. You are probably using the green node (node-red-pushover), There were other benefits to the blue version. I believe the blue version also let you set more of the parameters. Give it a shot and let me know if you still need the sample sequence shared.

I’ve got the green version, maybe I’ll look into the blue one instead

  • Edit: done! Yes, this blue version allows saving of key and token, nice! *
1 Like