You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
5.2 KiB
202 lines
5.2 KiB
import os
|
|
import time
|
|
import uuid
|
|
import requests
|
|
import json
|
|
from dotenv import load_dotenv
|
|
from sungrow_state import load_ps_id
|
|
|
|
|
|
|
|
BASE_URL = os.getenv("SUNGROW_BASE_URL")
|
|
USERNAME = os.getenv("SUNGROW_USERNAME")
|
|
PASSWORD = os.getenv("SUNGROW_PASSWORD")
|
|
APPKEY = os.getenv("SUNGROW_APPKEY")
|
|
KEY = os.getenv("SUNGROW_KEY")
|
|
|
|
|
|
def login():
|
|
url = f"{BASE_URL}/openapi/login"
|
|
|
|
headers = {
|
|
"Content-Type": "application/json;charset=UTF-8",
|
|
"sys_code": "901",
|
|
"x-access-key": KEY
|
|
}
|
|
|
|
body = {
|
|
"user_account": USERNAME,
|
|
"user_password": PASSWORD,
|
|
"appkey": APPKEY,
|
|
"lang": "_de_DE"
|
|
}
|
|
|
|
response = requests.post(url, json=body, headers=headers, timeout=10)
|
|
|
|
print("Login Status:", response.status_code)
|
|
print("Login Body:", response.text)
|
|
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
|
|
if data.get("result_code") != "1":
|
|
raise Exception(f"Login failed: {data}")
|
|
|
|
return data["result_data"]["token"]
|
|
|
|
|
|
def call_api(endpoint: str, token: str, params: dict = None, debug: bool = False):
|
|
if params is None:
|
|
params = {}
|
|
|
|
# Auto-inject ps_id if requested
|
|
if "ps_id" in params and params["ps_id"] is None:
|
|
stored_ps_id = load_ps_id()
|
|
if stored_ps_id is None:
|
|
raise ValueError("ps_id was requested but no stored ps_id found.")
|
|
params["ps_id"] = stored_ps_id
|
|
|
|
url = f"{BASE_URL}{endpoint}"
|
|
|
|
headers = {
|
|
"Content-Type": "application/json;charset=UTF-8",
|
|
"sys_code": "901",
|
|
"x-access-key": KEY
|
|
}
|
|
|
|
base_body = {
|
|
"appkey": APPKEY,
|
|
"token": token,
|
|
"lang": "_de_DE",
|
|
"timestamp": str(int(time.time() * 1000)),
|
|
"nonce": uuid.uuid4().hex
|
|
}
|
|
|
|
body = {**base_body, **params}
|
|
|
|
# Debug: print full request
|
|
if debug:
|
|
import json
|
|
print("\n=== API REQUEST ===")
|
|
print("URL:", url)
|
|
print("Headers:", json.dumps(headers, indent=4, ensure_ascii=False))
|
|
print("Body:", json.dumps(body, indent=4, ensure_ascii=False))
|
|
print("===================\n")
|
|
|
|
response = requests.post(url, json=body, headers=headers, timeout=10)
|
|
|
|
# Debug: print full response
|
|
if debug:
|
|
print("=== API RESPONSE ===")
|
|
print("Status:", response.status_code)
|
|
print("Body:", response.text)
|
|
print("====================\n")
|
|
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
|
|
def get_power_station_list(token: str, cur_page: int = 1, size: int = 10, debug: bool = False):
|
|
"""
|
|
Wrapper for /openapi/getPowerStationList
|
|
"""
|
|
params = {
|
|
"curPage": cur_page,
|
|
"size": size
|
|
}
|
|
|
|
return call_api("/openapi/getPowerStationList", token, params, debug=debug)
|
|
|
|
def get_device_list(
|
|
token: str,
|
|
ps_id: int,
|
|
cur_page: int = 1,
|
|
size: int = 50,
|
|
is_virtual_unit: str | None = None,
|
|
device_type_list: list | None = None,
|
|
rel_state: str | None = None,
|
|
is_get_firmware_version: str | None = None,
|
|
debug: bool = False
|
|
):
|
|
"""
|
|
Wrapper for /openapi/getDeviceList
|
|
|
|
Parameters:
|
|
ps_id (int): Plant ID (required)
|
|
cur_page (int): Page number (required)
|
|
size (int): Page size (required)
|
|
is_virtual_unit (str): "1" = virtual, "0" = physical
|
|
device_type_list (list): e.g. [1, 3, 11]
|
|
rel_state (str): "0" = unclaimed, "1" = claimed
|
|
is_get_firmware_version (str): "0" = no, "1" = yes
|
|
"""
|
|
|
|
params = {
|
|
"ps_id": ps_id,
|
|
"curPage": cur_page,
|
|
"size": size
|
|
}
|
|
|
|
# Only include optional parameters if they are provided
|
|
if is_virtual_unit is not None:
|
|
params["is_virtual_unit"] = is_virtual_unit
|
|
|
|
if device_type_list is not None:
|
|
params["device_type_list"] = device_type_list
|
|
|
|
if rel_state is not None:
|
|
params["rel_state"] = rel_state
|
|
|
|
if is_get_firmware_version is not None:
|
|
params["is_get_firmware_version"] = is_get_firmware_version
|
|
|
|
return call_api("/openapi/getDeviceList", token, params, debug=debug)
|
|
|
|
def get_device_realtime_data(
|
|
token: str,
|
|
point_id_list: list,
|
|
device_type: int,
|
|
ps_key_list: list | None = None,
|
|
sn_list: list | None = None,
|
|
debug: bool = False
|
|
):
|
|
"""
|
|
Wrapper for /openapi/getDeviceRealTimeData
|
|
|
|
Required:
|
|
point_id_list (list[str])
|
|
device_type (int)
|
|
|
|
Optional (choose one):
|
|
ps_key_list (list[str])
|
|
sn_list (list[str])
|
|
"""
|
|
|
|
if ps_key_list is None and sn_list is None:
|
|
raise ValueError("You must provide either ps_key_list or sn_list.")
|
|
|
|
params = {
|
|
"point_id_list": point_id_list,
|
|
"device_type": device_type
|
|
}
|
|
|
|
if ps_key_list is not None:
|
|
params["ps_key_list"] = ps_key_list
|
|
|
|
if sn_list is not None:
|
|
params["sn_list"] = sn_list
|
|
|
|
return call_api("/openapi/getDeviceRealTimeData", token, params, debug=debug)
|
|
|
|
|
|
def ensure_success(response: dict):
|
|
if response.get("result_code") != "1":
|
|
raise Exception(
|
|
f"API error {response.get('result_code')}: {response.get('result_msg')}"
|
|
)
|
|
|
|
|
|
def pretty_print(data):
|
|
import json
|
|
print(json.dumps(data, indent=4, ensure_ascii=False))
|
|
|