Skip to content

Commit 857abaf

Browse files
committed
feat: add power-reset turn_off for E-heater assist and fix sterilize profile mapping
1 parent bc4cae3 commit 857abaf

6 files changed

Lines changed: 120 additions & 11 deletions

File tree

custom_components/midea_heatpump_hws/binary_sensor.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
_LOGGER = logging.getLogger(__name__)
2323

2424
# Register 109 values that indicate an active sanitize cycle
25-
_SANITIZE_ACTIVE_VALUES = {32, 33}
25+
_SANITIZE_ACTIVE_VALUES = {32}
26+
_SANITIZE_SCHEDULED_VALUES = {33}
2627

2728

2829
async def async_setup_entry(
@@ -43,15 +44,19 @@ async def async_setup_entry(
4344
coordinator=coordinator,
4445
config=config,
4546
data_key="heater_assist_raw",
46-
name=f"Heater Assist{host_suffix}",
47+
name=f"Electric Booster Active{host_suffix}",
4748
register=config[CONF_HEATER_ASSIST_REGISTER],
4849
device_class=BinarySensorDeviceClass.RUNNING,
49-
is_on_fn=lambda v: v != 0,
50+
# R108 bit 3 (value 8) is set when the electric element relay is energised.
51+
# Values 0 and 2 represent idle/standby states; 13 = compressor+fan+element
52+
# running together in Hybrid. Using & 8 cleanly isolates the element state.
53+
is_on_fn=lambda v: bool(v & 8),
54+
unique_id_suffix="heater_assist_raw",
5055
)
5156
)
5257

