Hubitat > NodeRed > MySQL > Grafana (LONG READ)

The major difference between my queries and yours is that you select "distinct()" values. I select "mean()", once you change that you get the lines filled.
However, I think for the most of you @corerootedxb flow would make it much easier. @corerootedxb, do you mind sharing your JSON from the flow? I could add it to mine that is hosted to github and add a little setup instructions to it to use it with the MakerApi.

1 Like

I tried using the flow.set/get but it started to feel overly complicated having to parse through every event from the socket to determine if it needed to be stored (battery, temp, etc), then store it, then update it between polls..etc. It seemed possible but overcomplicated for something like node red. Your Maker Api method just seemed more tailored to nodered's flow based system. I'm very new to nodered so if there is an easy way to parse, update and push these types of data to influx easily, I would appreciate any guidance you can give.

Btw..thanks for the node detail. I'll check it out this afternoon.

Thanks @dan.t. I'll give that a shot as well.

    {
        "id": "d3fe7906.c98cf8",
        "type": "tab",
        "label": "Share InfluxDB",
        "disabled": false,
        "info": ""
    },
    {
        "id": "85925615.c36e68",
        "type": "websocket in",
        "z": "d3fe7906.c98cf8",
        "name": "Hubitat Event Socket Prod",
        "server": "",
        "client": "ad49e56f.20d8f8",
        "x": 150,
        "y": 80,
        "wires": [
            [
                "64484b87.e62d44"
            ]
        ]
    },
    {
        "id": "64484b87.e62d44",
        "type": "filter",
        "z": "d3fe7906.c98cf8",
        "name": "Filter attributes",
        "property": "name",
        "propertyType": "msg",
        "asArray": false,
        "itemProperty": "",
        "itemPropertyType": "item",
        "rules": [
            {
                "t": "eq",
                "v": "acceleration",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "alarm",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "battery",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "presence",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "carbonMonoxide",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "carbonDioxide",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "contact",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "door",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "energy",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "humidity",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "illuminance",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "lock",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "motion",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "status",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "steps",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "goal",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "pH",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "power",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "voltage",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "current",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "powerFactor",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "presence",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "pressure",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "shock",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "lqi",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "rssi",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "sleeping",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "smoke",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "tamper",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "temperature",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "heatingSetpoint",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "thermostatSetpoint",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "thermostatMode",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "thermostatFanMode",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "thermostatOperatingState",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "thermostatSetpointMode",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "scheduledSetpoint",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "optimisation",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "windowFunction",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "threeAxis",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "touch",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "ultravioletIndex",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "contact",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "voltage",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "water",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "windowShade",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "switch",
                "vt": "str",
                "output": 1
            },
            {
                "t": "eq",
                "v": "level",
                "vt": "str",
                "output": 1
            }
        ],
        "checkall": "false",
        "outputs": 1,
        "x": 380,
        "y": 80,
        "wires": [
            [
                "7891c733.1ec2d8"
            ]
        ]
    },
    {
        "id": "7891c733.1ec2d8",
        "type": "function",
        "z": "d3fe7906.c98cf8",
        "name": "Set Influx Data",
        "func": "\nconst ACTIVE = 'active';\nconst CLOSED = 'closed';\nconst DETECTED = 'detected';\nconst FOLLOW_SCHEDULE = 'follow schedule';\nconst GOOD = 'good';\nconst HEATING = 'heating';\nconst LOCKED = 'locked';\nconst MUTED = 'muted';\nconst OFF = 'off';\nconst ON = 'on';\nconst OPEN = 'open';\nconst PRESENT = 'present';\nconst PUSHED = 'pushed';\nconst SLEEPING = 'sleeping';\nconst TOUCHED = 'touched';\nconst WET = 'wet';\n\nvar v = {\n    'value' : null,\n    'isBinary' : null\n};\n\nif(msg.name == \"acceleration\") {\n    v.value = (msg.value == ACTIVE ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"alarm\") {\n    v.value = (msg.value != OFF ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"button\") {\n    v.value = (msg.value != PUSHED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"carbonMonoxide\") {\n    v.value = (msg.value == DETECTED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"consumableStatus\") {\n    v.value = (msg.value == GOOD ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"contact\") {\n    v.value = (msg.value == OPEN ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"door\") {\n    v.value = (msg.value != OPEN ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"lock\") {\n    v.value = (msg.value == LOCKED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"motion\") {\n    v.value = (msg.value == ACTIVE ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"mute\") {\n    v.value = (msg.value == MUTED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"presence\") {\n    v.value = (msg.value == PRESENT ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"shock\") {\n    v.value = (msg.value == DETECTED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"sleeping\") {\n    v.value = (msg.value == SLEEPING ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"smoke\") {\n    v.value = (msg.value == DETECTED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"sound\") {\n    v.value = (msg.value == DETECTED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"switch\") {\n    v.value = (msg.value == ON ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"tamper\") {\n    v.value = (msg.value == DETECTED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"thermostatMode\") {\n    v.value = (msg.value != OFF ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"thermostatFanMode\") {\n    v.value = (msg.value != OFF ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"thermostatOperatingState\") {\n    v.value = (msg.value == HEATING ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"thermostatSetpointMode\") {\n    v.value = (msg.value != FOLLOW_SCHEDULE ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"touch\") {\n    v.value = (msg.value != TOUCHED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"optimisation\") {\n    v.value = (msg.value == ACTIVE ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"windowFunction\") {\n    v.value = (msg.value == ACTIVE ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"water\") {\n    v.value = (msg.value == WET ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"windowShade\") {\n    v.value = (msg.value == CLOSED ? 1 : 0);\n    v.isBinary = true;\n}\n\nelse if(msg.name == \"threeAxis\") {\n    /*var vz = msg.value.split(',');\n    var x = vz[0];\n    var y = vz[1];\n    var z = vz[2];\n    v.value = {\n        x: vz[0],\n        y: vz[1],\n        z: vz[2]\n    };*/\n    v.value = msg.value;\n    v.isBinary = false;\n}\nelse if (typeof msg.value === 'string' && msg.value.match(/.*[^0-9\\.,-].*/)) { // match if any characters are not digits, period, comma, or hyphen.\n    v.value = '\"' + msg.value + '\"';\n    v.isBinary = false;\n}\n\n// Catch any other general numerical event (carbonDioxide, power, energy, humidity, level, temperature, ultravioletIndex, voltage, etc).\nelse {\n    v.value = Number(msg.value);\n    v.isBinary = false;\n}\n\nif (v.isBinary)\n{\n    msg.payload = [\n        {\n            measurement: msg.name,\n            fields: {\n                value: msg.value,\n                valueBinary: v.value\n            },\n            tags:{\n                hub: msg.hubId ? msg.hubId : \"HE1\",\n                deviceId: msg.deviceId,\n                displayName: msg.displayName,\n                unit: msg.unit\n            },\n            timestamp: new Date()\n        }\n    ]    \n}\nelse\n{\n    msg.payload = [\n        {\n            measurement: msg.name,\n            fields: {\n                value: (Array.isArray(v.value) ? v.value.join(',') : v.value),\n            },\n            tags:{\n                hub: msg.hubId ? msg.hubId : \"HE1\",\n                deviceId: msg.deviceId,\n                displayName: msg.displayName,\n                unit: msg.unit\n            },\n            timestamp: new Date()\n        }\n    ]\n}\n//console.log(util.inspect(msg, {showHidden: false, depth: null}))\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 960,
        "y": 120,
        "wires": [
            [
                "a53b5b74.207808"
            ]
        ]
    },
    {
        "id": "a53b5b74.207808",
        "type": "influxdb batch",
        "z": "d3fe7906.c98cf8",
        "influxdb": "227f748e.a9c7fc",
        "precision": "",
        "retentionPolicy": "",
        "name": "",
        "x": 1210,
        "y": 120,
        "wires": []
    },
    {
        "id": "e021ed07.18972",
        "type": "http request",
        "z": "d3fe7906.c98cf8",
        "name": "Get Temps",
        "method": "GET",
        "ret": "obj",
        "paytoqs": false,
        "url": "http://[ip]/apps/api/[id]/attribute/temperature?access_token=[at]",
        "tls": "",
        "proxy": "",
        "x": 310,
        "y": 200,
        "wires": [
            [
                "ed375789.8968b8"
            ]
        ]
    },
    {
        "id": "d9fa6d0a.a765a",
        "type": "splitter",
        "z": "d3fe7906.c98cf8",
        "name": "Split payload array",
        "property": "payload",
        "x": 490,
        "y": 240,
        "wires": [
            [
                "51241053.5bca9"
            ]
        ]
    },
    {
        "id": "e862a8ef.6323e8",
        "type": "comment",
        "z": "d3fe7906.c98cf8",
        "name": "Calls Maker API every hour",
        "info": "",
        "x": 130,
        "y": 140,
        "wires": []
    },
    {
        "id": "e0173fa3.3fae2",
        "type": "inject",
        "z": "d3fe7906.c98cf8",
        "name": "",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "3600",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "x": 110,
        "y": 220,
        "wires": [
            [
                "e021ed07.18972",
                "831162e8.546b"
            ]
        ]
    },
    {
        "id": "5e2a7ded.834124",
        "type": "catch",
        "z": "d3fe7906.c98cf8",
        "name": "",
        "scope": [
            "e862a8ef.6323e8",
            "4092165a.b0fc58",
            "ac83b17c.ad064",
            "64484b87.e62d44",
            "831162e8.546b",
            "e021ed07.18972",
            "85925615.c36e68",
            "a53b5b74.207808",
            "9ed67807.976e38",
            "523d6a99.b7df54",
            "7891c733.1ec2d8",
            "d9fa6d0a.a765a",
            "e0173fa3.3fae2"
        ],
        "uncaught": false,
        "x": 100,
        "y": 560,
        "wires": [
            [
                "4092165a.b0fc58",
                "ac83b17c.ad064"
            ]
        ]
    },
    {
        "id": "4092165a.b0fc58",
        "type": "change",
        "z": "d3fe7906.c98cf8",
        "name": "Convert error.message to msg.payload",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "HE Logging has thrown an error",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 350,
        "y": 560,
        "wires": [
            [
                "523d6a99.b7df54"
            ]
        ]
    },
    {
        "id": "523d6a99.b7df54",
        "type": "debug",
        "z": "d3fe7906.c98cf8",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "x": 610,
        "y": 560,
        "wires": []
    },
    {
        "id": "ac83b17c.ad064",
        "type": "logger",
        "z": "d3fe7906.c98cf8",
        "name": "Error Logger",
        "filename": "/var/log/hubitat/nr-inf-errors.log",
        "maxsize": "10",
        "maxfiles": 10,
        "complete": "true",
        "console": false,
        "file": true,
        "debug": false,
        "zip": true,
        "logtype": "info",
        "x": 270,
        "y": 520,
        "wires": []
    },
    {
        "id": "831162e8.546b",
        "type": "http request",
        "z": "d3fe7906.c98cf8",
        "name": "Get Batteries",
        "method": "GET",
        "ret": "obj",
        "paytoqs": false,
        "url": "http://[ip]/apps/api/[id]/attribute/battery?access_token=[access]",
        "tls": "",
        "proxy": "",
        "x": 310,
        "y": 240,
        "wires": [
            [
                "d9fa6d0a.a765a"
            ]
        ]
    },
    {
        "id": "51241053.5bca9",
        "type": "function",
        "z": "d3fe7906.c98cf8",
        "name": "Transform Battery",
        "func": "var op = msg.payload;\n\nmsg.name = \"battery\";\nmsg.value = (op.attributes['battery'] === null ? 0 : op.attributes['battery']);\nmsg.deviceId = op.id;\nmsg.displayName = op.label;\nmsg.unit = '%';\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 690,
        "y": 240,
        "wires": [
            [
                "7891c733.1ec2d8"
            ]
        ]
    },
    {
        "id": "ed375789.8968b8",
        "type": "splitter",
        "z": "d3fe7906.c98cf8",
        "name": "Split payload array",
        "property": "payload",
        "x": 490,
        "y": 200,
        "wires": [
            [
                "3f62a5ce.e5f93a"
            ]
        ]
    },
    {
        "id": "3f62a5ce.e5f93a",
        "type": "function",
        "z": "d3fe7906.c98cf8",
        "name": "Transform Temp",
        "func": "var op = msg.payload;\n\nmsg.name = \"temperature\";\nmsg.value = op.attributes['temperature'];\nmsg.deviceId = op.id;\nmsg.displayName = op.label;\nmsg.unit = 'F';\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 680,
        "y": 200,
        "wires": [
            [
                "7891c733.1ec2d8"
            ]
        ]
    },
    {
        "id": "ad49e56f.20d8f8",
        "type": "websocket-client",
        "z": "",
        "path": "ws://192.168.1.110/eventsocket",
        "tls": "",
        "wholemsg": "true"
    },
    {
        "id": "227f748e.a9c7fc",
        "type": "influxdb",
        "z": "",
        "hostname": "localhost",
        "port": "8086",
        "protocol": "http",
        "database": "hubitat_nodered",
        "name": "",
        "usetls": false,
        "tls": ""
    }
]
1 Like

