diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 382fe63ab69..8c37ea04079 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -187,28 +187,34 @@ BASE_SWITCH_SCHEMA = BASE_COMPONENT_SCHEMA.extend( ) -CLIMATE_SCHEMA = BASE_COMPONENT_SCHEMA.extend( - { - vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_REGISTER_HOLDING): vol.In( - [ - CALL_TYPE_REGISTER_HOLDING, - CALL_TYPE_REGISTER_INPUT, - ] - ), - vol.Required(CONF_TARGET_TEMP): cv.positive_int, - vol.Optional(CONF_DATA_COUNT, default=2): cv.positive_int, - vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_FLOAT): vol.In( - [DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, DATA_TYPE_CUSTOM] - ), - vol.Optional(CONF_PRECISION, default=1): cv.positive_int, - vol.Optional(CONF_SCALE, default=1): vol.Coerce(float), - vol.Optional(CONF_OFFSET, default=0): vol.Coerce(float), - vol.Optional(CONF_MAX_TEMP, default=35): cv.positive_int, - vol.Optional(CONF_MIN_TEMP, default=5): cv.positive_int, - vol.Optional(CONF_STEP, default=0.5): vol.Coerce(float), - vol.Optional(CONF_STRUCTURE, default=DEFAULT_STRUCTURE_PREFIX): cv.string, - vol.Optional(CONF_TEMPERATURE_UNIT, default=DEFAULT_TEMP_UNIT): cv.string, - } +CLIMATE_SCHEMA = vol.All( + cv.deprecated(CONF_DATA_COUNT, replacement_key=CONF_COUNT), + BASE_COMPONENT_SCHEMA.extend( + { + vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_REGISTER_HOLDING): vol.In( + [ + CALL_TYPE_REGISTER_HOLDING, + CALL_TYPE_REGISTER_INPUT, + ] + ), + vol.Required(CONF_TARGET_TEMP): cv.positive_int, + vol.Optional(CONF_COUNT, default=2): cv.positive_int, + vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_FLOAT): vol.In( + [DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, DATA_TYPE_CUSTOM] + ), + vol.Optional(CONF_PRECISION, default=1): cv.positive_int, + vol.Optional(CONF_SCALE, default=1): vol.Coerce(float), + vol.Optional(CONF_OFFSET, default=0): vol.Coerce(float), + vol.Optional(CONF_MAX_TEMP, default=35): cv.positive_int, + vol.Optional(CONF_MIN_TEMP, default=5): cv.positive_int, + vol.Optional(CONF_STEP, default=0.5): vol.Coerce(float), + vol.Optional(CONF_STRUCTURE, default=DEFAULT_STRUCTURE_PREFIX): cv.string, + vol.Optional(CONF_TEMPERATURE_UNIT, default=DEFAULT_TEMP_UNIT): cv.string, + vol.Optional(CONF_SWAP, default=CONF_SWAP_NONE): vol.In( + [CONF_SWAP_NONE, CONF_SWAP_BYTE, CONF_SWAP_WORD, CONF_SWAP_WORD_BYTE] + ), + } + ), ) COVERS_SCHEMA = BASE_COMPONENT_SCHEMA.extend( diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 15a14b7eca9..a178f6f0f84 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -11,6 +11,7 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ( + CONF_COUNT, CONF_NAME, CONF_OFFSET, CONF_STRUCTURE, @@ -28,13 +29,16 @@ from .const import ( CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_WRITE_REGISTERS, CONF_CLIMATES, - CONF_DATA_COUNT, CONF_DATA_TYPE, CONF_MAX_TEMP, CONF_MIN_TEMP, CONF_PRECISION, CONF_SCALE, CONF_STEP, + CONF_SWAP, + CONF_SWAP_BYTE, + CONF_SWAP_WORD, + CONF_SWAP_WORD_BYTE, CONF_TARGET_TEMP, DATA_TYPE_CUSTOM, DEFAULT_STRUCT_FORMAT, @@ -59,7 +63,7 @@ async def async_setup_platform( entities = [] for entity in discovery_info[CONF_CLIMATES]: hub: ModbusHub = hass.data[MODBUS_DOMAIN][discovery_info[CONF_NAME]] - count = entity[CONF_DATA_COUNT] + count = entity[CONF_COUNT] data_type = entity[CONF_DATA_TYPE] name = entity[CONF_NAME] structure = entity[CONF_STRUCTURE] @@ -110,7 +114,7 @@ class ModbusThermostat(BasePlatform, RestoreEntity, ClimateEntity): self._current_temperature = None self._data_type = config[CONF_DATA_TYPE] self._structure = config[CONF_STRUCTURE] - self._count = config[CONF_DATA_COUNT] + self._count = config[CONF_COUNT] self._precision = config[CONF_PRECISION] self._scale = config[CONF_SCALE] self._offset = config[CONF_OFFSET] @@ -118,6 +122,7 @@ class ModbusThermostat(BasePlatform, RestoreEntity, ClimateEntity): self._max_temp = config[CONF_MAX_TEMP] self._min_temp = config[CONF_MIN_TEMP] self._temp_step = config[CONF_STEP] + self._swap = config[CONF_SWAP] async def async_added_to_hass(self): """Handle entity which will be added.""" @@ -194,6 +199,21 @@ class ModbusThermostat(BasePlatform, RestoreEntity, ClimateEntity): self._available = result is not None await self.async_update() + def _swap_registers(self, registers): + """Do swap as needed.""" + if self._swap in [CONF_SWAP_BYTE, CONF_SWAP_WORD_BYTE]: + # convert [12][34] --> [21][43] + for i, register in enumerate(registers): + registers[i] = int.from_bytes( + register.to_bytes(2, byteorder="little"), + byteorder="big", + signed=False, + ) + if self._swap in [CONF_SWAP_WORD, CONF_SWAP_WORD_BYTE]: + # convert [12][34] ==> [34][12] + registers.reverse() + return registers + async def async_update(self, now=None): """Update Target & Current Temperature.""" # remark "now" is a dummy parameter to avoid problems with @@ -216,9 +236,8 @@ class ModbusThermostat(BasePlatform, RestoreEntity, ClimateEntity): self._available = False return -1 - byte_string = b"".join( - [x.to_bytes(2, byteorder="big") for x in result.registers] - ) + registers = self._swap_registers(result.registers) + byte_string = b"".join([x.to_bytes(2, byteorder="big") for x in registers]) val = struct.unpack(self._structure, byte_string) if len(val) != 1 or not isinstance(val[0], (float, int)): _LOGGER.error( diff --git a/tests/components/modbus/test_climate.py b/tests/components/modbus/test_climate.py index 50ba518bb36..c5201593480 100644 --- a/tests/components/modbus/test_climate.py +++ b/tests/components/modbus/test_climate.py @@ -3,14 +3,11 @@ import pytest from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN from homeassistant.components.climate.const import HVAC_MODE_AUTO -from homeassistant.components.modbus.const import ( - CONF_CLIMATES, - CONF_DATA_COUNT, - CONF_TARGET_TEMP, -) +from homeassistant.components.modbus.const import CONF_CLIMATES, CONF_TARGET_TEMP from homeassistant.const import ( ATTR_TEMPERATURE, CONF_ADDRESS, + CONF_COUNT, CONF_NAME, CONF_SCAN_INTERVAL, CONF_SLAVE, @@ -28,7 +25,7 @@ from tests.common import mock_restore_cache {}, { CONF_SCAN_INTERVAL: 20, - CONF_DATA_COUNT: 2, + CONF_COUNT: 2, }, ], ) @@ -73,7 +70,7 @@ async def test_temperature_climate(hass, regs, expected): CONF_SLAVE: 1, CONF_TARGET_TEMP: 117, CONF_ADDRESS: 117, - CONF_DATA_COUNT: 2, + CONF_COUNT: 2, }, climate_name, CLIMATE_DOMAIN,