1 """Support for Modbus."""
3 from __future__
import annotations
6 from typing
import cast
8 import voluptuous
as vol
11 DEVICE_CLASSES_SCHEMA
as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
14 DEVICE_CLASSES_SCHEMA
as COVER_DEVICE_CLASSES_SCHEMA,
18 DEVICE_CLASSES_SCHEMA
as SENSOR_DEVICE_CLASSES_SCHEMA,
19 STATE_CLASSES_SCHEMA
as SENSOR_STATE_CLASSES_SCHEMA,
22 DEVICE_CLASSES_SCHEMA
as SWITCH_DEVICE_CLASSES_SCHEMA,
44 CONF_TEMPERATURE_UNIT,
48 CONF_UNIT_OF_MEASUREMENT,
57 CALL_TYPE_REGISTER_HOLDING,
58 CALL_TYPE_REGISTER_INPUT,
60 CALL_TYPE_X_REGISTER_HOLDINGS,
67 CONF_FAN_MODE_DIFFUSE,
75 CONF_FAN_MODE_REGISTER,
82 CONF_HVAC_MODE_FAN_ONLY,
84 CONF_HVAC_MODE_HEAT_COOL,
86 CONF_HVAC_MODE_REGISTER,
87 CONF_HVAC_MODE_VALUES,
88 CONF_HVAC_ONOFF_REGISTER,
106 CONF_STATUS_REGISTER,
107 CONF_STATUS_REGISTER_TYPE,
114 CONF_SWING_MODE_REGISTER,
115 CONF_SWING_MODE_SWING_BOTH,
116 CONF_SWING_MODE_SWING_HORIZ,
117 CONF_SWING_MODE_SWING_OFF,
118 CONF_SWING_MODE_SWING_ON,
119 CONF_SWING_MODE_SWING_VERT,
120 CONF_SWING_MODE_VALUES,
122 CONF_TARGET_TEMP_WRITE_REGISTERS,
125 CONF_WRITE_REGISTERS,
129 DEFAULT_SCAN_INTERVAL,
131 MODBUS_DOMAIN
as DOMAIN,
138 from .modbus
import ModbusHub, async_modbus_setup
139 from .validators
import (
140 duplicate_fan_mode_validator,
141 duplicate_swing_mode_validator,
142 hvac_fixedsize_reglist_validator,
144 register_int_list_validator,
148 _LOGGER = logging.getLogger(__name__)
151 BASE_SCHEMA = vol.Schema({vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string})
154 BASE_COMPONENT_SCHEMA = vol.Schema(
156 vol.Required(CONF_NAME): cv.string,
157 vol.Required(CONF_ADDRESS): cv.positive_int,
158 vol.Exclusive(CONF_DEVICE_ADDRESS,
"slave_addr"): cv.positive_int,
159 vol.Exclusive(CONF_SLAVE,
"slave_addr"): cv.positive_int,
161 CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
163 vol.Optional(CONF_UNIQUE_ID): cv.string,
168 BASE_STRUCT_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
170 vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_REGISTER_HOLDING): vol.In(
172 CALL_TYPE_REGISTER_HOLDING,
173 CALL_TYPE_REGISTER_INPUT,
176 vol.Optional(CONF_COUNT): cv.positive_int,
177 vol.Optional(CONF_DATA_TYPE, default=DataType.INT16): vol.In(
192 vol.Optional(CONF_STRUCTURE): cv.string,
193 vol.Optional(CONF_SCALE, default=1): vol.Coerce(float),
194 vol.Optional(CONF_OFFSET, default=0): vol.Coerce(float),
195 vol.Optional(CONF_PRECISION): cv.positive_int,
209 BASE_SWITCH_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
211 vol.Optional(CONF_WRITE_TYPE, default=CALL_TYPE_REGISTER_HOLDING): vol.In(
213 CALL_TYPE_REGISTER_HOLDING,
216 CALL_TYPE_X_REGISTER_HOLDINGS,
219 vol.Optional(CONF_COMMAND_OFF, default=0x00): cv.positive_int,
220 vol.Optional(CONF_COMMAND_ON, default=0x01): cv.positive_int,
221 vol.Optional(CONF_VERIFY): vol.Maybe(
223 vol.Optional(CONF_ADDRESS): cv.positive_int,
224 vol.Optional(CONF_INPUT_TYPE): vol.In(
226 CALL_TYPE_REGISTER_HOLDING,
228 CALL_TYPE_REGISTER_INPUT,
231 CALL_TYPE_X_REGISTER_HOLDINGS,
234 vol.Optional(CONF_STATE_OFF): vol.All(
235 cv.ensure_list, [cv.positive_int]
237 vol.Optional(CONF_STATE_ON): vol.All(cv.ensure_list, [cv.positive_int]),
238 vol.Optional(CONF_DELAY, default=0): cv.positive_int,
245 CLIMATE_SCHEMA = vol.All(
246 BASE_STRUCT_SCHEMA.extend(
248 vol.Required(CONF_TARGET_TEMP): hvac_fixedsize_reglist_validator,
249 vol.Optional(CONF_TARGET_TEMP_WRITE_REGISTERS, default=
False): cv.boolean,
250 vol.Optional(CONF_MAX_TEMP, default=35): vol.Coerce(float),
251 vol.Optional(CONF_MIN_TEMP, default=5): vol.Coerce(float),
252 vol.Optional(CONF_STEP, default=0.5): vol.Coerce(float),
253 vol.Optional(CONF_TEMPERATURE_UNIT, default=DEFAULT_TEMP_UNIT): cv.string,
254 vol.Optional(CONF_HVAC_ONOFF_REGISTER): cv.positive_int,
255 vol.Optional(CONF_WRITE_REGISTERS, default=
False): cv.boolean,
256 vol.Optional(CONF_HVAC_MODE_REGISTER): vol.Maybe(
258 CONF_ADDRESS: cv.positive_int,
259 CONF_HVAC_MODE_VALUES: {
260 vol.Optional(CONF_HVAC_MODE_OFF): vol.Any(
261 cv.positive_int, [cv.positive_int]
263 vol.Optional(CONF_HVAC_MODE_HEAT): vol.Any(
264 cv.positive_int, [cv.positive_int]
266 vol.Optional(CONF_HVAC_MODE_COOL): vol.Any(
267 cv.positive_int, [cv.positive_int]
269 vol.Optional(CONF_HVAC_MODE_HEAT_COOL): vol.Any(
270 cv.positive_int, [cv.positive_int]
272 vol.Optional(CONF_HVAC_MODE_AUTO): vol.Any(
273 cv.positive_int, [cv.positive_int]
275 vol.Optional(CONF_HVAC_MODE_DRY): vol.Any(
276 cv.positive_int, [cv.positive_int]
278 vol.Optional(CONF_HVAC_MODE_FAN_ONLY): vol.Any(
279 cv.positive_int, [cv.positive_int]
282 vol.Optional(CONF_WRITE_REGISTERS, default=
False): cv.boolean,
285 vol.Optional(CONF_FAN_MODE_REGISTER): vol.Maybe(
288 vol.Required(CONF_ADDRESS): register_int_list_validator,
289 CONF_FAN_MODE_VALUES: {
290 vol.Optional(CONF_FAN_MODE_ON): cv.positive_int,
291 vol.Optional(CONF_FAN_MODE_OFF): cv.positive_int,
292 vol.Optional(CONF_FAN_MODE_AUTO): cv.positive_int,
293 vol.Optional(CONF_FAN_MODE_LOW): cv.positive_int,
294 vol.Optional(CONF_FAN_MODE_MEDIUM): cv.positive_int,
295 vol.Optional(CONF_FAN_MODE_HIGH): cv.positive_int,
296 vol.Optional(CONF_FAN_MODE_TOP): cv.positive_int,
297 vol.Optional(CONF_FAN_MODE_MIDDLE): cv.positive_int,
298 vol.Optional(CONF_FAN_MODE_FOCUS): cv.positive_int,
299 vol.Optional(CONF_FAN_MODE_DIFFUSE): cv.positive_int,
302 duplicate_fan_mode_validator,
305 vol.Optional(CONF_SWING_MODE_REGISTER): vol.Maybe(
308 vol.Required(CONF_ADDRESS): register_int_list_validator,
309 CONF_SWING_MODE_VALUES: {
310 vol.Optional(CONF_SWING_MODE_SWING_ON): cv.positive_int,
311 vol.Optional(CONF_SWING_MODE_SWING_OFF): cv.positive_int,
312 vol.Optional(CONF_SWING_MODE_SWING_HORIZ): cv.positive_int,
313 vol.Optional(CONF_SWING_MODE_SWING_VERT): cv.positive_int,
314 vol.Optional(CONF_SWING_MODE_SWING_BOTH): cv.positive_int,
317 duplicate_swing_mode_validator,
324 COVERS_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
328 default=CALL_TYPE_REGISTER_HOLDING,
331 CALL_TYPE_REGISTER_HOLDING,
335 vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA,
336 vol.Optional(CONF_STATE_CLOSED, default=0): cv.positive_int,
337 vol.Optional(CONF_STATE_CLOSING, default=3): cv.positive_int,
338 vol.Optional(CONF_STATE_OPEN, default=1): cv.positive_int,
339 vol.Optional(CONF_STATE_OPENING, default=2): cv.positive_int,
340 vol.Optional(CONF_STATUS_REGISTER): cv.positive_int,
342 CONF_STATUS_REGISTER_TYPE,
343 default=CALL_TYPE_REGISTER_HOLDING,
344 ): vol.In([CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_INPUT]),
348 SWITCH_SCHEMA = BASE_SWITCH_SCHEMA.extend(
350 vol.Optional(CONF_DEVICE_CLASS): SWITCH_DEVICE_CLASSES_SCHEMA,
354 LIGHT_SCHEMA = BASE_SWITCH_SCHEMA.extend({})
356 FAN_SCHEMA = BASE_SWITCH_SCHEMA.extend({})
358 SENSOR_SCHEMA = vol.All(
359 BASE_STRUCT_SCHEMA.extend(
361 vol.Optional(CONF_DEVICE_CLASS): SENSOR_DEVICE_CLASSES_SCHEMA,
362 vol.Optional(CONF_STATE_CLASS): SENSOR_STATE_CLASSES_SCHEMA,
363 vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
364 vol.Exclusive(CONF_VIRTUAL_COUNT,
"vir_sen_count"): cv.positive_int,
365 vol.Exclusive(CONF_SLAVE_COUNT,
"vir_sen_count"): cv.positive_int,
366 vol.Optional(CONF_MIN_VALUE): vol.Coerce(float),
367 vol.Optional(CONF_MAX_VALUE): vol.Coerce(float),
368 vol.Optional(CONF_NAN_VALUE): nan_validator,
369 vol.Optional(CONF_ZERO_SUPPRESS): cv.positive_float,
374 BINARY_SENSOR_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
376 vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
377 vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_COIL): vol.In(
381 CALL_TYPE_REGISTER_HOLDING,
382 CALL_TYPE_REGISTER_INPUT,
385 vol.Exclusive(CONF_VIRTUAL_COUNT,
"vir_bin_count"): cv.positive_int,
386 vol.Exclusive(CONF_SLAVE_COUNT,
"vir_bin_count"): cv.positive_int,
390 MODBUS_SCHEMA = vol.Schema(
392 vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string,
393 vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
394 vol.Optional(CONF_DELAY, default=0): cv.positive_int,
395 vol.Optional(CONF_MSG_WAIT): cv.positive_int,
396 vol.Optional(CONF_BINARY_SENSORS): vol.All(
397 cv.ensure_list, [BINARY_SENSOR_SCHEMA]
399 vol.Optional(CONF_CLIMATES): vol.All(
400 cv.ensure_list, [vol.All(CLIMATE_SCHEMA, struct_validator)]
402 vol.Optional(CONF_COVERS): vol.All(cv.ensure_list, [COVERS_SCHEMA]),
403 vol.Optional(CONF_LIGHTS): vol.All(cv.ensure_list, [LIGHT_SCHEMA]),
404 vol.Optional(CONF_SENSORS): vol.All(
405 cv.ensure_list, [vol.All(SENSOR_SCHEMA, struct_validator)]
407 vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCH_SCHEMA]),
408 vol.Optional(CONF_FANS): vol.All(cv.ensure_list, [FAN_SCHEMA]),
412 SERIAL_SCHEMA = MODBUS_SCHEMA.extend(
414 vol.Required(CONF_TYPE): SERIAL,
415 vol.Required(CONF_BAUDRATE): cv.positive_int,
416 vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8),
417 vol.Required(CONF_METHOD): vol.Any(
"rtu",
"ascii"),
418 vol.Required(CONF_PORT): cv.string,
419 vol.Required(CONF_PARITY): vol.Any(
"E",
"O",
"N"),
420 vol.Required(CONF_STOPBITS): vol.Any(1, 2),
424 ETHERNET_SCHEMA = MODBUS_SCHEMA.extend(
426 vol.Required(CONF_HOST): cv.string,
427 vol.Required(CONF_PORT): cv.port,
428 vol.Required(CONF_TYPE): vol.Any(TCP, UDP, RTUOVERTCP),
432 CONFIG_SCHEMA = vol.Schema(
437 vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA),
441 extra=vol.ALLOW_EXTRA,
445 def get_hub(hass: HomeAssistant, name: str) -> ModbusHub:
446 """Return modbus hub with name."""
447 return cast(ModbusHub, hass.data[DOMAIN][name])
450 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
451 """Set up Modbus component."""
452 if DOMAIN
not in config:
461 """Release modbus resources."""
462 if DOMAIN
not in hass.data:
463 _LOGGER.error(
"Modbus cannot reload, because it was never loaded")
465 _LOGGER.debug(
"Modbus reloading")
466 hubs = hass.data[DOMAIN]
468 await hubs[name].async_close()
bool async_modbus_setup(HomeAssistant hass, ConfigType config)
ModbusHub get_hub(HomeAssistant hass, str name)
None async_reset_platform(HomeAssistant hass, str integration_name)
bool async_setup(HomeAssistant hass, ConfigType config)