5358
if config.get(CONF_SANITIZE_STATE_REGISTER) is not None:
54-
entities.append(
59+
entities.extend([
5560
MideaBinarySensor(
5661
coordinator=coordinator,
5762
config=config,
@@ -60,8 +65,19 @@ async def async_setup_entry(
6065
register=config[CONF_SANITIZE_STATE_REGISTER],
6166
device_class=BinarySensorDeviceClass.RUNNING,
6267
is_on_fn=lambda v: v in _SANITIZE_ACTIVE_VALUES,
68+
unique_id_suffix="sanitize_state_raw",
69+
),
70+
MideaBinarySensor(
71+
coordinator=coordinator,
72+
config=config,
73+
data_key="sanitize_state_raw",
74+
name=f"Sanitize Cycle Scheduled{host_suffix}",
75+
register=config[CONF_SANITIZE_STATE_REGISTER],
76+
device_class=BinarySensorDeviceClass.RUNNING,
77+
is_on_fn=lambda v: v in _SANITIZE_SCHEDULED_VALUES,
78+
unique_id_suffix="sanitize_scheduled",
6379
)
64-
)
80+
])
6581

6682
async_add_entities(entities)
6783

@@ -78,6 +94,7 @@ def __init__(
7894
register: int,
7995
device_class: BinarySensorDeviceClass,
8096
is_on_fn,
97+
unique_id_suffix: str | None = None,
8198
) -> None:
8299
"""Initialize the binary sensor."""
83100
super().__init__(coordinator)
@@ -87,7 +104,8 @@ def __init__(
87104
self._is_on_fn = is_on_fn
88105

89106
self._attr_name = name
90-
self._attr_unique_id = f"midea_{config['host']}_{config[CONF_MODBUS_UNIT]}_{data_key}"
107+
suffix = unique_id_suffix or data_key
108+
self._attr_unique_id = f"midea_{config['host']}_{config[CONF_MODBUS_UNIT]}_{suffix}"
91109
self._attr_device_class = device_class
92110

93111
@property

custom_components/midea_heatpump_hws/const.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,5 @@
7373
CONF_SUCTION_TEMP_REGISTER = "suction_temp_register"
7474
CONF_ENABLE_ADDITIONAL_SENSORS = "enable_additional_sensors"
7575
CONF_HEATER_ASSIST_REGISTER = "heater_assist_register"
76-
CONF_SANITIZE_STATE_REGISTER = "sanitize_state_register"
76+
CONF_SANITIZE_STATE_REGISTER = "sanitize_state_register"
77+
CONF_HEATER_ASSIST_TRIGGER_REGISTER = "heater_assist_trigger_register"

custom_components/midea_heatpump_hws/coordinator.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
CONF_SUCTION_TEMP_REGISTER,
3838
CONF_HEATER_ASSIST_REGISTER,
3939
CONF_SANITIZE_STATE_REGISTER,
40+
CONF_HEATER_ASSIST_TRIGGER_REGISTER,
4041
)
4142

4243
_LOGGER = logging.getLogger(__name__)
@@ -106,6 +107,7 @@ def __init__(
106107
# Diagnostic state registers (raw integer, no scaling)
107108
self.heater_assist_register = config.get(CONF_HEATER_ASSIST_REGISTER)
108109
self.sanitize_state_register = config.get(CONF_SANITIZE_STATE_REGISTER)
110+
self.heater_assist_trigger_register = config.get(CONF_HEATER_ASSIST_TRIGGER_REGISTER)
109111

110112
self._client: AsyncModbusTcpClient | None = None
111113
self._lock = asyncio.Lock()
@@ -261,6 +263,7 @@ async def _async_update_data(self) -> dict[str, Any]:
261263
for reg_name, register in [
262264
("heater_assist_raw", self.heater_assist_register),
263265
("sanitize_state_raw", self.sanitize_state_register),
266+
("heater_assist_trigger_raw", self.heater_assist_trigger_register),
264267
]:
265268
if register is None:
266269
continue
@@ -390,6 +393,18 @@ async def _process_pending_writes(self) -> None:
390393
else:
391394
_LOGGER.debug("Successfully wrote sterilize mode = %s", params)
392395

396+
elif operation == "heater_assist_trigger":
397+
if self.heater_assist_trigger_register is not None:
398+
result = await self._client.write_register(
399+
address=self.heater_assist_trigger_register,
400+
value=params,
401+
device_id=self.modbus_unit
402+
)
403+
if result.isError():
404+
_LOGGER.error("Failed to write heater assist trigger: %s", result)
405+
else:
406+
_LOGGER.debug("Successfully wrote heater assist trigger = %s", params)
407+
393408
elif operation == "operation_mode":
394409
# Handle water heater operation mode changes (now lowercase!)
395410
if params == "off": # lowercase!
@@ -488,6 +503,17 @@ async def write_register(self, operation: str, value: Any) -> bool:
488503
self.data["sterilize_mode"] = bool(result.registers[0])
489504
_LOGGER.debug("Read back sterilize mode: %s", self.data["sterilize_mode"])
490505

506+
elif operation == "heater_assist_trigger":
507+
if self.heater_assist_trigger_register is not None:
508+
result = await self._client.read_holding_registers(
509+
address=self.heater_assist_trigger_register,
510+
count=1,
511+
device_id=self.modbus_unit
512+
)
513+
if not result.isError() and result.registers:
514+
self.data["heater_assist_trigger_raw"] = result.registers[0]
515+
_LOGGER.debug("Read back heater assist trigger: %s", self.data["heater_assist_trigger_raw"])
516+
491517
elif operation == "operation_mode":
492518
# After setting operation mode, read both power and mode
493519
power_result = await self._client.read_holding_registers(

custom_components/midea_heatpump_hws/models/defaults/ecospring_hp300.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"exhaust_temp": 105,
2727
"suction_temp": 106,
2828
"heater_assist_register": 108,
29-
"sanitize_state_register": 109
29+
"sanitize_state_register": 109,
30+
"heater_assist_trigger_register": 4
3031
},
3132

3233
"mode_values": {

custom_components/midea_heatpump_hws/profile_manager.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def save_profile(self, name: str, config: dict[str, Any], model: str = "Custom")
109109
"suction_temp": config.get("suction_temp_register", 106),
110110
**({} if config.get("heater_assist_register") is None else {"heater_assist_register": config["heater_assist_register"]}),
111111
**({} if config.get("sanitize_state_register") is None else {"sanitize_state_register": config["sanitize_state_register"]}),
112+
**({} if config.get("heater_assist_trigger_register") is None else {"heater_assist_trigger_register": config["heater_assist_trigger_register"]}),
112113
},
113114

114115
"mode_values": {
@@ -235,6 +236,7 @@ def apply_profile_to_config(self, profile_data: dict[str, Any], user_input: dict
235236
config["mode_register"] = registers.get("mode", 1)
236237
config["temp_register"] = registers.get("current_temp", 102)
237238
config["target_temp_register"] = registers.get("target_temp", 2)
239+
config["sterilize_register"] = registers.get("sterilize", 3)
238240
config["tank_top_temp_register"] = registers.get("tank_top_temp", 101)
239241
config["tank_bottom_temp_register"] = registers.get("tank_bottom_temp", 102)
240242
config["condensor_temp_register"] = registers.get("condensor_temp", 103)
@@ -246,6 +248,8 @@ def apply_profile_to_config(self, profile_data: dict[str, Any], user_input: dict
246248
config["heater_assist_register"] = registers["heater_assist_register"]
247249
if "sanitize_state_register" in registers:
248250
config["sanitize_state_register"] = registers["sanitize_state_register"]
251+
if "heater_assist_trigger_register" in registers:
252+
config["heater_assist_trigger_register"] = registers["heater_assist_trigger_register"]
249253

250254
# Apply mode values
251255
mode_values = profile_data.get("mode_values", {})

custom_components/midea_heatpump_hws/switch.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from homeassistant.helpers.entity_platform import AddEntitiesCallback
99
from homeassistant.helpers.update_coordinator import CoordinatorEntity
1010

11-
from .const import DOMAIN, CONF_MODBUS_UNIT
11+
from .const import DOMAIN, CONF_MODBUS_UNIT, CONF_HEATER_ASSIST_TRIGGER_REGISTER
1212
from .coordinator import MideaModbusCoordinator
1313

1414
_LOGGER = logging.getLogger(__name__)
@@ -30,9 +30,17 @@ async def async_setup_entry(
3030
entities = [MideaPowerSwitch(coordinator, config, host_suffix)]
3131

3232
# Add sterilize switch if register is configured
33-
if config.get("sterilize_register") is not None:
33+
if coordinator.sterilize_register is not None:
3434
entities.append(MideaSterilizeSwitch(coordinator, config, host_suffix))
3535

36+
# Add manual heater assist switch only if the profile exposes both the write
37+
# trigger and the R108 diagnostic register it is functionally paired with.
38+
if (
39+
coordinator.heater_assist_trigger_register is not None
40+
and coordinator.heater_assist_register is not None
41+
):
42+
entities.append(MideaHeaterAssistSwitch(coordinator, config, host_suffix))
43+
3644
async_add_entities(entities)
3745

3846

@@ -141,4 +149,55 @@ async def async_turn_off(self, **kwargs: Any) -> None:
141149
@callback
142150
def _handle_coordinator_update(self) -> None:
143151
"""Handle updated data from the coordinator."""
144-
self.async_write_ha_state()
152+
self.async_write_ha_state()
153+
154+
155+
class MideaHeaterAssistSwitch(CoordinatorEntity, SwitchEntity):
156+
"""Representation of the manual E-Heater assist trigger switch."""
157+
158+
def __init__(
159+
self,
160+
coordinator: MideaModbusCoordinator,
161+
config: dict,
162+
host_suffix: str
163+
):
164+
"""Initialize the heater assist switch."""
165+
super().__init__(coordinator)
166+
self._config = config
167+
168+
# Entity attributes - include host for uniqueness
169+
self._attr_name = f"Manual Heater Assist{host_suffix}"
170+
self._attr_unique_id = f"midea_{config['host']}_{config[CONF_MODBUS_UNIT]}_heater_assist_trigger"
171+
self._attr_icon = "mdi:fire"
172+
173+
@property
174+
def device_info(self):
175+
"""Return device info to link this switch to the main device."""
176+
return {
177+
"identifiers": {(DOMAIN, f"{self._config['host']}_{self._config[CONF_MODBUS_UNIT]}")},
178+
"name": f"Midea Heat Pump ({self._config['host']})",
179+
"manufacturer": "Midea",
180+
"model": "Heat Pump Water Heater",
181+
}
182+
183+
@property
184+
def is_on(self) -> bool:
185+
"""Return true if e-heater is triggered."""
186+
if self.coordinator.data:
187+
return self.coordinator.data.get("heater_assist_trigger_raw") == 1
188+
return False
189+
190+
@property
191+
def available(self) -> bool:
192+
"""Return if entity is available."""
193+
return self.coordinator.last_update_success
194+
195+
async def async_turn_on(self, **kwargs: Any) -> None:
196+
"""Trigger manual heater assist."""
197+
await self.coordinator.write_register("heater_assist_trigger", 1)
198+
199+
async def async_turn_off(self, **kwargs: Any) -> None:
200+
"""Turn off E-heater assist by resetting power state to clear R4 trigger."""
201+
_LOGGER.info("Resetting power state to turn off manual E-heater assist")
202+
await self.coordinator.write_register("power_state", False)
203+
await self.coordinator.write_register("power_state", True)

0 commit comments

Comments
 (0)