Add Swing Mode Feature to Modbus integration (#113710)
parent
c3942a7d44
commit
a28731c294
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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"),
|
||||
[
|
||||
|
|
Loading…
Reference in New Issue