Repository: jasonacox/pypowerwall
Pull request #176: Additional values /json endpoint
Author: login: erikgieseler
Created: 2025-05-11T09:31:27Z | Merged: 2025-05-13T03:32:16Z

I have added the following values to the `/json` endpoint:

`nominal_full_pack_energy`
`nominal_energy_remaining`
`strings`

`nominal_full_pack_energy `and `nominal_energy_remaining `are tested.

I cannot test the `strings `because I have a SolarEdge inverter.


Diff:
diff --git a/proxy/RELEASE.md b/proxy/RELEASE.md
index f032ab1..9c3e7fc 100644
--- a/proxy/RELEASE.md
+++ b/proxy/RELEASE.md
@@ -1,19 +1,82 @@
 ## pyPowerwall Proxy Release Notes
 
+### Proxy t74 (12 May 2025)
+
+* Add additional data elements to `/json` route:
+
+```json
+{
+  "grid": 2423,
+  "home": 3708.5000000000005,
+  "solar": 1307,
+  "battery": -26,
+  "soe": 70.88757396449705,
+  "grid_status": 1,
+  "reserve": 70.0,
+  "time_remaining_hours": 8.076041526223541,
+  "full_pack_energy": 42250,
+  "energy_remaining": 29950.000000000004,
+  "strings": {
+    "A": {
+      "State": "Pv_Active",
+      "Voltage": 188,
+      "Current": 1.5999999999999996,
+      "Power": 300.79999999999995,
+      "Connected": true
+    },
+    "B": {
+      "State": "Pv_Active",
+      "Voltage": 318,
+      "Current": 1.2999999999999998,
+      "Power": 413.3999999999999,
+      "Connected": true
+    },
+    "C": {
+      "State": "Pv_Active",
+      "Voltage": 152,
+      "Current": 1.7499999999999998,
+      "Power": 265.99999999999994,
+      "Connected": true
+    },
+    "D": {
+      "State": "Pv_Active",
+      "Voltage": 190,
+      "Current": 1.7499999999999998,
+      "Power": 332.49999999999994,
+      "Connected": true
+    },
+    "E": {
+      "State": "Pv_Active",
+      "Voltage": 0,
+      "Current": 0.09999999999999964,
+      "Power": 0.0,
+      "Connected": true
+    },
+    "F": {
+      "State": "Pv_Active_Parallel",
+      "Voltage": 0,
+      "Current": 0,
+      "Power": 0,
+      "Connected": true
+    }
+  }
+}
+```
+
 ### Proxy t73 (10 May 2025)
 
 * Add `/json` route to return basic metrics:
 
 ```json
 {
-"grid": -3,
-"home": 917.5,
-"solar": 5930,
-"battery": -5030,
-"soe": 61.391932759907306,
-"grid_status": 1,
-"reserve": 20,
-"time_remaining_hours": 17.03651226158038
+    "grid": -3,
+    "home": 917.5,
+    "solar": 5930,
+    "battery": -5030,
+    "soe": 61.391932759907306,
+    "grid_status": 1,
+    "reserve": 20,
+    "time_remaining_hours": 17.03651226158038
 }
 ```
 
diff --git a/proxy/server.py b/proxy/server.py
index feb6762..77fd39d 100755
--- a/proxy/server.py
+++ b/proxy/server.py
@@ -54,7 +54,7 @@
 import pypowerwall
 from pypowerwall import parse_version
 
