mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
Add Swing Mode Feature to Modbus integration (#113710)
This commit is contained in:
parent
c3942a7d44
commit
a28731c294
@ -113,6 +113,13 @@ from .const import (
|
|||||||
CONF_SWAP_BYTE,
|
CONF_SWAP_BYTE,
|
||||||
CONF_SWAP_WORD,
|
CONF_SWAP_WORD,
|
||||||
CONF_SWAP_WORD_BYTE,
|
CONF_SWAP_WORD_BYTE,
|
||||||
|
CONF_SWING_MODE_REGISTER,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH,
|
||||||
|
CONF_SWING_MODE_SWING_HORIZ,
|
||||||
|
CONF_SWING_MODE_SWING_OFF,
|
||||||
|
CONF_SWING_MODE_SWING_ON,
|
||||||
|
CONF_SWING_MODE_SWING_VERT,
|
||||||
|
CONF_SWING_MODE_VALUES,
|
||||||
CONF_TARGET_TEMP,
|
CONF_TARGET_TEMP,
|
||||||
CONF_TARGET_TEMP_WRITE_REGISTERS,
|
CONF_TARGET_TEMP_WRITE_REGISTERS,
|
||||||
CONF_VERIFY,
|
CONF_VERIFY,
|
||||||
@ -134,6 +141,7 @@ from .modbus import ModbusHub, async_modbus_setup
|
|||||||
from .validators import (
|
from .validators import (
|
||||||
check_hvac_target_temp_registers,
|
check_hvac_target_temp_registers,
|
||||||
duplicate_fan_mode_validator,
|
duplicate_fan_mode_validator,
|
||||||
|
duplicate_swing_mode_validator,
|
||||||
hvac_fixedsize_reglist_validator,
|
hvac_fixedsize_reglist_validator,
|
||||||
nan_validator,
|
nan_validator,
|
||||||
register_int_list_validator,
|
register_int_list_validator,
|
||||||
@ -296,6 +304,21 @@ CLIMATE_SCHEMA = vol.All(
|
|||||||
duplicate_fan_mode_validator,
|
duplicate_fan_mode_validator,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
vol.Optional(CONF_SWING_MODE_REGISTER): vol.Maybe(
|
||||||
|
vol.All(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_ADDRESS): register_int_list_validator,
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
vol.Optional(CONF_SWING_MODE_SWING_ON): cv.positive_int,
|
||||||
|
vol.Optional(CONF_SWING_MODE_SWING_OFF): cv.positive_int,
|
||||||
|
vol.Optional(CONF_SWING_MODE_SWING_HORIZ): cv.positive_int,
|
||||||
|
vol.Optional(CONF_SWING_MODE_SWING_VERT): cv.positive_int,
|
||||||
|
vol.Optional(CONF_SWING_MODE_SWING_BOTH): cv.positive_int,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
duplicate_swing_mode_validator,
|
||||||
|
)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
check_hvac_target_temp_registers,
|
check_hvac_target_temp_registers,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
@ -17,6 +18,11 @@ from homeassistant.components.climate import (
|
|||||||
FAN_OFF,
|
FAN_OFF,
|
||||||
FAN_ON,
|
FAN_ON,
|
||||||
FAN_TOP,
|
FAN_TOP,
|
||||||
|
SWING_BOTH,
|
||||||
|
SWING_HORIZONTAL,
|
||||||
|
SWING_OFF,
|
||||||
|
SWING_ON,
|
||||||
|
SWING_VERTICAL,
|
||||||
ClimateEntity,
|
ClimateEntity,
|
||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
HVACMode,
|
HVACMode,
|
||||||
@ -28,6 +34,7 @@ from homeassistant.const import (
|
|||||||
CONF_TEMPERATURE_UNIT,
|
CONF_TEMPERATURE_UNIT,
|
||||||
PRECISION_TENTHS,
|
PRECISION_TENTHS,
|
||||||
PRECISION_WHOLE,
|
PRECISION_WHOLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -67,6 +74,13 @@ from .const import (
|
|||||||
CONF_MAX_TEMP,
|
CONF_MAX_TEMP,
|
||||||
CONF_MIN_TEMP,
|
CONF_MIN_TEMP,
|
||||||
CONF_STEP,
|
CONF_STEP,
|
||||||
|
CONF_SWING_MODE_REGISTER,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH,
|
||||||
|
CONF_SWING_MODE_SWING_HORIZ,
|
||||||
|
CONF_SWING_MODE_SWING_OFF,
|
||||||
|
CONF_SWING_MODE_SWING_ON,
|
||||||
|
CONF_SWING_MODE_SWING_VERT,
|
||||||
|
CONF_SWING_MODE_VALUES,
|
||||||
CONF_TARGET_TEMP,
|
CONF_TARGET_TEMP,
|
||||||
CONF_TARGET_TEMP_WRITE_REGISTERS,
|
CONF_TARGET_TEMP_WRITE_REGISTERS,
|
||||||
CONF_WRITE_REGISTERS,
|
CONF_WRITE_REGISTERS,
|
||||||
@ -74,6 +88,8 @@ from .const import (
|
|||||||
)
|
)
|
||||||
from .modbus import ModbusHub
|
from .modbus import ModbusHub
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
HVACMODE_TO_TARG_TEMP_REG_INDEX_ARRAY = {
|
HVACMODE_TO_TARG_TEMP_REG_INDEX_ARRAY = {
|
||||||
@ -204,11 +220,35 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
|||||||
self._attr_fan_modes.append(fan_mode)
|
self._attr_fan_modes.append(fan_mode)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# No HVAC modes defined
|
# No FAN modes defined
|
||||||
self._fan_mode_register = None
|
self._fan_mode_register = None
|
||||||
self._attr_fan_mode = FAN_AUTO
|
self._attr_fan_mode = FAN_AUTO
|
||||||
self._attr_fan_modes = [FAN_AUTO]
|
self._attr_fan_modes = [FAN_AUTO]
|
||||||
|
|
||||||
|
# No SWING modes defined
|
||||||
|
self._swing_mode_register = None
|
||||||
|
if CONF_SWING_MODE_REGISTER in config:
|
||||||
|
self._attr_supported_features = (
|
||||||
|
self._attr_supported_features | ClimateEntityFeature.SWING_MODE
|
||||||
|
)
|
||||||
|
mode_config = config[CONF_SWING_MODE_REGISTER]
|
||||||
|
self._swing_mode_register = mode_config[CONF_ADDRESS]
|
||||||
|
self._attr_swing_modes = cast(list[str], [])
|
||||||
|
self._attr_swing_mode = None
|
||||||
|
self._swing_mode_modbus_mapping: list[tuple[int, str]] = []
|
||||||
|
mode_value_config = mode_config[CONF_SWING_MODE_VALUES]
|
||||||
|
for swing_mode_kw, swing_mode in (
|
||||||
|
(CONF_SWING_MODE_SWING_ON, SWING_ON),
|
||||||
|
(CONF_SWING_MODE_SWING_OFF, SWING_OFF),
|
||||||
|
(CONF_SWING_MODE_SWING_HORIZ, SWING_HORIZONTAL),
|
||||||
|
(CONF_SWING_MODE_SWING_VERT, SWING_VERTICAL),
|
||||||
|
(CONF_SWING_MODE_SWING_BOTH, SWING_BOTH),
|
||||||
|
):
|
||||||
|
if swing_mode_kw in mode_value_config:
|
||||||
|
value = mode_value_config[swing_mode_kw]
|
||||||
|
self._swing_mode_modbus_mapping.append((value, swing_mode))
|
||||||
|
self._attr_swing_modes.append(swing_mode)
|
||||||
|
|
||||||
if CONF_HVAC_ONOFF_REGISTER in config:
|
if CONF_HVAC_ONOFF_REGISTER in config:
|
||||||
self._hvac_onoff_register = config[CONF_HVAC_ONOFF_REGISTER]
|
self._hvac_onoff_register = config[CONF_HVAC_ONOFF_REGISTER]
|
||||||
self._hvac_onoff_write_registers = config[CONF_WRITE_REGISTERS]
|
self._hvac_onoff_write_registers = config[CONF_WRITE_REGISTERS]
|
||||||
@ -287,6 +327,29 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
|||||||
|
|
||||||
await self.async_update()
|
await self.async_update()
|
||||||
|
|
||||||
|
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||||
|
"""Set new target swing mode."""
|
||||||
|
if self._swing_mode_register:
|
||||||
|
# Write a value to the mode register for the desired mode.
|
||||||
|
for value, smode in self._swing_mode_modbus_mapping:
|
||||||
|
if swing_mode == smode:
|
||||||
|
if isinstance(self._swing_mode_register, list):
|
||||||
|
await self._hub.async_pb_call(
|
||||||
|
self._slave,
|
||||||
|
self._swing_mode_register[0],
|
||||||
|
[value],
|
||||||
|
CALL_TYPE_WRITE_REGISTERS,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self._hub.async_pb_call(
|
||||||
|
self._slave,
|
||||||
|
self._swing_mode_register,
|
||||||
|
value,
|
||||||
|
CALL_TYPE_WRITE_REGISTER,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
await self.async_update()
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
target_temperature = (
|
target_temperature = (
|
||||||
@ -387,6 +450,26 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
|||||||
int(fan_mode), self._attr_fan_mode
|
int(fan_mode), self._attr_fan_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Read the Swing mode register if defined
|
||||||
|
if self._swing_mode_register:
|
||||||
|
swing_mode = await self._async_read_register(
|
||||||
|
CALL_TYPE_REGISTER_HOLDING,
|
||||||
|
self._swing_mode_register
|
||||||
|
if isinstance(self._swing_mode_register, int)
|
||||||
|
else self._swing_mode_register[0],
|
||||||
|
raw=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._attr_swing_mode = STATE_UNKNOWN
|
||||||
|
for value, smode in self._swing_mode_modbus_mapping:
|
||||||
|
if swing_mode == value:
|
||||||
|
self._attr_swing_mode = smode
|
||||||
|
break
|
||||||
|
|
||||||
|
if self._attr_swing_mode is STATE_UNKNOWN:
|
||||||
|
_err = f"{self.name}: No answer received from Swing mode register. State is Unknown"
|
||||||
|
_LOGGER.error(_err)
|
||||||
|
|
||||||
# Read the on/off register if defined. If the value in this
|
# Read the on/off register if defined. If the value in this
|
||||||
# register is "OFF", it will take precedence over the value
|
# register is "OFF", it will take precedence over the value
|
||||||
# in the mode register.
|
# in the mode register.
|
||||||
|
@ -70,6 +70,13 @@ CONF_HVAC_MODE_AUTO = "state_auto"
|
|||||||
CONF_HVAC_MODE_DRY = "state_dry"
|
CONF_HVAC_MODE_DRY = "state_dry"
|
||||||
CONF_HVAC_MODE_FAN_ONLY = "state_fan_only"
|
CONF_HVAC_MODE_FAN_ONLY = "state_fan_only"
|
||||||
CONF_HVAC_MODE_VALUES = "values"
|
CONF_HVAC_MODE_VALUES = "values"
|
||||||
|
CONF_SWING_MODE_REGISTER = "swing_mode_register"
|
||||||
|
CONF_SWING_MODE_SWING_BOTH = "swing_mode_state_both"
|
||||||
|
CONF_SWING_MODE_SWING_HORIZ = "swing_mode_state_horizontal"
|
||||||
|
CONF_SWING_MODE_SWING_OFF = "swing_mode_state_off"
|
||||||
|
CONF_SWING_MODE_SWING_ON = "swing_mode_state_on"
|
||||||
|
CONF_SWING_MODE_SWING_VERT = "swing_mode_state_vertical"
|
||||||
|
CONF_SWING_MODE_VALUES = "values"
|
||||||
CONF_WRITE_REGISTERS = "write_registers"
|
CONF_WRITE_REGISTERS = "write_registers"
|
||||||
CONF_VERIFY = "verify"
|
CONF_VERIFY = "verify"
|
||||||
CONF_VIRTUAL_COUNT = "virtual_count"
|
CONF_VIRTUAL_COUNT = "virtual_count"
|
||||||
|
@ -42,6 +42,8 @@ from .const import (
|
|||||||
CONF_SWAP_BYTE,
|
CONF_SWAP_BYTE,
|
||||||
CONF_SWAP_WORD,
|
CONF_SWAP_WORD,
|
||||||
CONF_SWAP_WORD_BYTE,
|
CONF_SWAP_WORD_BYTE,
|
||||||
|
CONF_SWING_MODE_REGISTER,
|
||||||
|
CONF_SWING_MODE_VALUES,
|
||||||
CONF_TARGET_TEMP,
|
CONF_TARGET_TEMP,
|
||||||
CONF_VIRTUAL_COUNT,
|
CONF_VIRTUAL_COUNT,
|
||||||
CONF_WRITE_TYPE,
|
CONF_WRITE_TYPE,
|
||||||
@ -256,8 +258,25 @@ def duplicate_fan_mode_validator(config: dict[str, Any]) -> dict:
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def duplicate_swing_mode_validator(config: dict[str, Any]) -> dict:
|
||||||
|
"""Control modbus climate swing mode values for duplicates."""
|
||||||
|
swing_modes: set[int] = set()
|
||||||
|
errors = []
|
||||||
|
for key, value in config[CONF_SWING_MODE_VALUES].items():
|
||||||
|
if value in swing_modes:
|
||||||
|
warn = f"Modbus swing mode {key} has a duplicate value {value}, not loaded, values must be unique!"
|
||||||
|
_LOGGER.warning(warn)
|
||||||
|
errors.append(key)
|
||||||
|
else:
|
||||||
|
swing_modes.add(value)
|
||||||
|
|
||||||
|
for key in reversed(errors):
|
||||||
|
del config[CONF_SWING_MODE_VALUES][key]
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
def check_hvac_target_temp_registers(config: dict) -> dict:
|
def check_hvac_target_temp_registers(config: dict) -> dict:
|
||||||
"""Check conflicts among HVAC target temperature registers and HVAC ON/OFF, HVAC register, Fan Modes."""
|
"""Check conflicts among HVAC target temperature registers and HVAC ON/OFF, HVAC register, Fan Modes, Swing Modes."""
|
||||||
|
|
||||||
if (
|
if (
|
||||||
CONF_HVAC_MODE_REGISTER in config
|
CONF_HVAC_MODE_REGISTER in config
|
||||||
@ -281,6 +300,17 @@ def check_hvac_target_temp_registers(config: dict) -> dict:
|
|||||||
_LOGGER.warning(wrn)
|
_LOGGER.warning(wrn)
|
||||||
del config[CONF_FAN_MODE_REGISTER]
|
del config[CONF_FAN_MODE_REGISTER]
|
||||||
|
|
||||||
|
if CONF_SWING_MODE_REGISTER in config:
|
||||||
|
regToTest = (
|
||||||
|
config[CONF_SWING_MODE_REGISTER][CONF_ADDRESS]
|
||||||
|
if isinstance(config[CONF_SWING_MODE_REGISTER][CONF_ADDRESS], int)
|
||||||
|
else config[CONF_SWING_MODE_REGISTER][CONF_ADDRESS][0]
|
||||||
|
)
|
||||||
|
if regToTest in config[CONF_TARGET_TEMP]:
|
||||||
|
wrn = f"{CONF_SWING_MODE_REGISTER} overlaps CONF_TARGET_TEMP register(s). {CONF_SWING_MODE_REGISTER} is not loaded!"
|
||||||
|
_LOGGER.warning(wrn)
|
||||||
|
del config[CONF_SWING_MODE_REGISTER]
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
@ -294,7 +324,7 @@ def register_int_list_validator(value: Any) -> Any:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(
|
||||||
f"Invalid {CONF_ADDRESS} register for fan mode. Required type: positive integer, allowed 1 or list of 1 register."
|
f"Invalid {CONF_ADDRESS} register for fan/swing mode. Required type: positive integer, allowed 1 or list of 1 register."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -421,6 +451,12 @@ def validate_entity(
|
|||||||
loc_addr.add(f"{hub_name}{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}")
|
loc_addr.add(f"{hub_name}{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}")
|
||||||
if CONF_FAN_MODE_REGISTER in entity:
|
if CONF_FAN_MODE_REGISTER in entity:
|
||||||
loc_addr.add(f"{hub_name}{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}")
|
loc_addr.add(f"{hub_name}{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}")
|
||||||
|
if CONF_SWING_MODE_REGISTER in entity:
|
||||||
|
loc_addr.add(
|
||||||
|
f"{hub_name}{entity[CONF_SWING_MODE_REGISTER][CONF_ADDRESS]
|
||||||
|
if isinstance(entity[CONF_SWING_MODE_REGISTER][CONF_ADDRESS],int)
|
||||||
|
else entity[CONF_SWING_MODE_REGISTER][CONF_ADDRESS][0]}_{inx}"
|
||||||
|
)
|
||||||
|
|
||||||
dup_addrs = ent_addr.intersection(loc_addr)
|
dup_addrs = ent_addr.intersection(loc_addr)
|
||||||
if len(dup_addrs) > 0:
|
if len(dup_addrs) > 0:
|
||||||
|
@ -8,6 +8,8 @@ from homeassistant.components.climate.const import (
|
|||||||
ATTR_FAN_MODES,
|
ATTR_FAN_MODES,
|
||||||
ATTR_HVAC_MODE,
|
ATTR_HVAC_MODE,
|
||||||
ATTR_HVAC_MODES,
|
ATTR_HVAC_MODES,
|
||||||
|
ATTR_SWING_MODE,
|
||||||
|
ATTR_SWING_MODES,
|
||||||
FAN_AUTO,
|
FAN_AUTO,
|
||||||
FAN_DIFFUSE,
|
FAN_DIFFUSE,
|
||||||
FAN_FOCUS,
|
FAN_FOCUS,
|
||||||
@ -18,6 +20,11 @@ from homeassistant.components.climate.const import (
|
|||||||
FAN_OFF,
|
FAN_OFF,
|
||||||
FAN_ON,
|
FAN_ON,
|
||||||
FAN_TOP,
|
FAN_TOP,
|
||||||
|
SWING_BOTH,
|
||||||
|
SWING_HORIZONTAL,
|
||||||
|
SWING_OFF,
|
||||||
|
SWING_ON,
|
||||||
|
SWING_VERTICAL,
|
||||||
HVACMode,
|
HVACMode,
|
||||||
)
|
)
|
||||||
from homeassistant.components.modbus.const import (
|
from homeassistant.components.modbus.const import (
|
||||||
@ -45,6 +52,13 @@ from homeassistant.components.modbus.const import (
|
|||||||
CONF_HVAC_ONOFF_REGISTER,
|
CONF_HVAC_ONOFF_REGISTER,
|
||||||
CONF_MAX_TEMP,
|
CONF_MAX_TEMP,
|
||||||
CONF_MIN_TEMP,
|
CONF_MIN_TEMP,
|
||||||
|
CONF_SWING_MODE_REGISTER,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH,
|
||||||
|
CONF_SWING_MODE_SWING_HORIZ,
|
||||||
|
CONF_SWING_MODE_SWING_OFF,
|
||||||
|
CONF_SWING_MODE_SWING_ON,
|
||||||
|
CONF_SWING_MODE_SWING_VERT,
|
||||||
|
CONF_SWING_MODE_VALUES,
|
||||||
CONF_TARGET_TEMP,
|
CONF_TARGET_TEMP,
|
||||||
CONF_TARGET_TEMP_WRITE_REGISTERS,
|
CONF_TARGET_TEMP_WRITE_REGISTERS,
|
||||||
CONF_WRITE_REGISTERS,
|
CONF_WRITE_REGISTERS,
|
||||||
@ -58,6 +72,7 @@ from homeassistant.const import (
|
|||||||
CONF_SCAN_INTERVAL,
|
CONF_SCAN_INTERVAL,
|
||||||
CONF_SLAVE,
|
CONF_SLAVE,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, State
|
from homeassistant.core import HomeAssistant, State
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
@ -282,6 +297,41 @@ async def test_config_fan_mode_register(hass: HomeAssistant, mock_modbus) -> Non
|
|||||||
assert FAN_FOCUS not in state.attributes[ATTR_FAN_MODES]
|
assert FAN_FOCUS not in state.attributes[ATTR_FAN_MODES]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"do_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
CONF_CLIMATES: [
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_TARGET_TEMP: 117,
|
||||||
|
CONF_ADDRESS: 117,
|
||||||
|
CONF_SLAVE: 10,
|
||||||
|
CONF_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: 11,
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_ON: 0,
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 1,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH: 2,
|
||||||
|
CONF_SWING_MODE_SWING_HORIZ: 3,
|
||||||
|
CONF_SWING_MODE_SWING_VERT: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_config_swing_mode_register(hass: HomeAssistant, mock_modbus) -> None:
|
||||||
|
"""Run configuration test for Fan mode register."""
|
||||||
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
assert SWING_ON in state.attributes[ATTR_SWING_MODES]
|
||||||
|
assert SWING_OFF in state.attributes[ATTR_SWING_MODES]
|
||||||
|
assert SWING_BOTH in state.attributes[ATTR_SWING_MODES]
|
||||||
|
assert SWING_HORIZONTAL in state.attributes[ATTR_SWING_MODES]
|
||||||
|
assert SWING_VERTICAL in state.attributes[ATTR_SWING_MODES]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"do_config",
|
"do_config",
|
||||||
[
|
[
|
||||||
@ -572,6 +622,146 @@ async def test_service_climate_fan_update(
|
|||||||
assert hass.states.get(ENTITY_ID).attributes[ATTR_FAN_MODE] == result
|
assert hass.states.get(ENTITY_ID).attributes[ATTR_FAN_MODE] == 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_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: [118],
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 0,
|
||||||
|
CONF_SWING_MODE_SWING_ON: 1,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
SWING_BOTH,
|
||||||
|
[0x02],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
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_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: [118],
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 0,
|
||||||
|
CONF_SWING_MODE_SWING_ON: 1,
|
||||||
|
CONF_SWING_MODE_SWING_VERT: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
SWING_ON,
|
||||||
|
[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_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: [118],
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 0,
|
||||||
|
CONF_SWING_MODE_SWING_ON: 1,
|
||||||
|
CONF_SWING_MODE_SWING_HORIZ: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CONF_HVAC_ONOFF_REGISTER: 119,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
SWING_HORIZONTAL,
|
||||||
|
[0x03],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
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_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: 118,
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 0,
|
||||||
|
CONF_SWING_MODE_SWING_ON: 1,
|
||||||
|
CONF_SWING_MODE_SWING_VERT: 2,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
SWING_OFF,
|
||||||
|
[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_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: 118,
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 0,
|
||||||
|
CONF_SWING_MODE_SWING_ON: 1,
|
||||||
|
CONF_SWING_MODE_SWING_VERT: 2,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
[0x05],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_service_climate_swing_update(
|
||||||
|
hass: HomeAssistant, mock_modbus, mock_ha, result, register_words
|
||||||
|
) -> None:
|
||||||
|
"""Run test for service homeassistant.update_entity."""
|
||||||
|
mock_modbus.read_holding_registers.return_value = ReadResult(register_words)
|
||||||
|
await hass.services.async_call(
|
||||||
|
"homeassistant", "update_entity", {"entity_id": ENTITY_ID}, blocking=True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get(ENTITY_ID).attributes[ATTR_SWING_MODE] == result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("temperature", "result", "do_config"),
|
("temperature", "result", "do_config"),
|
||||||
[
|
[
|
||||||
@ -843,6 +1033,69 @@ async def test_service_set_fan_mode(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("swing_mode", "result", "do_config"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
SWING_OFF,
|
||||||
|
[0x00],
|
||||||
|
{
|
||||||
|
CONF_CLIMATES: [
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_TARGET_TEMP: 117,
|
||||||
|
CONF_ADDRESS: 117,
|
||||||
|
CONF_SLAVE: 10,
|
||||||
|
CONF_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: [118],
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_ON: 1,
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SWING_ON,
|
||||||
|
[0x01],
|
||||||
|
{
|
||||||
|
CONF_CLIMATES: [
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_TARGET_TEMP: 117,
|
||||||
|
CONF_ADDRESS: 117,
|
||||||
|
CONF_SLAVE: 10,
|
||||||
|
CONF_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: 118,
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_ON: 1,
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_service_set_swing_mode(
|
||||||
|
hass: HomeAssistant, swing_mode, result, mock_modbus, mock_ha
|
||||||
|
) -> None:
|
||||||
|
"""Test set Swing mode."""
|
||||||
|
mock_modbus.read_holding_registers.return_value = ReadResult(result)
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
"set_swing_mode",
|
||||||
|
{
|
||||||
|
"entity_id": ENTITY_ID,
|
||||||
|
ATTR_SWING_MODE: swing_mode,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
test_value = State(ENTITY_ID, 35)
|
test_value = State(ENTITY_ID, 35)
|
||||||
test_value.attributes = {ATTR_TEMPERATURE: 37}
|
test_value.attributes = {ATTR_TEMPERATURE: 37}
|
||||||
|
|
||||||
|
@ -66,6 +66,11 @@ from homeassistant.components.modbus.const import (
|
|||||||
CONF_SWAP_BYTE,
|
CONF_SWAP_BYTE,
|
||||||
CONF_SWAP_WORD,
|
CONF_SWAP_WORD,
|
||||||
CONF_SWAP_WORD_BYTE,
|
CONF_SWAP_WORD_BYTE,
|
||||||
|
CONF_SWING_MODE_REGISTER,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH,
|
||||||
|
CONF_SWING_MODE_SWING_OFF,
|
||||||
|
CONF_SWING_MODE_SWING_ON,
|
||||||
|
CONF_SWING_MODE_VALUES,
|
||||||
CONF_TARGET_TEMP,
|
CONF_TARGET_TEMP,
|
||||||
CONF_VIRTUAL_COUNT,
|
CONF_VIRTUAL_COUNT,
|
||||||
DEFAULT_SCAN_INTERVAL,
|
DEFAULT_SCAN_INTERVAL,
|
||||||
@ -84,6 +89,7 @@ from homeassistant.components.modbus.validators import (
|
|||||||
check_config,
|
check_config,
|
||||||
check_hvac_target_temp_registers,
|
check_hvac_target_temp_registers,
|
||||||
duplicate_fan_mode_validator,
|
duplicate_fan_mode_validator,
|
||||||
|
duplicate_swing_mode_validator,
|
||||||
hvac_fixedsize_reglist_validator,
|
hvac_fixedsize_reglist_validator,
|
||||||
nan_validator,
|
nan_validator,
|
||||||
register_int_list_validator,
|
register_int_list_validator,
|
||||||
@ -629,6 +635,42 @@ async def test_check_config_sensor(hass: HomeAssistant, do_config) -> None:
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
[ # Testing Swing modes
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_MODBUS_NAME,
|
||||||
|
CONF_TYPE: TCP,
|
||||||
|
CONF_HOST: TEST_MODBUS_HOST,
|
||||||
|
CONF_PORT: TEST_PORT_TCP,
|
||||||
|
CONF_TIMEOUT: 3,
|
||||||
|
CONF_CLIMATES: [
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_ADDRESS: 117,
|
||||||
|
CONF_SLAVE: 0,
|
||||||
|
CONF_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: 120,
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_ON: 0,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME + " 2",
|
||||||
|
CONF_ADDRESS: 119,
|
||||||
|
CONF_SLAVE: 0,
|
||||||
|
CONF_TARGET_TEMP: 118,
|
||||||
|
CONF_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: [120],
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_ON: 0,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
CONF_NAME: TEST_MODBUS_NAME,
|
CONF_NAME: TEST_MODBUS_NAME,
|
||||||
@ -733,6 +775,29 @@ async def test_check_config_climate(hass: HomeAssistant, do_config) -> None:
|
|||||||
CONF_FAN_MODE_REGISTER: {
|
CONF_FAN_MODE_REGISTER: {
|
||||||
CONF_ADDRESS: 117,
|
CONF_ADDRESS: 117,
|
||||||
},
|
},
|
||||||
|
CONF_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: 117,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_ADDRESS: 1,
|
||||||
|
CONF_TARGET_TEMP: [117],
|
||||||
|
CONF_HVAC_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: 117,
|
||||||
|
CONF_HVAC_MODE_VALUES: {
|
||||||
|
CONF_HVAC_MODE_COOL: 0,
|
||||||
|
CONF_HVAC_MODE_HEAT: 1,
|
||||||
|
CONF_HVAC_MODE_HEAT_COOL: 2,
|
||||||
|
CONF_HVAC_MODE_DRY: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CONF_HVAC_ONOFF_REGISTER: 117,
|
||||||
|
CONF_SWING_MODE_REGISTER: {
|
||||||
|
CONF_ADDRESS: [117],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -743,6 +808,7 @@ async def test_climate_conflict_addresses(do_config) -> None:
|
|||||||
assert CONF_HVAC_MODE_REGISTER not in do_config[0]
|
assert CONF_HVAC_MODE_REGISTER not in do_config[0]
|
||||||
assert CONF_HVAC_ONOFF_REGISTER not in do_config[0]
|
assert CONF_HVAC_ONOFF_REGISTER not in do_config[0]
|
||||||
assert CONF_FAN_MODE_REGISTER not in do_config[0]
|
assert CONF_FAN_MODE_REGISTER not in do_config[0]
|
||||||
|
assert CONF_SWING_MODE_REGISTER not in do_config[0]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -764,6 +830,25 @@ async def test_duplicate_fan_mode_validator(do_config) -> None:
|
|||||||
assert len(do_config[CONF_FAN_MODE_VALUES]) == 2
|
assert len(do_config[CONF_FAN_MODE_VALUES]) == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"do_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
CONF_ADDRESS: 11,
|
||||||
|
CONF_SWING_MODE_VALUES: {
|
||||||
|
CONF_SWING_MODE_SWING_ON: 7,
|
||||||
|
CONF_SWING_MODE_SWING_OFF: 9,
|
||||||
|
CONF_SWING_MODE_SWING_BOTH: 9,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_duplicate_swing_mode_validator(do_config) -> None:
|
||||||
|
"""Test duplicate modbus validator."""
|
||||||
|
duplicate_swing_mode_validator(do_config)
|
||||||
|
assert len(do_config[CONF_SWING_MODE_VALUES]) == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("do_config", "sensor_cnt"),
|
("do_config", "sensor_cnt"),
|
||||||
[
|
[
|
||||||
|
Loading…
x
Reference in New Issue
Block a user