Skip to content

Teslemetry: flat-priced tariff across the whole export window lets Tesla's own TBC defer most of the discharge to the last ~30 minutes #4887

Description

@springfall2008

Describe the bug

teslemetry.py's handoff to Tesla's own Time-Based Control (TBC) engine gives it no reason to export early within a committed discharge window, and in a live capture this let a Powerwall sit almost completely idle for most of a ~2.5 hour export window before dumping most of the discharge in the last ~30 minutes — after Predbat had already correctly and continuously asserted a fully permissive device state the whole time.

Unlike every other supported inverter, Predbat never sends the Powerwall a live "stop now" command — inverter.py::adjust_force_export always writes the hardware discharge-target register as self.reserve_percent (the absolute physical minimum reserve), not the plan's live/shrinking export target. That's correct by design for inverters where Predbat's own 5-minute loop is the real-time enforcement (it flips force_export off itself the instant SoC crosses the live plan target). Tesla Powerwall has no equivalent "stop discharging now" API — teslemetry.py can only set operation_mode + backup_reserve + a synthetic TOU tariff (build_tariff / _carve_interval), and then Tesla's own onboard optimizer decides in real time how to spend the discharge budget across whatever window it was given.

_carve_interval/build_tariff prices the entire committed discharge window as a single flat ON_PEAK band. There is no time-varying incentive inside the window, so exporting at the start of the window and exporting at the end are equally "optimal" from Tesla's own cost-minimising point of view — nothing in the signal discourages its optimizer from procrastinating.

Captured timeline (relative to when Predbat committed the discharge window; all writes below returned HTTP 200 / Operation mode updated):

T+0:00   Discharge window committed (later extended once, still same day).
         Asserted to device: operation_mode=autonomous, backup_reserve=4%
         (the configured hardware minimum), grid export=battery_ok.
T+0:00 → T+2:25
         No further commands sent — correct, nothing Predbat-side had
         changed (dedup on write-on-change). Powerwall battery power
         stayed ~0 kW the entire time, aside from two ~5-minute blips
         of 1.3–1.6 kW roughly a third of the way through.
T+2:30   Powerwall begins discharging at its full ~5 kW rate for the
         first time.
T+3:00   Discharge stops abruptly as the (by-then-extended) window
         closes. SoC had only fallen from ~100% to ~69% — nowhere
         near the 4% floor Predbat had asserted (and left unchanged)
         throughout.

Predbat's own logs/config were correct and stable for the entire window — this isn't a mode-flapping or wrong-command bug like #4165, and optimization_strategy=economics / the day-of-week mapping are already correctly applied (#4600, #4610 are fixed and present). The problem is purely that a uniformly-priced window leaves Tesla's own TBC free to defer almost all of the export to the very end, so a wide, flat-priced window converts into only a fraction of its intended export.

Expected behaviour

The synthetic tariff Predbat pushes for a committed discharge window should give Tesla's own optimizer an actual incentive to act early in the window rather than at its own discretion near the end — e.g. front-load/decline the boost price across the window, or split it into several narrower priced sub-slots — so a wide export opportunity doesn't collapse into a last-minute partial discharge.

Predbat version

v8.54.2

Environment details

  • Tesla Powerwall (13.5 kWh class), Teslemetry/Fleet API integration (inverter_type: TESLA, teslemetry_automatic)
  • Octopus-style time-of-day export tariff with a multi-hour high-price window

Log file & apps.yaml

Not attached to keep this report anonymised — I can provide a sanitised log excerpt covering the window above on request. Happy to point at specific line numbers in teslemetry.py/execute.py/inverter.py if useful for triage.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions