Skip to content

Commit

Permalink
fix #65: handle disabled battery packs (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrester authored Feb 24, 2024
1 parent 89a8ce2 commit 597089b
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [WIP]

- Use yarl for URL parsing by @bdraco (https://github.com/jrester/tesla_powerwall/pull/62)
- Correctly handle disabled battery packs (https://github.com/jrester/tesla_powerwall/pull/66/)

## [0.5.1]

Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ await powerwall.get_capacity()

Get information about the battery packs that are installed:

Assuming that the battery is operational, you can retrive a number of values about each battery:
```python
batteries = await powerwall.get_batteries()
#=> [<Battery ...>, <Battery ...>]
Expand Down Expand Up @@ -200,6 +201,22 @@ batteries[0].i_out
#=> -7.4
batteries[0].grid_state
#=> GridState.COMPLIANT
batteries[0].disabled_reasons
#=> []

```

If a battery is disabled it's `grid_state` will be `GridState.DISABLED` and some values will be `None`. The variable `disabled_reasons` might contain more information why the battery is disabled:
```python
...
batteries[1].grid_state
#=> GridState.DISABLED
batteries[1].disabled_reasons
#=> ["DisabledExcessiveVoltageDrop"]
batteries[1].p_out
#=> None
batteries[1].energy_charged
#=> None
```

### Powerwall Status
Expand Down
1 change: 1 addition & 0 deletions tesla_powerwall/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class IslandMode(Enum):


class GridState(Enum):
DISABLED = "Disabled"
COMPLIANT = "Grid_Compliant"
QUALIFYING = "Grid_Qualifying"
UNCOMPLIANT = "Grid_Uncompliant"
Expand Down
35 changes: 26 additions & 9 deletions tesla_powerwall/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,22 +272,38 @@ def from_dict(src: dict) -> "SolarResponse":

@dataclass
class BatteryResponse(ResponseBase):
"""
A battery pack as part of the system_status response.
"""

part_number: str
serial_number: str
energy_charged: int
energy_discharged: int
wobble_detected: bool
energy_remaining: int
capacity: int
wobble_detected: bool
p_out: int
q_out: int
v_out: float
f_out: float
i_out: float
# Values might be None if this battery is in GridState.DISABLED
energy_charged: Optional[int]
energy_discharged: Optional[int]
p_out: Optional[int]
q_out: Optional[int]
v_out: Optional[float]
f_out: Optional[float]
i_out: Optional[float]
grid_state: GridState
disabled_reasons: List[str]

@staticmethod
def from_dict(src: dict) -> "BatteryResponse":
# Check if the battery is disabled. A battery is considered disabled if:
# - there is at least one disabled reason present in the response,
# - or the pinv_grid_state is empty
disabled_reasons = src["disabled_reasons"]
raw_grid_state = src["pinv_grid_state"]
grid_state = (
GridState.DISABLED
if len(disabled_reasons) > 0 or len(raw_grid_state) == 0
else GridState(raw_grid_state)
)
return BatteryResponse(
src,
part_number=src["PackagePartNumber"],
Expand All @@ -302,5 +318,6 @@ def from_dict(src: dict) -> "BatteryResponse":
v_out=src["v_out"],
f_out=src["f_out"],
i_out=src["i_out"],
grid_state=GridState(src["pinv_grid_state"]),
grid_state=grid_state,
disabled_reasons=disabled_reasons,
)
26 changes: 26 additions & 0 deletions tests/unit/fixtures/system_status.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,32 @@
"version": "67f943cb05d12d",
"vf_mode": false,
"wobble_detected": false
},
{
"OpSeqState": "Standby",
"PackagePartNumber": "XXX-E",
"PackageSerialNumber": "XXX",
"Type": "",
"backup_ready": false,
"charge_power_clamped": false,
"disabled_reasons": [
"DisabledExcessiveVoltageDrop"
],
"energy_charged": null,
"energy_discharged": null,
"f_out": null,
"i_out": null,
"nominal_energy_remaining": 0,
"nominal_full_pack_energy": 14714,
"off_grid": false,
"p_out": null,
"pinv_grid_state": "",
"pinv_state": "",
"q_out": null,
"v_out": null,
"version": "eb113390162784",
"vf_mode": false,
"wobble_detected": false
}
],
"battery_target_power": -3646.2544361664613,
Expand Down
9 changes: 8 additions & 1 deletion tests/unit/test_powerwall.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ async def test_system_status(self):

self.add_response("system_status", body=SYSTEM_STATUS_RESPONSE)
batteries = await self.powerwall.get_batteries()
self.assertEqual(len(batteries), 2)
self.assertEqual(len(batteries), 3)
self.assertEqual(batteries[0].part_number, "XXX-G")
self.assertEqual(batteries[0].serial_number, "TGXXX")
self.assertEqual(batteries[0].energy_remaining, 7378)
Expand All @@ -256,6 +256,13 @@ async def test_system_status(self):
self.assertEqual(batteries[0].q_out, 30)
self.assertEqual(batteries[0].v_out, 226.60000000000002)
self.assertEqual(batteries[0].grid_state, GridState.COMPLIANT)
self.assertEqual(batteries[2].grid_state, GridState.DISABLED)
self.assertEqual(batteries[2].p_out, None)
self.assertEqual(batteries[2].i_out, None)
self.assertEqual(batteries[2].energy_charged, None)
self.assertEqual(
batteries[2].disabled_reasons, ["DisabledExcessiveVoltageDrop"]
)
self.aresponses.assert_plan_strictly_followed()

async def test_islanding_mode_offgrid(self):
Expand Down

0 comments on commit 597089b

Please sign in to comment.