From eeb907aeb7bce166da5ac993fca46f53213d3c89 Mon Sep 17 00:00:00 2001 From: Jason Cox Date: Sun, 1 Sep 2024 12:57:43 -0700 Subject: [PATCH 1/5] Add PW3 Vitals --- .gitignore | 4 + RELEASE.md | 4 + proxy/server.py | 10 +- pypowerwall/__init__.py | 2 +- pypowerwall/tedapi/__init__.py | 167 +++++++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7d83ce3..ad98f63 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,7 @@ j config.json status.json tools/tedapi/firmware.raw +tools/tedapi/components.json +tools/tedapi/manypw3.json +tools/tedapi/pw3.json +tools/tedapi/test.py diff --git a/RELEASE.md b/RELEASE.md index cf5c4a4..f8a563a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,9 @@ # RELEASE NOTES +## v0.11.0 - Add PW3 Vitals + +* Add polling of Powerwall 3 Devices to pull in PW3 specific string data, capacity, voltages, frequencies, and alerts. This creates mock TEPOD, PVAC and PVS capatible payloads available in vitals(). + ## v0.10.10 - Add Grid Control * Add a function and command line options to allow user to get and set grid charging and exporting modes (see https://github.com/jasonacox/pypowerwall/issues/108). diff --git a/proxy/server.py b/proxy/server.py index 9fefabc..261276f 100755 --- a/proxy/server.py +++ b/proxy/server.py @@ -54,7 +54,7 @@ import pypowerwall from pypowerwall import parse_version -BUILD = "t63" +BUILD = "t64" ALLOWLIST = [ '/api/status', '/api/site_info/site_name', '/api/meters/site', '/api/meters/solar', '/api/sitemaster', '/api/powerwalls', @@ -113,6 +113,7 @@ 'cloudmode': False, 'fleetapi': False, 'tedapi': False, + 'pw3': False, 'tedapi_mode': "off", 'siteid': None, 'counter': 0 @@ -207,6 +208,7 @@ def get_value(a, key): if pw.tedapi: proxystats['tedapi'] = True proxystats['tedapi_mode'] = pw.tedapi_mode + proxystats['pw3'] = pw.tedapi.pw3 log.info(f"TEDAPI Mode Enabled for Device Vitals ({pw.tedapi_mode})") pw_control = None @@ -551,11 +553,15 @@ def do_GET(self): elif self.path.startswith('/tedapi'): # TEDAPI Specific Calls if pw.tedapi: - message = '{"error": "Use /tedapi/config, /tedapi/status"}' + message = '{"error": "Use /tedapi/config, /tedapi/status, /tedapi/components, /tedapi/battery"}' if self.path == '/tedapi/config': message = json.dumps(pw.tedapi.get_config()) if self.path == '/tedapi/status': message = json.dumps(pw.tedapi.get_status()) + if self.path == '/tedapi/components': + message = json.dumps(pw.tedapi.get_components()) + if self.path == '/tedapi/battery': + message = json.dumps(pw.tedapi.get_battery_block()) else: message = '{"error": "TEDAPI not enabled"}' elif self.path.startswith('/cloud'): diff --git a/pypowerwall/__init__.py b/pypowerwall/__init__.py index d44ca60..a5b08d0 100644 --- a/pypowerwall/__init__.py +++ b/pypowerwall/__init__.py @@ -88,7 +88,7 @@ from typing import Union, Optional import time -version_tuple = (0, 10, 10) +version_tuple = (0, 11, 0) version = __version__ = '%d.%d.%d' % version_tuple __author__ = 'jasonacox' diff --git a/pypowerwall/tedapi/__init__.py b/pypowerwall/tedapi/__init__.py index 9e271bf..5e71eb9 100644 --- a/pypowerwall/tedapi/__init__.py +++ b/pypowerwall/tedapi/__init__.py @@ -534,6 +534,168 @@ def get_components(self, force=False): return components + def get_pw3_vitals(self, force=False): + """ + Get Powerwall 3 Battery Vitals Data + + Returns: + { + "PVAC--{part}--{sn}" { + "PVAC_PvState_A": "PV_Active", + "PVAC_PVCurrent_A": 0.0, + ... + "PVAC_PVMeasuredVoltage_A": 0.0, + ... + "PVAC_PVMeasuredPower_A": 0.0, + ... + "PVAC_Fout": 60.0, + "PVAC_Pout": 0.0, + "PVAC_State": X, + "PVAC_VL1Ground": lookup(p, ['PVAC_Logging', 'PVAC_VL1Ground']), + "PVAC_VL2Ground": lookup(p, ['PVAC_Logging', 'PVAC_VL2Ground']), + "PVAC_Vout": lookup(p, ['PVAC_Status', 'PVAC_Vout']), + "manufacturer": "TESLA", + "partNumber": packagePartNumber, + "serialNumber": packageSerialNumber, + }. + "PVS--{part}--{sn}" { + "PVS_StringA_Connected": true, + ... + }, + "TEPOD--{part}--{sn}" { + "alerts": [], + "POD_nom_energy_remaining": 0.0, + "POD_nom_full_pack_energy": 0.0, + "POD_nom_energy_to_be_charged": 0.0, + } + } + """ + # Check Connection + if not self.din: + if not self.connect(): + log.error("Not Connected - Unable to get configuration") + return None + # Check Cache + if not force and "pw3_vitals" in self.pwcachetime: + if time.time() - self.pwcachetime["pw3_vitals"] < self.pwconfigexpire: + log.debug("Using Cached Components") + return self.pwcache["pw3_vitals"] + if not force and self.pwcooldown > time.perf_counter(): + # Rate limited - return None + log.debug('Rate limit cooldown period - Pausing API calls') + return None + components = self.get_components(force) + din = self.din + if not components: + log.error("Unable to get Powerwall 3 Components") + return None + + response = {} + config = self.get_config(force) + battery_blocks = config['battery_blocks'] + + # Loop through all the battery blocks (Powerwalls) + for battery in battery_blocks: + pw_din = battery['vin'] # 1707000-11-J--TG12xxxxxx3A8Z + pw_part, pw_serial = pw_din.split('--') + battery_type = battery['type'] + if "Powerwall3" not in battery_type: + continue + # Fetch Device ComponentsQuery from each Powerwall + pb = tedapi_pb2.Message() + pb.message.deliveryChannel = 1 + pb.message.sender.local = 1 + pb.message.sender.din = din # DIN of Primary Powerwall 3 / System + pb.message.recipient.din = pw_din # DIN of Powerwall of Interest + pb.message.payload.send.num = 2 + pb.message.payload.send.payload.value = 1 + pb.message.payload.send.payload.text = " query ComponentsQuery (\n $pchComponentsFilter: ComponentFilter,\n $pchSignalNames: [String!],\n $pwsComponentsFilter: ComponentFilter,\n $pwsSignalNames: [String!],\n $bmsComponentsFilter: ComponentFilter,\n $bmsSignalNames: [String!],\n $hvpComponentsFilter: ComponentFilter,\n $hvpSignalNames: [String!],\n $baggrComponentsFilter: ComponentFilter,\n $baggrSignalNames: [String!],\n ) {\n # TODO STST-57686: Introduce GraphQL fragments to shorten\n pw3Can {\n firmwareUpdate {\n isUpdating\n progress {\n updating\n numSteps\n currentStep\n currentStepProgress\n progress\n }\n }\n }\n components {\n pws: components(filter: $pwsComponentsFilter) {\n signals(names: $pwsSignalNames) {\n name\n value\n textValue\n boolValue\n timestamp\n }\n activeAlerts {\n name\n }\n }\n pch: components(filter: $pchComponentsFilter) {\n signals(names: $pchSignalNames) {\n name\n value\n textValue\n boolValue\n timestamp\n }\n activeAlerts {\n name\n }\n }\n bms: components(filter: $bmsComponentsFilter) {\n signals(names: $bmsSignalNames) {\n name\n value\n textValue\n boolValue\n timestamp\n }\n activeAlerts {\n name\n }\n }\n hvp: components(filter: $hvpComponentsFilter) {\n partNumber\n serialNumber\n signals(names: $hvpSignalNames) {\n name\n value\n textValue\n boolValue\n timestamp\n }\n activeAlerts {\n name\n }\n }\n baggr: components(filter: $baggrComponentsFilter) {\n signals(names: $baggrSignalNames) {\n name\n value\n textValue\n boolValue\n timestamp\n }\n activeAlerts {\n name\n }\n }\n }\n}\n" + pb.message.payload.send.code = b'0\201\210\002B\000\270q\354>\243m\325p\371S\253\231\346~:\032\216~\242\263\207\017L\273O\203u\241\270\333w\233\354\276\246h\262\243\255\261\007\202D\277\353x\023O\022\303\216\264\010-\'i6\360>B\237\236\304\244m\002B\001\023Pk\033)\277\236\342R\264\247g\260u\036\023\3662\354\242\353\035\221\234\027\245\321J\342\345\037q\262O\3446-\353\315m1\237zai0\341\207C4\307\300Z\177@h\335\327\0239\252f\n\206W' + pb.message.payload.send.b.value = "{\"pwsComponentsFilter\":{\"types\":[\"PW3SAF\"]},\"pwsSignalNames\":[\"PWS_SelfTest\",\"PWS_PeImpTestState\",\"PWS_PvIsoTestState\",\"PWS_RelaySelfTest_State\",\"PWS_MciTestState\",\"PWS_appGitHash\",\"PWS_ProdSwitch_State\"],\"pchComponentsFilter\":{\"types\":[\"PCH\"]},\"pchSignalNames\":[\"PCH_State\",\"PCH_PvState_A\",\"PCH_PvState_B\",\"PCH_PvState_C\",\"PCH_PvState_D\",\"PCH_PvState_E\",\"PCH_PvState_F\",\"PCH_AcFrequency\",\"PCH_AcVoltageAB\",\"PCH_AcVoltageAN\",\"PCH_AcVoltageBN\",\"PCH_packagePartNumber_1_7\",\"PCH_packagePartNumber_8_14\",\"PCH_packagePartNumber_15_20\",\"PCH_packageSerialNumber_1_7\",\"PCH_packageSerialNumber_8_14\",\"PCH_PvVoltageA\",\"PCH_PvVoltageB\",\"PCH_PvVoltageC\",\"PCH_PvVoltageD\",\"PCH_PvVoltageE\",\"PCH_PvVoltageF\",\"PCH_PvCurrentA\",\"PCH_PvCurrentB\",\"PCH_PvCurrentC\",\"PCH_PvCurrentD\",\"PCH_PvCurrentE\",\"PCH_PvCurrentF\",\"PCH_BatteryPower\",\"PCH_AcRealPowerAB\",\"PCH_SlowPvPowerSum\",\"PCH_AcMode\",\"PCH_AcFrequency\",\"PCH_DcdcState_A\",\"PCH_DcdcState_B\",\"PCH_appGitHash\"],\"bmsComponentsFilter\":{\"types\":[\"PW3BMS\"]},\"bmsSignalNames\":[\"BMS_nominalEnergyRemaining\",\"BMS_nominalFullPackEnergy\",\"BMS_appGitHash\"],\"hvpComponentsFilter\":{\"types\":[\"PW3HVP\"]},\"hvpSignalNames\":[\"HVP_State\",\"HVP_appGitHash\"],\"baggrComponentsFilter\":{\"types\":[\"BAGGR\"]},\"baggrSignalNames\":[\"BAGGR_State\",\"BAGGR_OperationRequest\",\"BAGGR_NumBatteriesConnected\",\"BAGGR_NumBatteriesPresent\",\"BAGGR_NumBatteriesExpected\",\"BAGGR_LOG_BattConnectionStatus0\",\"BAGGR_LOG_BattConnectionStatus1\",\"BAGGR_LOG_BattConnectionStatus2\",\"BAGGR_LOG_BattConnectionStatus3\"]}" + pb.tail.value = 2 + url = f'https://{GW_IP}/tedapi/device/{pw_din}/v1' + r = requests.post(url, auth=('Tesla_Energy_Device', self.gw_pwd), verify=False, + headers={'Content-type': 'application/octet-string'}, + data=pb.SerializeToString(), timeout=self.timeout) + if r.status_code == 200: + # Decode response + tedapi = tedapi_pb2.Message() + tedapi.ParseFromString(r.content) + payload = tedapi.message.payload.recv.text + if payload: + data = json.loads(payload) + # TEDPOD + alerts = [] + components = data['components'] + for component in components: + for alert in components[component][0]['activeAlerts']: + if alert['name'] not in alerts: + alerts.append(alert['name']) + bms_component = data['components']['bms'][0] # TODO: Process all BMS components + signals = bms_component['signals'] + nom_energy_remaining = 0 + nom_full_pack_energy = 0 + for signal in signals: + if "BMS_nominalEnergyRemaining" == signal['name']: + nom_energy_remaining = int(signal['value'] * 1000) # Convert to Wh + elif "BMS_nominalFullPackEnergy" == signal['name']: + nom_full_pack_energy = int(signal['value'] * 1000) # Convert to Wh + response[f"TEPOD--{pw_din}"] = { + "alerts": alerts, + "POD_nom_energy_remaining": nom_energy_remaining, + "POD_nom_energy_to_be_charged": nom_full_pack_energy - nom_energy_remaining, + "POD_nom_full_pack_energy": nom_full_pack_energy, + } + # PVAC and PVS + response[f"PVAC--{pw_din}"] = {} + response[f"PVS--{pw_din}"] = {} + pch_components = data['components']['pch'] + # pch_components contain: + # PCH_PvState_A through F - textValue in [Pv_Active, Pv_Active_Parallel, Pv_Standby] + # PCH_PvVoltageA through F - value + # PCH_PvCurrentA through F - value + # Loop through and find all the strings - PW3 has 6 strings A-F + for n in ["A", "B", "C", "D", "E", "F"]: + pv_state = "Unknown" + pv_voltage = 0 + pv_current = 0 + for component in pch_components: # TODO: Probably better way to do this + signals = component['signals'] + for signal in signals: + if f'PCH_PvState_{n}' == signal['name']: + pv_state = signal['textValue'] + elif f'PCH_PvVoltage{n}' == signal['name']: + pv_voltage = signal['value'] if signal['value'] > 0 else 0 + elif f'PCH_PvCurrent{n}' == signal['name']: + pv_current = signal['value'] if signal['value'] > 0 else 0 + elif f'PCH_AcFrequency' == signal['name']: + response[f"PVAC--{pw_din}"][f"PVAC_Fout"] = signal['value'] + elif f'PCH_AcVoltageAN' == signal['name']: + response[f"PVAC--{pw_din}"][f"PVAC_VL1Ground"] = signal['value'] + elif f'PCH_AcVoltageBN' == signal['name']: + response[f"PVAC--{pw_din}"][f"PVAC_VL2Ground"] = signal['value'] + elif f'PCH_AcVoltageAB' == signal['name']: + response[f"PVAC--{pw_din}"][f"PVAC_Vout"] = signal['value'] + elif f'PCH_AcRealPowerAB' == signal['name']: + response[f"PVAC--{pw_din}"][f"PVAC_Pout"] = signal['value'] + elif f'PCH_AcMode' == signal['name']: + response[f"PVAC--{pw_din}"][f"PVAC_State"] = signal['textValue'] + pv_power = pv_voltage * pv_current # Calculate power + response[f"PVAC--{pw_din}"][f"PVAC_PvState_{n}"] = pv_state + response[f"PVAC--{pw_din}"][f"PVAC_PVMeasuredVoltage_{n}"] = pv_voltage + response[f"PVAC--{pw_din}"][f"PVAC_PVCurrent_{n}"] = pv_current + response[f"PVAC--{pw_din}"][f"PVAC_PVMeasuredPower_{n}"] = pv_power + response[f"PVAC--{pw_din}"]["manufacturer"] = "TESLA" + response[f"PVAC--{pw_din}"]["partNumber"] = pw_part + response[f"PVAC--{pw_din}"]["serialNumber"] = pw_serial + response[f"PVS--{pw_din}"][f"PVS_String{n}_Connected"] = True if "Pv_Active" in pv_state else False + else: + log.debug(f"No payload for {pw_din}") + else: + log.debug(f"Error fetching components: {r.status_code}") + return response + + def get_battery_block(self, din=None, force=False): """ Get the Powerwall 3 Battery Block Information @@ -1108,6 +1270,11 @@ def calculate_dc_power(V, I): **tesync, **tethc, } + # Merge in the Powerwall 3 data if available + if self.pw3: + pw3_data = self.get_pw3_vitals(force) or {} + vitals.update(pw3_data) + return vitals From cb60f53064a6e8b00d1ed7a67af00b3be180ad0e Mon Sep 17 00:00:00 2001 From: Jason Cox Date: Sun, 1 Sep 2024 13:06:55 -0700 Subject: [PATCH 2/5] Release notes --- RELEASE.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index f8a563a..96ec1b1 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,7 +2,14 @@ ## v0.11.0 - Add PW3 Vitals -* Add polling of Powerwall 3 Devices to pull in PW3 specific string data, capacity, voltages, frequencies, and alerts. This creates mock TEPOD, PVAC and PVS capatible payloads available in vitals(). +* Add polling of Powerwall 3 Devices to pull in PW3 specific string data, capacity, voltages, frequencies, and alerts. +* This creates mock TEPOD, PVAC and PVS compatible payloads available in vitals(). + +Proxy URLs updated for PW3: +* http://localhost:8675/vitals +* http://localhost:8675/help (verify pw3 shows True) +* http://localhost:8675/tedapi/components +* http://localhost:8675/tedapi/battery ## v0.10.10 - Add Grid Control From 4b9d80be4f2664c9170ff6982c3dd9255d7ca3ca Mon Sep 17 00:00:00 2001 From: Jason Cox Date: Sun, 1 Sep 2024 14:25:43 -0700 Subject: [PATCH 3/5] Fix bug with battery_block --- proxy/server.py | 2 +- pypowerwall/tedapi/__init__.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/proxy/server.py b/proxy/server.py index 261276f..7d8cb52 100755 --- a/proxy/server.py +++ b/proxy/server.py @@ -561,7 +561,7 @@ def do_GET(self): if self.path == '/tedapi/components': message = json.dumps(pw.tedapi.get_components()) if self.path == '/tedapi/battery': - message = json.dumps(pw.tedapi.get_battery_block()) + message = json.dumps(pw.tedapi.get_battery_blocks()) else: message = '{"error": "TEDAPI not enabled"}' elif self.path.startswith('/cloud'): diff --git a/pypowerwall/tedapi/__init__.py b/pypowerwall/tedapi/__init__.py index 5e71eb9..58c955a 100644 --- a/pypowerwall/tedapi/__init__.py +++ b/pypowerwall/tedapi/__init__.py @@ -696,6 +696,15 @@ def get_pw3_vitals(self, force=False): return response + def get_battery_blocks(self, force=False): + """ + Get Powerwall 3 Battery Block Information + """ + config = self.get_config(force) + battery_blocks = config.get('battery_blocks') or [] + return battery_blocks + + def get_battery_block(self, din=None, force=False): """ Get the Powerwall 3 Battery Block Information @@ -708,8 +717,6 @@ def get_battery_block(self, din=None, force=False): """ data = None # Make sure we have a DIN - if not din: - din = self.get_din() if not din: log.error("No DIN specified - Unable to get battery block") return None From 982047baf49eff0ff28416d7a1fe12415ca50fc4 Mon Sep 17 00:00:00 2001 From: Jason Cox Date: Sun, 1 Sep 2024 15:25:35 -0700 Subject: [PATCH 4/5] Add TEPINV for PW3 vitals --- pypowerwall/tedapi/__init__.py | 37 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pypowerwall/tedapi/__init__.py b/pypowerwall/tedapi/__init__.py index 58c955a..249ae34 100644 --- a/pypowerwall/tedapi/__init__.py +++ b/pypowerwall/tedapi/__init__.py @@ -646,9 +646,10 @@ def get_pw3_vitals(self, force=False): "POD_nom_energy_to_be_charged": nom_full_pack_energy - nom_energy_remaining, "POD_nom_full_pack_energy": nom_full_pack_energy, } - # PVAC and PVS + # PVAC, PVS and TEPINV response[f"PVAC--{pw_din}"] = {} response[f"PVS--{pw_din}"] = {} + response[f"TEPINV--{pw_din}"] = {} pch_components = data['components']['pch'] # pch_components contain: # PCH_PvState_A through F - textValue in [Pv_Active, Pv_Active_Parallel, Pv_Standby] @@ -668,18 +669,24 @@ def get_pw3_vitals(self, force=False): pv_voltage = signal['value'] if signal['value'] > 0 else 0 elif f'PCH_PvCurrent{n}' == signal['name']: pv_current = signal['value'] if signal['value'] > 0 else 0 - elif f'PCH_AcFrequency' == signal['name']: - response[f"PVAC--{pw_din}"][f"PVAC_Fout"] = signal['value'] - elif f'PCH_AcVoltageAN' == signal['name']: - response[f"PVAC--{pw_din}"][f"PVAC_VL1Ground"] = signal['value'] - elif f'PCH_AcVoltageBN' == signal['name']: - response[f"PVAC--{pw_din}"][f"PVAC_VL2Ground"] = signal['value'] - elif f'PCH_AcVoltageAB' == signal['name']: - response[f"PVAC--{pw_din}"][f"PVAC_Vout"] = signal['value'] - elif f'PCH_AcRealPowerAB' == signal['name']: - response[f"PVAC--{pw_din}"][f"PVAC_Pout"] = signal['value'] - elif f'PCH_AcMode' == signal['name']: - response[f"PVAC--{pw_din}"][f"PVAC_State"] = signal['textValue'] + elif 'PCH_AcFrequency' == signal['name']: + response[f"PVAC--{pw_din}"]["PVAC_Fout"] = signal['value'] + response[f"TEPINV--{pw_din}"]["PINV_Fout"] = signal['value'] + elif 'PCH_AcVoltageAN' == signal['name']: + response[f"PVAC--{pw_din}"]["PVAC_VL1Ground"] = signal['value'] + response[f"TEPINV--{pw_din}"]["PINV_VSplit1"] = signal['value'] + elif 'PCH_AcVoltageBN' == signal['name']: + response[f"PVAC--{pw_din}"]["PVAC_VL2Ground"] = signal['value'] + response[f"TEPINV--{pw_din}"]["PINV_VSplit2"] = signal['value'] + elif 'PCH_AcVoltageAB' == signal['name']: + response[f"PVAC--{pw_din}"]["PVAC_Vout"] = signal['value'] + response[f"TEPINV--{pw_din}"]["PINV_Vout"] = signal['value'] + elif 'PCH_AcRealPowerAB' == signal['name']: + response[f"PVAC--{pw_din}"]["PVAC_Pout"] = signal['value'] + response[f"TEPINV--{pw_din}"]["PINV_Pout"] = (signal['value'] or 0) / 1000 + elif 'PCH_AcMode' == signal['name']: + response[f"PVAC--{pw_din}"]["PVAC_State"] = signal['textValue'] + response[f"TEPINV--{pw_din}"]["PINV_State"] = signal['textValue'] pv_power = pv_voltage * pv_current # Calculate power response[f"PVAC--{pw_din}"][f"PVAC_PvState_{n}"] = pv_state response[f"PVAC--{pw_din}"][f"PVAC_PVMeasuredVoltage_{n}"] = pv_voltage @@ -688,7 +695,7 @@ def get_pw3_vitals(self, force=False): response[f"PVAC--{pw_din}"]["manufacturer"] = "TESLA" response[f"PVAC--{pw_din}"]["partNumber"] = pw_part response[f"PVAC--{pw_din}"]["serialNumber"] = pw_serial - response[f"PVS--{pw_din}"][f"PVS_String{n}_Connected"] = True if "Pv_Active" in pv_state else False + response[f"PVS--{pw_din}"][f"PVS_String{n}_Connected"] = ("Pv_Active" in pv_state) else: log.debug(f"No payload for {pw_din}") else: @@ -698,7 +705,7 @@ def get_pw3_vitals(self, force=False): def get_battery_blocks(self, force=False): """ - Get Powerwall 3 Battery Block Information + Return Powerwall Battery Blocks """ config = self.get_config(force) battery_blocks = config.get('battery_blocks') or [] From 5e45d07269ff8c32dd94a1406900cfa173d7e150 Mon Sep 17 00:00:00 2001 From: Jason Cox Date: Sun, 1 Sep 2024 15:50:39 -0700 Subject: [PATCH 5/5] Update docs --- pypowerwall/tedapi/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pypowerwall/tedapi/__init__.py b/pypowerwall/tedapi/__init__.py index 249ae34..d9735b5 100644 --- a/pypowerwall/tedapi/__init__.py +++ b/pypowerwall/tedapi/__init__.py @@ -27,8 +27,10 @@ battery_level() - Get the battery level as a percentage vitals() - Use tedapi data to create a vitals dictionary get_firmware_version() - Get the Powerwall Firmware Version + get_battery_blocks() - Get list of Powerwall Battery Blocks get_components() - Get the Powerwall 3 Device Information - get_battery_block() - Get the Powerwall 3 Battery Blocks + get_battery_block(din) - Get the Powerwall 3 Battery Block Information + get_pw3_vitals() - Get the Powerwall 3 Vitals Information Note: This module requires access to the Powerwall Gateway. You can add a route to