Trying to get data from solis API

Hello

I am looking to utilise the Solis API to get data into nodered about my PV inverter. The API documentation is here: https://oss.soliscloud.com/templet/SolisCloud%20Platform%20API%20Document%20V2.0.pdf

I am struggling to understand what payload to send the http request (POST) node based on what they are describing in section 2.2. The table is beyond me. Are they showing examples of what to populate when requesting data via the API?

If anyone could help that would be appreciated! :frowning:

the payload is just an Object.

image

the difficult part is the Auth header.

This is my take from the (not so great docs).
NOTE: The URL and some other fields will need to be set up.
NOTE2: I have no idea if this will work but it should get you moving.

// MD5 the payload
const payload = JSON.stringify(msg.payload)
const md5Hash = crypto.createHash('md5').update(payload, 'utf8').digest();
const contentMD5 = md5Hash.toString('base64')

// extract other params or defaults
msg.method = msg.method || 'POST'
const canonicalResource = msg.canonicalResource || "/v1/api/inverterDetail"
const apiSecret = msg.secret || 'test-key'
const apiId = msg.apiId || 'your-api-id'
const contentType = 'application/json;charset=UTF-8' // fixed

// this might need some work - just hoping the UTC format works
const date = new Date().toUTCString()

// Generate the HMAC SHA-1 signature
const stringToSign = [
    msg.method,  // HTTP verb
    contentMD5,
    contentType,
    date,
    canonicalResource
].join('\n')

const hmac = crypto.createHmac('sha1', apiSecret)
hmac.update(stringToSign)
const signature = hmac.digest('base64')

// add auth header
msg.headers = msg.headers || {}
msg.headers.Authorization = `API ${apiId}:${signature}`

// set full URL
msg.url = `https://host/${canonicalResource}`

return msg;

example:

demo flow

[{"id":"097d84fb54289894","type":"function","z":"02d4291f2e0c055e","name":"apply auth header","func":"// MD5 the payload\nconst payload = JSON.stringify(msg.payload)\nconst md5Hash = crypto.createHash('md5').update(payload, 'utf8').digest();\nconst contentMD5 = md5Hash.toString('base64')\n\n// extract other params or defaults\nmsg.method = msg.method || 'POST'\nconst canonicalResource = msg.canonicalResource || \"/v1/api/inverterDetail\"\nconst apiSecret = msg.secret || 'test-key'\nconst apiId = msg.apiId || 'your-api-id'\nconst contentType = 'application/json;charset=UTF-8' // fixed\n\n// this might need some work - just hoping the UTC format works\nconst date = new Date().toUTCString()\n\n// Generate the HMAC SHA-1 signature\nconst stringToSign = [\n    msg.method,  // HTTP verb\n    contentMD5,\n    contentType,\n    date,\n    canonicalResource\n].join('\\n')\n\nconst hmac = crypto.createHmac('sha1', apiSecret)\nhmac.update(stringToSign)\nconst signature = hmac.digest('base64')\n\n// add auth header\nmsg.headers = msg.headers || {}\nmsg.headers.Authorization = `API ${apiId}:${signature}`\n\n// set full URL\nmsg.url = `https://host/${canonicalResource}`\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"crypto","module":"crypto"}],"x":560,"y":300,"wires":[["171900bd52625a4f","0bb9c7398219b913"]]},{"id":"1fa080ee367fed3e","type":"inject","z":"02d4291f2e0c055e","name":"test object id + sn","props":[{"p":"payload"},{"p":"method","v":"POST","vt":"str"},{"p":"canonicalResource","v":"/v1/api/inverterDetail","vt":"str"},{"p":"apiId","v":"my-api-id","vt":"str"},{"p":"secret","v":"my=secret","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{ \"id\": \"1308675217944611083\", \"sn\": \"120B40198150131\" }","payloadType":"json","x":340,"y":300,"wires":[["097d84fb54289894"]]},{"id":"171900bd52625a4f","type":"http request","z":"02d4291f2e0c055e","d":true,"name":"","method":"use","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":790,"y":300,"wires":[["80adcc939017ae7d"]]},{"id":"0bb9c7398219b913","type":"debug","z":"02d4291f2e0c055e","name":"debug 199","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":790,"y":240,"wires":[]},{"id":"80adcc939017ae7d","type":"debug","z":"02d4291f2e0c055e","name":"debug 198","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1010,"y":300,"wires":[]}]

Thanks for the response - much appreciated.

I get the following response from the http server: 403,"error":"Forbidden","message":"Date can not be empty"

I've checked against the documentation, the date you've got is in the correct format. Any ideas?

Not really - I dont use it.

However, it kinda looks like in an example further down the doc that they include Date and Content-MD5 as headers

Try this:

// MD5 the payload
const payload = JSON.stringify(msg.payload)
const md5Hash = crypto.createHash('md5').update(payload, 'utf8').digest();
const contentMD5 = md5Hash.toString('base64')

// extract other params or defaults
msg.method = msg.method || 'POST'
const canonicalResource = msg.canonicalResource || "/v1/api/inverterDetail"
const apiSecret = msg.secret || 'test-key'
const apiId = msg.apiId || 'your-api-id'
const contentType = 'application/json;charset=UTF-8' // fixed

// this might need some work - just hoping the UTC format works
const date = new Date().toUTCString()

// Generate the HMAC SHA-1 signature
const stringToSign = [
    msg.method,  // HTTP verb
    contentMD5,
    contentType,
    date,
    canonicalResource
].join('\n')

const hmac = crypto.createHmac('sha1', apiSecret)
hmac.update(stringToSign)
const signature = hmac.digest('base64')

// add auth header
msg.headers = msg.headers || {}
msg.headers.Authorization = `API ${apiId}:${signature}`
msg.headers.Date = date
msg.headers['Content-MD5'] = contentMD5


// set full URL
msg.url = `https://host/${canonicalResource}`

return msg;

Yeah I finally got it to work after 3 hours lol. You were missing some headers for content-md5, content-type and date.

Seem to do the trick! Thanks for your help

Hey,
I'm using this docker to read my solis data and it will be written into MQTT and/or InfluxDB

image

It works fine, maybe you can replicate some of the code

Hi Sirhc,
You seem to be able to access the Solis Cloud API. Would you like to share your procedure here? An importable flow would be nice. Thank you!

Not sure you meant to reply to my post but as i said:

PS, welcome to the forum.

Thank you Steve,
for welcoming.

Ich have meant Sirhc because of his "Yeah I finally got it to work after 3 hours lol. You were missing some headers for content-md5, content-type and date."

Greetings

That was not me, that was @Sirhc

This happened because you used the "reply" button attached to my comment. I was simply helping the OP.

@Sirhc should see this and reply when he can however I am pretty sure all the info you need is contained in this thread! Just put the code I wrote in a function (include the extra headers mentioned by Sirhc) and pass the msg from the function to a HTTP Request node!

Hi Steve, I have tried to access the Solis API again based on your advice. Unfortunately, I am not getting any feedback at all. Can you help me (Sirhc is unfortunately not responding)?

What do I have to do to implement Sirhc's instructions? Where and how can I insert the extra headers he mentioned? Do you have an example of what it should look like?

Do you need any further information from me?

Again, I do not use or have access to solis.

At a guess tho...

the document states

All interface requests require adding Content MD5, Content Type, Date, and Authorization to the
header

So I updated the demo flow

[{"id":"097d84fb54289894","type":"function","z":"99321f37fd961305","name":"apply headers","func":"// MD5 the payload\nconst payload = JSON.stringify(msg.payload)\nconst md5Hash = crypto.createHash('md5').update(payload, 'utf8').digest();\nconst contentMD5 = md5Hash.toString('base64')\n\n// extract other params or defaults\nmsg.method = msg.method || 'POST'\nconst canonicalResource = msg.canonicalResource || \"/v1/api/inverterDetail\"\nconst apiSecret = msg.secret || 'test-key'   // UPDATE ME\nconst host = msg.host || 'blahblah.com'  // UPDATE ME\nconst apiId = msg.apiId || 'your-api-id'  // UPDATE ME\nconst contentType = 'application/json;charset=UTF-8' // fixed\n\n// this might need some work - just hoping the UTC format works\nconst date = new Date().toUTCString()\n\n// Generate the HMAC SHA-1 signature\nconst stringToSign = [\n    msg.method,  // HTTP verb\n    contentMD5,\n    contentType,\n    date,\n    canonicalResource\n].join('\\n')\n\nconst hmac = crypto.createHmac('sha1', apiSecret)\nhmac.update(stringToSign)\nconst signature = hmac.digest('base64')\n\n// add headers\n// doc states: \"all interface requests require adding\" \n// * Content MD5\n// * Content Type\n// * Date\n// * Authorization\nmsg.headers = msg.headers || {}\nmsg.headers['Content-MD5'] = contentMD5\nmsg.headers['Content-Type'] = contentType\nmsg.headers.Date = date\nmsg.headers.Authorization = `API ${apiId}:${signature}`\n\n// set full URL\nmsg.url = `https://${host}/${canonicalResource}`\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"crypto","module":"crypto"}],"x":500,"y":1980,"wires":[["171900bd52625a4f","0bb9c7398219b913"]]},{"id":"1fa080ee367fed3e","type":"inject","z":"99321f37fd961305","name":"test object id + sn - UPDATE ME","props":[{"p":"payload"},{"p":"method","v":"POST","vt":"str"},{"p":"canonicalResource","v":"/v1/api/inverterDetail","vt":"str"},{"p":"apiId","v":"my-api-id","vt":"str"},{"p":"secret","v":"my-secret","vt":"str"},{"p":"domain","v":"blahbalh.com","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{ \"id\": \"1308675217944611083\", \"sn\": \"120B40198150131\" }","payloadType":"json","x":260,"y":1980,"wires":[["097d84fb54289894"]]},{"id":"171900bd52625a4f","type":"http request","z":"99321f37fd961305","name":"","method":"use","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":690,"y":1980,"wires":[["80adcc939017ae7d"]]},{"id":"0bb9c7398219b913","type":"debug","z":"99321f37fd961305","name":"CHECK ME","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":1920,"wires":[]},{"id":"80adcc939017ae7d","type":"debug","z":"99321f37fd961305","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":860,"y":1980,"wires":[]}]

It might work, it might not!

I have no way of testing.

Hi Steve, unfortunately it doesn't work. I get the message ‘xmlrpc server close timeout’ from time to time. Otherwise there is no response at all.

My approach:

  1. import the demo flow provided by you
  2. edit the Inject node: msg.payload, msg. apiid, msg.secret (otherwise no changes)
  3. edit the http request node: URL (https://www.soliscloud.com:13333/)
  4. deploy

Is there anything else missing, I'm not very familiar with Red node?

Try replacing the function with this...

// MD5 the payload
const payload = JSON.stringify(msg.payload)
const md5Hash = crypto.createHash('md5').update(payload, 'utf8').digest();
const contentMD5 = md5Hash.toString('base64')

// extract other params or defaults
msg.method = msg.method || 'POST'
const canonicalResource = msg.canonicalResource || "/v1/api/inverterDetail"
const apiSecret = msg.secret || 'test-key'   // UPDATE ME
const domain = msg.domain || 'blahblah.com'  // UPDATE ME
const apiId = msg.apiId || 'your-api-id'  // UPDATE ME
const contentType = 'application/json;charset=UTF-8' // fixed

// this might need some work - just hoping the UTC format works
const date = new Date().toUTCString()

// Generate the HMAC SHA-1 signature
const stringToSign = [
    msg.method,  // HTTP verb
    contentMD5,
    contentType,
    date,
    canonicalResource
].join('\n')

const hmac = crypto.createHmac('sha1', apiSecret)
hmac.update(stringToSign)
const signature = hmac.digest('base64')

// add headers
// doc states: "all interface requests require adding" 
// * Content MD5
// * Content Type
// * Date
// * Authorization
msg.headers = msg.headers || {}
msg.headers['Content-MD5'] = contentMD5
msg.headers['Content-Type'] = contentType
msg.headers.Date = date
msg.headers.Authorization = `API ${apiId}:${signature}`

// set full URL
msg.url = `https://${domain}/${canonicalResource}`

return msg;

I had host in the function but domain in the inject.

Other than that, sorry, cant help.

Thank you very much, Steve!
I tried your last approach but had no success. Now I hope for responding Sirhc.

Thank you again!

Edit:
I do have one more question, Steve!
I get this message in the debug window:

image

Could this be the problem and how can I solve it?

Hi Sirhc,
could you please send me your solution for accessing the Soliscloud API? Ideally as an importable flow? That would be very kind. Thank you!

Yes. When you import the flow i did and you open the function node you should see:

image

Are you using a really old version of Node-RED?

Look on the main menu for version number.

Thanks Steve,
I think you are right. I am using an outdated Red Node version (v1.2.9). It's a bit difficult to update this because it's an addon called Redmatic for Raspberrymatic from 2021. Unfortunately, this addon has not been developed any further since then. But there is probably a way to update this addon as well, which I am currently taking care of. After that I will continue to test the solution you offered!

I have now been able to update to V4.05. Now I get this debug message:

image

I would like to insert my flow here, but unfortunately I don't know how to do it. Can anyone help me?

Ok, Thank you Colin, here ist my flow:

[
    {
        "id": "d9511de4bf3a6841",
        "type": "tab",
        "label": "Solis API Demo",
        "disabled": false,
        "info": ""
    },
    {
        "id": "0595b7fdf2f8a18d",
        "type": "function",
        "z": "d9511de4bf3a6841",
        "name": "apply headers",
        "func": "// MD5 the payload\nconst payload = JSON.stringify(msg.payload)\nconst md5Hash = crypto.createHash('md5').update(payload, 'utf8').digest();\nconst contentMD5 = md5Hash.toString('base64')\n\n// extract other params or defaults\nmsg.method = msg.method || 'POST'\nconst canonicalResource = msg.canonicalResource || \"/v1/api/inverterDetail\"\nconst apiSecret = msg.secret || 'test-key'   // UPDATE ME\nconst domain = msg.domain || 'blahblah.com'  // UPDATE ME\nconst apiId = msg.apiId || 'your-api-id'  // UPDATE ME\nconst contentType = 'application/json;charset=UTF-8' // fixed\n\n// this might need some work - just hoping the UTC format works\nconst date = new Date().toUTCString()\n\n// Generate the HMAC SHA-1 signature\nconst stringToSign = [\n    msg.method,  // HTTP verb\n    contentMD5,\n    contentType,\n    date,\n    canonicalResource\n].join('\\n')\n\nconst hmac = crypto.createHmac('sha1', apiSecret)\nhmac.update(stringToSign)\nconst signature = hmac.digest('base64')\n\n// add headers\n// doc states: \"all interface requests require adding\" \n// * Content MD5\n// * Content Type\n// * Date\n// * Authorization\nmsg.headers = msg.headers || {}\nmsg.headers['Content-MD5'] = contentMD5\nmsg.headers['Content-Type'] = contentType\nmsg.headers.Date = date\nmsg.headers.Authorization = `API ${apiId}:${signature}`\n\n// set full URL\nmsg.url = `https://${domain}${canonicalResource}`\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "crypto",
                "module": "crypto"
            }
        ],
        "x": 680,
        "y": 320,
        "wires": [
            [
                "1c201af348326481",
                "12096b57ec3e815b"
            ]
        ]
    },
    {
        "id": "ae84178bed4264ea",
        "type": "inject",
        "z": "d9511de4bf3a6841",
        "name": "test object id + sn - UPDATE ME",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "method",
                "v": "POST",
                "vt": "str"
            },
            {
                "p": "canonicalResource",
                "v": "/v1/api/inverterDetail",
                "vt": "str"
            },
            {
                "p": "apiId",
                "v": "KeyID",
                "vt": "str"
            },
            {
                "p": "secret",
                "v": "KeySecret",
                "vt": "str"
            },
            {
                "p": "domain",
                "v": "www.soliscloud.com:13333",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "{ \"id\": \"14A67F\", \"sn\": \"1031030222210234\" }",
        "payloadType": "json",
        "x": 390,
        "y": 320,
        "wires": [
            [
                "0595b7fdf2f8a18d"
            ]
        ]
    },
    {
        "id": "1c201af348326481",
        "type": "http request",
        "z": "d9511de4bf3a6841",
        "name": "",
        "method": "use",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 890,
        "y": 320,
        "wires": [
            [
                "b4cce3e89b906a99"
            ]
        ]
    },
    {
        "id": "12096b57ec3e815b",
        "type": "debug",
        "z": "d9511de4bf3a6841",
        "name": "CHECK ME",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 890,
        "y": 260,
        "wires": []
    },
    {
        "id": "b4cce3e89b906a99",
        "type": "debug",
        "z": "d9511de4bf3a6841",
        "name": "Result",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1060,
        "y": 320,
        "wires": []
    }
]

Select just the nodes of interest (not the whole flow) and use Export > Copy to clipboard, then paste it here. In order to make code readable and usable it is necessary to surround your code with three backticks (also known as a left quote or backquote ```)

``` 
   code goes here 
```

See this post for more details - How to share code or flow json

1 Like