So I enabled the "fill previous:" setting and refreshed all my battery devices yesterday and they all showed up. This morning however more then half have now dropped off my Grafana. So even the last reported state is not working.

@corerootedxb Also I cannot import that code into Node Red I receive the error "Input Not a JSON Array"

1 Like

Try this:

2 Likes

That works, thanks.

1 Like

The term for this is RBE or Report by Exception. Node-red has a function node to convert a continuous data stream into RBE. I realize what we are needing here is the opposite function, ie creating a virtual continuous stream from what is essentially RBE data. Just mentioning it out of academic interest. Love node-red.

1 Like

I'm working on a flow to periodically refresh data points in InfluxDB without hitting the Maker API. So far so good, but it's nowhere near finished. If it works, I'll be sure to post it.

You could just read the last value out of the Influx DB and give it a new timestamp and store it in Influx again :slight_smile:

1 Like

That's sort of what I'm doing.

I've got an event websocket listener filtering out everything but the types of data we want to do this to (humidity, temperature, battery etc...). Whenever it gets an event for a valid measurement type, it updates an array stored in the flow state with the measurement, value, and a timestamp.

Then, periodically, it walks that array and looks for anything whose last reported value is older than X minutes. It builds an array of these points with updated timestamps, and shoves them into InfluxDB.

