Not too sure what I've messed up here. I'm trying to send a command to an API to control my aircon system

Hi all,

So If I send the following command via curl in a terminal, it does work fine:

curl \
-H 'Host: que.actronair.com.au' \
-H 'Accept: application/json' \
-H 'Accept-Language: en-AU;q=1' \
-H 'User-Agent: nxgen-ios/1.1.2 (iPhone; iOS 12.1.4; Scale/3.00),SignalR.Client.iOS/2.0.0.0 (iPhone 12.1.4)' \ 
-H 'Authorization: Bearer ABCDE' \ 
-d '{"command":{"UserAirconSettings.isOn”:true,”type":"set-settings"}}' \ 
--compressed 'https://que.actronair.com.au/api/v0/client/ac-systems/cmds/send?serial=12345’

This turns on my aircon and gives the following response:

{"correlationId":"271ae385-18a2-4a3b-9f09-0db55815b2f3","type":"ack","value":{"UserAirconSettings.isOn":true,"type":"set-settings"},"mwcResponseTime":"00:00:00.5460783"}

The below flow (ID's redacted obviously) doesn't work:

[{"id":"7eb9f18e.d50aa","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"ed9d0ae0.1660a8","type":"inject","z":"7eb9f18e.d50aa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":356.5,"y":302,"wires":[["a718d353.9a125"]]},{"id":"a718d353.9a125","type":"function","z":"7eb9f18e.d50aa","name":"set payload and headers","func":"msg.payload = '{\"command\":{\"UserAirconSettings.isOn”:true,”type\":\"set-settings\"}}';\nmsg.headers = {};\nmsg.headers['Host'] = 'que.actronair.com.au';\nmsg.headers['Accept'] = 'application/json';\nmsg.headers['Accept-Language'] = 'en-AU;q=1';\nmsg.headers['User-Agent'] = 'nxgen-ios/1.1.2 (iPhone; iOS 12.1.4; Scale/3.00),SignalR.Client.iOS/2.0.0.0 (iPhone 12.1.4)';\nmsg.headers['Authorization'] = 'Bearer ABCDE'\nreturn msg; ","outputs":1,"noerr":0,"x":606,"y":290,"wires":[["baafbbe0.99deb8"]]},{"id":"baafbbe0.99deb8","type":"http request","z":"7eb9f18e.d50aa","name":"Token Response","method":"POST","ret":"txt","paytoqs":false,"url":"https://que.actronair.com.au/api/v0/client/ac-systems/cmds/send?serial=12345","tls":"","proxy":"","x":847,"y":290,"wires":[["14969213.2f520e"]]},{"id":"14969213.2f520e","type":"debug","z":"7eb9f18e.d50aa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1088.5,"y":280,"wires":[]}]    

And gives the following error:

"An internal server error prevented this request from being successfully processed. Error Reference: 48b9312c-4d98-4784-a84a-ff3cb7985010"

Previously when I've received a similar error via curl in the terminal, it's because I've used the wrong quotation mark or something similar. Basically it seems to mean that the request is getting through OK but there's something wrong with the formatting maybe?

Just hoping someone can see something obvious I'm doing wrong, as I only started playing with Node-Red yesterday so I'm fumbling through it.

EDIT: I should say that I know I'm using --compressed in the curl statement, but it's not necessary, it works fine without it. It's just a product of testing.

Thanks!!!!

Spot the deliberate mistake (the quote after isOn is not valid)

1 Like

and also before the word type

1 Like

Ugh! Thanks guys, it's so much clearer typed out like that, I must have stared at it 1000 times and not noticed.

Works great now!

2 Likes

Hi Simon, Im trying to set something up similar with my actron que system...

Are you still developing this? Have you had much success?

Thanks

Hi mate, yep I've got it all working and integrated with home assistant. I did start typing out a Medium blog to explain how to set it up, but there's a lot to it and I haven't really had the time.

Happy to answer any questions though.

Nice, fair enough I can imagine you've spent some time on it.
I've spent a fair chuck of time and don't seem to be getting very far.

I've got to the point of obtaining a pairing token then a bearer and creating a new device. When I login to que.actronair.com.au I'm able to see it on my device list, but that's about as far as I'm getting. As soon as I try to send any commands I get the below

"{"message":"Authorization has been denied for this request."}"

I've tried to copy your flow from above and ive imported it into node red and obviously entered all my relevant info also without success...

OK, so bear in mind that your bearer token expires after 72 hours. I do a couple of things to check if my token needs to be refreshed and then grab a new one, so my syntax is a little different to the below, but try this.

First get a new bearer token.

Create a function node and add:

msg.headers = {};
msg.headers['Host'] = 'que.actronair.com.au';
msg.headers['Accept'] = 'application/json';
msg.headers['Accept-Language'] = 'en-AU;q=1';
msg.headers['User-Agent'] = 'nxgen-ios/1.1.2 (iPhone; iOS 12.1.4; Scale/3.00),SignalR.Client.iOS/2.0.0.0 (iPhone 12.1.4)';
msg.headers['Authorization'] = 'yourtoken'
return msg;

then add an http request node after that and configure that as :

Method: GET
URL: https://que.actronair.com.au/api/v0//client/ac-systems/status/latest?serial=yourserialnumber

then add a debug node after that and run the thing. You should hopefully see the current state of your unit returned in the debug pane in node-red...

That'll at least confirm it's working for now

Thanks, I followed the above steps but in the debug pane am getting the below

<!DOCTYPE html><html>    <head>        <title>Runtime Error</title>        <meta name="viewport" content="width=device-width" />        <style>         body {font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;}          p {font-family:"Verdana";font-weight:normal;color:black;margin-top: -5px}         b {font-family:"Verdana";font-weight:bold;color:black;margin-top: -5px}         H1 { font-family:"Verdana";font-weight:normal;font-size:18pt;color:red }         H2 { font-family:"Verdana";font-weight:normal;font-size:14pt;color:maroon }         pre {font-family:"Consolas","Lucida Console",Monospace;font-size:11pt;margin:0;padding:0.5em;line-height:14pt}         .marker {font-weight: bold; color: black;text-decoration: none;}         .version {color: gray;}         .error {margin-bottom: 10px;}         .expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:hand; }         @media screen and (max-width: 639px) {       ...

The Below flow (id's removed) is what is giving me the above in the debug window.

[{"id":"d0773bd1.4efcb","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"d07a7e34.13b9d8","type":"function","z":"d0773bd1.4efcb","name":"Get status","func":"msg.headers = {};\nmsg.headers['Host'] = 'que.actronair.com.au';\nmsg.headers['Accept'] = 'application/json';\nmsg.headers['Accept-Language'] = 'en-AU;q=1';\nmsg.headers['User-Agent'] = 'nxgen-ios/1.1.2 (iPhone; iOS 12.1.4; Scale/3.00),SignalR.Client.iOS/2.0.0.0 (iPhone 12.1.4)';\nmsg.headers['Authorization'] = '\"mybearertoken"';\nreturn msg;\n","outputs":1,"noerr":0,"x":290,"y":220,"wires":[["e82bebab.97814"]]},{"id":"e82bebab.97814","type":"http request","z":"d0773bd1.4efcb","name":"","method":"GET","ret":"txt","paytoqs":false,"url":"http://que.actronair.com.au/api/v0//client/ac-systems/status/latest?serial=myserialnumber","tls":"","persist":false,"proxy":"","authType":"","x":470,"y":220,"wires":[["6c7314e7.ce4c54"]]},{"id":"6c7314e7.ce4c54","type":"debug","z":"d0773bd1.4efcb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":650,"y":220,"wires":[]},{"id":"70425691.23ac28","type":"inject","z":"d0773bd1.4efcb","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":220,"wires":[["d07a7e34.13b9d8"]]}]

Just confirming you changed http://que.actronair.com.au/api/v0//client/ac-systems/status/latest?serial=myserialnumber to your actual unit serial number?

Yes, I have put the wall controller serial number there is that correct?
It’s the same s/n that is displayed in the que devices list.

Yep that's the one.

Ok interesting. When I get a chance I'll import your flow and see if it works for me.

Ok great thanks.
I think ive managed to work part of it out, I had the bearer token line incorrect.

its was

' "extra extra long token" '

I updated to

' "Bearer extra extra long token" '

This has started to give me the below response in the debug pane

{"isOnline":false,"timeSinceLastContact":"737389.15:07:05.7385530","lastStatusUpdate":"0001-01-01T00:00:00+00:00","lastKnownState":null}

I have been able to send the same request via postman and get the full device status report so can only assume there is something incorrect in my flow?

Sorry, I'm an idiot. I store my token in a variable and just call that so I missed a vital part. You want the Auth lline of the Get Status function to be:

msg.headers['Authorization'] = 'Bearer 1234567';

(No " marks)

Where 1234567 is your big long bearer token.

[{"id":"8a28568c.e4c928","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"200098c3.928cd8","type":"function","z":"8a28568c.e4c928","name":"Get status","func":"msg.headers = {};\nmsg.headers['Host'] = 'que.actronair.com.au';\nmsg.headers['Accept'] = 'application/json';\nmsg.headers['Accept-Language'] = 'en-AU;q=1';\nmsg.headers['User-Agent'] = 'nxgen-ios/1.1.2 (iPhone; iOS 12.1.4; Scale/3.00),SignalR.Client.iOS/2.0.0.0 (iPhone 12.1.4)';\nmsg.headers['Authorization'] = 'Bearer QrpKUalInx_niPcUlT1jYOfZzw9lBmP2QLxxxxxxxxxxxNrrcHHUBHIjJSGWUopc6FyP39A-0XLZ4yAKkiUmOcqsbwLbe4AolFBsZsWBdeGHBI3n1PyaUAaV4WtcoGdblg8XvbK8orH87df6MYr3jHHR757Sf9oU5iQBHZcyjM__IQD3rDfpiA91M8QMED_Wpn3D1a5S8lfevE0guXZBYYNkux_d_XS1d9iChvmuzGkXKMLX_9QQROqJoR8gQfgJ61H1vbmNTv2jiAAJv1JZKMheNAQHlvjezNbRr5A_NPpZJVeQ80dAarzFnEypHP4naPBIiFgVPL6E60BC15ddNQbtmAQYqoy3KyJM5hFrAQ3Cy72Y4S4eENNwO4jCZqycZyvmF-eqoXoLb8gSYXqN11hpWmOqnBdzZM8-fCKEXMLCvTfYUUyX5H30vpDMHAUb_SG-fgtk7RRV_jgaqZv94Z8t01jkr6umGnSotE7BFAty7eHL0A6atDyD5'\nreturn msg;\n","outputs":1,"noerr":0,"x":290,"y":220,"wires":[["e392e293.9160a"]]},{"id":"e392e293.9160a","type":"http request","z":"8a28568c.e4c928","name":"","method":"GET","ret":"txt","paytoqs":false,"url":"http://que.actronair.com.au/api/v0//client/ac-systems/status/latest?serial=xxxxxx","tls":"","persist":false,"proxy":"","authType":"","x":470,"y":220,"wires":[["83039081.a012b"]]},{"id":"83039081.a012b","type":"debug","z":"8a28568c.e4c928","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":650,"y":220,"wires":[]},{"id":"632b5c08.111194","type":"inject","z":"8a28568c.e4c928","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":220,"wires":[["200098c3.928cd8"]]}]

Thanks, finally got it working and got a response. I Have even managed to send commands and have it function.

Few odd things though, when I send the control zone[1] command it controls what is set in my system as zone 2, seems i need to send zone[0] to control zone 1...

What have you done to manage the constant need to refresh the bearer token?

I’ll send you a copy of my flow for that. Essentially though I grab a new token and store it as a variable (they call it a context) then start a 72 hour timer (pretty much), and once that timer is up it deletes that variable. Any time I try and send a command the first thing it does is check if that variable exists, and if it doesn’t it branches off and grabs a new token and then goes back to doing whatever you asked. Works well though there’s probably a more elegant solution :smile:

And yes you’re right, it starts from zone 0.

Ok,

This flow (I use this at the start of every flow that requests or sends to Que) firstly checks for the existence of the Token variable. If it doesn't exist, it requests a new one and saves both the token variable and the time it was grabbed in another variable. If the Token already exists it ignores that part and just carries on. You'll need to replace the Request token with your own here, and replace your serial number. I've added the Get Status function onto the end of this just so you can test it works easily:

[{"id":"64bdfb5e.4acc64","type":"switch","z":"4393de3b.33b5c","name":"Check Token Store","property":"token","propertyType":"global","rules":[{"t":"nnull"},{"t":"null"}],"checkall":"false","repair":false,"outputs":2,"x":230,"y":100,"wires":[["f85ecd4d.76a62"],["4faee4a3.abc79c"]]},{"id":"4faee4a3.abc79c","type":"function","z":"4393de3b.33b5c","name":"Format Headers","func":"msg.payload = \"grant_type=refresh_token&refresh_token=xxxxxxxx=&client_id=app\";\nmsg.headers = {};\nmsg.headers['Host'] = 'que.actronair.com.au';\nmsg.headers['Accept'] = '*/*' ;\nmsg.headers['Accept-Language'] = 'en-au';\nmsg.headers['User-Agent'] = 'nxgen-ios/1214 CFNetwork/976 Darwin/18.2.0';\nreturn msg; ","outputs":1,"noerr":0,"x":220,"y":160,"wires":[["dc93b59e.adee18"]]},{"id":"dc93b59e.adee18","type":"http request","z":"4393de3b.33b5c","name":"POST Auth","method":"POST","ret":"obj","paytoqs":true,"url":"https://que.actronair.com.au/api/v0/oauth/token","tls":"","proxy":"","x":390,"y":160,"wires":[["754fd3cd.70471c"]]},{"id":"754fd3cd.70471c","type":"function","z":"4393de3b.33b5c","name":"Split Bearer Token","func":"var access = msg.payload.access_token;\nglobal.set(\"token\", access);\n\nvar check = flow.get(\"time\");\nif (check===undefined){\n   flow.set(\"time\", Date.now())\n}\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":160,"wires":[["f85ecd4d.76a62"]]},{"id":"f85ecd4d.76a62","type":"function","z":"4393de3b.33b5c","name":"Format Headers","func":"var token = global.get(\"token\");\nvar Bearheader = `Bearer ${token}`;\nmsg.headers = {};\nmsg.headers['Host'] = 'que.actronair.com.au';\nmsg.headers['Accept'] = 'application/json';\nmsg.headers['Accept-Language'] = 'en-AU;q=1';\nmsg.headers['User-Agent'] = 'nxgen-ios/1.1.2 (iPhone; iOS 12.1.4; Scale/3.00),SignalR.Client.iOS/2.0.0.0 (iPhone 12.1.4)';\nmsg.headers['Authorization'] = Bearheader\nreturn msg;","outputs":1,"noerr":0,"x":480,"y":100,"wires":[["3a549b14.d05cf4"]]},{"id":"3a549b14.d05cf4","type":"http request","z":"4393de3b.33b5c","name":"GET Latest Status","method":"GET","ret":"txt","paytoqs":false,"url":"https://que.actronair.com.au/api/v0//client/ac-systems/status/latest?serial=xxxx","tls":"","proxy":"","x":670,"y":100,"wires":[["9e4e4c0e.da5d4"]]}]

This flow checks the time every minute and once 72 hours have passed since getting the token, it deletes both the Time variable and the Token variable:

[{"id":"484b7f92.43c65","type":"change","z":"4393de3b.33b5c","name":"","rules":[{"t":"delete","p":"token","pt":"global"},{"t":"delete","p":"time","pt":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":2720,"wires":[["630d7b5.734cc84"]]},{"id":"fb81c897.5fbc98","type":"function","z":"4393de3b.33b5c","name":"Compare time","func":"msg.payload = \"test\";\nvar timestamp = flow.get(\"time\");\nvar diffMS = Date.now() - timestamp;\nconst maxMS = 72*60*60*1000; //72h in ms 72*60*\nif(diffMS >= maxMS){\n    if (timestamp !== undefined) {\n        return msg;} //trigger next node\n} else {\nreturn null; } //don't trigger next node\n","outputs":1,"noerr":0,"x":260,"y":2720,"wires":[["484b7f92.43c65"]]},{"id":"120f3744.79a3f9","type":"inject","z":"4393de3b.33b5c","name":"","topic":"","payload":"","payloadType":"date","repeat":"60","crontab":"","once":true,"onceDelay":0.1,"x":90,"y":2720,"wires":[["fb81c897.5fbc98"]]},{"id":"682f43b5.2c57ac","type":"comment","z":"4393de3b.33b5c","name":"Time check every minute","info":"Runs every minute and checks the timestamp\nsaved in flow.time against the current time. If\n72 hours have passed, deletes flow.token and flow.time\nso that a new Bearer token will be grabbed next run.","x":110,"y":2680,"wires":[]}]

For testing, you can change the "compare time" function to something really short, like 2 mins, just so you can confirm the variables are deleted properly.

Cheers, a few questions where is the token variable actually saved?
Should I be able to see when a new bearer is obtained and what it actually is?

I have noticed since adding this flow in the "GET Status" request comes back significantly shorter
Below is the response I'm getting.

{"isOnline":true,"timeSinceLastContact":"00:01:33.9226084","lastStatusUpdate":"2019-12-04T17:11:25+00:00","lastKnownState":{"<18J09664>":{"Cloud":{"ConnectionState":"Connected","FailedSentPackets":174,"ReceivedPackets":17,"SentPackets":3562},"Modbus":{"LinkPort":"Opened"},"NV_SystemSettings_Local":{"Screen":{"AutoWake":true,"BrightnessAuto":true,"Brightness_pc":80,"Timeout_sec":60},"ScreenOffDisabled":false,"Sounds":{"Mute":false,"Volume_pc":50},"amMasterController":true,"isConfigured":true,"isLinkedToCloud":true},"NV_Updates":{"WCPendingUpdates":false,"WCUpdateRefusedVersion":"","ZCPendingUpdates":false,"ZCUpdateRefusedVersion":""},"SystemState":{"CpuFreq_MHz":996,"CpuId":"0xe6b3fc851b1e29d4","CpuLoad_User":4,"CpuRev":"1.2\n","CpuTempMax_oC":45.65,"CpuTemp_oC":40.25,"LastBootWasSafe":true,"LastShutdownReason":"Unknown","LinkedToMaster":false,"MemUsage_K":507048,"NV_LastBootFromUnsafeUTC":"2019-12-02 16:27:11","NV_UnsafeShutdowns":28,"ScreenIsOn":false,"WCFirmwareVersion":"1.326.1.458"...

Side note are you using HA UI? If so what are you doing to get the changes into node-red? I have used the call service function but it isn't really functioning as expected, seems it calls twice but I could definitely be doing something wrong.

Also have you created an individual flow for each command or is there a node that you can put all the commands in and select them via a call service or containing a message?

Thanks again!