- feat(tariff): read the site's utility tariff and update Time-of-Use settings -
pw.get_tariff()/pw.set_tariff(tou_settings)(endpoints/api/tesla/tariff_rateand/api/tesla/time_of_use_settings). Cloud reads the Owner API tariff and FleetAPI readstariff_contentfrom site info; writes follow Tesla'stime_of_use_settingscontract (tariff_content_v2). Responses are normalized (e.g.{"Message": "Updated", "Code": 201}), a successful write invalidates the cached tariff, and TEDAPI (mock read,Noneon write) and local mode (None) fail cleanly. Thanks @nesys (#382) - fix(cloud): recover when Tesla replaces or re-provisions a site - after a 404 for a site ID that's gone from the account, switch to the site with the same
gateway_id(or uniquesite_name), or the only site on a single-site account, persist it to.pypowerwall.site, and retry once; with several sites and no match, leave the site unchanged and log an error. Serialized, rate-limited to one attempt per minute. Long-running processes no longer need a restart after a site change. (#382) - fix(tedapi): v1r failover to the WiFi fallback host (
wifi_host) now actually happens when the wired LAN is down. The v1r LAN and WiFi-fallback sessions retry once instead of 2-3 times, so a dead host fails in ~2x timeout instead of holding the per-method API lock long enough for callers' outer timeouts to abandon it (later polls then only sawTimeout waiting for API lockand never reached WiFi). And a start or LAN recovery probe with the LAN down now adopts the DIN over WiFi and serves data there, where it used to return None and never come up; the LAN retry backoff still doubles on each failed probe. Both hosts down still returns None. Basic (WiFi) and bearer TEDAPI sessions are unchanged. Thanks @erikgieseler, requested by @jasonacox-sam (#394). - feat: new
failoveroption onPowerwall,PyPowerwallTEDAPIandTEDAPI(defaultTrue, unchanged behavior — what the proxy and pypowerwall-server use).failover=Falseis strict:connect()tries only the configured mode (no fallback to FleetAPI/Cloud; withretry_modesit retries that mode), and in v1r mode the leader always uses the wired LAN — no switch to the WiFi host, no startup on WiFi, andget_native_api()stays on the gateway host — so a failed request returnsNoneinstead of being rerouted. Followers still usewifi_host, their only route in v1r mode. For scripts that test which modes and transports work. (#395) - fix(tedapi): the v1r LAN recovery probe runs once per window. When the window opened, every concurrent getter (each holds its own per-method API lock) passed the unguarded check and reconnected at once — with the LAN down and a WiFi host up, N LAN logins and N WiFi DIN fetches per probe — and the failure counter could be bumped once per racing thread, pushing the next probe out extra backoff steps. One request now claims the probe while the others keep routing via WiFi; the failover state (including the #394 startup-on-WiFi path) changes only in a few small methods under one lock, never held across a network call; and LAN failures of requests already in flight when the failover tripped no longer escalate it. Backoff schedule unchanged (8 minutes, doubling to ~2 hours). (#395)
- fix(tedapi): a known v1r leader DIN is no longer cleared by a failed reconnect (recovery probe or
connect(force=True);connect()still returnsNone). Clearing it opened a window where concurrent requests saw DINNone— starting a second concurrent reconnect or signing LAN requests withNone— and, when the WiFi host was also briefly unreachable, left every getter running a full reconnect each poll (ignoring the backoff) and returning "Not Connected" instead of serving via WiFi once it answered again. (#395) - fix(tedapi): v1r without a
wifi_hostno longer stops trying the LAN after 3 failures. With nowhere to fail over to, the tripped state only returnedNonewithout trying — for 8 minutes, even after the LAN came back. Every request now tries the LAN, and data resumes on the first one that succeeds. (#395) - fix(tedapi):
connect()is single-flight. Getters under different per-method locks that all found no DIN each ranconnect()at once: in WiFi/basic mode each cleared the DIN and closed the session other threads were mid-request on; in v1r mode each ran a full login + DIN + key probe. A caller that finds a connect in flight now gets the current DIN (Noneif still unknown) instead of starting another. (#395) - fix(tedapi):
get_pw3_vitals()no longer raises. It calls the transport directly, so a gateway timeout, connection reset or malformed payload propagated intovitals(),get_blocks()and/api/meters/aggregates; it now logs and returnsNone, and a transport error on one Powerwall skips only that Powerwall. Its result is now cached for the data-cache window,pwcacheexpire(the cache was read but never written, so one proxy poll re-queried every Powerwall three or more times). (#395) - fix(tedapi):
@uses_api_lockgetters accept positional arguments:get_battery_block("DIN"), as the module docstring documents it, raisedTypeError(multiple values forself_function). Keyword calls are unchanged. (#395) - fix(tedapi): the cache read no longer raises
KeyErrorwhen another thread (config-write invalidation) pops the entry mid-read, and treats an entry stamped in the future (wall clock stepped back) as expired instead of fresh. (#395) - fix(tedapi): a successful WiFi TEDAPI request clears a WiFi cooldown another thread set while it was in flight. (#395)
- fix(v1r): transport read/write methods called without a DIN return their failure value (
None/False) instead of raising from the signing code; the retry after a 401/403 re-login is re-signed (the signature expires 12 s after signing, which the first attempt plus the login can exceed);write_config_v1r()no longer raises internally on a non-filestore reply (MessageEnvelopehas noerrorfield) and reports failure for a filestore reply that isn't an update acknowledgement;get_din()handles a gzip-compressed body (as/tedapi/dinsends on firmware 25.42.2+); concurrent key-auth failures emit theirUserWarningonce. (#395) - docs: README "v1r WiFi Fallback" now documents the leader failover (it described only follower routing) and
failover=False; API.md liststedapi_auth_modeandfailover. (#395) - tests: new
test_failover_option.py,test_v1r_lan_probe.py,test_tedapi_hardening.py,test_v1r_transport_hardening.py, andproxy/tests/test_transport_health.py. (#395)
Contributors
Thanks to the community members who made this release possible:
- @nesys — Tesla tariff and Time-of-Use API support (
get_tariff()/set_tariff()) and the cloud site-replacement recovery (PR #382) - @erikgieseler — reported and diagnosed the v1r WiFi failover that never triggered when the wired LAN was down (issue behind #394)
- @jasonacox-sam — independent hardware validation of the v1r failover, strict-mode
failoveroption, and TEDAPI hardening on a live two-Powerwall-3 fleet (PR #395)