mirror of
https://github.com/home-assistant/core.git
synced 2025-04-19 14:57:52 +00:00
Add modbus climate hvac action (#139864)
* Added the hvac action attribute for modbus climate entities. * Fixed issue in hvac action unit test, was incorrectly referencing the hvac mode attribute. * Fixed the modbus climate test for hvac action, it now correctly checks that hvac actions in the config match HVACActions. * Made changes recommended by @crug80 to remove dead code and to add ability to use input or holding register for hvac action. * Moved action test case in test_climate.py * Updated comment for `test_service_climate_action_update` * Fixed ruff formatting error. * Addressed request to update labels from `state_*` to `action_*`
This commit is contained in:
parent
ad126a745a
commit
95afebceb4
@ -79,6 +79,16 @@ from .const import (
|
||||
CONF_FAN_MODE_TOP,
|
||||
CONF_FAN_MODE_VALUES,
|
||||
CONF_FANS,
|
||||
CONF_HVAC_ACTION_COOLING,
|
||||
CONF_HVAC_ACTION_DEFROSTING,
|
||||
CONF_HVAC_ACTION_DRYING,
|
||||
CONF_HVAC_ACTION_FAN,
|
||||
CONF_HVAC_ACTION_HEATING,
|
||||
CONF_HVAC_ACTION_IDLE,
|
||||
CONF_HVAC_ACTION_OFF,
|
||||
CONF_HVAC_ACTION_PREHEATING,
|
||||
CONF_HVAC_ACTION_REGISTER,
|
||||
CONF_HVAC_ACTION_VALUES,
|
||||
CONF_HVAC_MODE_AUTO,
|
||||
CONF_HVAC_MODE_COOL,
|
||||
CONF_HVAC_MODE_DRY,
|
||||
@ -297,6 +307,45 @@ CLIMATE_SCHEMA = vol.All(
|
||||
vol.Optional(CONF_WRITE_REGISTERS, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
vol.Optional(CONF_HVAC_ACTION_REGISTER): vol.Maybe(
|
||||
{
|
||||
CONF_ADDRESS: cv.positive_int,
|
||||
CONF_HVAC_ACTION_VALUES: {
|
||||
vol.Optional(CONF_HVAC_ACTION_COOLING): vol.Any(
|
||||
cv.positive_int, [cv.positive_int]
|
||||
),
|
||||
vol.Optional(CONF_HVAC_ACTION_DEFROSTING): vol.Any(
|
||||
cv.positive_int, [cv.positive_int]
|
||||
),
|
||||
vol.Optional(CONF_HVAC_ACTION_DRYING): vol.Any(
|
||||
cv.positive_int, [cv.positive_int]
|
||||
),
|
||||
vol.Optional(CONF_HVAC_ACTION_FAN): vol.Any(
|
||||
cv.positive_int, [cv.positive_int]
|
||||
),
|
||||
vol.Optional(CONF_HVAC_ACTION_HEATING): vol.Any(
|
||||
cv.positive_int, [cv.positive_int]
|
||||
),
|
||||
vol.Optional(CONF_HVAC_ACTION_IDLE): vol.Any(
|
||||
cv.positive_int, [cv.positive_int]
|
||||
),
|
||||
vol.Optional(CONF_HVAC_ACTION_OFF): vol.Any(
|
||||
cv.positive_int, [cv.positive_int]
|
||||
),
|
||||
vol.Optional(CONF_HVAC_ACTION_PREHEATING): vol.Any(
|
||||
cv.positive_int, [cv.positive_int]
|
||||
),
|
||||
},
|
||||
vol.Optional(
|
||||
CONF_INPUT_TYPE, default=CALL_TYPE_REGISTER_HOLDING
|
||||
): vol.In(
|
||||
[
|
||||
CALL_TYPE_REGISTER_HOLDING,
|
||||
CALL_TYPE_REGISTER_INPUT,
|
||||
]
|
||||
),
|
||||
}
|
||||
),
|
||||
vol.Optional(CONF_FAN_MODE_REGISTER): vol.Maybe(
|
||||
vol.All(
|
||||
{
|
||||
|
@ -24,6 +24,7 @@ from homeassistant.components.climate import (
|
||||
SWING_VERTICAL,
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACAction,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
@ -61,6 +62,16 @@ from .const import (
|
||||
CONF_FAN_MODE_REGISTER,
|
||||
CONF_FAN_MODE_TOP,
|
||||
CONF_FAN_MODE_VALUES,
|
||||
CONF_HVAC_ACTION_COOLING,
|
||||
CONF_HVAC_ACTION_DEFROSTING,
|
||||
CONF_HVAC_ACTION_DRYING,
|
||||
CONF_HVAC_ACTION_FAN,
|
||||
CONF_HVAC_ACTION_HEATING,
|
||||
CONF_HVAC_ACTION_IDLE,
|
||||
CONF_HVAC_ACTION_OFF,
|
||||
CONF_HVAC_ACTION_PREHEATING,
|
||||
CONF_HVAC_ACTION_REGISTER,
|
||||
CONF_HVAC_ACTION_VALUES,
|
||||
CONF_HVAC_MODE_AUTO,
|
||||
CONF_HVAC_MODE_COOL,
|
||||
CONF_HVAC_MODE_DRY,
|
||||
@ -74,6 +85,7 @@ from .const import (
|
||||
CONF_HVAC_ON_VALUE,
|
||||
CONF_HVAC_ONOFF_COIL,
|
||||
CONF_HVAC_ONOFF_REGISTER,
|
||||
CONF_INPUT_TYPE,
|
||||
CONF_MAX_TEMP,
|
||||
CONF_MIN_TEMP,
|
||||
CONF_STEP,
|
||||
@ -188,6 +200,34 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
||||
self._attr_hvac_mode = HVACMode.AUTO
|
||||
self._attr_hvac_modes = [HVACMode.AUTO]
|
||||
|
||||
if CONF_HVAC_ACTION_REGISTER in config:
|
||||
action_config = config[CONF_HVAC_ACTION_REGISTER]
|
||||
self._hvac_action_register = action_config[CONF_ADDRESS]
|
||||
self._hvac_action_type = action_config[CONF_INPUT_TYPE]
|
||||
|
||||
self._attr_hvac_action = None
|
||||
self._hvac_action_mapping: list[tuple[int, HVACAction]] = []
|
||||
action_value_config = action_config[CONF_HVAC_ACTION_VALUES]
|
||||
|
||||
for hvac_action_kw, hvac_action in (
|
||||
(CONF_HVAC_ACTION_COOLING, HVACAction.COOLING),
|
||||
(CONF_HVAC_ACTION_DEFROSTING, HVACAction.DEFROSTING),
|
||||
(CONF_HVAC_ACTION_DRYING, HVACAction.DRYING),
|
||||
(CONF_HVAC_ACTION_FAN, HVACAction.FAN),
|
||||
(CONF_HVAC_ACTION_HEATING, HVACAction.HEATING),
|
||||
(CONF_HVAC_ACTION_IDLE, HVACAction.IDLE),
|
||||
(CONF_HVAC_ACTION_OFF, HVACAction.OFF),
|
||||
(CONF_HVAC_ACTION_PREHEATING, HVACAction.PREHEATING),
|
||||
):
|
||||
if hvac_action_kw in action_value_config:
|
||||
values = action_value_config[hvac_action_kw]
|
||||
if not isinstance(values, list):
|
||||
values = [values]
|
||||
for value in values:
|
||||
self._hvac_action_mapping.append((value, hvac_action))
|
||||
else:
|
||||
self._hvac_action_register = None
|
||||
|
||||
if CONF_FAN_MODE_REGISTER in config:
|
||||
self._attr_supported_features = (
|
||||
self._attr_supported_features | ClimateEntityFeature.FAN_MODE
|
||||
@ -216,7 +256,6 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
||||
self._fan_mode_mapping_from_modbus[value] = fan_mode
|
||||
self._fan_mode_mapping_to_modbus[fan_mode] = value
|
||||
self._attr_fan_modes.append(fan_mode)
|
||||
|
||||
else:
|
||||
# No FAN modes defined
|
||||
self._fan_mode_register = None
|
||||
@ -457,6 +496,20 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
||||
self._attr_hvac_mode = mode
|
||||
break
|
||||
|
||||
# Read the HVAC action register if defined
|
||||
if self._hvac_action_register is not None:
|
||||
hvac_action = await self._async_read_register(
|
||||
self._hvac_action_type, self._hvac_action_register, raw=True
|
||||
)
|
||||
|
||||
# Translate the value received
|
||||
if hvac_action is not None:
|
||||
self._attr_hvac_action = None
|
||||
for value, action in self._hvac_action_mapping:
|
||||
if hvac_action == value:
|
||||
self._attr_hvac_action = action
|
||||
break
|
||||
|
||||
# Read the Fan mode register if defined
|
||||
if self._fan_mode_register is not None:
|
||||
fan_mode = await self._async_read_register(
|
||||
|
@ -63,6 +63,16 @@ CONF_HVAC_ONOFF_REGISTER = "hvac_onoff_register"
|
||||
CONF_HVAC_ON_VALUE = "hvac_on_value"
|
||||
CONF_HVAC_OFF_VALUE = "hvac_off_value"
|
||||
CONF_HVAC_ONOFF_COIL = "hvac_onoff_coil"
|
||||
CONF_HVAC_ACTION_REGISTER = "hvac_action_register"
|
||||
CONF_HVAC_ACTION_COOLING = "action_cooling"
|
||||
CONF_HVAC_ACTION_DEFROSTING = "action_defrosting"
|
||||
CONF_HVAC_ACTION_DRYING = "action_drying"
|
||||
CONF_HVAC_ACTION_FAN = "action_fan"
|
||||
CONF_HVAC_ACTION_HEATING = "action_heating"
|
||||
CONF_HVAC_ACTION_IDLE = "action_idle"
|
||||
CONF_HVAC_ACTION_OFF = "action_off"
|
||||
CONF_HVAC_ACTION_PREHEATING = "action_preheating"
|
||||
CONF_HVAC_ACTION_VALUES = "values"
|
||||
CONF_HVAC_MODE_OFF = "state_off"
|
||||
CONF_HVAC_MODE_HEAT = "state_heat"
|
||||
CONF_HVAC_MODE_COOL = "state_cool"
|
||||
|
@ -5,6 +5,7 @@ import pytest
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_FAN_MODE,
|
||||
ATTR_FAN_MODES,
|
||||
ATTR_HVAC_ACTION,
|
||||
ATTR_HVAC_MODE,
|
||||
ATTR_HVAC_MODES,
|
||||
ATTR_SWING_MODE,
|
||||
@ -31,6 +32,7 @@ from homeassistant.components.climate import (
|
||||
SWING_OFF,
|
||||
SWING_ON,
|
||||
SWING_VERTICAL,
|
||||
HVACAction,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY
|
||||
@ -47,6 +49,16 @@ from homeassistant.components.modbus.const import (
|
||||
CONF_FAN_MODE_REGISTER,
|
||||
CONF_FAN_MODE_TOP,
|
||||
CONF_FAN_MODE_VALUES,
|
||||
CONF_HVAC_ACTION_COOLING,
|
||||
CONF_HVAC_ACTION_DEFROSTING,
|
||||
CONF_HVAC_ACTION_DRYING,
|
||||
CONF_HVAC_ACTION_FAN,
|
||||
CONF_HVAC_ACTION_HEATING,
|
||||
CONF_HVAC_ACTION_IDLE,
|
||||
CONF_HVAC_ACTION_OFF,
|
||||
CONF_HVAC_ACTION_PREHEATING,
|
||||
CONF_HVAC_ACTION_REGISTER,
|
||||
CONF_HVAC_ACTION_VALUES,
|
||||
CONF_HVAC_MODE_AUTO,
|
||||
CONF_HVAC_MODE_COOL,
|
||||
CONF_HVAC_MODE_DRY,
|
||||
@ -224,6 +236,43 @@ ENTITY_ID = f"{CLIMATE_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_")
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
CONF_CLIMATES: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_TARGET_TEMP: 117,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 10,
|
||||
CONF_HVAC_ONOFF_REGISTER: 12,
|
||||
CONF_HVAC_MODE_REGISTER: {
|
||||
CONF_ADDRESS: 11,
|
||||
CONF_WRITE_REGISTERS: True,
|
||||
CONF_HVAC_MODE_VALUES: {
|
||||
CONF_HVAC_MODE_OFF: 0,
|
||||
CONF_HVAC_MODE_HEAT: 1,
|
||||
CONF_HVAC_MODE_COOL: 2,
|
||||
CONF_HVAC_MODE_HEAT_COOL: 3,
|
||||
CONF_HVAC_MODE_DRY: 4,
|
||||
CONF_HVAC_MODE_FAN_ONLY: 5,
|
||||
CONF_HVAC_MODE_AUTO: 6,
|
||||
},
|
||||
},
|
||||
CONF_HVAC_ACTION_REGISTER: {
|
||||
CONF_ADDRESS: 14,
|
||||
CONF_HVAC_ACTION_VALUES: {
|
||||
CONF_HVAC_ACTION_COOLING: 0,
|
||||
CONF_HVAC_ACTION_DEFROSTING: 1,
|
||||
CONF_HVAC_ACTION_DRYING: 2,
|
||||
CONF_HVAC_ACTION_FAN: 3,
|
||||
CONF_HVAC_ACTION_HEATING: 4,
|
||||
CONF_HVAC_ACTION_IDLE: 5,
|
||||
CONF_HVAC_ACTION_OFF: 6,
|
||||
CONF_HVAC_ACTION_PREHEATING: 7,
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_config_climate(hass: HomeAssistant, mock_modbus) -> None:
|
||||
@ -745,6 +794,95 @@ async def test_hvac_onoff_coil_update(
|
||||
assert state.state == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("do_config", "result", "register_words"),
|
||||
[
|
||||
(
|
||||
{
|
||||
CONF_CLIMATES: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_TARGET_TEMP: 116,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 10,
|
||||
CONF_SCAN_INTERVAL: 0,
|
||||
CONF_DATA_TYPE: DataType.INT32,
|
||||
CONF_HVAC_ACTION_REGISTER: {
|
||||
CONF_ADDRESS: 118,
|
||||
CONF_HVAC_ACTION_VALUES: {
|
||||
CONF_HVAC_ACTION_IDLE: 0,
|
||||
CONF_HVAC_ACTION_HEATING: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
HVACAction.HEATING,
|
||||
[0x01],
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_CLIMATES: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_TARGET_TEMP: 116,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 10,
|
||||
CONF_SCAN_INTERVAL: 0,
|
||||
CONF_DATA_TYPE: DataType.INT32,
|
||||
CONF_HVAC_ACTION_REGISTER: {
|
||||
CONF_ADDRESS: 118,
|
||||
CONF_HVAC_ACTION_VALUES: {
|
||||
CONF_HVAC_ACTION_COOLING: 0,
|
||||
CONF_HVAC_ACTION_HEATING: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
HVACAction.COOLING,
|
||||
[0x00],
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_CLIMATES: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_TARGET_TEMP: 116,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 10,
|
||||
CONF_SCAN_INTERVAL: 0,
|
||||
CONF_DATA_TYPE: DataType.INT32,
|
||||
CONF_HVAC_ACTION_REGISTER: {
|
||||
CONF_ADDRESS: 118,
|
||||
CONF_HVAC_ACTION_VALUES: {
|
||||
CONF_HVAC_ACTION_OFF: 0,
|
||||
CONF_HVAC_ACTION_DRYING: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
HVACAction.DRYING,
|
||||
[0x01],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_service_climate_action_update(
|
||||
hass: HomeAssistant, mock_modbus_ha, result, register_words
|
||||
) -> None:
|
||||
"""Test HVAC action updates."""
|
||||
mock_modbus_ha.read_holding_registers.return_value = ReadResult(register_words)
|
||||
await hass.services.async_call(
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(ENTITY_ID).attributes[ATTR_HVAC_ACTION] == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("do_config", "result", "register_words"),
|
||||
[
|
||||
|
Loading…
x
Reference in New Issue
Block a user