Add Swing Mode Feature to Modbus integration (#113710)

This commit is contained in:
Claudio Ruggeri - CR-Tech 2024-04-06 11:01:44 +02:00 committed by GitHub
parent c3942a7d44
commit a28731c294
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 490 additions and 3 deletions

View file

@ -113,6 +113,13 @@ from .const import (
CONF_SWAP_BYTE,
CONF_SWAP_WORD,
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_WRITE_REGISTERS,
CONF_VERIFY,
@ -134,6 +141,7 @@ from .modbus import ModbusHub, async_modbus_setup
from .validators import (
check_hvac_target_temp_registers,
duplicate_fan_mode_validator,
duplicate_swing_mode_validator,
hvac_fixedsize_reglist_validator,
nan_validator,
register_int_list_validator,
@ -296,6 +304,21 @@ CLIMATE_SCHEMA = vol.All(
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,

View file

@ -3,6 +3,7 @@
from __future__ import annotations
from datetime import datetime
import logging
import struct
from typing import Any, cast
@ -17,6 +18,11 @@ from homeassistant.components.climate import (
FAN_OFF,
FAN_ON,
FAN_TOP,
SWING_BOTH,
SWING_HORIZONTAL,
SWING_OFF,
SWING_ON,
SWING_VERTICAL,
ClimateEntity,
ClimateEntityFeature,
HVACMode,
@ -28,6 +34,7 @@ from homeassistant.const import (
CONF_TEMPERATURE_UNIT,
PRECISION_TENTHS,
PRECISION_WHOLE,
STATE_UNKNOWN,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@ -67,6 +74,13 @@ from .const import (
CONF_MAX_TEMP,
CONF_MIN_TEMP,
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_WRITE_REGISTERS,
CONF_WRITE_REGISTERS,
@ -74,6 +88,8 @@ from .const import (
)
from .modbus import ModbusHub
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
HVACMODE_TO_TARG_TEMP_REG_INDEX_ARRAY = {
@ -204,11 +220,35 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
self._attr_fan_modes.append(fan_mode)
else:
# No HVAC modes defined
# No FAN modes defined
self._fan_mode_register = None
self._attr_fan_mode = 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:
self._hvac_onoff_register = config[CONF_HVAC_ONOFF_REGISTER]
self._hvac_onoff_write_registers = config[CONF_WRITE_REGISTERS]
@ -287,6 +327,29 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
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:
"""Set new target temperature."""
target_temperature = (
@ -387,6 +450,26 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
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
# register is "OFF", it will take precedence over the value
# in the mode register.

View file

@ -70,6 +70,13 @@ CONF_HVAC_MODE_AUTO = "state_auto"
CONF_HVAC_MODE_DRY = "state_dry"
CONF_HVAC_MODE_FAN_ONLY = "state_fan_only"
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_VERIFY = "verify"
CONF_VIRTUAL_COUNT = "virtual_count"

View file

@ -42,6 +42,8 @@ from .const import (
CONF_SWAP_BYTE,
CONF_SWAP_WORD,
CONF_SWAP_WORD_BYTE,
CONF_SWING_MODE_REGISTER,
CONF_SWING_MODE_VALUES,
CONF_TARGET_TEMP,
CONF_VIRTUAL_COUNT,
CONF_WRITE_TYPE,
@ -256,8 +258,25 @@ def duplicate_fan_mode_validator(config: dict[str, Any]) -> dict:
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:
"""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 (
CONF_HVAC_MODE_REGISTER in config
@ -281,6 +300,17 @@ def check_hvac_target_temp_registers(config: dict) -> dict:
_LOGGER.warning(wrn)
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
@ -294,7 +324,7 @@ def register_int_list_validator(value: Any) -> Any:
return value
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}")
if CONF_FAN_MODE_REGISTER in entity:
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)
if len(dup_addrs) > 0:

View file

@ -8,6 +8,8 @@ from homeassistant.components.climate.const import (
ATTR_FAN_MODES,
ATTR_HVAC_MODE,
ATTR_HVAC_MODES,
ATTR_SWING_MODE,
ATTR_SWING_MODES,
FAN_AUTO,
FAN_DIFFUSE,
FAN_FOCUS,
@ -18,6 +20,11 @@ from homeassistant.components.climate.const import (
FAN_OFF,
FAN_ON,
FAN_TOP,
SWING_BOTH,
SWING_HORIZONTAL,
SWING_OFF,
SWING_ON,
SWING_VERTICAL,
HVACMode,
)
from homeassistant.components.modbus.const import (
@ -45,6 +52,13 @@ from homeassistant.components.modbus.const import (
CONF_HVAC_ONOFF_REGISTER,
CONF_MAX_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_WRITE_REGISTERS,
CONF_WRITE_REGISTERS,
@ -58,6 +72,7 @@ from homeassistant.const import (
CONF_SCAN_INTERVAL,
CONF_SLAVE,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, State
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]
@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(
"do_config",
[
@ -572,6 +622,146 @@ async def test_service_climate_fan_update(
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(
("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.attributes = {ATTR_TEMPERATURE: 37}

View file

@ -66,6 +66,11 @@ from homeassistant.components.modbus.const import (
CONF_SWAP_BYTE,
CONF_SWAP_WORD,
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_VIRTUAL_COUNT,
DEFAULT_SCAN_INTERVAL,
@ -84,6 +89,7 @@ from homeassistant.components.modbus.validators import (
check_config,
check_hvac_target_temp_registers,
duplicate_fan_mode_validator,
duplicate_swing_mode_validator,
hvac_fixedsize_reglist_validator,
nan_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,
@ -733,6 +775,29 @@ async def test_check_config_climate(hass: HomeAssistant, do_config) -> None:
CONF_FAN_MODE_REGISTER: {
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_ONOFF_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(
@ -764,6 +830,25 @@ async def test_duplicate_fan_mode_validator(do_config) -> None:
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(
("do_config", "sensor_cnt"),
[