Repository: jrester/tesla_powerwall

Issue:
FEATURE REQUEST - Separate data for multiple PW batteries connected to a single gateway
Is it possible to extract data from separate PW batteries attached to a single gateway? I have 2 PW batteries connected to separate phases of a 3 phase installation. Specifically, I would like to know Battery AC Power (charge/discharge rate) of each battery so I can deduce total load on each phase.

This separate data is available on //[gateway ip address]/system page, as well as voltage and state of charge, but I don't know how to extract it in HA. The system page is only accessible if logged in as the installer, and not as the customer. Logging in as an installer requires a physical toggle of a battery on/off switch

The individual data points visible on the installer system page are:
- Powerwall State (eg Grid Following)
- Charge Level (eg 100%)
- AC Voltage (eg 247V)
- AC Power (eg 470.0W Charging)

Discussion:

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index ec03f2a..dc6f4d0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,7 +2,8 @@
 
 ## [0.5.1]
 
-- Use orjson for parsing json
+- Use orjson for parsing json (https://github.com/jrester/tesla_powerwall/pull/59)
+- Expose low level information for each battery pack (https://github.com/jrester/tesla_powerwall/pull/60)
 
 ## [0.5.0]
 
diff --git a/README.md b/README.md
index 1d2d8aa..b7b50cf 100644
--- a/README.md
+++ b/README.md
@@ -190,6 +190,18 @@ batteries[0].energy_discharged
 #=> 4659550 (Wh)
 batteries[0].wobble_detected
 #=> False
+batteries[0].p_out
+#=> 260
+batteries[0].q_out
+#=> -1080
+batteries[0].v_out
+#=> 245.70
+batteries[0].f_out
+#=> 49.953
+batteries[0].i_out
+#=> -7.4
+batteries[0].grid_state
+#=> GridState.COMPLIANT
 ```
 
 ### Powerwall Status
diff --git a/tesla_powerwall/responses.py b/tesla_powerwall/responses.py
index 1616308..b72236a 100644
--- a/tesla_powerwall/responses.py
+++ b/tesla_powerwall/responses.py
@@ -3,7 +3,13 @@ from dataclasses import dataclass
 from datetime import datetime, timedelta
 from typing import Any, Dict, List, Optional
 
-from .const import DEFAULT_KW_ROUND_PERSICION, DeviceType, MeterType, Roles
+from .const import (
+    DEFAULT_KW_ROUND_PERSICION,
+    DeviceType,
+    GridState,
+    MeterType,
+    Roles,
+)
 from .error import MeterNotAvailableError
 from .helpers import convert_to_kw
 
@@ -273,6 +279,12 @@ class BatteryResponse(ResponseBase):
     energy_remaining: int
     capacity: int
     wobble_detected: bool
+    p_out: int
+    q_out: int
+    v_out: float
+    f_out: float
+    i_out: float
+    grid_state: GridState
 
     @staticmethod
     def from_dict(src: dict) -> "BatteryResponse":
@@ -285,4 +297,10 @@ class BatteryResponse(ResponseBase):
             energy_remaining=src["nominal_energy_remaining"],
             capacity=src["nominal_full_pack_energy"],
             wobble_detected=src["wobble_detected"],
+            p_out=src["p_out"],
+            q_out=src["q_out"],
+            v_out=src["v_out"],
+            f_out=src["f_out"],
+            i_out=src["i_out"],
+            grid_state=GridState(src["pinv_grid_state"]),
         )


Test patch:
diff --git a/tests/unit/test_powerwall.py b/tests/unit/test_powerwall.py
index 5a79eb3..88bccee 100644
--- a/tests/unit/test_powerwall.py
+++ b/tests/unit/test_powerwall.py
@@ -9,6 +9,7 @@ import aresponses
 from tesla_powerwall import (
     API,
     DeviceType,
+    GridState,
     GridStatus,
     IslandMode,
     MeterDetailsReadings,
@@ -18,12 +19,12 @@ from tesla_powerwall import (
     MetersAggregatesResponse,
     MeterType,
     MissingAttributeError,
+    OperationMode,
     Powerwall,
     SiteMasterResponse,
     assert_attribute,
     convert_to_kw,
 )
-from tesla_powerwall.const import OperationMode
 from tests.unit import (
     ENDPOINT,
     ENDPOINT_HOST,
@@ -249,6 +250,12 @@ class TestPowerWall(unittest.IsolatedAsyncioTestCase):
         self.assertEqual(batteries[0].energy_charged, 5525740)
         self.assertEqual(batteries[0].energy_discharged, 4659550)
         self.assertEqual(batteries[0].wobble_detected, False)
+        self.assertEqual(batteries[0].p_out, -1830)
+        self.assertEqual(batteries[0].i_out, 39)
+        self.assertEqual(batteries[0].f_out, 50.067)
+        self.assertEqual(batteries[0].q_out, 30)
+        self.assertEqual(batteries[0].v_out, 226.60000000000002)
+        self.assertEqual(batteries[0].grid_state, GridState.COMPLIANT)
         self.aresponses.assert_plan_strictly_followed()
 
     async def test_islanding_mode_offgrid(self):