-BUILD = "t73"
+BUILD = "t74"
 ALLOWLIST = [
     '/api/status', '/api/site_info/site_name', '/api/meters/site',
     '/api/meters/solar', '/api/sitemaster', '/api/powerwalls',
@@ -587,7 +587,8 @@ def do_GET(self):
             pod["backup_reserve_percent"] = pw.get_reserve()
             message: str = json.dumps(pod)
         elif request_path == '/json':
-            # JSON - Grid,Home,Solar,Battery,Level,GridStatus,Reserve
+            # JSON - Grid,Home,Solar,Battery,Level,GridStatus,Reserve,TimeRemaining,FullEnergy,RemainingEnergy,Strings
+            d = pw.system_status() or {}
             values = {
                 'grid': pw.grid() or 0,
                 'home': pw.home() or 0, 
@@ -596,7 +597,10 @@ def do_GET(self):
                 'soe': pw.level() or 0,
                 'grid_status': int(pw.grid_status() == 'UP'),
                 'reserve': pw.get_reserve() or 0,
-		'time_remaining_hours': pw.get_time_remaining() or 0
+                'time_remaining_hours': pw.get_time_remaining() or 0,
+                'full_pack_energy': get_value(d, 'nominal_full_pack_energy') or 0,
+                'energy_remaining': get_value(d, 'nominal_energy_remaining') or 0,
+                'strings': pw.strings(jsonformat=False) or {}
             }
             if not neg_solar and values['solar'] < 0:
                 # Shift negative solar to load


Reviews:
user: login: jasonacox
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-12T03:23:02Z
user: login: rawslaw
body: I get expected data with these changes; will post in the morning once the sun is up for more interesting test result.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:51:19Z
user: login: rawslaw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:52:25Z
user: login: erikgieseler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T15:20:26Z
user: login: rawslaw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T17:13:25Z

Review comments:
diff_hunk: @@ -596,7 +597,10 @@ def do_GET(self):
                 'soe': pw.level() or 0,
                 'grid_status': int(pw.grid_status() == 'UP'),
                 'reserve': pw.get_reserve() or 0,
-		'time_remaining_hours': pw.get_time_remaining() or 0
+                'time_remaining_hours': pw.get_time_remaining() or 0,
+                'full_pack_energy': get_value(d, 'nominal_full_pack_energy') or 0,
+                'energy_remaining': get_value(d, 'nominal_energy_remaining') or 0,
+                'strings': pw.strings(jsonformat=True) or {}
path: proxy/server.py
user: login: jasonacox
body: We will want the pythonic dictionary values for strings instead of JSON, so we need to change this to `jsonformat=False`.
created_at: 2025-05-12T03:23:01Z
author_association: OWNER
diff_hunk: @@ -588,6 +588,7 @@ def do_GET(self):
             message: str = json.dumps(pod)
         elif request_path == '/json':
             # JSON - Grid,Home,Solar,Battery,Level,GridStatus,Reserve
path: proxy/server.py
user: login: rawslaw
body: should update the comment for the additional values.  Doesn't have to be _this_, but my local edits have
```suggestion
            # JSON formatted basic values for smart charging external integration
            # Grid,Home,Solar,Battery,SoE,GridStatus,Reserve,TimeRemaining,FullEnergy,RemainingEnergy,Strings{}
```
created_at: 2025-05-12T06:59:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -596,7 +597,10 @@ def do_GET(self):
                 'soe': pw.level() or 0,
                 'grid_status': int(pw.grid_status() == 'UP'),
                 'reserve': pw.get_reserve() or 0,
-		'time_remaining_hours': pw.get_time_remaining() or 0
+                'time_remaining_hours': pw.get_time_remaining() or 0,
+                'full_pack_energy': get_value(d, 'nominal_full_pack_energy') or 0,
+                'energy_remaining': get_value(d, 'nominal_energy_remaining') or 0,
path: proxy/server.py
user: login: rawslaw
body: I'm on the fence about truncating this value.  

```suggestion
                'energy_remaining': math.trunc(get_value(d, 'nominal_energy_remaining')) or 0,
```

1. I like reporting the value you get;
1. The actual value that is returned in my case has a 13 digit decimal.  The unit here is Watt-Hours, so there's little significance in reporting a 4x10^-13 Watt-hours... I did the math: that's 14 milliwatt-µseconds, hah. 

created_at: 2025-05-12T07:37:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -596,7 +597,10 @@ def do_GET(self):
                 'soe': pw.level() or 0,
                 'grid_status': int(pw.grid_status() == 'UP'),
                 'reserve': pw.get_reserve() or 0,
-		'time_remaining_hours': pw.get_time_remaining() or 0
+                'time_remaining_hours': pw.get_time_remaining() or 0,
+                'full_pack_energy': get_value(d, 'nominal_full_pack_energy') or 0,
+                'energy_remaining': get_value(d, 'nominal_energy_remaining') or 0,
path: proxy/server.py
user: login: rawslaw
body: ```json
  "full_pack_energy": 42250,
  "energy_remaining": 30200.000000000004,
  ```
created_at: 2025-05-12T07:52:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -596,7 +597,10 @@ def do_GET(self):
                 'soe': pw.level() or 0,
                 'grid_status': int(pw.grid_status() == 'UP'),
                 'reserve': pw.get_reserve() or 0,
-		'time_remaining_hours': pw.get_time_remaining() or 0
+                'time_remaining_hours': pw.get_time_remaining() or 0,
+                'full_pack_energy': get_value(d, 'nominal_full_pack_energy') or 0,
+                'energy_remaining': get_value(d, 'nominal_energy_remaining') or 0,
path: proxy/server.py
user: login: erikgieseler
body: In my opinion, the proxy should output exactly the values as they come from the TEDAPI. 
Further processing, e.g. rounding, is normally the task of a downstream system. 
@jasonacox What is your opinion on this?
created_at: 2025-05-12T15:20:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -596,7 +597,10 @@ def do_GET(self):
                 'soe': pw.level() or 0,
                 'grid_status': int(pw.grid_status() == 'UP'),
                 'reserve': pw.get_reserve() or 0,
-		'time_remaining_hours': pw.get_time_remaining() or 0
+                'time_remaining_hours': pw.get_time_remaining() or 0,
+                'full_pack_energy': get_value(d, 'nominal_full_pack_energy') or 0,
+                'energy_remaining': get_value(d, 'nominal_energy_remaining') or 0,
path: proxy/server.py
user: login: rawslaw
body: Yeah, after sleeping on it, seems like a post-processing thing to drop the decimal.
created_at: 2025-05-12T17:13:24Z
author_association: CONTRIBUTOR

PR comments:
user: login: rawslaw
created_at: 2025-05-12T17:18:10Z
body: Looks good! My results with PW3 on this rainy day.

```json
{
  "grid": 2423,
  "home": 3708.5000000000005,
  "solar": 1307,
  "battery": -26,
  "soe": 70.88757396449705,
  "grid_status": 1,
  "reserve": 70.0,
  "time_remaining_hours": 8.076041526223541,
  "full_pack_energy": 42250,
  "energy_remaining": 29950.000000000004,
  "strings": {
    "A": {
      "State": "Pv_Active",
      "Voltage": 188,
      "Current": 1.5999999999999996,
      "Power": 300.79999999999995,
      "Connected": true
    },
    "B": {
      "State": "Pv_Active",
      "Voltage": 318,
      "Current": 1.2999999999999998,
      "Power": 413.3999999999999,
      "Connected": true
    },
    "C": {
      "State": "Pv_Active",
      "Voltage": 152,
      "Current": 1.7499999999999998,
      "Power": 265.99999999999994,
      "Connected": true
    },
    "D": {
      "State": "Pv_Active",
      "Voltage": 190,
      "Current": 1.7499999999999998,
      "Power": 332.49999999999994,
      "Connected": true
    },
    "E": {
      "State": "Pv_Active",
      "Voltage": 0,
      "Current": 0.09999999999999964,
      "Power": 0.0,
      "Connected": true
    },
    "F": {
      "State": "Pv_Active_Parallel",
      "Voltage": 0,
      "Current": 0,
      "Power": 0,
      "Connected": true
    }
  }
}
```
author_association: CONTRIBUTOR
user: login: jasonacox
created_at: 2025-05-13T03:33:13Z
body: Thanks @erikgieseler for this addition!

@rawslaw Thanks for the test! I added your example to the release notes. 👍 
author_association: OWNER
user: login: jasonacox
created_at: 2025-05-13T03:35:19Z
body: Docker image: jasonacox/pypowerwall:0.12.12t74

author_association: OWNER