From 205e7945aad79f698b0c9e788c478a1a80d0b26c Mon Sep 17 00:00:00 2001 From: "Sam (jasonacox-sam)" Date: Sat, 16 May 2026 09:33:15 -0700 Subject: [PATCH 1/3] docs: warn about 80% reserve limit in cloud/FleetAPI mode Tesla's cloud APIs enforce a maximum backup reserve of 80%. Setting reserve above 80% requires v1r LAN mode. This adds: - CLI warning when set_reserve > 80% in cloud or FleetAPI mode - Post-set verification showing actual capped value - README documentation in LAN Control table and CLI usage section - API reference note on set_reserve() Refs: jasonacox/pypowerwall/discussions/303 --- README.md | 9 ++++++++- pypowerwall/__main__.py | 20 ++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b366aa6..dc812b7 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,8 @@ Supported control operations: | Control | Method | Values | |---------|--------|--------| | Backup reserve | `set_reserve(level)` | 0-100 (percentage) | + +> **Cloud/FleetAPI Reserve Limit:** Tesla's cloud APIs enforce a maximum backup reserve of 80%. If you need to set the reserve above 80% (e.g., for a full charge before an outage), use the v1r LAN mode shown below. The `set` CLI will print a warning when you attempt to exceed 80% in cloud or FleetAPI mode. | Operation mode | `set_mode(mode)` | `self_consumption`, `backup` | | Grid charging | `set_grid_charging(enable)` | `True` / `False` | | Grid export | `set_grid_export(mode)` | `battery_ok`, `pv_only`, `never` | @@ -597,7 +599,7 @@ print("System Status: %r\n" % pw.system_status()) is_connected() # Returns True if able to connect and login to Powerwall get_reserve(scale) # Get Battery Reserve Percentage get_mode() # Get Current Battery Operation Mode - set_reserve(level) # Set Battery Reserve Percentage + set_reserve(level) # Set Battery Reserve Percentage (cloud/FleetAPI max 80% - use v1r for higher) set_mode(mode) # Set Current Battery Operation Mode get_time_remaining() # Get the backup time remaining on the battery set_operation(level, mode, json) # Set Battery Reserve Percentage and/or Operation Mode @@ -743,6 +745,11 @@ python -m pypowerwall set -reserve 20 # Set reserve to current charge level python -m pypowerwall set -current +# ⚠️ Cloud and FleetAPI modes limit backup reserve to 80% max. +# To set reserve above 80%, use v1r LAN mode: +python -m pypowerwall set -v1r -host 10.42.1.40 -gw_pwd ABCDEXXXXX \ + -rsa_key_path ./tedapi_rsa_private.pem -reserve 100 + # Grid charging and export python -m pypowerwall set -gridcharging on python -m pypowerwall set -gridcharging off diff --git a/pypowerwall/__main__.py b/pypowerwall/__main__.py index a42647f..d2b2621 100644 --- a/pypowerwall/__main__.py +++ b/pypowerwall/__main__.py @@ -448,12 +448,28 @@ def main(): pw.set_mode(mode) if args.reserve != -1: reserve = args.reserve + if reserve > 80 and pw.mode in ('cloud', 'fleetapi'): + print(f"WARNING: Tesla cloud/FleetAPI limits backup reserve to 80%% max. " + f"Requesting {reserve}%% but Tesla may cap it at 80%%. " + f"Use TEDAPI v1r mode (-v1r) to set reserve above 80%%.") print("Setting Powerwall Reserve to %s" % reserve) - pw.set_reserve(reserve) + result = pw.set_reserve(reserve) + if reserve > 80 and pw.mode in ('cloud', 'fleetapi'): + actual = pw.get_reserve() + if actual is not None and actual <= 80: + print(f"NOTE: Tesla capped reserve at {actual}% instead of {reserve}%.") if args.current: current = float(pw.level()) + if current > 80 and pw.mode in ('cloud', 'fleetapi'): + print(f"WARNING: Tesla cloud/FleetAPI limits backup reserve to 80% max. " + f"Current charge is {current:.0f}% but Tesla may cap reserve at 80%. " + f"Use TEDAPI v1r mode (-v1r) to set reserve above 80%.") print("Setting Powerwall Reserve to Current Charge Level %s" % current) - pw.set_reserve(current) + result = pw.set_reserve(current) + if current > 80 and pw.mode in ('cloud', 'fleetapi'): + actual = pw.get_reserve() + if actual is not None and actual <= 80: + print(f"NOTE: Tesla capped reserve at {actual}% instead of {current:.0f}%.") if args.gridcharging: gridcharging = args.gridcharging.lower() if gridcharging not in ['on', 'off']: From 6cbaacd2cf0b2e51da89703622d544d55ff09394 Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 16 May 2026 11:17:56 -0700 Subject: [PATCH 2/3] Address Copilot review: fix %% in f-strings, use force=True for verification, move README note outside table --- README.md | 2 +- RELEASE.md | 4 ++++ pypowerwall/__main__.py | 14 +++++++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dc812b7..6c1a6e1 100644 --- a/README.md +++ b/README.md @@ -346,9 +346,9 @@ Supported control operations: | Control | Method | Values | |---------|--------|--------| | Backup reserve | `set_reserve(level)` | 0-100 (percentage) | +| Operation mode | `set_mode(mode)` | `self_consumption`, `backup` | > **Cloud/FleetAPI Reserve Limit:** Tesla's cloud APIs enforce a maximum backup reserve of 80%. If you need to set the reserve above 80% (e.g., for a full charge before an outage), use the v1r LAN mode shown below. The `set` CLI will print a warning when you attempt to exceed 80% in cloud or FleetAPI mode. -| Operation mode | `set_mode(mode)` | `self_consumption`, `backup` | | Grid charging | `set_grid_charging(enable)` | `True` / `False` | | Grid export | `set_grid_export(mode)` | `battery_ok`, `pv_only`, `never` | diff --git a/RELEASE.md b/RELEASE.md index 4c7b964..c59cfa4 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,6 +2,10 @@ ## v0.15.7 - Grid Noise Suppression and v1r Owner API Login Fix +* Docs: Document Tesla cloud/FleetAPI 80% backup reserve limit + * Added warning in README and CLI examples for `set -reserve` when using cloud or FleetAPI mode + * CLI now prints a WARNING when attempting to set reserve above 80% in cloud/FleetAPI mode, and a NOTE if Tesla caps the actual value + * Added v1r LAN mode example showing how to set reserve above 80% * Feat: Add `PW_SITE_ZERO_THRESHOLD` environment variable to suppress phantom grid noise readings * When set to a positive integer value (in watts), site power readings with absolute value at or below the threshold are reported as 0 * Applies to `/api/meters/aggregates` site power, `/csv`/`/csv/v2` grid power, and `/json` grid power endpoints diff --git a/pypowerwall/__main__.py b/pypowerwall/__main__.py index d2b2621..eb3a4b5 100644 --- a/pypowerwall/__main__.py +++ b/pypowerwall/__main__.py @@ -449,13 +449,13 @@ def main(): if args.reserve != -1: reserve = args.reserve if reserve > 80 and pw.mode in ('cloud', 'fleetapi'): - print(f"WARNING: Tesla cloud/FleetAPI limits backup reserve to 80%% max. " - f"Requesting {reserve}%% but Tesla may cap it at 80%%. " - f"Use TEDAPI v1r mode (-v1r) to set reserve above 80%%.") + print(f"WARNING: Tesla cloud/FleetAPI limits backup reserve to 80% max. " + f"Requesting {reserve}% but Tesla may cap it at 80%. " + f"Use TEDAPI v1r mode (-v1r) to set reserve above 80%.") print("Setting Powerwall Reserve to %s" % reserve) - result = pw.set_reserve(reserve) + pw.set_reserve(reserve) if reserve > 80 and pw.mode in ('cloud', 'fleetapi'): - actual = pw.get_reserve() + actual = pw.get_reserve(scale=False, force=True) if actual is not None and actual <= 80: print(f"NOTE: Tesla capped reserve at {actual}% instead of {reserve}%.") if args.current: @@ -465,9 +465,9 @@ def main(): f"Current charge is {current:.0f}% but Tesla may cap reserve at 80%. " f"Use TEDAPI v1r mode (-v1r) to set reserve above 80%.") print("Setting Powerwall Reserve to Current Charge Level %s" % current) - result = pw.set_reserve(current) + pw.set_reserve(current) if current > 80 and pw.mode in ('cloud', 'fleetapi'): - actual = pw.get_reserve() + actual = pw.get_reserve(scale=False, force=True) if actual is not None and actual <= 80: print(f"NOTE: Tesla capped reserve at {actual}% instead of {current:.0f}%.") if args.gridcharging: From 7b93b1aa02fb2546737eba188204e52b92587675 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 17 May 2026 00:02:59 -0700 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20use=20scale=3DTrue=20for=20reserve?= =?UTF-8?q?=20verification=20=E2=80=94=20user=20expects=20Tesla-app=20scal?= =?UTF-8?q?ed=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pypowerwall/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pypowerwall/__main__.py b/pypowerwall/__main__.py index eb3a4b5..d9fc09a 100644 --- a/pypowerwall/__main__.py +++ b/pypowerwall/__main__.py @@ -455,7 +455,7 @@ def main(): print("Setting Powerwall Reserve to %s" % reserve) pw.set_reserve(reserve) if reserve > 80 and pw.mode in ('cloud', 'fleetapi'): - actual = pw.get_reserve(scale=False, force=True) + actual = pw.get_reserve(scale=True, force=True) if actual is not None and actual <= 80: print(f"NOTE: Tesla capped reserve at {actual}% instead of {reserve}%.") if args.current: @@ -467,7 +467,7 @@ def main(): print("Setting Powerwall Reserve to Current Charge Level %s" % current) pw.set_reserve(current) if current > 80 and pw.mode in ('cloud', 'fleetapi'): - actual = pw.get_reserve(scale=False, force=True) + actual = pw.get_reserve(scale=True, force=True) if actual is not None and actual <= 80: print(f"NOTE: Tesla capped reserve at {actual}% instead of {current:.0f}%.") if args.gridcharging: