Add TeslemetryAPI component — Tesla Powerwall control via Teslemetry/Fleet API#4177
Open
mgazza wants to merge 9 commits into
Open
Add TeslemetryAPI component — Tesla Powerwall control via Teslemetry/Fleet API#4177mgazza wants to merge 9 commits into
mgazza wants to merge 9 commits into
Conversation
Implement control path for Tesla Powerwall integration: - Add site_info_done latch to ensure soc_max publishes eventually - Register virtual control entities: operation_mode, backup_reserve, allow_charging_from_grid, allow_export, tariff_mode - Implement command handlers: set_operation_mode, set_backup_reserve, set_grid_charging, set_export_rule - Add set_tariff stub (completed in Task 6) - Implement event handler overrides: select_event, number_event, switch_event - Add 6 control tests + 1 latch test (16 passing tests total) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement Task 6 of Tesla Powerwall integration: tariff builder, export-now trick, and boot reconciliation. - Add EXPORT_SELL_RATE constant and current_rates() to read from base.rate_import/export - Implement build_tariff(mode, now) with export_now (current 30-min window ON_PEAK) and normal (flat) modes, returning tariff_content_v2 dict - Replace set_tariff() stub with real implementation POSTing /time_of_use_settings - Add reconcile_on_start() to restore safe state if previous run died mid-export - Wire reconcile_on_start() into run() after site_info latch All 5 new tests pass (build tariff, set tariff, reconcile); regression on ge_cloud also passes (66 tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address review findings on the export tariff-trick: - CRITICAL: export_now windows crossing midnight (e.g. 23:30-00:30) produced overlapping SUPER_OFF_PEAK periods covering the full day. The complement is now a proper circle complement of [start, end): a single wrapped segment when the window crosses midnight, two segments otherwise. - IMPORTANT: ON_PEAK sell is now clamped to max(EXPORT_SELL_RATE, 2x live export rate) so the trick cannot silently invert when the live export rate exceeds 0.50. - MINOR: buy-side ON_PEAK mirrors the high sell rate to discourage grid-charging during the export window. - Tests: midnight-wrap (23:40) and exact-midnight (23:10) cases with a partition-of-day assertion (no overlap, no gap), sell-clamp with a live 0.60 export rate, and a reconcile_on_start partial-failure pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Registers TeslemetryAPI in COMPONENT_LIST (phase 1, event_filter predbat_teslemetry_) and adds teslemetry_key/teslemetry_site_id/ teslemetry_base_url to APPS_SCHEMA, placed alongside the fox_key inverter block (config.py has no deye_key entry to anchor on).
…ation
reconcile_on_start previously read the local tariff_mode entity, which
register_control_entities() unconditionally reseeds to "normal" on every
boot before run(first=True) fires - making the export_now recovery path
dead code (a pod restart mid-export could never self-heal). It now reads
the Powerwall's actual tariff via GET tariff_rate and walks the response
for a "code" matching build_tariff("export_now"), respecting read-only
mode. _command also treated any parsed 2xx body as success; it now fails
on an application-level "error" key or response.code >= 400. fetch_site_info
seeds the operation_mode/backup_reserve entity states from device data
instead of leaving them at hardcoded defaults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_is_read_only() read base.set_read_only directly, but the PredBat
constructor defaults that attribute to True (predbat.py:504) and it is
only refreshed from config in the fetch cycle (fetch.py:2401), which
runs AFTER phase-1 components start - so at reconcile_on_start time the
attribute was always True and the recovery write was unreachable again,
with a misleading read-only log. load_user_config(load_config=True)
runs before phase-1 start (predbat.py:1574 vs 1577), so reading via
get_arg("set_read_only", False) returns the real configured value.
Also split get_current_tariff_code's failure logging: "read failed"
(no response) vs "no tariff code" (response without a code key).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New TeslemetryAPI component (
apps/predbat/teslemetry.py): Tesla Powerwall support over the Teslemetry REST API (which mirrors Tesla Fleet API paths — thebase_urlis configurable so a direct Fleet API connection works with zero component changes). Ports the proventemplates/tesla_powerwall.yamlcontrol semantics (operation mode / backup reserve / grid-charging / export rule / export tariff-trick) from HA service hooks onto component virtual entities, driven by the existing*_servicehook loopback.live_status(120 s),site_info(capacity →soc_max; seeds operation-mode/reserve entity states),calendar_history(daily kWh) → publishespredbat_teslemetry_*sensors. 401/403 latches an auth-failed guard (recovery via live_status probe only); 429 exponential backoff.operation_mode,backup_reserve,allow_charging_from_grid,allow_export,tariff_mode) with write-only-on-success semantics — a failed REST command leaves entity state untouched so therepeat: truehooks re-assert next cycle.tariff_mode=export_nowposts a synthetic ToU tariff (current 30-min window ON_PEAK, sell pricemax(0.50, 2× live export rate)); correct circle-complement off-peak periods including midnight wrap (property-tested: exact partition of the day for all 48 slots).normalrestores a flat tariff from the customer's live rates.GET /tariff_rate) and, if ourPREDBAT-EXPORT-NOWmarker is present (pod died mid-export), restores the normal tariff + disables export — gated on the realset_read_onlyconfig viaget_arg.components.pyentry (event_filter: predbat_teslemetry_) +APPS_SCHEMAkeysteslemetry_key/teslemetry_site_id/teslemetry_base_url.Companion SaaS PR: Predictive-Cloud-Ltd/predbat-saas
feat/tesla-powerwall-teslemetry(onboarding + config generation). Part of Predictive-Cloud-Ltd/predbat-saas#1346.Testing
tests/test_teslemetry.py(registered inunit_test.py): field mapping/kWh conversions, real_requeststatus branching (401 latch, 429 retry+backoff, recovery),run()bool contract, all five command handlers incl. failure paths, tariff builder (incl. midnight-wrap partition invariant and sell clamp), reconcile (marker found / absent / read-failed / read-only / boot-default gating)../run_all --quick: 122 groups pass (1 pre-existing unrelated failuremulti_car_iog_load_slots_regression, verified pre-existing via stash-baseline).Known follow-ups
GET /tariff_rateresponse shape is inferred from docs (defensive code-walker parse; fails safe) — to be validated on live PW3 hardware during the beta pilot.🤖 Generated with Claude Code