1 """Support for Generic Modbus Thermostats."""
3 from __future__
import annotations
5 from datetime
import datetime
8 from typing
import Any, cast
34 CONF_TEMPERATURE_UNIT,
47 CALL_TYPE_REGISTER_HOLDING,
48 CALL_TYPE_WRITE_REGISTER,
49 CALL_TYPE_WRITE_REGISTERS,
52 CONF_FAN_MODE_DIFFUSE,
60 CONF_FAN_MODE_REGISTER,
66 CONF_HVAC_MODE_FAN_ONLY,
68 CONF_HVAC_MODE_HEAT_COOL,
70 CONF_HVAC_MODE_REGISTER,
71 CONF_HVAC_MODE_VALUES,
72 CONF_HVAC_ONOFF_REGISTER,
76 CONF_SWING_MODE_REGISTER,
77 CONF_SWING_MODE_SWING_BOTH,
78 CONF_SWING_MODE_SWING_HORIZ,
79 CONF_SWING_MODE_SWING_OFF,
80 CONF_SWING_MODE_SWING_ON,
81 CONF_SWING_MODE_SWING_VERT,
82 CONF_SWING_MODE_VALUES,
84 CONF_TARGET_TEMP_WRITE_REGISTERS,
88 from .entity
import BaseStructPlatform
89 from .modbus
import ModbusHub
91 _LOGGER = logging.getLogger(__name__)
95 HVACMODE_TO_TARG_TEMP_REG_INDEX_ARRAY = {
101 HVACMode.HEAT_COOL: 5,
110 async_add_entities: AddEntitiesCallback,
111 discovery_info: DiscoveryInfoType |
None =
None,
113 """Read configuration and create Modbus climate."""
114 if discovery_info
is None:
118 for entity
in discovery_info[CONF_CLIMATES]:
119 hub: ModbusHub =
get_hub(hass, discovery_info[CONF_NAME])
126 """Representation of a Modbus Thermostat."""
128 _attr_supported_features = (
129 ClimateEntityFeature.TARGET_TEMPERATURE
130 | ClimateEntityFeature.TURN_OFF
131 | ClimateEntityFeature.TURN_ON
133 _enable_turn_on_off_backwards_compatibility =
False
139 config: dict[str, Any],
141 """Initialize the modbus thermostat."""
145 CONF_TARGET_TEMP_WRITE_REGISTERS
147 self.
_unit_unit = config[CONF_TEMPERATURE_UNIT]
151 UnitOfTemperature.FAHRENHEIT
152 if self.
_unit_unit ==
"F"
153 else UnitOfTemperature.CELSIUS
156 PRECISION_TENTHS
if self.
_precision_precision >= 1
else PRECISION_WHOLE
162 if CONF_HVAC_MODE_REGISTER
in config:
163 mode_config = config[CONF_HVAC_MODE_REGISTER]
167 self._hvac_mode_mapping: list[tuple[int, HVACMode]] = []
169 mode_value_config = mode_config[CONF_HVAC_MODE_VALUES]
171 for hvac_mode_kw, hvac_mode
in (
172 (CONF_HVAC_MODE_OFF, HVACMode.OFF),
173 (CONF_HVAC_MODE_HEAT, HVACMode.HEAT),
174 (CONF_HVAC_MODE_COOL, HVACMode.COOL),
175 (CONF_HVAC_MODE_HEAT_COOL, HVACMode.HEAT_COOL),
176 (CONF_HVAC_MODE_AUTO, HVACMode.AUTO),
177 (CONF_HVAC_MODE_DRY, HVACMode.DRY),
178 (CONF_HVAC_MODE_FAN_ONLY, HVACMode.FAN_ONLY),
180 if hvac_mode_kw
in mode_value_config:
181 values = mode_value_config[hvac_mode_kw]
182 if not isinstance(values, list):
185 self._hvac_mode_mapping.append((value, hvac_mode))
193 if CONF_FAN_MODE_REGISTER
in config:
197 mode_config = config[CONF_FAN_MODE_REGISTER]
201 self._fan_mode_mapping_to_modbus: dict[str, int] = {}
202 self._fan_mode_mapping_from_modbus: dict[int, str] = {}
203 mode_value_config = mode_config[CONF_FAN_MODE_VALUES]
204 for fan_mode_kw, fan_mode
in (
205 (CONF_FAN_MODE_ON, FAN_ON),
206 (CONF_FAN_MODE_OFF, FAN_OFF),
207 (CONF_FAN_MODE_AUTO, FAN_AUTO),
208 (CONF_FAN_MODE_LOW, FAN_LOW),
209 (CONF_FAN_MODE_MEDIUM, FAN_MEDIUM),
210 (CONF_FAN_MODE_HIGH, FAN_HIGH),
211 (CONF_FAN_MODE_TOP, FAN_TOP),
212 (CONF_FAN_MODE_MIDDLE, FAN_MIDDLE),
213 (CONF_FAN_MODE_FOCUS, FAN_FOCUS),
214 (CONF_FAN_MODE_DIFFUSE, FAN_DIFFUSE),
216 if fan_mode_kw
in mode_value_config:
217 value = mode_value_config[fan_mode_kw]
218 self._fan_mode_mapping_from_modbus[value] = fan_mode
219 self._fan_mode_mapping_to_modbus[fan_mode] = value
230 if CONF_SWING_MODE_REGISTER
in config:
234 mode_config = config[CONF_SWING_MODE_REGISTER]
238 self._swing_mode_modbus_mapping: list[tuple[int, str]] = []
239 mode_value_config = mode_config[CONF_SWING_MODE_VALUES]
240 for swing_mode_kw, swing_mode
in (
241 (CONF_SWING_MODE_SWING_ON, SWING_ON),
242 (CONF_SWING_MODE_SWING_OFF, SWING_OFF),
243 (CONF_SWING_MODE_SWING_HORIZ, SWING_HORIZONTAL),
244 (CONF_SWING_MODE_SWING_VERT, SWING_VERTICAL),
245 (CONF_SWING_MODE_SWING_BOTH, SWING_BOTH),
247 if swing_mode_kw
in mode_value_config:
248 value = mode_value_config[swing_mode_kw]
249 self._swing_mode_modbus_mapping.append((value, swing_mode))
252 if CONF_HVAC_ONOFF_REGISTER
in config:
261 """Handle entity which will be added."""
264 if state
and state.attributes.get(ATTR_TEMPERATURE):
268 """Set new target hvac mode."""
272 await self.
_hub_hub.async_pb_call(
275 [0
if hvac_mode == HVACMode.OFF
else 1],
276 CALL_TYPE_WRITE_REGISTERS,
279 await self.
_hub_hub.async_pb_call(
282 0
if hvac_mode == HVACMode.OFF
else 1,
283 CALL_TYPE_WRITE_REGISTER,
288 for value, mode
in self._hvac_mode_mapping:
289 if mode == hvac_mode:
291 await self.
_hub_hub.async_pb_call(
295 CALL_TYPE_WRITE_REGISTERS,
298 await self.
_hub_hub.async_pb_call(
302 CALL_TYPE_WRITE_REGISTER,
309 """Set new target fan mode."""
312 value = self._fan_mode_mapping_to_modbus[fan_mode]
314 await self.
_hub_hub.async_pb_call(
318 CALL_TYPE_WRITE_REGISTERS,
321 await self.
_hub_hub.async_pb_call(
325 CALL_TYPE_WRITE_REGISTER,
331 """Set new target swing mode."""
334 for value, smode
in self._swing_mode_modbus_mapping:
335 if swing_mode == smode:
337 await self.
_hub_hub.async_pb_call(
341 CALL_TYPE_WRITE_REGISTERS,
344 await self.
_hub_hub.async_pb_call(
348 CALL_TYPE_WRITE_REGISTER,
354 """Set new target temperature."""
355 target_temperature = (
366 target_temperature =
int(target_temperature)
367 as_bytes = struct.pack(self._structure, target_temperature)
369 int.from_bytes(as_bytes[i : i + 2],
"big")
370 for i
in range(0, len(as_bytes), 2)
379 result = await self.
_hub_hub.async_pb_call(
382 HVACMODE_TO_TARG_TEMP_REG_INDEX_ARRAY[self.
_attr_hvac_mode_attr_hvac_mode]
385 CALL_TYPE_WRITE_REGISTERS,
388 result = await self.
_hub_hub.async_pb_call(
391 HVACMODE_TO_TARG_TEMP_REG_INDEX_ARRAY[self.
_attr_hvac_mode_attr_hvac_mode]
394 CALL_TYPE_WRITE_REGISTER,
397 result = await self.
_hub_hub.async_pb_call(
400 HVACMODE_TO_TARG_TEMP_REG_INDEX_ARRAY[self.
_attr_hvac_mode_attr_hvac_mode]
403 CALL_TYPE_WRITE_REGISTERS,
409 """Update Target & Current Temperature."""
414 CALL_TYPE_REGISTER_HOLDING,
416 HVACMODE_TO_TARG_TEMP_REG_INDEX_ARRAY[self.
_attr_hvac_mode_attr_hvac_mode]
430 if hvac_mode
is not None:
432 for value, mode
in self._hvac_mode_mapping:
433 if hvac_mode == value:
440 CALL_TYPE_REGISTER_HOLDING,
448 if fan_mode
is not None:
456 CALL_TYPE_REGISTER_HOLDING,
464 for value, smode
in self._swing_mode_modbus_mapping:
465 if swing_mode == value:
470 _err = f
"{self.name}: No answer received from Swing mode register. State is Unknown"
486 self, register_type: str, register: int, raw: bool |
None =
False
488 """Read register using the Modbus hub slave."""
489 result = await self.
_hub_hub.async_pb_call(
490 self.
_slave_slave, register, self.
_count_count, register_type
500 return int(result.registers[0])
_target_temperature_register
None async_set_temperature(self, **Any kwargs)
_hvac_onoff_write_registers
None async_set_swing_mode(self, str swing_mode)
_target_temperature_write_registers
_attr_current_temperature
_hvac_mode_write_registers
None async_set_fan_mode(self, str fan_mode)
None __init__(self, HomeAssistant hass, ModbusHub hub, dict[str, Any] config)
None async_update(self, datetime|None now=None)
None async_added_to_hass(self)
float|None _async_read_register(self, str register_type, int register, bool|None raw=False)
tuple _attr_supported_features
_attr_target_temperature_step
None async_set_hvac_mode(self, HVACMode hvac_mode)
None async_write_ha_state(self)
State|None async_get_last_state(self)
web.Response get(self, web.Request request, str config_key)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
ModbusHub get_hub(HomeAssistant hass, str name)