Flume integration

Flume has a well documented REST API that uses oauth2. Anyone that can do an oauth2 integration should be able to knock this out easily.

Alternatively, there is already an integration for Flume in Home Assistant. Could use that, then move it over to hubitat via node-red or @kevin Hubitat MQTT integration.

2 Likes

Just got my Flume V1. Would love to see integration. It'd be fantastic to automatically trigger my Dome valve to close if flow is detected while away from home. Perfect companion to water leak sensors.

Like I said - ya need to find someone that has already done, or is good at, oauth2 rest integrations. There are only 2 endpoints that apply to "away mode" (one get and one set), so it's pretty straight forward.

Took about 5 minutes to setup in node-red after I figured out my user# and location# (which they don't make easy - user# has to be decoded from the jwt token, then you can get location# with an api call).

Bump. Also very interested in Hubitat integration.

I would 5th or 6th the request for an integration. I have a Flume 2 for a little over a month and it works very well and is surprisingly accurate. It would be great to see an integration into HE or some workaround to get the summary data and leak alerts. I don't think that SmartThings is the way to go with Flume but maybe interfacing with Home Assistant like others have mentioned is a good intermediate solution.

Agree. My Flume 2 works shockingly well. Pretty cool considering how simple it is to install.

I have my Flume 2 in Home Assistant and Node-RED (just wanted to see if I could do it - I could). As I do all my logic in Node-RED, I have no initiative to write an integration for Hubitat. Sooner or later someone else probably will though - if you know how to do an Oauth2 app (along with key refreshing, etc) it is super simple.

I wanted to try out the Oauth flow in a driver, so I took a swing at implementing the API accesses natively in Hubitat. It seems to be working now with the Flume account I created, but I don't have any hardware to check with against my assumptions on data formats for things like notifications and home/away settings.

Does anyone from this thread with a working Flume setup want to be a guinea pig so that I can build this out into a full integration?

1 Like

@tomw I could stick it on my dev hub. I already know my user and location ID, and have the client account setup so shouldn't be a big deal to try it out.

I do away mode set/get in my node-red integration, so know what that response should look like. I don't have any notifications setup in Node-RED, though, as that isn't what I was going for. Have tested them before, though.

Cool, I was able to extract those from the responses in my driver (should be useful for someone who hasn't already done the legwork like you did), but it breaks down at the device and notifications queries since I don't have any devices. Let me clean it up a bit and I'll ping you on the side.

1 Like

Any chance you'd want to share that Flume/Node-RED integration work? I promise, I'll just use it as example code and won't request any support!

Yeah, let me get it together. it is pretty easy though - once you have your userId, locationId, and api login that is. My integration was never intended to be generic for other users, so I didn't bother extracting things like the userId and locationId automatically.

I'm working out the kinks on a Hubitat native (albeit still cloud reliant) integration, and I'll pick out the IDs automatically during setup. But I'm sure @JasonJoel's is great if you're already using node-red and don't mind the little bit of legwork.

Thanks guys, just having an example would be great. I'm in the process of moving all of my 42 Hubitat rules over to NR so I'm getting to know my way around that platform fairly well. Any examples, no matter how basic or hard-wired will be helpful. It's appreciated!

Here you go. This isn't the actual one I run, but a simplified flow with private info removed. It should give you an idea of how to get an oauth2 toke, refresh a token, and turn the Flume away mode on/off. There are other endpoints for fetching water use, etc, of course.

Edit the 2 oauth function nodes with your info. Edit the Set Location State and Get Location State node URLs and put in your clientid # and location #.

node red flow
[
    {
        "id": "c30e715.8ba869",
        "type": "tab",
        "label": "Flow 3",
        "disabled": false,
        "info": ""
    },
    {
        "id": "35055acc.f39056",
        "type": "http request",
        "z": "c30e715.8ba869",
        "name": "Get Tokens",
        "method": "POST",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://api.flumewater.com/oauth/token",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "x": 470,
        "y": 80,
        "wires": [
            [
                "5a06d574.6d262c",
                "9725c581.21d138",
                "30dfe80.8bf0118"
            ]
        ]
    },
    {
        "id": "ed82bfa9.80bb",
        "type": "function",
        "z": "c30e715.8ba869",
        "name": "oauth2Request",
        "func": "msg.headers='Content-Type: application/json'\n\nmsg.payload= {\n    \"grant_type\":\"password\",\n    \"client_id\":\"Enter_Client_ID_Here\",\n    \"client_secret\":\"Enter_Client_Secret_Here\",\n    \"username\":\"Enter_account_username_here\",\n    \"password\":\"Enter_account_password_here\"\n}\n\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 280,
        "y": 80,
        "wires": [
            [
                "35055acc.f39056"
            ]
        ]
    },
    {
        "id": "e678dae.b1a1d28",
        "type": "inject",
        "z": "c30e715.8ba869",
        "name": "",
        "props": [
            {
                "p": "payload",
                "v": "",
                "vt": "date"
            },
            {
                "p": "topic",
                "v": "",
                "vt": "string"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 100,
        "y": 80,
        "wires": [
            [
                "ed82bfa9.80bb"
            ]
        ]
    },
    {
        "id": "5a06d574.6d262c",
        "type": "debug",
        "z": "c30e715.8ba869",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 670,
        "y": 80,
        "wires": []
    },
    {
        "id": "63868470.6c669c",
        "type": "http request",
        "z": "c30e715.8ba869",
        "name": "Refresh Tokens",
        "method": "POST",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://api.flumewater.com/oauth/token",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "x": 480,
        "y": 220,
        "wires": [
            [
                "abef5ab6.d8d3c8",
                "9725c581.21d138",
                "30dfe80.8bf0118"
            ]
        ]
    },
    {
        "id": "c3b8c6c.6b47c38",
        "type": "function",
        "z": "c30e715.8ba869",
        "name": "oauth2Request",
        "func": "msg.headers='Content-Type: application/json'\n\nmsg.payload= {\n    \"grant_type\":\"password\",\n    \"client_id\":\"Enter_client_id_here\",\n    \"client_secret\":\"Enter_client_secret_here\",\n    \"username\":\"Enter_account_username_here\",\n    \"password\":\"Enter_account_password_here\"\n}\n\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 280,
        "y": 220,
        "wires": [
            [
                "63868470.6c669c"
            ]
        ]
    },
    {
        "id": "fc96cf26.5b4b2",
        "type": "inject",
        "z": "c30e715.8ba869",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "43200",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 110,
        "y": 220,
        "wires": [
            [
                "c3b8c6c.6b47c38"
            ]
        ]
    },
    {
        "id": "abef5ab6.d8d3c8",
        "type": "debug",
        "z": "c30e715.8ba869",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 670,
        "y": 220,
        "wires": []
    },
    {
        "id": "b83c3058.70473",
        "type": "http request",
        "z": "c30e715.8ba869",
        "name": "Get Location State",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://api.flumewater.com/users/YOUR_CLIENTID/locations/YOUR_LOCATION",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "x": 490,
        "y": 580,
        "wires": [
            [
                "f0275e1b.3d8eb",
                "dcb23dac.e2f2a"
            ]
        ]
    },
    {
        "id": "77b4f79.8e3e808",
        "type": "function",
        "z": "c30e715.8ba869",
        "name": "oauth2Request",
        "func": "my_token = global.get(\"flume_access_token\");\n\n//node.warn(my_token);\n\nif (my_token) {\n    msg.headers = {\n        Authorization: \"Bearer \" + my_token\n    }\n\n    return msg;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 280,
        "y": 580,
        "wires": [
            [
                "b83c3058.70473",
                "f0275e1b.3d8eb"
            ]
        ]
    },
    {
        "id": "facab48b.205d58",
        "type": "inject",
        "z": "c30e715.8ba869",
        "name": "",
        "props": [
            {
                "p": "payload",
                "v": "",
                "vt": "date"
            },
            {
                "p": "topic",
                "v": "",
                "vt": "string"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 100,
        "y": 580,
        "wires": [
            [
                "77b4f79.8e3e808"
            ]
        ]
    },
    {
        "id": "f0275e1b.3d8eb",
        "type": "debug",
        "z": "c30e715.8ba869",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 670,
        "y": 540,
        "wires": []
    },
    {
        "id": "dcb23dac.e2f2a",
        "type": "change",
        "z": "c30e715.8ba869",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "flume_away_mode",
                "pt": "global",
                "to": "payload.data[0].away_mode",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 740,
        "y": 580,
        "wires": [
            []
        ]
    },
    {
        "id": "ef818986.3a7708",
        "type": "http request",
        "z": "c30e715.8ba869",
        "name": "Set Location State",
        "method": "use",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://api.flumewater.com/users/YOUR_CLIENTID/locations/YOUR_LOCATION",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "x": 490,
        "y": 420,
        "wires": [
            [
                "e93b7ba0.f2b8b8",
                "201a4847.db7e08"
            ]
        ]
    },
    {
        "id": "663350aa.9bc6f",
        "type": "function",
        "z": "c30e715.8ba869",
        "name": "false",
        "func": "my_token = global.get(\"flume_access_token\");\n\nif (my_token) {\n    msg.method = \"PATCH\";\n\n    msg.headers = {\n        \"Authorization\": \"Bearer \" + my_token,\n        \"Content-Type\": \"application/json\"\n    }\n\n    msg.payload = {\n        \"away_mode\": false\n    }\n\n    return msg;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 250,
        "y": 420,
        "wires": [
            [
                "ef818986.3a7708"
            ]
        ]
    },
    {
        "id": "5f65cdae.4f3ab4",
        "type": "inject",
        "z": "c30e715.8ba869",
        "name": "",
        "props": [
            {
                "p": "payload",
                "v": "",
                "vt": "date"
            },
            {
                "p": "topic",
                "v": "",
                "vt": "string"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 100,
        "y": 420,
        "wires": [
            [
                "663350aa.9bc6f"
            ]
        ]
    },
    {
        "id": "e93b7ba0.f2b8b8",
        "type": "debug",
        "z": "c30e715.8ba869",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 670,
        "y": 420,
        "wires": []
    },
    {
        "id": "a481ee26.19848",
        "type": "function",
        "z": "c30e715.8ba869",
        "name": "true",
        "func": "my_token = global.get(\"flume_access_token\");\n\nif (my_token) {\n    msg.method = \"PATCH\";\n\n    msg.headers = {\n        \"Authorization\": \"Bearer \" + my_token,\n        \"Content-Type\": \"application/json\"\n    }\n\n    msg.payload = {\"away_mode\": true}\n\n    return msg;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 250,
        "y": 480,
        "wires": [
            [
                "ef818986.3a7708"
            ]
        ]
    },
    {
        "id": "a7e66bd5.11cad8",
        "type": "inject",
        "z": "c30e715.8ba869",
        "name": "",
        "props": [
            {
                "p": "payload",
                "v": "",
                "vt": "date"
            },
            {
                "p": "topic",
                "v": "",
                "vt": "string"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 100,
        "y": 480,
        "wires": [
            [
                "a481ee26.19848"
            ]
        ]
    },
    {
        "id": "201a4847.db7e08",
        "type": "delay",
        "z": "c30e715.8ba869",
        "name": "",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "x": 680,
        "y": 460,
        "wires": [
            [
                "77b4f79.8e3e808"
            ]
        ]
    },
    {
        "id": "9725c581.21d138",
        "type": "change",
        "z": "c30e715.8ba869",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "flume_refresh_token",
                "pt": "global",
                "to": "payload.data[0].refresh_token",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 790,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "30dfe80.8bf0118",
        "type": "change",
        "z": "c30e715.8ba869",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "flume_access_token",
                "pt": "global",
                "to": "payload.data[0].access_token",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 790,
        "y": 180,
        "wires": [
            []
        ]
    }
]

I posted my Flume driver for Hubitat here: GitHub - tomwpublic/hubitat_flume

It is also listed on my HPM repo.

This version pulls usage data in two styles- one that matches the Flume app and one that is a straight lookback to make comparisons easier for different periods of time. See the readme for more info.

Thanks a ton to @jpalovick for a lot of testing and good suggestions for improvements.

7 Likes

FYI: @markb, @1a7dc3c08d03c361e0c5, @bruce4, @JoshBast, @YapFlapper, @pmusselman, @dtusia

Awesome work! Thank you @tomw

1 Like

Thank you gentlemen, most appreciated.

This evening my Hubitat seems to have died. It won't fully boot to a point where I can access it over http. As soon as I get that problem settled, I'll jump into this. But if I have to replace hub hardware I'm going to be wrestling z-wave devices for a few weeks. :weary:

I loaded the driver through HPM and it seems like all is good with it.

I had an Internet outage this morning for about 15 minutes and as I found out later of course Flume updates stopped with commStatus = error. I didn't look at it until about 3 hours later and it was still in the error state. Simply hitting Refresh on the device panel fixed it. Is there a way for the driver to automatically reconnect after an outage like that via a watchdog or is that a function of the cloud based interface that it may need to be refreshed after Internet outages. I'm most concerned because of the leak detection function which is best if it persistently runs. Thanks, Joe

I moved the next Refresh scheduling to after error handling for a failed Refresh. It was previously just not scheduled in the event of a failure. If your connection or their cloud goes down for a while it may introduce some additional unneeded traffic, but it is small and I agree this would be important to auto-recover.

I updated the version to 1.0.1, so an HPM Update should pick it up.

1 Like