From 3b5455bc4960ce60cd958d93a72b4a4939adb2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=BB=D0=BB=D1=8F=20=D0=9F=D1=96=D1=81=D0=BA=D1=83?= =?UTF-8?q?=D1=80=D1=8C=D0=BE=D0=B2?= <74793674+illia-piskurov@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:18:05 +0200 Subject: [PATCH] Add support for specifying hvac_onoff_register value on modbus (#128366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: AbĂ­lio Costa --- homeassistant/components/modbus/__init__.py | 10 +++++ homeassistant/components/modbus/climate.py | 19 +++++++-- homeassistant/components/modbus/const.py | 4 ++ tests/components/modbus/test_climate.py | 45 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index bbd2ba5c02d..a7b32119917 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -89,6 +89,8 @@ from .const import ( CONF_HVAC_MODE_OFF, CONF_HVAC_MODE_REGISTER, CONF_HVAC_MODE_VALUES, + CONF_HVAC_OFF_VALUE, + CONF_HVAC_ON_VALUE, CONF_HVAC_ONOFF_REGISTER, CONF_INPUT_TYPE, CONF_MAX_TEMP, @@ -130,6 +132,8 @@ from .const import ( CONF_WRITE_TYPE, CONF_ZERO_SUPPRESS, DEFAULT_HUB, + DEFAULT_HVAC_OFF_VALUE, + DEFAULT_HVAC_ON_VALUE, DEFAULT_SCAN_INTERVAL, DEFAULT_TEMP_UNIT, MODBUS_DOMAIN as DOMAIN, @@ -256,6 +260,12 @@ CLIMATE_SCHEMA = vol.All( vol.Optional(CONF_STEP, default=0.5): vol.Coerce(float), vol.Optional(CONF_TEMPERATURE_UNIT, default=DEFAULT_TEMP_UNIT): cv.string, vol.Optional(CONF_HVAC_ONOFF_REGISTER): cv.positive_int, + vol.Optional( + CONF_HVAC_ON_VALUE, default=DEFAULT_HVAC_ON_VALUE + ): cv.positive_int, + vol.Optional( + CONF_HVAC_OFF_VALUE, default=DEFAULT_HVAC_OFF_VALUE + ): cv.positive_int, vol.Optional(CONF_WRITE_REGISTERS, default=False): cv.boolean, vol.Optional(CONF_HVAC_MODE_REGISTER): vol.Maybe( { diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 111c0458ef4..ba09bd08377 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -69,6 +69,8 @@ from .const import ( CONF_HVAC_MODE_OFF, CONF_HVAC_MODE_REGISTER, CONF_HVAC_MODE_VALUES, + CONF_HVAC_OFF_VALUE, + CONF_HVAC_ON_VALUE, CONF_HVAC_ONOFF_REGISTER, CONF_MAX_TEMP, CONF_MIN_TEMP, @@ -251,6 +253,8 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity): if CONF_HVAC_ONOFF_REGISTER in config: self._hvac_onoff_register = config[CONF_HVAC_ONOFF_REGISTER] self._hvac_onoff_write_registers = config[CONF_WRITE_REGISTERS] + self._hvac_on_value = config[CONF_HVAC_ON_VALUE] + self._hvac_off_value = config[CONF_HVAC_OFF_VALUE] if HVACMode.OFF not in self._attr_hvac_modes: self._attr_hvac_modes.append(HVACMode.OFF) else: @@ -266,19 +270,26 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity): async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" if self._hvac_onoff_register is not None: - # Turn HVAC Off by writing 0 to the On/Off register, or 1 otherwise. + # Turn HVAC Off by writing self._hvac_off_value to the On/Off + # register, or self._hvac_on_value otherwise. if self._hvac_onoff_write_registers: await self._hub.async_pb_call( self._slave, self._hvac_onoff_register, - [0 if hvac_mode == HVACMode.OFF else 1], + [ + self._hvac_off_value + if hvac_mode == HVACMode.OFF + else self._hvac_on_value + ], CALL_TYPE_WRITE_REGISTERS, ) else: await self._hub.async_pb_call( self._slave, self._hvac_onoff_register, - 0 if hvac_mode == HVACMode.OFF else 1, + self._hvac_off_value + if hvac_mode == HVACMode.OFF + else self._hvac_on_value, CALL_TYPE_WRITE_REGISTER, ) @@ -476,7 +487,7 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity): onoff = await self._async_read_register( CALL_TYPE_REGISTER_HOLDING, self._hvac_onoff_register, raw=True ) - if onoff == 0: + if onoff == self._hvac_off_value: self._attr_hvac_mode = HVACMode.OFF self.async_write_ha_state() diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index 7a1a4121a93..e11e15fff20 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -60,6 +60,8 @@ CONF_FAN_MODE_DIFFUSE = "state_fan_diffuse" CONF_FAN_MODE_VALUES = "values" CONF_HVAC_MODE_REGISTER = "hvac_mode_register" CONF_HVAC_ONOFF_REGISTER = "hvac_onoff_register" +CONF_HVAC_ON_VALUE = "hvac_on_value" +CONF_HVAC_OFF_VALUE = "hvac_off_value" CONF_HVAC_MODE_OFF = "state_off" CONF_HVAC_MODE_HEAT = "state_heat" CONF_HVAC_MODE_COOL = "state_cool" @@ -139,6 +141,8 @@ DEFAULT_SCAN_INTERVAL = 15 # seconds DEFAULT_SLAVE = 1 DEFAULT_STRUCTURE_PREFIX = ">f" DEFAULT_TEMP_UNIT = "C" +DEFAULT_HVAC_ON_VALUE = 1 +DEFAULT_HVAC_OFF_VALUE = 0 MODBUS_DOMAIN = "modbus" ACTIVE_SCAN_INTERVAL = 2 # limit to force an extra update diff --git a/tests/components/modbus/test_climate.py b/tests/components/modbus/test_climate.py index d34846639b5..1520e4478c6 100644 --- a/tests/components/modbus/test_climate.py +++ b/tests/components/modbus/test_climate.py @@ -24,6 +24,8 @@ from homeassistant.components.climate import ( SERVICE_SET_HVAC_MODE, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, @@ -54,6 +56,8 @@ from homeassistant.components.modbus.const import ( CONF_HVAC_MODE_OFF, CONF_HVAC_MODE_REGISTER, CONF_HVAC_MODE_VALUES, + CONF_HVAC_OFF_VALUE, + CONF_HVAC_ON_VALUE, CONF_HVAC_ONOFF_REGISTER, CONF_MAX_TEMP, CONF_MIN_TEMP, @@ -362,6 +366,47 @@ async def test_config_hvac_onoff_register(hass: HomeAssistant, mock_modbus) -> N assert HVACMode.AUTO in state.attributes[ATTR_HVAC_MODES] +@pytest.mark.parametrize( + "do_config", + [ + { + CONF_CLIMATES: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_TARGET_TEMP: 117, + CONF_ADDRESS: 117, + CONF_SLAVE: 10, + CONF_HVAC_ONOFF_REGISTER: 11, + CONF_HVAC_ON_VALUE: 0xAA, + CONF_HVAC_OFF_VALUE: 0xFF, + } + ], + }, + ], +) +async def test_hvac_onoff_values(hass: HomeAssistant, mock_modbus) -> None: + """Run configuration test for On/Off register values.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_ID}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_modbus.write_register.assert_called_with(11, 0xAA, slave=10) + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: ENTITY_ID}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_modbus.write_register.assert_called_with(11, 0xFF, slave=10) + + @pytest.mark.parametrize( "do_config", [