Skip to content

Commit 168517e

Browse files
Added Growatt OpenAPI V1 Support (#100)
Co-authored-by: indykoning <[email protected]>
1 parent ecb7784 commit 168517e

File tree

9 files changed

+2156
-1176
lines changed

9 files changed

+2156
-1176
lines changed

README.md

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ Package to retrieve PV information from the growatt server.
55
Special thanks to [Sjoerd Langkemper](https://github.com/Sjord) who has provided a strong base to start off from https://github.com/Sjord/growatt_api_client
66
These projects may merge in the future since they are simmilar in code and function.
77

8+
This library now supports both the legacy password-based authentication and the V1 API with token-based authentication for MIN systems (TLX are identified as MIN system in the public API). Certain endpoints are not supported anymore by openapi.growatt.com. For example `api.min_write_parameter()` should be used instead of old `api.update_tlx_inverter_setting()`.
9+
810
## Usage
911

12+
### Legacy API
13+
14+
Uses username/password basic authentication
15+
1016
```python
1117
import growattServer
1218

@@ -16,16 +22,39 @@ login_response = api.login(<username>, <password>)
1622
print(api.plant_list(login_response['user']['id']))
1723
```
1824

25+
### V1 API
26+
27+
The public v1 API requires token-based authentication
28+
29+
```python
30+
import growattServer
31+
32+
api = growattServer.OpenApiV1(token="YOUR_API_TOKEN")
33+
#Get a list of growatt plants.
34+
plants = api.plant_list_v1()
35+
print(plants)
36+
```
37+
1938
## Methods and Variables
2039

2140
### Methods
2241

2342
Any methods that may be useful.
2443

25-
`api.login(username, password)` Log into the growatt API. This must be done before making any request. After this you will be logged in. You will want to capture the response to get the `userId` variable.
44+
#### Legacy API Methods
45+
46+
`api.login(username, password)` Log into the growatt API. This must be done before making any request. After this you will be logged in. You will want to capture the response to get the `userId` variable. Should not be used for public v1 APIs.
2647

2748
`api.plant_list(user_id)` Get a list of plants registered to your account.
2849

50+
`api.plant_list_v1()` Get a list of plants registered to your account, using public v1 API.
51+
52+
`api.plant_details_v1(plant_id)` Get detailed information about a power station, using public v1 API.
53+
54+
`api.plant_energy_overview_v1(plant_id)` Get energy overview data for a plant, using public v1 API.
55+
56+
`api.plant_energy_history_v1(plant_id, start_date, end_date, time_unit, page, perpage)` Get historical energy data for a plant for multiple days/months/years, using public v1 API.
57+
2958
`api.plant_info(plant_id)` Get info for specified plant.
3059

3160
`api.plant_settings(plant_id)` Get the current settings for the specified plant
@@ -38,6 +67,8 @@ Any methods that may be useful.
3867

3968
`api.device_list(plant_id)` Get a list of devices in specified plant.
4069

70+
`api.device_list_v1(plant_id)` Get a list of devices in specified plant using the public v1 API.
71+
4172
`api.inverter_data(inverter_id, date)` Get some basic data of a specific date for the inverter.
4273

4374
`api.inverter_detail(inverter_id)` Get detailed data on inverter.
@@ -96,6 +127,34 @@ Any methods that may be useful.
96127

97128
`api.update_noah_settings(serial_number, setting_type, parameters)` Applies the provided parameters (dictionary or array) for the specified setting on the specified noah device; see 'Noah settings' below for more information
98129

130+
#### V1 API Methods
131+
132+
`api.plant_list()` Get a list of plants registered to your account, using public v1 API.
133+
134+
`api.plant_details(plant_id)` Get detailed information about a power station, using public v1 API.
135+
136+
`api.plant_energy_overview(plant_id)` Get energy overview data for a plant, using public v1 API.
137+
138+
`api.plant_energy_history(plant_id, start_date, end_date, time_unit, page, perpage)` Get historical energy data for a plant for multiple days/months/years, using public v1 API.
139+
140+
`api.device_list(plant_id)` Get a list of devices in specified plant using the public v1 API.
141+
142+
`api.min_energy(device_sn)` Get current energy data for a min inverter, including power and energy values.
143+
144+
`api.min_detail(device_sn)` Get detailed data for a min inverter.
145+
146+
`api.min_energy_history(device_sn, start_date=None, end_date=None, timezone=None, page=None, limit=None)` Get energy history data for a min inverter (7-day max range).
147+
148+
`api.min_settings(device_sn)` Get all settings for a min inverter.
149+
150+
`api.min_read_parameter(device_sn, parameter_id, start_address=None, end_address=None)` Read a specific setting for a min inverter.
151+
152+
`api.min_write_parameter(device_sn, parameter_id, parameter_values)` Set parameters on a min inverter. Parameter values can be a single value, a list, or a dictionary.
153+
154+
`api.min_write_time_segment(device_sn, segment_id, batt_mode, start_time, end_time, enabled=True)` Update a specific time segment for a min inverter.
155+
156+
`api.min_read_time_segments(device_sn, settings_data=None)` Read all time segments from a MIN inverter. Optionally pass settings_data to avoid redundant API calls.
157+
99158
### Variables
100159

101160
Some variables you may want to set.
@@ -118,6 +177,8 @@ The library can be initialised to introduce randomness into the User Agent field
118177

119178
This has been added since the Growatt servers started checking for the presence of a `User-Agent` field in the headers that are sent.
120179

180+
#### Legacy API Initialization
181+
121182
By default the library will use a pre-set `User-Agent` value which identifies this library while also appearing like an Android device. However, it is also possible to pass in parameters to the intialisation of the library to override this entirely, or just add a random ID to the value. e.g.
122183

123184
```python
@@ -128,6 +189,12 @@ api = growattServer.GrowattApi(True) # Adds a randomly generated User ID to the
128189
api = growattServer.GrowattApi(False, "my_user_agent_value") # Overrides the default and uses "my_user_agent_value" in the User-Agent header
129190
```
130191

192+
#### V1 API Initialization
193+
194+
```python
195+
api = growattServer.GrowattApiV1(token="YOUR_API_TOKEN") # Initialize with your API token
196+
```
197+
131198
Please see the `user_agent_options.py` example in the `examples` directory if you wish to investigate further.
132199

133200
## Examples
@@ -240,6 +307,40 @@ The four functions `update_tlx_inverter_setting`, `update_mix_inverter_setting`,
240307

241308
Only the settings described above have been tested with `update_tlx_inverter_setting` and they all take only one single parameter. It is very likely that the function works with all settings returned by `tlx_get_enabled_settings`, but this has not been tested. A helper function `update_tlx_inverter_time_segment` is provided for the settings that require more than one parameter.
242309

310+
## MIN/TLX Inverter Settings Using V1 API
311+
312+
For MIN/TLX systems, the public V1 API provides a more robust way to read and write inverter settings:
313+
314+
* **Read Parameter**
315+
* function: `api.min_read_parameter`
316+
* parameters:
317+
* `device_sn`: The device serial number
318+
* `parameter_id`: Parameter ID to read (e.g., "discharge_power")
319+
* `start_address`, `end_address`: Optional, for reading registers by address
320+
321+
* **Write Parameter**
322+
* function: `api.min_write_parameter`
323+
* parameters:
324+
* `device_sn`: The device serial number
325+
* `parameter_id`: Parameter ID to write (e.g., "ac_charge")
326+
* `parameter_values`: Value to set (single value, list, or dictionary)
327+
328+
* **Time Segments**
329+
* function: `api.min_write_time_segment`
330+
* parameters:
331+
* `device_sn`: The device serial number
332+
* `segment_id`: Segment number (1-9)
333+
* `batt_mode`: Battery mode (0=Load First, 1=Battery First, 2=Grid First)
334+
* `start_time`: Datetime.time object for segment start
335+
* `end_time`: Datetime.time object for segment end
336+
* `enabled`: Boolean to enable/disable segment
337+
338+
* **Read Time Segments**
339+
* function: `api.min_read_time_segments`
340+
* parameters:
341+
* `device_sn`: The device serial number
342+
* `settings_data`: Optional settings data to avoid redundant API calls
343+
243344
## Noah Settings
244345
The noah settings function allow you to change individual values on your noah system e.g. system default output power, battery management, operation mode and currency
245346
From what has been reverse engineered from the api, each setting has a `setting_type` and a set of `parameters` that are relevant to it.
@@ -287,4 +388,4 @@ The library contains functions that allow you to modify the configuration of you
287388

288389
To the best of our knowledge only the `settings` functions perform modifications to your system and all other operations are read only. Regardless of the operation:
289390

290-
***The library is used entirely at your own risk.***
391+
***The library is used entirely at your own risk.***

examples/min_example.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import growattServer
2+
import datetime
3+
import json
4+
import requests
5+
6+
"""
7+
# Example script controlling a MID/TLX Growatt (MID-30KTL3-XH + APX battery) system using the public growatt API
8+
# You can obtain an API token from the Growatt API documentation or developer portal.
9+
"""
10+
11+
# Get the API token from user input or environment variable
12+
# api_token = os.environ.get("GROWATT_API_TOKEN") or input("Enter your Growatt API token: ")
13+
14+
# test token from official API docs https://www.showdoc.com.cn/262556420217021/1494053950115877
15+
api_token = "6eb6f069523055a339d71e5b1f6c88cc" # gitleaks:allow
16+
17+
try:
18+
# Initialize the API with token instead of using login
19+
api = growattServer.OpenApiV1(token=api_token)
20+
21+
# Plant info
22+
plants = api.plant_list()
23+
print(f"Plants: Found {plants['count']} plants")
24+
plant_id = plants['plants'][0]['plant_id']
25+
26+
# Devices
27+
devices = api.device_list(plant_id)
28+
29+
for device in devices['devices']:
30+
if device['type'] == 7: # (MIN/TLX)
31+
inverter_sn = device['device_sn']
32+
print(f"Processing inverter: {inverter_sn}")
33+
34+
# Get device details
35+
inverter_data = api.min_detail(inverter_sn)
36+
print("Saving inverter data to inverter_data.json")
37+
with open('inverter_data.json', 'w') as f:
38+
json.dump(inverter_data, f, indent=4, sort_keys=True)
39+
40+
# Get energy data
41+
energy_data = api.min_energy(device_sn=inverter_sn)
42+
print("Saving energy data to energy_data.json")
43+
with open('energy_data.json', 'w') as f:
44+
json.dump(energy_data, f, indent=4, sort_keys=True)
45+
46+
# Get energy history
47+
energy_history_data = api.min_energy_history(inverter_sn)
48+
print("Saving energy history data to energy_history.json")
49+
with open('energy_history.json', 'w') as f:
50+
json.dump(energy_history_data['datas'],
51+
f, indent=4, sort_keys=True)
52+
53+
# Get settings
54+
settings_data = api.min_settings(device_sn=inverter_sn)
55+
print("Saving settings data to settings_data.json")
56+
with open('settings_data.json', 'w') as f:
57+
json.dump(settings_data, f, indent=4, sort_keys=True)
58+
59+
# Read time segments
60+
tou = api.min_read_time_segments(inverter_sn, settings_data)
61+
print(json.dumps(tou, indent=4))
62+
63+
# Read discharge power
64+
discharge_power = api.min_read_parameter(
65+
inverter_sn, 'discharge_power')
66+
print("Current discharge power:", discharge_power, "%")
67+
68+
# Settings parameters - Uncomment to test
69+
70+
# Turn on AC charging
71+
# api.min_write_parameter(inverter_sn, 'ac_charge', 1)
72+
# print("AC charging enabled successfully")
73+
74+
# Enable Load First between 00:00 and 11:59 using time segment 1
75+
# api.min_write_time_segment(
76+
# device_sn=inverter_sn,
77+
# segment_id=1,
78+
# batt_mode=growattServer.BATT_MODE_BATTERY_FIRST,
79+
# start_time=datetime.time(0, 0),
80+
# end_time=datetime.time(00, 59),
81+
# enabled=True
82+
# )
83+
# print("Time segment updated successfully")
84+
85+
86+
except growattServer.GrowattV1ApiError as e:
87+
print(f"API Error: {e} (Code: {e.error_code}, Message: {e.error_msg})")
88+
except growattServer.GrowattParameterError as e:
89+
print(f"Parameter Error: {e}")
90+
except requests.exceptions.RequestException as e:
91+
print(f"Network Error: {e}")
92+
except Exception as e:
93+
print(f"Unexpected error: {e}")

examples/min_example_dashboard.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import growattServer
2+
import json
3+
import requests
4+
5+
"""
6+
Example script fetching key power and today+total energy metrics from a Growatt MID-30KTL3-XH (TLX) + APX battery hybrid system
7+
using the V1 API with token-based authentication.
8+
"""
9+
10+
# Get the API token from user input or environment variable
11+
# api_token = os.environ.get("GROWATT_API_TOKEN") or input("Enter your Growatt API token: ")
12+
13+
# test token from official API docs https://www.showdoc.com.cn/262556420217021/1494053950115877
14+
api_token = "6eb6f069523055a339d71e5b1f6c88cc" # gitleaks:allow
15+
16+
try:
17+
# Initialize the API with token
18+
api = growattServer.OpenApiV1(token=api_token)
19+
20+
# Get plant list using V1 API
21+
plants = api.plant_list()
22+
plant_id = plants['plants'][0]['plant_id']
23+
24+
# Get devices in plant
25+
devices = api.device_list(plant_id)
26+
27+
# Iterate over all devices
28+
energy_data = None
29+
for device in devices['devices']:
30+
if device['type'] == 7: # (MIN/TLX)
31+
inverter_sn = device['device_sn']
32+
33+
# Get energy data
34+
energy_data = api.min_energy(device_sn=inverter_sn)
35+
with open('energy_data.json', 'w') as f:
36+
json.dump(energy_data, f, indent=4, sort_keys=True)
37+
38+
# energy data does not contain epvToday for some reason, so we need to calculate it
39+
epv_today = energy_data["epv1Today"] + energy_data["epv2Today"]
40+
41+
solar_production = f'{float(epv_today):.1f}/{float(energy_data["epvTotal"]):.1f}'
42+
solar_production_pv1 = f'{float(energy_data["epv1Today"]):.1f}/{float(energy_data["epv1Total"]):.1f}'
43+
solar_production_pv2 = f'{float(energy_data["epv2Today"]):.1f}/{float(energy_data["epv2Total"]):.1f}'
44+
energy_output = f'{float(energy_data["eacToday"]):.1f}/{float(energy_data["eacTotal"]):.1f}'
45+
system_production = f'{float(energy_data["esystemToday"]):.1f}/{float(energy_data["esystemTotal"]):.1f}'
46+
battery_charged = f'{float(energy_data["echargeToday"]):.1f}/{float(energy_data["echargeTotal"]):.1f}'
47+
battery_grid_charge = f'{float(energy_data["eacChargeToday"]):.1f}/{float(energy_data["eacChargeTotal"]):.1f}'
48+
battery_discharged = f'{float(energy_data["edischargeToday"]):.1f}/{float(energy_data["edischargeTotal"]):.1f}'
49+
exported_to_grid = f'{float(energy_data["etoGridToday"]):.1f}/{float(energy_data["etoGridTotal"]):.1f}'
50+
imported_from_grid = f'{float(energy_data["etoUserToday"]):.1f}/{float(energy_data["etoUserTotal"]):.1f}'
51+
load_consumption = f'{float(energy_data["elocalLoadToday"]):.1f}/{float(energy_data["elocalLoadTotal"]):.1f}'
52+
self_consumption = f'{float(energy_data["eselfToday"]):.1f}/{float(energy_data["eselfTotal"]):.1f}'
53+
battery_charged = f'{float(energy_data["echargeToday"]):.1f}/{float(energy_data["echargeTotal"]):.1f}'
54+
55+
# Output the dashboard
56+
print("\nGeneration overview Today/Total(kWh)")
57+
print(f'Solar production {solar_production:>22}')
58+
print(f' Solar production, PV1 {solar_production_pv1:>22}')
59+
print(f' Solar production, PV2 {solar_production_pv2:>22}')
60+
print(f'Energy Output {energy_output:>22}')
61+
print(f'System production {system_production:>22}')
62+
print(f'Self consumption {self_consumption:>22}')
63+
print(f'Load consumption {load_consumption:>22}')
64+
print(f'Battery Charged {battery_charged:>22}')
65+
print(f' Charged from grid {battery_grid_charge:>22}')
66+
print(f'Battery Discharged {battery_discharged:>22}')
67+
print(f'Import from grid {imported_from_grid:>22}')
68+
print(f'Export to grid {exported_to_grid:>22}')
69+
70+
print("\nPower overview (Watts)")
71+
print(f'AC Power {float(energy_data["pac"]):>22.1f}')
72+
print(f'Self power {float(energy_data["pself"]):>22.1f}')
73+
print(
74+
f'Export power {float(energy_data["pacToGridTotal"]):>22.1f}')
75+
print(
76+
f'Import power {float(energy_data["pacToUserTotal"]):>22.1f}')
77+
print(
78+
f'Local load power {float(energy_data["pacToLocalLoad"]):>22.1f}')
79+
print(f'PV power {float(energy_data["ppv"]):>22.1f}')
80+
print(f'PV #1 power {float(energy_data["ppv1"]):>22.1f}')
81+
print(f'PV #2 power {float(energy_data["ppv2"]):>22.1f}')
82+
print(
83+
f'Battery charge power {float(energy_data["bdc1ChargePower"]):>22.1f}')
84+
print(
85+
f'Battery discharge power {float(energy_data["bdc1DischargePower"]):>22.1f}')
86+
print(f'Battery SOC {int(energy_data["bdc1Soc"]):>21}%')
87+
88+
except growattServer.GrowattV1ApiError as e:
89+
print(f"API Error: {e} (Code: {e.error_code}, Message: {e.error_msg})")
90+
except growattServer.GrowattParameterError as e:
91+
print(f"Parameter Error: {e}")
92+
except requests.exceptions.RequestException as e:
93+
print(f"Network Error: {e}")
94+
except Exception as e:
95+
print(f"Unexpected error: {e}")

examples/tlx_example_dashboard.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
print(f'Export power {float(inverter_detail["pacToGridTotal"]):>22.1f}')
109109
print(f'Import power {float(inverter_detail["pacToUserTotal"]):>22.1f}')
110110
print(f'Local load power {float(inverter_detail["pacToLocalLoad"]):>22.1f}')
111-
print(f'PV power {float(inverter_detail["psystem"]):>22.1f}')
111+
print(f'PV power {float(inverter_detail["ppv"]):>22.1f}')
112112
print(f'PV #1 power {float(inverter_detail["ppv1"]):>22.1f}')
113113
print(f'PV #2 power {float(inverter_detail["ppv2"]):>22.1f}')
114114
print(f'Battery charge power {float(system_status["chargePower"])*1000:>22.1f}')

0 commit comments

Comments
 (0)