|
| 1 | + |
| 2 | +import growattServer |
| 3 | +import getpass |
| 4 | + |
| 5 | +# Example script fetching key power and today+total energy metrics from a Growatt MID-30KTL3-XH (TLX) + APX battery hybrid system |
| 6 | +# |
| 7 | +# There is a lot of overlap in what the various Growatt APIs returns. |
| 8 | +# tlx_detail() contains the bulk of the needed data, but some info is missing and is fetched from |
| 9 | +# tlx_system_status(), tlx_energy_overview() and tlx_battery_info_detailed() instead |
| 10 | + |
| 11 | + |
| 12 | +# Prompt user for username |
| 13 | +username=input("Enter username:") |
| 14 | + |
| 15 | +# Prompt user to input password |
| 16 | +user_pass=getpass.getpass("Enter password:") |
| 17 | + |
| 18 | +# Login, emulating the Growatt app |
| 19 | +user_agent = 'ShinePhone/8.1.17 (iPhone; iOS 15.6.1; Scale/2.00)' |
| 20 | +api = growattServer.GrowattApi(agent_identifier=user_agent) |
| 21 | +login_response = api.login(username, user_pass) |
| 22 | +if not login_response['success']: |
| 23 | + print(f"Failed to log in, msg: {login_response['msg']}, error: {login_response['error']}") |
| 24 | + exit() |
| 25 | + |
| 26 | +# Get plant(s) |
| 27 | +plant_list = api.plant_list_two() |
| 28 | +plant_id = plant_list[0]['id'] |
| 29 | + |
| 30 | +# Get devices in plant |
| 31 | +devices = api.device_list(plant_id) |
| 32 | + |
| 33 | +# Iterate over all devices. Here we are interested in data from 'tlx' inverters and 'bat' devices |
| 34 | +batteries_info = [] |
| 35 | +for device in devices: |
| 36 | + if device['deviceType'] == 'tlx': |
| 37 | + inverter_sn = device['deviceSn'] |
| 38 | + |
| 39 | + # Inverter detail, contains the bulk of energy and power values |
| 40 | + inverter_detail = api.tlx_detail(inverter_sn).get('data') |
| 41 | + |
| 42 | + # Energy overview is used to retrieve "epvToday" which is not present in tlx_detail() for some reason |
| 43 | + energy_overview = api.tlx_energy_overview(plant_id, inverter_sn) |
| 44 | + |
| 45 | + # System status, contains power values, not available in inverter_detail() |
| 46 | + system_status = api.tlx_system_status(plant_id, inverter_sn) |
| 47 | + |
| 48 | + if device['deviceType'] == 'bat': |
| 49 | + batt_info = api.tlx_battery_info(device['deviceSn']) |
| 50 | + if batt_info.get('lost'): |
| 51 | + # Disconnected batteries are listed with 'old' power/energy/SOC data |
| 52 | + # Therefore we check it it's 'lost' and skip it in that case. |
| 53 | + print("'Lost' battery found, skipping") |
| 54 | + continue |
| 55 | + |
| 56 | + # Battery info |
| 57 | + batt_info = api.tlx_battery_info_detailed(plant_id, device['deviceSn']).get('data') |
| 58 | + |
| 59 | + if float(batt_info['chargeOrDisPower']) > 0: |
| 60 | + bdcChargePower = float(batt_info['chargeOrDisPower']) |
| 61 | + bdcDischargePower = 0 |
| 62 | + else: |
| 63 | + bdcChargePower = 0 |
| 64 | + bdcDischargePower = float(batt_info['chargeOrDisPower']) |
| 65 | + bdcDischargePower = -bdcDischargePower |
| 66 | + |
| 67 | + battery_data = { |
| 68 | + 'serialNum': device['deviceSn'], |
| 69 | + 'bdcChargePower': bdcChargePower, |
| 70 | + 'bdcDischargePower': bdcDischargePower, |
| 71 | + 'dischargeTotal': batt_info['dischargeTotal'], |
| 72 | + 'soc': batt_info['soc'] |
| 73 | + } |
| 74 | + batteries_info.append(battery_data) |
| 75 | + |
| 76 | + |
| 77 | +solar_production = f'{float(energy_overview["epvToday"]):.1f}/{float(energy_overview["epvTotal"]):.1f}' |
| 78 | +solar_production_pv1 = f'{float(inverter_detail["epv1Today"]):.1f}/{float(inverter_detail["epv1Total"]):.1f}' |
| 79 | +solar_production_pv2 = f'{float(inverter_detail["epv2Today"]):.1f}/{float(inverter_detail["epv2Total"]):.1f}' |
| 80 | +energy_output = f'{float(inverter_detail["eacToday"]):.1f}/{float(inverter_detail["eacTotal"]):.1f}' |
| 81 | +system_production = f'{float(inverter_detail["esystemToday"]):.1f}/{float(inverter_detail["esystemTotal"]):.1f}' |
| 82 | +battery_charged = f'{float(inverter_detail["echargeToday"]):.1f}/{float(inverter_detail["echargeTotal"]):.1f}' |
| 83 | +battery_grid_charge = f'{float(inverter_detail["eacChargeToday"]):.1f}/{float(inverter_detail["eacChargeTotal"]):.1f}' |
| 84 | +battery_discharged = f'{float(inverter_detail["edischargeToday"]):.1f}/{float(inverter_detail["edischargeTotal"]):.1f}' |
| 85 | +exported_to_grid = f'{float(inverter_detail["etoGridToday"]):.1f}/{float(inverter_detail["etoGridTotal"]):.1f}' |
| 86 | +imported_from_grid = f'{float(inverter_detail["etoUserToday"]):.1f}/{float(inverter_detail["etoUserTotal"]):.1f}' |
| 87 | +load_consumption = f'{float(inverter_detail["elocalLoadToday"]):.1f}/{float(inverter_detail["elocalLoadTotal"]):.1f}' |
| 88 | +self_consumption = f'{float(inverter_detail["eselfToday"]):.1f}/{float(inverter_detail["eselfTotal"]):.1f}' |
| 89 | +battery_charged = f'{float(inverter_detail["echargeToday"]):.1f}/{float(inverter_detail["echargeTotal"]):.1f}' |
| 90 | + |
| 91 | +print("\nGeneration overview Today/Total(kWh)") |
| 92 | +print(f'Solar production {solar_production:>22}') |
| 93 | +print(f' Solar production, PV1 {solar_production_pv1:>22}') |
| 94 | +print(f' Solar production, PV2 {solar_production_pv2:>22}') |
| 95 | +print(f'Energy Output {energy_output:>22}') |
| 96 | +print(f'System production {system_production:>22}') |
| 97 | +print(f'Self consumption {self_consumption:>22}') |
| 98 | +print(f'Load consumption {load_consumption:>22}') |
| 99 | +print(f'Battery Charged {battery_charged:>22}') |
| 100 | +print(f' Charged from grid {battery_grid_charge:>22}') |
| 101 | +print(f'Battery Discharged {battery_discharged:>22}') |
| 102 | +print(f'Import from grid {imported_from_grid:>22}') |
| 103 | +print(f'Export to grid {exported_to_grid:>22}') |
| 104 | + |
| 105 | +print("\nPower overview (Watts)") |
| 106 | +print(f'AC Power {float(inverter_detail["pac"]):>22.1f}') |
| 107 | +print(f'Self power {float(inverter_detail["pself"]):>22.1f}') |
| 108 | +print(f'Export power {float(inverter_detail["pacToGridTotal"]):>22.1f}') |
| 109 | +print(f'Import power {float(inverter_detail["pacToUserTotal"]):>22.1f}') |
| 110 | +print(f'Local load power {float(inverter_detail["pacToLocalLoad"]):>22.1f}') |
| 111 | +print(f'PV power {float(inverter_detail["psystem"]):>22.1f}') |
| 112 | +print(f'PV #1 power {float(inverter_detail["ppv1"]):>22.1f}') |
| 113 | +print(f'PV #2 power {float(inverter_detail["ppv2"]):>22.1f}') |
| 114 | +print(f'Battery charge power {float(system_status["chargePower"])*1000:>22.1f}') |
| 115 | +if len(batteries_info) > 0: |
| 116 | + print(f'Batt #1 charge power {float(batteries_info[0]["bdcChargePower"]):>22.1f}') |
| 117 | +if len(batteries_info) > 1: |
| 118 | + print(f'Batt #2 charge power {float(batteries_info[1]["bdcChargePower"]):>22.1f}') |
| 119 | +print(f'Battery discharge power {float(system_status["pdisCharge"])*1000:>18.1f}') |
| 120 | +if len(batteries_info) > 0: |
| 121 | + print(f'Batt #1 discharge power {float(batteries_info[0]["bdcDischargePower"]):>22.1f}') |
| 122 | +if len(batteries_info) > 1: |
| 123 | + print(f'Batt #2 discharge power {float(batteries_info[1]["bdcDischargePower"]):>22.1f}') |
| 124 | +if len(batteries_info) > 0: |
| 125 | + print(f'Batt #1 SOC {int(batteries_info[0]["soc"]):>21}%') |
| 126 | +if len(batteries_info) > 1: |
| 127 | + print(f'Batt #2 SOC {int(batteries_info[1]["soc"]):>21}%') |
0 commit comments