Symptom
On a Growatt MID 15KTL3-XH with the wills106/homeassistant-solax-modbus integration, the BESS-planned battery mode never actually takes effect. The schedule shows e.g. EXPORT_ARBITRAGE → grid_first, the BESS log shows Modbus: TOU segment 1 = grid_first (00:00-23:59), but the inverter behaves as Load First — battery discharges at ~200W following house load rather than the planned 15kW push to grid.
Root cause
set_tou_segment_via_entities() in ha_api_controller.py writes the four parts of TOU slot 1 to these entities:
| Slot field |
Entity used |
Outcome |
tou_time_1_enabled |
select.growatt_inverter_time_1_active |
✅ written, _read sensor updates |
tou_time_1_mode |
select.growatt_inverter_time_1_mode |
✅ written, _read sensor updates |
tou_time_1_begin |
select.growatt_inverter_time_1_begin |
❌ silently dropped |
tou_time_1_end |
select.growatt_inverter_time_1_end |
❌ silently dropped |
The select.*_time_N_begin/end entities exist in HA's registry but their state is permanently unavailable with restored: true. The SolaX Modbus integration drops select.select_option calls against these entities — service_call returns success at HA's service layer, but the value never reaches the Modbus register. The subsequent button.press time_1_update then commits whatever begin/end was already in the inverter (in my case 18:45-23:10, presumably a stale value from an earlier session or factory default), with the new mode.
Net effect: slot 1 is active only inside that stale window, and outside that window the inverter falls back to default Load First — regardless of what BESS planned.
Reproduction
- Growatt MID 15KTL3-XH +
wills106/homeassistant-solax-modbus integration
- Let BESS write TOU slot 1 normally (any cycle)
- Inspect:
select.growatt_inverter_time_1_begin.state → unavailable, restored: true
sensor.growatt_inverter_time_1_begin_read.state → some old time (NOT what BESS just wrote)
- Battery behavior mismatches the planned mode outside the stale window
Reloading the integration via homeassistant.reload_config_entry does NOT clear the unavailable state.
The working alternative
The SolaX Modbus integration also exposes a parallel set of entities under the time domain:
time.growatt_time_1_begin state: 18:45:00 (works — valid state, writes accepted)
time.growatt_time_1_end state: 23:10:00 (works — valid state, writes accepted)
Calling time.set_value on these entities:
- Updates the HA state immediately
- Stages the value for the next
button.press time_1_update
- The
_read sensor reflects the new value after the button press
- The inverter actually behaves accordingly
I verified this live: after manually writing time.growatt_time_1_begin = 00:00 and time.growatt_time_1_end = 23:59 then pressing the update button, time_1_begin_read flipped to 00:00, time_1_end_read to 23:59, and battery discharge jumped from 184W → 14876W (Grid First export activated).
Proposed fix (defensive — needs design discussion)
I don't yet know whether:
- The
select.* route is broken on every Growatt model with this integration, or only MID/MOD
- The
time.* entities exist on Growatt MIN / SPH platforms
Naively switching select.select_option → time.set_value for begin/end could break MIN/SPH users if their integration version doesn't expose the time.* entities yet.
Suggestion for a safe PR:
- At controller init, probe whether
time.<prefix>_begin exists and is not unavailable
- If yes → write via
time.set_value; if no → fall back to select.select_option
- Either path: still call the
button.press *_update to commit
Would appreciate input from other users on whether they see this on MIN or SPH Growatt inverters before scoping the PR.
Workaround for affected users right now
In HA Developer Tools → Services:
time.set_value on time.growatt_time_1_begin with time: 00:00:00
time.set_value on time.growatt_time_1_end with time: 23:59:00
button.press on button.growatt_inverter_time_1_update
After this, BESS continues to write mode/active correctly via select.* (those work), the broken begin/end writes are no-ops, and the update-button commits the existing 00:00-23:59 window — self-healing from there.
Environment
- BESS Manager 9.6.2-jvdd.6 (fork of 9.6.2 — relevant TOU code unchanged from upstream)
- Home Assistant Core (recent)
wills106/homeassistant-solax-modbus integration (current release)
- Growatt MID 15KTL3-XH, ~30 kWh battery
Symptom
On a Growatt MID 15KTL3-XH with the
wills106/homeassistant-solax-modbusintegration, the BESS-planned battery mode never actually takes effect. The schedule shows e.g.EXPORT_ARBITRAGE → grid_first, the BESS log showsModbus: TOU segment 1 = grid_first (00:00-23:59), but the inverter behaves as Load First — battery discharges at ~200W following house load rather than the planned 15kW push to grid.Root cause
set_tou_segment_via_entities()inha_api_controller.pywrites the four parts of TOU slot 1 to these entities:tou_time_1_enabledselect.growatt_inverter_time_1_active_readsensor updatestou_time_1_modeselect.growatt_inverter_time_1_mode_readsensor updatestou_time_1_beginselect.growatt_inverter_time_1_begintou_time_1_endselect.growatt_inverter_time_1_endThe
select.*_time_N_begin/endentities exist in HA's registry but their state is permanentlyunavailablewithrestored: true. The SolaX Modbus integration dropsselect.select_optioncalls against these entities —service_callreturns success at HA's service layer, but the value never reaches the Modbus register. The subsequentbutton.press time_1_updatethen commits whatever begin/end was already in the inverter (in my case18:45-23:10, presumably a stale value from an earlier session or factory default), with the new mode.Net effect: slot 1 is active only inside that stale window, and outside that window the inverter falls back to default Load First — regardless of what BESS planned.
Reproduction
wills106/homeassistant-solax-modbusintegrationselect.growatt_inverter_time_1_begin.state→unavailable,restored: truesensor.growatt_inverter_time_1_begin_read.state→ some old time (NOT what BESS just wrote)Reloading the integration via
homeassistant.reload_config_entrydoes NOT clear theunavailablestate.The working alternative
The SolaX Modbus integration also exposes a parallel set of entities under the
timedomain:Calling
time.set_valueon these entities:button.press time_1_update_readsensor reflects the new value after the button pressI verified this live: after manually writing
time.growatt_time_1_begin = 00:00andtime.growatt_time_1_end = 23:59then pressing the update button,time_1_begin_readflipped to00:00,time_1_end_readto23:59, and battery discharge jumped from 184W → 14876W (Grid First export activated).Proposed fix (defensive — needs design discussion)
I don't yet know whether:
select.*route is broken on every Growatt model with this integration, or only MID/MODtime.*entities exist on Growatt MIN / SPH platformsNaively switching
select.select_option→time.set_valuefor begin/end could break MIN/SPH users if their integration version doesn't expose thetime.*entities yet.Suggestion for a safe PR:
time.<prefix>_beginexists and is notunavailabletime.set_value; if no → fall back toselect.select_optionbutton.press *_updateto commitWould appreciate input from other users on whether they see this on MIN or SPH Growatt inverters before scoping the PR.
Workaround for affected users right now
In HA Developer Tools → Services:
time.set_valueontime.growatt_time_1_beginwithtime: 00:00:00time.set_valueontime.growatt_time_1_endwithtime: 23:59:00button.pressonbutton.growatt_inverter_time_1_updateAfter this, BESS continues to write mode/active correctly via
select.*(those work), the broken begin/end writes are no-ops, and the update-button commits the existing 00:00-23:59 window — self-healing from there.Environment
wills106/homeassistant-solax-modbusintegration (current release)