iAqualink for Pool Equipment

BACKGROUND

iAqualink is a service created for connecting Zodiac/Fluidra pool equipment to a cloud-based service for monitoring and control. There are apps for iOS and Android but no open-API for 3rd party control.
Someone has managed to sniff the login process using a http proxy and published a number of curl commands.

I'd like to create a project to do this with Node-Red but lack sufficient depth of knowledge to get it going. I can see parallels with an existing project for Miele home appliances and I'm trying to adapt this -

https://flows.nodered.org/flow/32ab59e2399e1b99f786fd40bfa0835a

MANUAL STEPS

The steps required to login to iAqualink are as follows (note the api_key is the same for all users) -

curl  -X POST -H "Host:prod.zodiac-io.com" -H "accept:application/json" -H "content-type:application/json" -H "accept-encoding:gzip" -H "user-agent:okhttp/3.12.0" -d '{"api_key":"EOOEMOW4YR6QNB11", "email":"XXXX@XXXX", "password":"XXXXXX"}' "https://prod.zodiac-io.com/users/v1/login"

The iAqualink server returns a page of data (X's blank out personal data) -

{
    "username": "XXXXXXXXXXXXXXXXXX",
    "email": "XXXXXXXXXXX@gmail.com",
    "first_name": "XXX",
    "last_name": "XXXXXX",
    "address": "XXX\n, XXX, XX, au, XXX",
    "address_1": "XXXXXXXXXXX",
    "address_2": null,
    "city": "XXXXXXXXXXX",
    "state": "XX",
    "country": "au",
    "postal_code": "XXXX",
    "id": "XXXXXX",
    "authentication_token": "XXXXXXXXXXXXXXXXXXXX",
    "session_id": "XXXXXXXXXXXXXXXXXXXX",
    "created_at": "2019-12-09T03:12:57.000Z",
    "updated_at": "2019-12-09T03:12:57.000Z",
    "time_zone": "Australia/XXXXXX",
    "phone": "XXXXXXXXXX",
    "opt_in_1": "1",
    "opt_in_2": "0",
    "role": "customer",
    "cognitoPool": {
        "appClientId": "XXXXXXXXXXXXXXXXXXXXX",
        "poolId": "us-east-1_XXXXXXXXX",
        "region": "us-east-1"
    },
    "credentials": {
        "AccessKeyId": "XXXXXXXXXXXXXXXXXXXX",
        "SecretKey": "XXXXXXXXXXXXXXXXXXXXXX",
        "SessionToken": "XXXXXX.............XXXXXXXX",
        "Expiration": "2020-04-26T05:24:59.000Z",
        "IdentityId": "us-east-1:XXXXXXXXXXXXXXXX"
    },
    "userPoolOAuth": {
        "AccessToken": "XXXXXXX...............XXXXXXX",
        "ExpiresIn": 3600,
        "TokenType": "Bearer",
        "RefreshToken": "XXXXXX..........XXXXXXXXX",
        "IdToken": "XXXXXXXXXX.........XXXXXXXXXXX"
    
}

The important fields in the preceeding data block are -

"id": (a 6 digit number that remains the same across logins),

"authentication_token": (a 20 character field that remains the same across logins),

"IdToken": (a 1740 character field that expires in 3600 seconds).

Armed with this data, a second command will elicit the device serial numbers registered to the user's account (user_id = id from block above) -

curl 'https://support.iaqualink.com/devices.json?api_key=EOOEMOW4YR6QNB07&authentication_token=XXXXXXXXXXXXXXXXXXXX&user_id=XXXXXX' -H "Host:support.iaqualink.com" -H "accept:application/json" -H "user-agent:okhttp/3.12.0" -H "content-type:application/json"

This is the returned data block for the two devices I have (a chlorinator and a heat pump) -

[
{
"id":"XXXXXX",
"serial_number":"XXXXXXXXXX",
"created_at":"2020-01-07T09:41:41.000Z",
"updated_at":"2020-01-07T09:41:41.000Z",
"name":"Z400 iQ TD7",
"device_type":"hpm",
"owner_id":null,
"updating":false,
"firmware_version":null,
"target_firmware_version":null,
"update_firmware_start_at":null,
"last_activity_at":null
},
{
"id":"XXXXXX",
"serial_number":"XXXXXXXXXX",
"created_at":"2019-12-09T03:18:18.000Z",
"updated_at":"2019-12-09T03:18:18.000Z",
"name":"eXO-Pro iQ",
"device_type":"exo",
"owner_id":null,
"updating":false,
"firmware_version":null,
"target_firmware_version":null,
"update_firmware_start_at":null,
"last_activity_at":null
}
]

INFORMATION RETRIEVAL AND CONTROL

With the DEVICE_SERIAL_NUMBER ("serial_number" in the block above), and ID_TOKEN ("IdToken" from the first block of data) the next GET command can be populated to retrieve the pH and ORP values for the chlorinator -

curl -X GET -H "Host:prod.zodiac-io.com" -H "accept:application/json" -H "authorization:ID_TOKEN" -H "accept-encoding:gzip" -H "user-agent:okhttp/3.12.0" "https://prod.zodiac-io.com/devices/v1/DEVICE_SERIAL_NUMBER/shadow"

Control of the chlorinator production (On/Off) can done using a POST command ("production" to be set to 0 or 1) -

curl -X POST -H "Host:prod.zodiac-io.com" -H "accept:application/json" -H "authorization:ID_TOKEN" -H "content-type:application/json; charset=UTF-8" -H "content-length:62" -H "accept-encoding:gzip" -H "user-agent:okhttp/3.12.0" -d '{"state":{"desired":{"equipment":{"swc_0":{"production":0}}}}}' "https://prod.zodiac-io.com/devices/v1/DEVICE_SERIAL_NUMBER/shadow"

I have the DEVICE_SERIAL_NUMBER for the components in my system (this value is fixed).
I now need a mechanism to record the variable ID_TOKEN and subsequently plug this into the commands above.

If anyone has a Node-Red project to suit Zodiac/Fluidra wifi devices, that would be very appreciated.

I’ll likely come back to this when I’ve a laptop at hand, but have you used the builtin http-request node where you manually set headers in a function or change node before through msg.headers at the msg object going into the HTTP request node. Looking through your provided samples, it appears pretty easy to me, but let me take a closer look when I’m more awake (and post-breakfast probably) to see if I’m not missing anything.
If this was my flow to create, I’d likely create subflows for common task, write permanent tokens in the flow directly (hardcoded in a change node or node-red-contrib-credentials), then saved in flow context. Then have the Auth tokens that change plus the expiration timestamp saved in an object in flow context and have a subflow that checks if the token has expired yet and if so grabs a new one from the site and stores it back in the context. I’d put that subflow instance right before any node/subflow part that would make use of that token.

Thank-you. I have done a lot of reading and started down the path of setting msg.headers in a Change node before the HTTP request node. That's when it dawned on me that the overall task is far more complex than my meagre skills can grip up.

If it helps, the initial link above to the Miele home appliance project has a structure that seems to match your description. The flow below also has a variation on this structure. I've been trying to adapt this, but with little success.

[{"id":"36e78bc0.1657a4","type":"subflow","name":"set action","info":"Set (execute) an action for the given appliance. Valid action depend on kind of appliance and state!","category":"Miele","in":[{"x":40,"y":75,"wires":[{"id":"e344f84b.6d63a8"}]}],"out":[{"x":1011,"y":74,"wires":[{"id":"3343768c.58a80a","port":0}]}],"env":[],"color":"#A70625","inputLabels":["appliance id in payload and an action "],"outputLabels":["result"],"icon":"font-awesome/fa-angle-double-right","status":{"x":1010,"y":240,"wires":[{"id":"ba23f155.0ced5","port":0},{"id":"734a9c46.931454","port":0},{"id":"ee31f24e.c3c59","port":0}]}},{"id":"824bdf4d.098d8","type":"http request","z":"36e78bc0.1657a4","name":"PUT to Miele","method":"PUT","ret":"obj","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":499,"y":188,"wires":[["3343768c.58a80a"]]},{"id":"44a71038.b994b","type":"change","z":"36e78bc0.1657a4","name":"url, body","rules":[{"t":"set","p":"url","pt":"msg","to":"\"https://api.mcs3.miele.com/v1/devices/\" & payload & \"/actions/\"","tot":"jsonata"},{"t":"move","p":"action","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":389,"y":68,"wires":[["48edae3a.b6207"]]},{"id":"e344f84b.6d63a8","type":"function","z":"36e78bc0.1657a4","name":"verify input","func":"// verify bearer & msg properties\nif (!global.get('system_bearerMiele')) {\n    node.error('global variable system_bearerMiele not defined', msg)\n    return [null, {payload:'invalid global variable'}]\n}\nif (!msg.payload){\n    node.error('invalid msg.payload - device id', msg)\n    return [null, {payload:\"invalid msg.payload - device id\"}]\n}\nif (!msg.action){\n    node.error('invalid msg.action - action', msg)\n    return [null, {payload:\"invalid msg.action - action\"}]\n}\nreturn [msg, null]","outputs":2,"noerr":0,"x":193,"y":75,"wires":[["44a71038.b994b"],["ee31f24e.c3c59"]]},{"id":"95c24064.ae356","type":"template","z":"36e78bc0.1657a4","name":"header with authorization","field":"headers","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"Authorization\":\"bearer {{global.system_bearerMiele}}\", \"Accept\": \"application/json\", \"Accept-Charset\": \"utf-8\"}","output":"json","x":459,"y":146,"wires":[["824bdf4d.098d8"]]},{"id":"3343768c.58a80a","type":"switch","z":"36e78bc0.1657a4","name":"ok?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":693,"y":80,"wires":[["ba23f155.0ced5"],["630aed8b.b40564"]]},{"id":"630aed8b.b40564","type":"function","z":"36e78bc0.1657a4","name":"throw error","func":"node.error('http error: ' + msg.payload.message, msg)\nreturn msg\n","outputs":1,"noerr":0,"x":716,"y":160,"wires":[["734a9c46.931454"]]},{"id":"734a9c46.931454","type":"template","z":"36e78bc0.1657a4","name":"error  http","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload.message}}\"}","output":"json","x":863,"y":160,"wires":[[]]},{"id":"ba23f155.0ced5","type":"template","z":"36e78bc0.1657a4","name":"ok!","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"green\",\"shape\":\"dot\",\"text\":\"ok!\"}","output":"json","x":873,"y":120,"wires":[[]]},{"id":"ee31f24e.c3c59","type":"template","z":"36e78bc0.1657a4","name":"error input","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload}}\"}","output":"json","x":330,"y":240,"wires":[[]]},{"id":"48edae3a.b6207","type":"json","z":"36e78bc0.1657a4","name":"","property":"payload","action":"obj","pretty":false,"x":399,"y":108,"wires":[["95c24064.ae356"]]},{"id":"7964a41c.2a733c","type":"subflow","name":"get actions","info":"Get an object with all valid actions for the given appliance id at the current appliance state.\n\nPrereq: bearer in global variable system_bearerMiele","category":"Miele","in":[{"x":40,"y":88,"wires":[{"id":"57067fcd.5fa24"}]}],"out":[{"x":1099,"y":38,"wires":[{"id":"cbad3a98.8e5798","port":0}]}],"env":[],"color":"#A70625","inputLabels":["appliance id"],"outputLabels":["all actions"],"icon":"node-red/debug.svg","status":{"x":1099,"y":158,"wires":[{"id":"dd0236a5.619cb8","port":0},{"id":"549c4889.0b7da8","port":0},{"id":"ea2ecac7.77b158","port":0}]}},{"id":"c50ec4c7.e088c8","type":"http request","z":"7964a41c.2a733c","name":"GET from Miele","method":"GET","ret":"obj","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":460,"y":118,"wires":[["cbad3a98.8e5798"]]},{"id":"61db77d9.0edc48","type":"change","z":"7964a41c.2a733c","name":"msg.url, no payload","rules":[{"t":"set","p":"url","pt":"msg","to":"\"https://api.mcs3.miele.com/v1/devices/\" & payload & \"/actions/\"","tot":"jsonata"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":78,"wires":[["c50ec4c7.e088c8"]]},{"id":"d08604e8.d744f8","type":"template","z":"7964a41c.2a733c","name":"msg.headers with authorization","field":"headers","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"Authorization\":\"bearer {{global.system_bearerMiele}}\", \"Accept\": \"application/json\", \"Accept-Charset\": \"utf-8\"}","output":"json","x":429,"y":38,"wires":[["61db77d9.0edc48"]]},{"id":"57067fcd.5fa24","type":"function","z":"7964a41c.2a733c","name":"verify input","func":"// verify bearer & msg properties\nif (!global.get('system_bearerMiele')) {\n    node.error('global variable system_bearerMiele not defined', msg)\n    return [null, {payload:'invalid global variable'}]\n}\nif (!msg.payload){\n    node.error('invalid msg.payload - device id', msg)\n    return [null, {payload:\"invalid msg.payload - device id\"}]\n}\nreturn [msg, null]","outputs":2,"noerr":0,"x":198,"y":88,"wires":[["d08604e8.d744f8"],["ea2ecac7.77b158"]]},{"id":"cbad3a98.8e5798","type":"switch","z":"7964a41c.2a733c","name":"ok?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":703,"y":38,"wires":[["dd0236a5.619cb8"],["db01768f.79f9f8"]]},{"id":"db01768f.79f9f8","type":"function","z":"7964a41c.2a733c","name":"throw error","func":"node.error('http error: ' + msg.payload.message, msg)\nreturn msg\n","outputs":1,"noerr":0,"x":815,"y":118,"wires":[["549c4889.0b7da8"]]},{"id":"549c4889.0b7da8","type":"template","z":"7964a41c.2a733c","name":"error  http","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload.message}}\"}","output":"json","x":962,"y":118,"wires":[[]]},{"id":"dd0236a5.619cb8","type":"template","z":"7964a41c.2a733c","name":"ok!","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"green\",\"shape\":\"dot\",\"text\":\"ok!\"}","output":"json","x":972,"y":78,"wires":[[]]},{"id":"ea2ecac7.77b158","type":"template","z":"7964a41c.2a733c","name":"error input","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload}}\"}","output":"json","x":310,"y":158,"wires":[[]]},{"id":"21a52172.bdd0be","type":"subflow","name":"get appliance","info":"Get an object with identification and state data for the given appliance id.\n\nPrereq: bearer in global variable system_bearerMiele","category":"Miele","in":[{"x":40,"y":97,"wires":[{"id":"f5c1f25c.0c873"}]}],"out":[{"x":1143,"y":33,"wires":[{"id":"75456de7.83a4b4","port":0}]}],"env":[{"name":"language","type":"str","value":"en","ui":{"label":{"en-US":"Language"},"type":"select","opts":{"opts":[{"l":{"en-US":"English"},"v":"en"},{"l":{"en-US":"Deutsch"},"v":"de"}]}}}],"color":"#A70625","inputLabels":["appliance id as payload"],"outputLabels":["appliance identification and state"],"icon":"font-awesome/fa-info","status":{"x":1140,"y":164,"wires":[{"id":"eaca9293.ae1e4","port":0},{"id":"a7786538.3aee78","port":0},{"id":"a1dd0fd9.3de","port":0}]}},{"id":"1d15b255.55957e","type":"http request","z":"21a52172.bdd0be","name":"GET from Miele","method":"GET","ret":"obj","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":460,"y":120,"wires":[["75456de7.83a4b4"]]},{"id":"a83812be.5deb5","type":"change","z":"21a52172.bdd0be","name":"msg.url, no payload","rules":[{"t":"set","p":"url","pt":"msg","to":"\"https://api.mcs3.miele.com/v1/devices/\" & payload & \"/?language=\" & $env('language')","tot":"jsonata"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":80,"wires":[["1d15b255.55957e"]]},{"id":"73f8a98e.36af08","type":"template","z":"21a52172.bdd0be","name":"msg.headers with authorization","field":"headers","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"Authorization\":\"bearer {{global.system_bearerMiele}}\", \"Accept\": \"application/json\", \"Accept-Charset\": \"utf-8\"}","output":"json","x":410,"y":40,"wires":[["a83812be.5deb5"]]},{"id":"f5c1f25c.0c873","type":"function","z":"21a52172.bdd0be","name":"verify input","func":"// verify bearer & msg properties\nif (!global.get('system_bearerMiele')) {\n    node.error('global variable system_bearerMiele not defined', msg)\n    return [null, {payload:'invalid global variable'}]\n}\nif (!msg.payload){\n    node.error('invalid msg.payload - device id', msg)\n    return [null, {payload:\"invalid msg.payload - device id\"}]\n}\nreturn [msg, null]","outputs":2,"noerr":0,"x":192,"y":97,"wires":[["73f8a98e.36af08"],["a7786538.3aee78"]]},{"id":"75456de7.83a4b4","type":"switch","z":"21a52172.bdd0be","name":"ok?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":670,"y":40,"wires":[["a1dd0fd9.3de"],["d643daad.b7c6b8"]]},{"id":"d643daad.b7c6b8","type":"function","z":"21a52172.bdd0be","name":"throw error","func":"node.error('http error: ' + msg.payload.message, msg)\nreturn msg\n","outputs":1,"noerr":0,"x":830,"y":120,"wires":[["eaca9293.ae1e4"]]},{"id":"eaca9293.ae1e4","type":"template","z":"21a52172.bdd0be","name":"error  http","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload.message}}\"}","output":"json","x":980,"y":120,"wires":[[]]},{"id":"a7786538.3aee78","type":"template","z":"21a52172.bdd0be","name":"error input","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload}}\"}","output":"json","x":350,"y":164,"wires":[[]]},{"id":"a1dd0fd9.3de","type":"template","z":"21a52172.bdd0be","name":"ok!","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"green\",\"shape\":\"dot\",\"text\":\"ok!\"}","output":"json","x":990,"y":80,"wires":[[]]},{"id":"34d56843.0c0358","type":"subflow","name":"get all appliances","info":"Get a list of all appliances with indentification and state.\n\nPrereq: bearer in global variable system_bearerMiele","category":"Miele","in":[{"x":40,"y":80,"wires":[{"id":"4748872d.85c1f8"}]}],"out":[{"x":1119,"y":74,"wires":[{"id":"774ba1f0.40b81","port":0}]}],"env":[{"name":"language","type":"str","value":"en","ui":{"label":{"en-US":"Language"},"type":"select","opts":{"opts":[{"l":{"en-US":"English"},"v":"en"},{"l":{"en-US":"Deutsch"},"v":"de"}]}}}],"color":"#A70625","inputLabels":["just trigger"],"outputLabels":["appliance list in household"],"icon":"font-awesome/fa-list-ul","status":{"x":1120,"y":220,"wires":[{"id":"74dc75d1.9f6abc","port":0},{"id":"2c704f68.3729a","port":0},{"id":"51dcabc.5e9ad54","port":0}]}},{"id":"73e280eb.1bf07","type":"http request","z":"34d56843.0c0358","name":"GET from Miele","method":"GET","ret":"obj","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":480,"y":160,"wires":[["774ba1f0.40b81"]]},{"id":"16f10d8a.86e662","type":"change","z":"34d56843.0c0358","name":"define url delete payload","rules":[{"t":"set","p":"url","pt":"msg","to":"\"https://api.mcs3.miele.com/v1/devices/?language=\" & $env('language')","tot":"jsonata"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":120,"wires":[["73e280eb.1bf07"]]},{"id":"c7df6745.d1d1e8","type":"template","z":"34d56843.0c0358","name":"header with authorization","field":"headers","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"Authorization\":\"bearer {{global.system_bearerMiele}}\", \"Accept\": \"application/json\", \"Accept-Charset\": \"utf-8\"}","output":"json","x":429,"y":80,"wires":[["16f10d8a.86e662"]]},{"id":"4748872d.85c1f8","type":"function","z":"34d56843.0c0358","name":"verify input","func":"// verify token\nif (!global.get('system_bearerMiele')) {\n    node.error('global variable system_bearerMiele not defined', msg)\n    return [null, {payload:'invalid global variable'}]\n}\nreturn [msg, null]","outputs":2,"noerr":0,"x":218,"y":80,"wires":[["c7df6745.d1d1e8"],["51dcabc.5e9ad54"]]},{"id":"774ba1f0.40b81","type":"switch","z":"34d56843.0c0358","name":"ok?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":650,"y":80,"wires":[["2c704f68.3729a"],["eb1db9b6.51f118"]]},{"id":"eb1db9b6.51f118","type":"function","z":"34d56843.0c0358","name":"throw error","func":"node.error('http error: ' + msg.payload.message, msg)\nreturn msg\n","outputs":1,"noerr":0,"x":810,"y":160,"wires":[["74dc75d1.9f6abc"]]},{"id":"74dc75d1.9f6abc","type":"template","z":"34d56843.0c0358","name":"error http","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload.message}}\"}","output":"json","x":963,"y":160,"wires":[[]]},{"id":"2c704f68.3729a","type":"template","z":"34d56843.0c0358","name":"ok!","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"green\",\"shape\":\"dot\",\"text\":\"ok!\"}","output":"json","x":954,"y":120,"wires":[[]]},{"id":"51dcabc.5e9ad54","type":"template","z":"34d56843.0c0358","name":"error input","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload}}\"}","output":"json","x":390,"y":218,"wires":[[]]},{"id":"d8085e03.ed6b3","type":"subflow","name":"get bearer","info":"Get a new bearer and refresh token and stores it in global variables. \n\nThe global variables system_bearerMiele and system_refreshMiele are being used.","category":"Miele","in":[{"x":30,"y":63,"wires":[{"id":"6b228a44.84cba4"}]}],"out":[{"x":1221,"y":43,"wires":[{"id":"abcdd5c8.0008b8","port":0}]}],"env":[{"name":"vg","type":"str","value":"en-au","ui":{"label":{"en-US":"Country"},"type":"select","opts":{"opts":[{"l":{"en-US":"Germany"},"v":"de-DE"},{"l":{"en-US":"Australia"},"v":"en-au"}]}}},{"name":"doRefresh","type":"bool","value":"false","ui":{"label":{"en-US":"Use refresh token"},"type":"checkbox","opts":{}}}],"color":"#A70625","inputLabels":["client and user credentials"],"outputLabels":["Miele reponse object"],"icon":"font-awesome/fa-key","status":{"x":1221,"y":254,"wires":[{"id":"eb6b2b96.85e218","port":0},{"id":"8f4ba21f.99fa7","port":0},{"id":"a1be738b.9bd1e","port":0}]}},{"id":"103f2273.95270e","type":"change","z":"d8085e03.ed6b3","name":"get env","rules":[{"t":"set","p":"vg","pt":"msg","to":"vg","tot":"env"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":63,"wires":[["b1fcf9b0.64ebf8"]]},{"id":"2fc77e76.9b70d2","type":"switch","z":"d8085e03.ed6b3","name":"ok?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":774,"y":143,"wires":[["eb6b2b96.85e218","abcdd5c8.0008b8"],["af183f22.11e16"]]},{"id":"af183f22.11e16","type":"function","z":"d8085e03.ed6b3","name":"throw error","func":"node.error('http error: ' + msg.payload.message, msg)\nreturn msg\n","outputs":1,"noerr":0,"x":934,"y":223,"wires":[["8f4ba21f.99fa7"]]},{"id":"8f4ba21f.99fa7","type":"template","z":"d8085e03.ed6b3","name":"error  http","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload.message}}\"}","output":"json","x":1081,"y":223,"wires":[[]]},{"id":"eb6b2b96.85e218","type":"template","z":"d8085e03.ed6b3","name":"ok!","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"green\",\"shape\":\"dot\",\"text\":\"ok!\"}","output":"json","x":1091,"y":183,"wires":[[]]},{"id":"a1be738b.9bd1e","type":"template","z":"d8085e03.ed6b3","name":"error input","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"fill\":\"red\",\"shape\":\"dot\",\"text\":\"error: {{payload}}\"}","output":"json","x":360,"y":258,"wires":[[]]},{"id":"6b228a44.84cba4","type":"function","z":"d8085e03.ed6b3","name":"verify input","func":"// verify bearer & msg properties\nif (!msg.username) {\n    node.error('msg.username not defined', msg)\n    return [null, {payload:'msg.username not defined'}]\n}\nif (!msg.password){\n    node.error('msg.password not defined', msg)\n    return [null, {payload:\"msg.password not defined\"}]\n}\nif (!msg.clientId){\n    node.error('msg.clientId not defined', msg)\n    return [null, {payload:\"msg.clientId not defined\"}]\n}\nif (!msg.clientSecret){\n    node.error('msg.clientSecret not defined', msg)\n    return [null, {payload:\"msg.clientSecret not defined\"}]\n}\nreturn [msg, null]","outputs":2,"noerr":0,"x":160,"y":63,"wires":[["103f2273.95270e"],["a1be738b.9bd1e"]]},{"id":"305215f6.6cc0ba","type":"template","z":"d8085e03.ed6b3","name":"create body new","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"grant_type=password&username={{username}}&password={{password}}&client_id={{clientId}}&client_secret={{clientSecret}}&vg={{vg}}","output":"str","x":527,"y":103,"wires":[["78040e47.71747"]]},{"id":"78040e47.71747","type":"change","z":"d8085e03.ed6b3","name":"define url, headers","rules":[{"t":"set","p":"url","pt":"msg","to":"https://api.mcs3.miele.com/thirdparty/token/","tot":"str"},{"t":"set","p":"headers","pt":"msg","to":"{\"Content-Type\":\"application/x-www-form-urlencoded\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":754,"y":63,"wires":[["34d76af9.cdd386"]]},{"id":"34d76af9.cdd386","type":"http request","z":"d8085e03.ed6b3","name":"","method":"POST","ret":"obj","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":774,"y":103,"wires":[["2fc77e76.9b70d2"]]},{"id":"7ccf7060.bb96","type":"comment","z":"d8085e03.ed6b3","name":"Miele only supports form encoded","info":"","x":530,"y":23,"wires":[]},{"id":"abcdd5c8.0008b8","type":"change","z":"d8085e03.ed6b3","name":"","rules":[{"t":"set","p":"system_bearerMiele","pt":"global","to":"payload.access_token","tot":"msg"},{"t":"set","p":"system_refreshMiele","pt":"global","to":"payload.refresh_token","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1001,"y":136,"wires":[[]]},{"id":"39b7be16.653682","type":"template","z":"d8085e03.ed6b3","name":"create body refresh","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"grant_type=refresh_token&refresh_token={{global.system_refreshMiele}}&client_id={{clientId}}&client_secret={{clientSecret}}&vg={{vg}}","output":"str","x":537,"y":63,"wires":[["78040e47.71747"]]},{"id":"b1fcf9b0.64ebf8","type":"switch","z":"d8085e03.ed6b3","name":"","property":"doRefresh","propertyType":"env","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":360,"y":103,"wires":[["39b7be16.653682"],["305215f6.6cc0ba"]]},{"id":"33da6c9c.88ebe4","type":"inject","z":"56863603.2a0738","name":"trigger","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":440,"wires":[["6dbb9704.279428"]]},{"id":"1fb2a00a.b5dc7","type":"comment","z":"56863603.2a0738","name":"B. get information about all devices","info":"","x":266,"y":407,"wires":[]},{"id":"10cc427a.38656e","type":"debug","z":"56863603.2a0738","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":867,"y":690,"wires":[]},{"id":"1e1f4023.a52c9","type":"inject","z":"56863603.2a0738","name":"","topic":"","payload":"000100000636","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":243,"y":613,"wires":[["ee1f9b0c.bb0268"]]},{"id":"bf015d63.e5af4","type":"comment","z":"56863603.2a0738","name":"C. get all information about one specifc device","info":"","x":294,"y":579,"wires":[]},{"id":"6855e393.2f06fc","type":"comment","z":"56863603.2a0738","name":"D. get all actions of one specific device - depend on the current state of the device","info":"","x":404,"y":905,"wires":[]},{"id":"75c3cf45.b8703","type":"comment","z":"56863603.2a0738","name":"A. get new bearer  - also do refresh token","info":"","x":284,"y":310,"wires":[]},{"id":"e981efe8.4619e","type":"inject","z":"56863603.2a0738","name":"trigger","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":206,"y":343,"wires":[["a02f485b.72ff88"]]},{"id":"5f0a934.5f9e56c","type":"subflow:d8085e03.ed6b3","z":"56863603.2a0738","name":"","env":[{"name":"vg","value":"de-DE","type":"str"},{"name":"language","value":"de","type":"str"},{"name":"clientId","value":"xx","type":"str"},{"name":"clientSecret","value":"xxx","type":"str"},{"name":"username","value":"xxxxx","type":"str"},{"name":"password","value":"xxxxxx","type":"str"}],"x":510,"y":343,"wires":[["10cc427a.38656e"]]},{"id":"6dbb9704.279428","type":"subflow:34d56843.0c0358","z":"56863603.2a0738","name":"","env":[{"name":"language","value":"en","type":"str"}],"x":373,"y":440,"wires":[["10cc427a.38656e"]]},{"id":"ee1f9b0c.bb0268","type":"subflow:21a52172.bdd0be","z":"56863603.2a0738","name":"","env":[],"x":424,"y":613,"wires":[["10cc427a.38656e"]]},{"id":"624bf1c4.7d2c5","type":"comment","z":"56863603.2a0738","name":"C. get current status of a specifc device","info":"","x":274,"y":733,"wires":[]},{"id":"c333809f.0c254","type":"inject","z":"56863603.2a0738","name":"","topic":"","payload":"000100000636","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":243,"y":766,"wires":[["5ac835f8.05600c"]]},{"id":"5ac835f8.05600c","type":"subflow:21a52172.bdd0be","z":"56863603.2a0738","name":"","env":[],"x":424,"y":766,"wires":[["33c16016.adf44"]]},{"id":"790267c2.7a3a18","type":"comment","z":"56863603.2a0738","name":"C. get current device type of a specifc device","info":"","x":294,"y":657,"wires":[]},{"id":"35be762d.b2ad4a","type":"inject","z":"56863603.2a0738","name":"","topic":"","payload":"000100000636","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":243,"y":690,"wires":[["eda3fd42.51f1f"]]},{"id":"eda3fd42.51f1f","type":"subflow:21a52172.bdd0be","z":"56863603.2a0738","name":"","env":[],"x":424,"y":690,"wires":[["b13ea67e.03e318"]]},{"id":"46a8b13e.24041","type":"comment","z":"56863603.2a0738","name":"B. get device id of first device","info":"","x":244,"y":481,"wires":[]},{"id":"6e23298a.e23d98","type":"inject","z":"56863603.2a0738","name":"trigger","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":209,"y":514,"wires":[["e9983582.3dda98"]]},{"id":"e9983582.3dda98","type":"subflow:34d56843.0c0358","z":"56863603.2a0738","name":"","env":[],"x":370,"y":514,"wires":[["5aa32bba.d64574"]]},{"id":"5aa32bba.d64574","type":"function","z":"56863603.2a0738","name":"id first object","func":"msg.payload = msg.payload[Object.keys(msg.payload)[0]].ident.deviceIdentLabel.fabNumber\nreturn msg;","outputs":1,"noerr":0,"x":552,"y":514,"wires":[["10cc427a.38656e"]]},{"id":"2de9833e.779f8c","type":"comment","z":"56863603.2a0738","name":"C. get remaining time of specific device","info":"","x":274,"y":810,"wires":[]},{"id":"890a99fc.f70b08","type":"inject","z":"56863603.2a0738","name":"","topic":"","payload":"000100000636","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":243,"y":843,"wires":[["7eb7147.08882ec"]]},{"id":"7eb7147.08882ec","type":"subflow:21a52172.bdd0be","z":"56863603.2a0738","name":"","env":[],"x":424,"y":843,"wires":[["880f000e.55508"]]},{"id":"880f000e.55508","type":"change","z":"56863603.2a0738","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.state.remainingTime[0]*60 + payload.state.remainingTime[1] &  \" min\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":843,"wires":[["10cc427a.38656e"]]},{"id":"b13ea67e.03e318","type":"change","z":"56863603.2a0738","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.ident.type.value_localized","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":690,"wires":[["10cc427a.38656e"]]},{"id":"33c16016.adf44","type":"change","z":"56863603.2a0738","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.state.status.value_localized","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":766,"wires":[["10cc427a.38656e"]]},{"id":"7947e7f7.f779e8","type":"subflow:7964a41c.2a733c","z":"56863603.2a0738","name":"","env":[{"name":"language","value":"de","type":"str"}],"x":414,"y":938,"wires":[["10cc427a.38656e"]]},{"id":"86854b98.814cf8","type":"inject","z":"56863603.2a0738","name":"","topic":"","payload":"000100000636","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":243,"y":938,"wires":[["7947e7f7.f779e8"]]},{"id":"83af58f9.576748","type":"comment","z":"56863603.2a0738","name":"E. execute command set name {\"deviceName\":\"My_Devicename\"}","info":"","x":364,"y":998,"wires":[]},{"id":"ffbe2df5.28ba1","type":"inject","z":"56863603.2a0738","name":"trigger","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":213,"y":1030,"wires":[["2317b941.d89696"]]},{"id":"d289f75d.5eeec8","type":"subflow:36e78bc0.1657a4","z":"56863603.2a0738","name":"","env":[{"name":"language","value":"de","type":"str"}],"x":542,"y":1030,"wires":[["10cc427a.38656e"]]},{"id":"932a737c.a8548","type":"subflow:36e78bc0.1657a4","z":"56863603.2a0738","name":"","env":[],"x":542,"y":1111,"wires":[["10cc427a.38656e"]]},{"id":"10405ca4.b7b083","type":"comment","z":"56863603.2a0738","name":"E. execute command stop {\"processAction\":2}","info":"","x":294,"y":1078,"wires":[]},{"id":"2317b941.d89696","type":"change","z":"56863603.2a0738","name":"machine id, action","rules":[{"t":"set","p":"payload","pt":"msg","to":"000149287451","tot":"str"},{"t":"set","p":"action","pt":"msg","to":"{\"deviceName\":\"hkMiele\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":368,"y":1030,"wires":[["d289f75d.5eeec8"]]},{"id":"2b533430.de3dac","type":"inject","z":"56863603.2a0738","name":"trigger","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":213,"y":1111,"wires":[["4429e699.96b598"]]},{"id":"4429e699.96b598","type":"change","z":"56863603.2a0738","name":"machine id, action","rules":[{"t":"set","p":"payload","pt":"msg","to":"000149287451","tot":"str"},{"t":"set","p":"action","pt":"msg","to":"{\"processAction\":2}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":368,"y":1111,"wires":[["932a737c.a8548"]]},{"id":"c98f7f4e.21a51","type":"comment","z":"56863603.2a0738","name":"both commands throw error 204 without error message although action was performed succesfully","info":"","x":1029,"y":996,"wires":[]},{"id":"a02f485b.72ff88","type":"credentials","z":"56863603.2a0738","name":"","props":[{"value":"username","type":"msg"},{"value":"password","type":"msg"},{"value":"clientId","type":"msg"},{"value":"clientSecret","type":"msg"}],"x":349,"y":343,"wires":[["5f0a934.5f9e56c"]]}]
1 Like

I posted one of my flows a bit ago that I still have to publish to the flows library. I’ll see if I can find it on mobile. It uses Spotify’s OAuth API (with the same kind of Bearer and refresh tokens), and uses just about only core nodes to handle everything. My day was just slowed down significantly due to the fire brigade arriving after my upstairs neighbour thought smoking in bed was a good idea. The new infrared smoke sensor stopped him from even lighting his cigarette, so nothing happened. And I’ve now the evidence the alarms/sirens here are top notch too. It will be a while before I take out my laptop to look up flows or write some quick examples though...

1 Like

Hello @afelix. If this flow is still on your radar, any assistance would be very appreciated.

Thanks for the reminder. After another week from hell I truly forgot. Dug it up in a forum search, here you go.

Thanks. I'll try it out.

Hello @afelix. I've spent a fair bit of time trying to adapt the Spotify API without success. The problem is that I know next to nothing about coding or Node-Red. I've tried the 'technique' of 100+ educated guesses to modify this for my application. If I try 1000 different guesses I might get there...but probably not. :confused:

If you have any time to look at the iAqualink process descibed above, any specific guidance (set at numpty level) would be very welcome.