Solar system from APsystems

I’m trying to access the API. Requested an API key and -secret from AP-Systems and got that without any issues. Also without an invoice :wink:
I’m not a developer, just tinkering and googling a lot.
Thus far I seem to be able to send a request to the web API, but the only result I get is '[200[ (which is ‘success’) and then ‘code 4000’, which is ‘request parameter exception’.
I got in touch with APsystems again, found that the timestamp should be in milliseconds, but no luck yet with retrieving JSON info from the APS site.
This is a snippet from my code, feel free to correct this.

#Variabelen
base_url = “https://api.apsystemsema.com:9282
nonce = str(uuid.uuid4()).replace(‘-’,‘’)
ts = int((time.time()*1000))
timestamp = str(ts)

#request_path = f"user/api/v2/systems/details/{sid}"

request_path = f"user/api/v2/systems/details/{sid}"
string_to_sign = f"{timestamp}/{nonce}/{api_key}/{request_path}/GET/HmacSHA256"
string_bytes = string_to_sign.encode(‘utf-8’)
api_bytes = api_secret.encode(‘utf-8’)
encrypted = hmac.new(api_bytes, msg = string_bytes, digestmod = hashlib.sha256).digest()
signature = str(base64.b64encode(encrypted)).replace(‘b’,‘’)
print("Hmac: ", signature)

header = {
‘X-CA-AppId’: api_key,
‘X-CA-Timestamp’: timestamp,
‘X-CA-Nonce’: nonce,
‘X-CA-Signature-Method’: ‘HmacSHA256’,
‘X-CA-Signature’: signature
}

url = f"{base_url}/{request_path}"
print (url)

response = requests.get(url = url, headers = header)
response.raise_for_status() # Controleer op HTTP-fouten
print(response)

if response.status_code == 200:
print(“Succes:”, response.json())
print(“Headers:”, response.headers)
print(response.url)
solar_data = response.json()
else:
print(“Fout:”, response.status_code, response.text)

I posted the API manual after getting an API key (for free from within my APSystems app). I can successfully connect to the ECU, but I get a (described) error because I couldn’t get the hashed data right. So, it seems this route work…if I can get some help with the code :wink:

Hi @Dick_J , thanks for posting your code. Did you run this in a HomeyScript? I copy/pasted it in a script on my Homey, but unfortunately it won’t run because of various errors on functions. I think Homey runs node.js. Where did you run this on?

Hi Rob, I ran this in visual studio code, in Python . I got a piece of code from AP systems as well, this was written for Postman (?). See pictures. This is a bit like JavaScript


Hi @Dick_J I see. That’s why it won’t run :slight_smile:

I can’t get it converted to JS on Homey’s node implementation, because it lacks the functions you use. I got the code to connect, working, but the hashing I don’t know.

That’s also where my problem is I think

Hi @Dick_J Did you ever got your Python code to work?

some action going on here: AP Systems support · Issue #68 · DiedB/Homey-SolarPanels · GitHub, though no conclusions there yet

Hi Rob, unfortunately not. There must be something going wrong with calculating the signature. I moved over to the app development environment, and there it worked, but of course it uses JavaScript. Crypto is not supported in Homey script, so this doesn’t work there.
I called out to Diederik (see reply from Patrick), because there’s already something that works.

// Requirements
const Homey = require(“homey”);
const crypto = require(“crypto”);
const { v4: uuidv4 } = require(“uuid”);
const fetch = require(“node-fetch”); // Voor Node.js, anders browser fetch

// Signature data
const api_key = Homey.env.CLIENT_ID;
const api_secret = Homey.env.CLIENT_SECRET;
const sid = Homey.env.CLIENT_SID;
const signature_method = “HmacSHA256”;
const nonce = uuidv4().replace(/-/g, “”);
const ts = Date.now();
const timestamp = ts.toString();

// Request data
const base_url = “https://api.apsystemsema.com:9282”;
const request_path = “/user/api/v2/systems/details/” + sid;
const http_method = “GET”;

// Create string to sign
var urlSegments = request_path.split(“/”);
var lastSegment = urlSegments[urlSegments.length - 1];
const stringToSign = ${timestamp}/${nonce}/${api_key}/${lastSegment}/${http_method}/${signature_method};

// Calculate signature
const hmacSha256 = crypto.createHmac(“sha256”, api_secret);
hmacSha256.update(stringToSign);
const signature = hmacSha256.digest(“base64”);

//Create headers
const header = {
“X-CA-AppId”: api_key,
“X-CA-Timestamp”: timestamp,
“X-CA-Nonce”: nonce,
“X-CA-Signature-Method”: “HmacSHA256”,
“X-CA-Signature”: signature,
};

// Create the full url
const url = base_url + request_path;
console.log(getpower);

// Make a GET request
fetch(url, { headers: header })
.then((response) => {
if (!response.ok) {
throw new Error(“Network response was not ok”);
}
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error(“Error:”, error);
});

class Driver extends Homey.Driver {
async onPair(session) {
const devices = “AP Systems”

}
}

module.exports = Driver;

Thanks @Dick_J . Guess we have to wait for better code :wink:

I found this web link with a option to switch on and off with a flow card. (I did not test this already)

