I am trying to connect with the AISWEI Solar API. I was sent a API user manual and a Token by email. When i log into my account online I also see an APP Key which consists of a 9 digit number and a alphanumeric string after that. My issue is that i have tried various HTTP request from python and ARC clients. I still seem to get no response back from their servers. I am trying to use the getPlantList
conmmand.
Going by the API document, i first thought the request URL was a typo but if I type is as is, I get a 400 - bad request instead of a 404 - not found. So I assume the URL is correct.
Has anyone used this API or can assist me in fixing my code?
Below is my python code:
import requests
def get_plant_list(token, page=1, size=20):
url = 'https://api.general.aisweicloud.com/planlist'
params = {
'token': token,
'page': str(page),
'size': str(size)
}
try:
response = requests.get(url, params=params, verify=False)
if response.status_code == 200:
return response.json()
else:
print(f"Request failed with status code: {response.status_code}")
return None
except requests.exceptions.RequestException as e:
print("An error occurred while making the request:", e)
return None
token = 'XXXXXXXXXXXXX'
plant_list = get_plant_list(token)
if plant_list:
print(plant_list)
Also I have share the API Manual here:
Sorry I don't know how to upload PDFs here.
Ok, getting data from the AISWEI Solar API...
There is a Pro (Business) version and a Consumer version of the API. The Consumer version only had an interval of 20 minutes (while the site at solplanet.net did have 10 minutes interval. You can upgrade to the Pro version via the App. The API's differ slightly.
Below is code for both Pro and Consumer versions of the API (done via the $pro variable). The getfromapi function will show that you need to sign your calls to the API. This is done by taking the header + url values and sign the value with the AppSecret. It's very important that the parameters in the url are in alphabetical order. Note: If something goes wrong, the API will throw back an error in the header X-Ca-Error-Message. So make sure to check the headers if it doesn't work.
First you need to get the ApiKey for your inverter. This should be under details
at the site (different login-page for Consumer and Business). You can also find the AppKey and AppSecret there under your account (Account > Safety settings > API authorization code
for Business and Account > Account and security > Safety settings
for Consumer). If it's not there you can contact solplanet.net to activate it. For the Pro API you also need a token which you also can get via e-mail to solplanet.net (which have excellent service).
Following code is for PHP (python3 is below that). I run it via a cron-job every 5 minutes to retrieve data and push it to a mysql database for a local dashboard. Everything runs on a Raspberry Pi 3 B. It first retrieves the status of the inverter (last updated and status). The it retrieves the production values of today (or given date). Consumer at 20 minutes interval and Business at 10 minutes interval. And lastly it retrieves the month production (all days of a month) for the daily-table.
I hope you have some use for the code and can implement it in your own program. If you have any question, let me now.
Extra note: The time and ludt (last update time) is always in timezone for China for me with no timezone information (maybe because this was a zeversolar inverter). So I convert it here to my own timezone (with timezone information). Check for yourself if the time/ludt is returned correctly.
<?php
error_reporting(E_ALL ^ E_NOTICE);
date_default_timezone_set('Europe/Amsterdam');
$crlf = (php_sapi_name()==='cli' ? "\n" : "<br>\n"); // we want html if we are a browser
$host='https://eu-api-genergal.aisweicloud.com';
$ApiKey = 'xx'; // apikey for the inverter
$AppKey = 'xx'; // appkey for pro or consumer version
$AppSecret = 'xx';
$token = 'xx'; // only needed for pro
$pro = false; // is the appkey pro?
$con=false; // actually write to mysql database, false for testing
// $today and $month for calls to get inverter output / 2023-08-18 and 2023-08
// if we call via a browser we can pass today or month by parameters
// otherwise current day and month is taken
$today = isset($_GET['today']) ? $_GET['today'] : date("Y-m-d");
$month = isset($_GET['month']) ? $_GET['month'] : date('Y-m',strtotime("-1 days"));
if (isset($_GET['today'])) { $month=substr($today,0,7); }
if ($con) {
include('database.php'); // file with $username, $password and $servername for mysql server
$conn = new mysqli($servername, $username, $password, "p1");
if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); }
}
// get data from api, pass url without host and without apikey/token
function getfromapi($url) {
global $pro, $host, $token, $AppKey, $AppSecret, $ApiKey;
$method = "GET";
$accept = "application/json";
$content_type = "application/json; charset=UTF-8";
// add apikey and token
$key = ($pro ? "apikey=$ApiKey" : "key=$ApiKey"); // pro uses apikey, otherwise key
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . $key; // add apikey
if ($pro) $url = $url . "&token=$token"; // add token
// disect and reshuffle url parameters in correct order, needed for signature
$s1 = explode('?', $url);
$s2 = explode('&', $s1[1]);
sort($s2);
$url = $s1[0].'?'.implode('&', $s2); // corrected url
// headers
$header = array();
$header["User-Agent"] = 'app 1.0';
$header["Content-Type"] = $content_type;
$header["Accept"] = $accept;
$header["X-Ca-Signature-Headers"] = "X-Ca-Key"; // we only pass extra ca-key in signature
$header["X-Ca-Key"] = $AppKey;
// sign
$str_sign = $method . "\n";
$str_sign .= $accept . "\n";
$str_sign .= "\n";
$str_sign .= $content_type . "\n";
$str_sign .= "\n"; // we use no Date header
$str_sign .= "X-Ca-Key:$AppKey" . "\n";
$str_sign .= $url;
$sign = base64_encode(hash_hmac('sha256', $str_sign, $AppSecret, true));
$header['X-Ca-Signature'] = $sign;
// push headers to an headerarray
$headerArray = array();
foreach ($header as $k => $v) { array_push($headerArray, $k . ": " . $v); }
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_URL, "$host$url");
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
// curl_setopt($ch, CURLOPT_POST, 1);
// curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
$data = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header_len = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header2 = substr($data, 0, $header_len);
$data = substr($data, $header_len);
$header1 = curl_getinfo($ch, CURLINFO_HEADER_OUT);
curl_close($ch);
$json = json_decode($data, true);
$json['httpcode'] = $httpcode;
if (!$pro) $json['status'] = '200'; // pro has no status, so use 200
if ($httpcode != '200') {
$json['status'] = $httpcode;
$json['headers'] = $header2;
}
return $json; // return as array
}
// ===============================================================
// reading inverter state
// ===============================================================
$url = "/pro/getDeviceListPro";
if (!$pro) $url = "/devicelist";
$json = getfromapi($url);
if ($json['status']=='200') {
if ($pro) {
$status=$json['data'][0]['inverters'][0]['istate'];
$update=$json['data'][0]['inverters'][0]['ludt'];
$current=$json['time'];
} else {
$status=$json['data']['list'][0]['inverters'][0]['istate'];
$update=$json['data']['list'][0]['inverters'][0]['ludt'];
$dt = new DateTime('now', new DateTimeZone('Asia/Shanghai'));
$current = $dt->format('Y-m-d H:i:s'); // no current time in json
}
// time and ludt are in China/Guizhou time, so add the timezone
$dt = new DateTime($current, new DateTimeZone('Asia/Shanghai'));
$current = $dt->format('Y-m-d H:i:sP'); // no current time in json
$dt = new DateTime($update, new DateTimeZone('Asia/Shanghai'));
$update = $dt->format('Y-m-d H:i:sP');
// and convert to our own timezone
$current = date('Y-m-d H:i:sP', strtotime($current));
$update = date('Y-m-d H:i:sP', strtotime($update));
$stat = 'warning';
if ($status == '0') $stat = 'offline';
if ($status == '1') $stat = 'normal';
if ($status == '2') $stat = 'warning';
if ($status == '3') $stat = 'error';
$json['state'] = $stat;
$json['last'] = $update;
echo "Current time = " . $current . $crlf;
echo "Last update = " . $update . $crlf;
echo "Inverter state = " . $stat . $crlf;
} else {
echo "Error reading state: " . $json['status'] . $crlf . $json['headers'];
}
// ===============================================================
// readings from today
// ===============================================================
$url = "/pro/getPlantOutputPro?period=bydays&date=$today";
if (!$pro) $url = "/getPlantOutput?period=bydays&date=$today";
$json = getfromapi($url);
if ($json['status']=='200') {
// process
if ($pro) {
$dt=$today;
$unit = $json['data']['dataunit'];
$x = $json['data']['result'];
} else {
$dt=$today;
$unit = $json['dataunit'];
$x = $json['data'];
}
foreach ($x as $key => $val) {
$tm=$val['time'];
$pw=$val['value'];
if ($unit=='W') $pw=$pw/1000;
$sql = "REPLACE INTO solar (tijd, power) VALUES ('$dt $tm', $pw);";
if ($con) {
if (!$conn->query($sql)) {
echo "No data for inserted !!!!!!!!!!!!!<br>".$conn->error;
}
} else {
//echo(".");
echo($sql.$crlf);
}
}
if (!$con) echo($crlf);
echo "Daily output processed" . $crlf;
} else {
echo "Error reading daily: " . $json['status'] . $crlf . $json['headers'];
}
// ===============================================================
// readings from month
// ===============================================================
$url = "/pro/getPlantOutputPro?period=bymonth&date=$month";
if (!$pro) $url = "/getPlantOutput?period=bymonth&date=$month";
$json = getfromapi($url);
if ($json['status']=='200') {
// process
if ($pro) {
$unit = $json['data']['dataunit'];
$x = $json['data']['result'];
} else {
$unit = $json['dataunit'];
$x = $json['data'];
}
foreach ($x as $key => $val) {
$tm=$val['time'];
$pw=$val['value'];
if ($unit=='W') $pw=$pw/1000;
$sql = "REPLACE INTO solarmonth (tijd, power) VALUES ('$tm', $pw);";
if ($con) {
if (!$conn->query($sql)) {
echo "No data for inserted !!!!!!!!!!!!!<br>".$conn->error;
}
} else {
//echo(".");
echo($sql.$crlf);
}
}
if (!$con) echo($crlf);
echo "Monthly output processed" . $crlf;
} else {
echo "Error reading monthly: " . $json['status'] . $crlf . $json['headers'];
}
echo("Done" . $crlf);
Edit: Ok, it's been a while since I programmed in python so I called on the help of my friend chatGPT :D The following is (after some adjustments) working correctly for me (besides the database stuff but that falls outside of this question).
import requests
import json
import datetime
import pytz
import os
import time
import hmac
import hashlib
import base64
from datetime import datetime, timezone, timedelta
os.environ['TZ'] = 'Europe/Amsterdam' # Setting the default timezone
time.tzset()
crlf = "\n" # Line break
host = 'https://eu-api-genergal.aisweicloud.com'
ApiKey = 'xx' # in the dashboard under details of the inverter/plant
AppKey = 'xx' # under your account info, if not there, contact solplanet
AppSecret = 'xx' # same as AppKey
token = 'xx' # not needed for consumer edition, otherwise contact solplanet
pro = False
con = False
today = datetime.today().strftime('%Y-%m-%d')
month = datetime.today().strftime('%Y-%m')
if con:
# Include database connection setup here if needed
pass
def getfromapi(url):
global pro, host, token, AppKey, AppSecret, ApiKey
method = "GET"
accept = "application/json"
content_type = "application/json; charset=UTF-8"
key = f"apikey={ApiKey}" if pro else f"key={ApiKey}"
url += ('&' if '?' in url else '?') + key
if pro:
url += f"&token={token}"
s1 = url.split('?')
s2 = sorted(s1[1].split('&'))
url = s1[0] + '?' + '&'.join(s2)
header = {
"User-Agent": "app 1.0",
"Content-Type": content_type,
"Accept": accept,
"X-Ca-Signature-Headers": "X-Ca-Key",
"X-Ca-Key": AppKey
}
str_sign = f"{method}\n{accept}\n\n{content_type}\n\nX-Ca-Key:{AppKey}\n{url}"
sign = base64.b64encode(hmac.new(AppSecret.encode('utf-8'), str_sign.encode('utf-8'), hashlib.sha256).digest())
header['X-Ca-Signature'] = sign
headerArray = [f"{k}: {v}" for k, v in header.items()]
response = requests.get(f"{host}{url}", headers=header)
httpcode = response.status_code
header_len = len(response.headers)
header2 = response.headers
data = response.text
try:
json_data = json.loads(data)
except:
json_data = {}
json_data['httpcode'] = httpcode
if not pro:
json_data['status'] = '200'
if httpcode != 200:
json_data['status'] = httpcode
json_data['headers'] = header2
return json_data
# ===============================================================
# reading inverter state
# ===============================================================
url = "/pro/getDeviceListPro" if pro else "/devicelist"
json1 = getfromapi(url)
if json1['status'] == '200':
if pro:
status = json1['data'][0]['inverters'][0]['istate']
update = json1['data'][0]['inverters'][0]['ludt']
current = json1['time']
else:
status = json1['data']['list'][0]['inverters'][0]['istate']
update = json1['data']['list'][0]['inverters'][0]['ludt']
current = datetime.now(timezone(timedelta(hours=8))) # China/Guizhou time
current = current.strftime('%Y-%m-%d %H:%M:%S %z') # format with timezone
update = datetime.strptime(update, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone(timedelta(hours=8)))
update = update.strftime('%Y-%m-%d %H:%M:%S %z')
# Convert to your own timezone
current = datetime.strptime(current, '%Y-%m-%d %H:%M:%S %z')
current = current.astimezone(timezone.utc)
current = current.strftime('%Y-%m-%d %H:%M:%S %z')
status_map = {0: 'offline', 1: 'normal', 2: 'warning', 3: 'error'}
stat = status_map.get(status, 'warning')
json1['state'] = stat
json1['last'] = update
print("Current time =", current)
print("Last update =", update)
print("Inverter state =", stat)
json1['current'] = current
json1['status'] = stat
json1['last'] = update
html = json.dumps(json1) # Assuming json is imported
#with open('/home/pi/solstatus.json', 'w') as file:
# file.write(html)
else:
print("Error reading state:", json1['status'], json1['headers'])
# ===============================================================
# readings from today
# ===============================================================
url = "/pro/getPlantOutputPro?period=bydays&date=" + today
if not pro:
url = "/getPlantOutput?period=bydays&date=" + today
json1 = getfromapi(url)
if json1['status'] == '200':
if pro:
dt = today
unit = json1['data']['dataunit']
x = json1['data']['result']
else:
dt = today
unit = json1['dataunit']
x = json1['data']
for val in x:
tm = val['time']
pw = val['value']
if unit == 'W':
pw /= 1000
sql = f"REPLACE INTO solar (tijd, power) VALUES ('{dt} {tm}', {pw});"
if con:
if not conn.query(sql):
print("No data for inserted !!!!!!!!!!!!!")
print(conn.error)
else:
# print(".")
print(sql)
if not con:
print("")
print("Daily output processed")
html = json.dumps(json1) # Assuming json is imported
#with open('/home/pi/solar1.json', 'w') as file:
# file.write(html)
else:
print("Error reading daily:", json1['status'], json1['headers'])
# ===============================================================
# readings from month
# ===============================================================
url = "/pro/getPlantOutputPro?period=bymonth&date=" + month
if not pro:
url = "/getPlantOutput?period=bymonth&date=" + month
json1 = getfromapi(url)
if json1['status'] == '200':
if pro:
unit = json1['data']['dataunit']
x = json1['data']['result']
else:
unit = json1['dataunit']
x = json1['data']
for val in x:
tm = val['time']
pw = val['value']
if unit == 'W':
pw /= 1000
sql = f"REPLACE INTO solarmonth (tijd, power) VALUES ('{tm}', {pw});"
if con:
if not conn.query(sql):
print("No data for inserted !!!!!!!!!!!!!")
print(conn.error)
else:
# print(".")
print(sql)
if not con:
print("")
print("Monthly output processed")
html = json.dumps(json1) # Assuming json is imported
#with open('/home/pi/solar2.json', 'w') as file:
# file.write(html)
else:
print("Error reading monthly:", json1['status'], json1['headers'])
print("Done")
Results for me in:
Current time = 2023-08-30 14:08:39 +0000
Last update = 2023-08-30 21:55:10 +0800
Inverter state = normal