Skip to content

Commit 051cfb6

Browse files
committed
fix(solax-modbus): mirror TOU begin/end writes to time.* entities
Workaround for SolaxModbus + Growatt MID where select.*_time_N_begin/end are permanently unavailable and writes get silently dropped by the integration (issue johanzander#181). The parallel time.* entities accept writes correctly. After each select.select_option for a begin/end field, mirror the same value via time.set_value on the derived time.* entity. The mirror is gated on the time.* entity existing and not being unavailable, so platforms where select.* works are unaffected. Mode and active writes go through select.* unchanged.
1 parent 31c6862 commit 051cfb6

1 file changed

Lines changed: 53 additions & 0 deletions

File tree

core/bess/ha_api_controller.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,16 @@ def set_tou_segment_via_entities(
13471347
option=option,
13481348
)
13491349

1350+
# SolaxModbus + Growatt MID workaround (issue #181):
1351+
# the select.<prefix>_begin/end entities are permanently `unavailable`
1352+
# with restored:true, and the integration silently drops writes to
1353+
# them while still returning success at HA's service layer. The
1354+
# parallel time.<X>_begin/end entities accept writes correctly.
1355+
# Mirror the begin/end write to the time.* entity if it exists;
1356+
# other entity_writes (enabled/mode) go through select.* fine.
1357+
if sensor_key.endswith("_begin") or sensor_key.endswith("_end"):
1358+
self._mirror_tou_time_to_time_entity(entity_id, option, sensor_key)
1359+
13501360
# Press update button to commit the slot to inverter
13511361
update_entity_id = self._get_entity_for_service(f"{prefix}_update")
13521362
self._service_call_with_retry(
@@ -1356,6 +1366,49 @@ def set_tou_segment_via_entities(
13561366
entity_id=update_entity_id,
13571367
)
13581368

1369+
def _mirror_tou_time_to_time_entity(
1370+
self, select_entity_id: str, hhmm_value: str, sensor_key: str
1371+
) -> None:
1372+
"""Mirror a TOU begin/end select-write to the parallel time.* entity.
1373+
1374+
The SolaxModbus integration exposes time slot begin/end as both a
1375+
``select.*_inverter_time_N_begin/end`` (broken on Growatt MID — writes
1376+
silently dropped) and a ``time.*_time_N_begin/end`` (works). Derive
1377+
the time-domain entity_id from the select-domain one and write via
1378+
``time.set_value``. Skip silently if the time entity is missing or
1379+
the call errors — the select.* write is still attempted upstream so
1380+
platforms where it works are unaffected.
1381+
"""
1382+
if not select_entity_id.startswith("select."):
1383+
return
1384+
time_entity_id = select_entity_id.replace("select.", "time.", 1).replace(
1385+
"_inverter_", "_", 1
1386+
)
1387+
# time.set_value expects HH:MM:SS
1388+
time_value = hhmm_value if hhmm_value.count(":") == 2 else f"{hhmm_value}:00"
1389+
try:
1390+
state = self._api_request(
1391+
"get", f"/api/states/{time_entity_id}", category="config"
1392+
)
1393+
except Exception:
1394+
return
1395+
if state is None or state.get("state") in ("unavailable", "unknown", None):
1396+
return
1397+
try:
1398+
self._service_call_with_retry(
1399+
"time",
1400+
"set_value",
1401+
operation=f"TOU {sensor_key} mirror → {time_entity_id}={time_value}",
1402+
entity_id=time_entity_id,
1403+
time=time_value,
1404+
)
1405+
except Exception as e:
1406+
logger.debug(
1407+
"TOU time.set_value mirror failed for %s: %s — relying on select.* path",
1408+
time_entity_id,
1409+
e,
1410+
)
1411+
13591412
def read_tou_segments_from_entities(self) -> list[dict]:
13601413
"""Read all 9 TOU segments from solax_modbus entity states.
13611414

0 commit comments

Comments
 (0)