I’m not a Homey user, but I stumbled upon this thread searching for how to get the API to work from the manual referenced.

After some trial and error, I was able to get data back using this python script. Hopefully that helps you out.

import hashlib
import hmac
import base64
import uuid
import time
import requests,urllib
import config

# Replace these with your actual API details
APP_ID = config.app_id
APP_SECRET = config.secret
API_BASE = "https://api.apsystemsema.com:9282"
REQUEST_PATH = "/user/api/v2/systems/details/"+config.sid
API_URL = API_BASE+REQUEST_PATH
HTTP_METHOD = "GET"  # or "POST", "DELETE", depending on the API call
SIGNATURE_METHOD = "HmacSHA256"  # or "HmacSHA1"

# Generate the required headers and signature
def generate_signature():
   # timestamp = str(int(time.time()))
    timestamp = str(int(time.time() * 1000))
    nonce = str(uuid.uuid4()).replace("-", "")
    
    path_part = REQUEST_PATH.rsplit("/",1)[1]
    # Build the string to sign
    string_to_sign = f"{timestamp}/{nonce}/{APP_ID}/{path_part}/{HTTP_METHOD}/{SIGNATURE_METHOD}"
    
    # Create the signature using HMAC with SHA256 or SHA1
    if SIGNATURE_METHOD == "HmacSHA256":
        signature = hmac.new(APP_SECRET.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha256).digest()
    elif SIGNATURE_METHOD == "HmacSHA1":
        signature = hmac.new(APP_SECRET.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha1).digest()
    else:
        raise ValueError("Unsupported signature method")

    # Base64 encode the signature
    return base64.b64encode(signature).decode("utf-8"), timestamp, nonce

# Make the API request with the required headers
def make_api_request():
    signature, timestamp, nonce = generate_signature()

    headers = {
        "X-CA-AppId": APP_ID,
        "X-CA-Timestamp": timestamp,
        "X-CA-Nonce": nonce,
        "X-CA-Signature-Method": SIGNATURE_METHOD,
        "X-CA-Signature": signature
    }

    # Send the request
    response = requests.get(API_URL, headers=headers)
    
    # Handle response
    if response.status_code == 200:
        print("Request successful:", response.json())
    else:
        print(f"Request failed with status code {response.status_code}:", response.text)

# Run the request
make_api_request()

Hi @doyouevencomputer I tried your code and yes, I do get a response from the APSystems API. However, the response is OK (200) with subcode 4000, which is a “Request parameter exception”.
Same result as @Dick_J I guess.

When you say that “I was able to get data back using this python script”, do you mean you got back actual data, or did you also get the 4000 code?

I get data back, I copied the code from this post just to be sure, here is what I get back:

Request successful: {‘code’: 0, ‘data’: {‘timezone’: ‘US/Central’, ‘ecu’: [‘XXXXXXXXXXXXX’], ‘create_date’: ‘2023-02-28’, ‘type’: 1, ‘sid’: ‘XXXXXXXXXXXXXXXX’, ‘capacity’: ‘18.72’}}

My config.py file in the same directory looks like this

secret = “0123456789ab”. # 12 char string
app_id = “0123456789abcdef0123456789abcdef” # 32 char string

If either of these are wrong (same length but change 1 character), I get the 4000 response.

I got the app_id and secret from the https://www.apsystemsema.com portal under Setting > OpenAPI Service > Developer Authorization.

Interestingly, under “Historical Call Statistics” I still see 0 for all months even though I was using the above code with many other endpoints to get various details:

Here are some others that require your sid and/or ecu:

REQUEST_PATH = "/user/api/v2/systems/details/"+config.sid
REQUEST_PATH = "/user/api/v2/systems/inverters/"+config.sid
REQUEST_PATH = "/user/api/v2/systems/summary/"+config.sid
REQUEST_PATH = f"/user/api/v2/systems/{config.sid}/devices/ecu/summary/{config.ecu}"

If you’ve only ever got a 4000 error, maybe you could try to generate new app_id/creds values. If the call stats history isn’t working, maybe neither is a prompt that you are locked out or been disabled?

@doyouevencomputer Thanks for the update! I’ll look into the credentials.

@doyouevencomputer I got it working!

The error I made was that I interpreted “RequestPath” to be a piece of the URL, where is should be just the last part (after the last slash).

So it works. Now, I have to figure out how the command “Get Energy in Period for a Particular System” works. This (and other commands) uses extra parameters, but it is not really described how to send these.

Thanks for now!

1 Like

Functioning depends on the series of your ECU

That is something I have not figured out yet, maybe I’ll ask AP for this. Ending the url with ?energy_level:yearly doesn’t work.
Unfortunately, my internet connector (Delta fiber) is now down for the last week, so I have paused my efforts.

Yes, i see. My ECU is not from the right series, so it is not working :confused: hopfully there wil be a app some day or a other solution

Okay, so to send parameters with your request, add a question mark and then parameter name and value. Like:
/user/api/v2/systems/SID/devices/inverter/batch/energy/ECUID?energy_level=power&date_range=2025-05-08
or:
/user/api/v2/systems/energy/ECUID?energy_level=hourly&date_range=2025-05-08