The main issue I'm seeing with this plan is that if a battery goes dead we'll just get a flat line.

1 Like

Looking forward to what you come up with.

Here's what I've got so far. Seems to be working well. Note that if there's a sensor that hasn't reported in lately, this flow will not start refreshing its data until it does.

EDIT: There's a bug in the flow that's causing some odd updates. Will re-link when I find and fix it.
EDIT2: Fixed, I think.

Found hub not responsive today. Also found node-red was also non-responsive. Node-red page wouldn't load on that computer either.

Node-red crashed because of my flows. Last change was the maker api thing but didn't completely set it up so that could be causing it. I saw error about reading the battery on the console when starting up node-red.

I've got no idea with NodeRed. I've got it installed on my raspberry pi (pi-hole), but never used NodeRed before. Can you recommend any good guides/videos?

Thanks

This is the site that I used when first starting out with NR: Introduction to Node Red

Lots of both textual and video tutorials.

2 Likes

Get ready...the rabbit hole is deeeeep!

2 Likes

See you on the other side of (southern hemisphere) winter, no doubt with questions.

I'm getting an "error: Input not a JSON Array" when I try and import the flow you've pasted at the bottom of your first post into nodeRed. it is very likely i'm doing something wrong as I'm definitely new to nodeRed.

Try this: Hubitat/he-logging.json at master ยท code-in-progress/Hubitat ยท GitHub

For whatever reason, NodeRed doesn't seem to like JSON that's been entered into Discord.