Skip to content

TOU write failures silently marked as applied; dashboard crashes on unavailable battery_soc #365

Description

@johanzander

Summary

Debug bundle bess-debug-2026-07-22-120026.md (reporter restarted the UI and
saw GRID CHARGING / Battery First / Grid Charge ON for the current
period, but the physical inverter's TOU period list had no period covering
that time window — screenshots confirm the mismatch).

Root cause 1: failed TOU hardware writes are silently treated as success

GrowattMinController.write_schedule_to_hardware (core/bess/growatt_min_controller.py)
catches each per-segment hardware write exception internally and only logs
it — it never re-raises. BatterySystemManager._apply_schedule
(core/bess/battery_system_manager.py:2481-2507) already has retry logic
built for exactly this case (self._hardware_write_pending = True on
exception, forcing a retry next cycle), but it never sees the exception
because it was swallowed one layer down. So:

  • 2026-07-22 11:30:13 — write of TOU segment 6 (15:30-15:44) is attempted.
  • 2026-07-22 11:30:55 — fails after 4 retries with 500 Server Error from
    growatt_server/update_time_segment (HA core itself was returning 500s
    intermittently most of the day — external instability, not a BESS Manager
    bug).
  • growatt_min_controller:1316 - FAILED: Failed to update TOU segment is
    logged, but write_schedule_to_hardware returns normally.
  • _apply_schedule's except never fires, _hardware_write_pending is
    incorrectly cleared to False, "Schedule applied successfully" is
    logged.
  • Because the in-memory controller (holding the intended schedule) had
    already been swapped in before the write (by design, so strategic intents
    stay available across a write failure), and schedule-diffing compares
    against this same in-memory intended state rather than a hardware
    readback, the failed write is now indistinguishable from a successful one
    on every later comparison — it's never retried, and the dashboard shows
    the intended schedule as fact.

Root cause 2: dashboard/inverter-status endpoints crash on unavailable battery_soc

Same debug bundle shows repeated Error generating dashboard data: unsupported operand type(s) for /: 'NoneType' and 'float' and Error getting inverter status: ... whenever sensor.battery_soc goes
unavailable (same HA-instability window).

  • backend/api_dataclasses.py:953battery_soe = (battery_soc / 100.0) * battery_capacitybattery_soc comes from
    controller.get_battery_soc() (core/bess/ha_api_controller.py:1129),
    which returns None when the sensor is unavailable.
  • backend/api.py:1543 — same pattern in get_inverter_status.

Both endpoints return an opaque 500 with a TypeError message instead of a
clear "sensor unavailable" error.

Fix

  1. write_schedule_to_hardware: collect per-segment failures and raise once
    after attempting all segments, so the existing retry plumbing in
    _apply_schedule actually engages.
  2. Raise a clear error before the division in both call sites when
    battery_soc is None, instead of an opaque TypeError.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions