From 5c8556f15a8e611f250c42a6587f507a0107ba9d Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Sat, 27 Oct 2018 19:28:32 +0800 Subject: [PATCH 01/10] modbus: support multiple modbus hub --- .../components/binary_sensor/modbus.py | 31 +- homeassistant/components/climate/modbus.py | 79 +++-- homeassistant/components/modbus.py | 269 ++++++++++-------- homeassistant/components/sensor/modbus.py | 193 ++++++++----- homeassistant/components/switch/modbus.py | 136 +++++---- 5 files changed, 413 insertions(+), 295 deletions(-) diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py index f9f2597593e64..b25d707f21e57 100644 --- a/homeassistant/components/binary_sensor/modbus.py +++ b/homeassistant/components/binary_sensor/modbus.py @@ -5,13 +5,15 @@ https://home-assistant.io/components/binary_sensor.modbus/ """ import logging + import voluptuous as vol from homeassistant.components import modbus -from homeassistant.const import CONF_NAME, CONF_SLAVE from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.helpers import config_validation as cv +from homeassistant.components.modbus import CONF_HUB_NAME from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME, CONF_SLAVE +from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] @@ -21,6 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COILS): [{ + vol.Required(CONF_HUB_NAME, default="default"): cv.string, vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_SLAVE): cv.positive_int @@ -32,18 +35,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Modbus binary sensors.""" sensors = [] for coil in config.get(CONF_COILS): - sensors.append(ModbusCoilSensor( - coil.get(CONF_NAME), - coil.get(CONF_SLAVE), - coil.get(CONF_COIL))) + sensors.append( + ModbusCoilSensor( + coil.get(CONF_HUB_NAME), coil.get(CONF_NAME), + coil.get(CONF_SLAVE), coil.get(CONF_COIL))) add_entities(sensors) class ModbusCoilSensor(BinarySensorDevice): """Modbus coil sensor.""" - def __init__(self, name, slave, coil): + def __init__(self, hub_name, name, slave, coil): """Initialize the modbus coil sensor.""" + self._hub_name = hub_name self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) @@ -59,13 +63,16 @@ def is_on(self): """Return the state of the sensor.""" return self._value + @property + def client(self) -> "BaseModbusClient": + """Find and return the client from modbus HUB.""" + return modbus.HUB[self._hub_name] + def update(self): """Update the state of the sensor.""" - result = modbus.HUB.read_coils(self._slave, self._coil, 1) + result = self.client.read_coils(self._slave, self._coil, 1) try: self._value = result.bits[0] except AttributeError: - _LOGGER.error( - 'No response from modbus slave %s coil %s', - self._slave, - self._coil) + _LOGGER.error('No response from modbus slave %s coil %s', + self._slave, self._coil) diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/climate/modbus.py index 1c5c03e4502a1..ce4915184bc94 100644 --- a/homeassistant/components/climate/modbus.py +++ b/homeassistant/components/climate/modbus.py @@ -13,13 +13,12 @@ import voluptuous as vol -from homeassistant.const import ( - CONF_NAME, CONF_SLAVE, ATTR_TEMPERATURE) -from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) - -from homeassistant.components import modbus import homeassistant.helpers.config_validation as cv +from homeassistant.components import modbus +from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE) +from homeassistant.components.modbus import CONF_HUB_NAME +from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, CONF_SLAVE DEPENDENCIES = ['modbus'] @@ -35,14 +34,22 @@ DATA_TYPE_FLOAT = 'float' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_SLAVE): cv.positive_int, - vol.Required(CONF_TARGET_TEMP): cv.positive_int, - vol.Required(CONF_CURRENT_TEMP): cv.positive_int, + vol.Required(CONF_HUB_NAME, default="default"): + cv.string, + vol.Required(CONF_NAME): + cv.string, + vol.Required(CONF_SLAVE): + cv.positive_int, + vol.Required(CONF_TARGET_TEMP): + cv.positive_int, + vol.Required(CONF_CURRENT_TEMP): + cv.positive_int, vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_FLOAT): vol.In([DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT]), - vol.Optional(CONF_COUNT, default=2): cv.positive_int, - vol.Optional(CONF_PRECISION, default=1): cv.positive_int + vol.Optional(CONF_COUNT, default=2): + cv.positive_int, + vol.Optional(CONF_PRECISION, default=1): + cv.positive_int }) _LOGGER = logging.getLogger(__name__) @@ -59,18 +66,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data_type = config.get(CONF_DATA_TYPE) count = config.get(CONF_COUNT) precision = config.get(CONF_PRECISION) + hub_name = config.get(CONF_HUB_NAME) - add_entities([ModbusThermostat(name, modbus_slave, - target_temp_register, current_temp_register, - data_type, count, precision)], True) + add_entities([ + ModbusThermostat(hub_name, name, modbus_slave, target_temp_register, + current_temp_register, data_type, count, precision) + ], True) class ModbusThermostat(ClimateDevice): """Representation of a Modbus Thermostat.""" - def __init__(self, name, modbus_slave, target_temp_register, + def __init__(self, hub_name, name, modbus_slave, target_temp_register, current_temp_register, data_type, count, precision): """Initialize the unit.""" + self._hub_name = hub_name self._name = name self._slave = modbus_slave self._target_temperature_register = target_temp_register @@ -82,12 +92,26 @@ def __init__(self, name, modbus_slave, target_temp_register, self._precision = precision self._structure = '>f' - data_types = {DATA_TYPE_INT: {1: 'h', 2: 'i', 4: 'q'}, - DATA_TYPE_UINT: {1: 'H', 2: 'I', 4: 'Q'}, - DATA_TYPE_FLOAT: {1: 'e', 2: 'f', 4: 'd'}} - - self._structure = '>{}'.format(data_types[self._data_type] - [self._count]) + data_types = { + DATA_TYPE_INT: { + 1: 'h', + 2: 'i', + 4: 'q' + }, + DATA_TYPE_UINT: { + 1: 'H', + 2: 'I', + 4: 'Q' + }, + DATA_TYPE_FLOAT: { + 1: 'e', + 2: 'f', + 4: 'd' + } + } + + self._structure = '>{}'.format( + data_types[self._data_type][self._count]) @property def supported_features(self): @@ -116,6 +140,11 @@ def target_temperature(self): """Return the temperature we try to reach.""" return self._target_temperature + @property + def client(self) -> "BaseModbusClient": + """Find and return the client from modbus HUB.""" + return modbus.HUB[self._hub_name] + def set_temperature(self, **kwargs): """Set new target temperature.""" target_temperature = kwargs.get(ATTR_TEMPERATURE) @@ -133,8 +162,8 @@ def set_temperature(self, **kwargs): def read_register(self, register): """Read holding register using the modbus hub slave.""" try: - result = modbus.HUB.read_holding_registers(self._slave, register, - self._count) + result = self.client.read_holding_registers( + self._slave, register, self._count) except AttributeError as ex: _LOGGER.error(ex) byte_string = b''.join( @@ -145,4 +174,4 @@ def read_register(self, register): def write_register(self, register, value): """Write register using the modbus hub slave.""" - modbus.HUB.write_registers(self._slave, register, [value, 0]) + self.client.write_registers(self._slave, register, [value, 0]) diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index c8d71af71b445..cb3b39e219125 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -6,151 +6,199 @@ """ import logging import threading +from typing import TYPE_CHECKING, Any, Dict, List import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - CONF_HOST, CONF_METHOD, CONF_PORT, CONF_TYPE, CONF_TIMEOUT, ATTR_STATE) + ATTR_STATE, CONF_HOST, CONF_METHOD, CONF_PORT, CONF_TIMEOUT, CONF_TYPE, + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) -DOMAIN = 'modbus' +if TYPE_CHECKING: + from pymodbus.client.sync import BaseModbusClient -REQUIREMENTS = ['pymodbus==1.3.1'] +DOMAIN = "modbus" + +REQUIREMENTS = ["pymodbus==1.3.1"] # Type of network -CONF_BAUDRATE = 'baudrate' -CONF_BYTESIZE = 'bytesize' -CONF_STOPBITS = 'stopbits' -CONF_PARITY = 'parity' +CONF_BAUDRATE = "baudrate" +CONF_BYTESIZE = "bytesize" +CONF_STOPBITS = "stopbits" +CONF_PARITY = "parity" +CONF_HUB_NAME = "hub_name" + +BASE_SCHEMA = vol.Schema({ + vol.Optional(CONF_HUB_NAME, default="default"): cv.string +}) -SERIAL_SCHEMA = { +SERIAL_SCHEMA = BASE_SCHEMA.extend({ vol.Required(CONF_BAUDRATE): cv.positive_int, vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8), - vol.Required(CONF_METHOD): vol.Any('rtu', 'ascii'), + vol.Required(CONF_METHOD): vol.Any("rtu", "ascii"), vol.Required(CONF_PORT): cv.string, - vol.Required(CONF_PARITY): vol.Any('E', 'O', 'N'), + vol.Required(CONF_PARITY): vol.Any("E", "O", "N"), vol.Required(CONF_STOPBITS): vol.Any(1, 2), - vol.Required(CONF_TYPE): 'serial', + vol.Required(CONF_TYPE): "serial", vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, -} +}) -ETHERNET_SCHEMA = { +ETHERNET_SCHEMA = BASE_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port, - vol.Required(CONF_TYPE): vol.Any('tcp', 'udp', 'rtuovertcp'), + vol.Required(CONF_TYPE): vol.Any("tcp", "udp", "rtuovertcp"), vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, -} +}) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA) -}, extra=vol.ALLOW_EXTRA) +def check_base_on_type(value: Any) -> Any: + """Check modbus component schema base on "type".""" + if value[CONF_TYPE] == "serial": + return SERIAL_SCHEMA(value) + if value[CONF_TYPE] in ("tcp", "udp", "rtuovertcp"): + return ETHERNET_SCHEMA(value) + raise vol.Invalid("%s %s is not supported" % (CONF_TYPE, value[CONF_TYPE])) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: + vol.All(cv.ensure_list, + vol.Schema([vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)])) + }, + extra=vol.ALLOW_EXTRA, +) _LOGGER = logging.getLogger(__name__) -SERVICE_WRITE_REGISTER = 'write_register' -SERVICE_WRITE_COIL = 'write_coil' +SERVICE_WRITE_REGISTER = "write_register" +SERVICE_WRITE_COIL = "write_coil" -ATTR_ADDRESS = 'address' -ATTR_UNIT = 'unit' -ATTR_VALUE = 'value' +ATTR_ADDRESS = "address" +ATTR_UNIT = "unit" +ATTR_VALUE = "value" SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema({ + vol.Optional(CONF_HUB_NAME, default="default"): cv.string, vol.Required(ATTR_UNIT): cv.positive_int, vol.Required(ATTR_ADDRESS): cv.positive_int, - vol.Required(ATTR_VALUE): vol.All(cv.ensure_list, [cv.positive_int]) + vol.Required(ATTR_VALUE): vol.All(cv.ensure_list, [cv.positive_int]), }) SERVICE_WRITE_COIL_SCHEMA = vol.Schema({ + vol.Optional(CONF_HUB_NAME, default="default"): cv.string, vol.Required(ATTR_UNIT): cv.positive_int, vol.Required(ATTR_ADDRESS): cv.positive_int, - vol.Required(ATTR_STATE): cv.boolean + vol.Required(ATTR_STATE): cv.boolean, }) -HUB = None +HUB = {} # type: Dict[str, BaseModbusClient] -def setup(hass, config): - """Set up Modbus component.""" - # Modbus connection type - client_type = config[DOMAIN][CONF_TYPE] +def setup_client(client_config: dict) -> "BaseModbusClient": + """Setup pymodbus client.""" + from pymodbus.client.sync import ( + ModbusTcpClient, + ModbusUdpClient, + ModbusSerialClient, + ) + from pymodbus.transaction import ModbusRtuFramer + + client_type = client_config[CONF_TYPE] # Connect to Modbus network # pylint: disable=import-error - if client_type == 'serial': - from pymodbus.client.sync import ModbusSerialClient as ModbusClient - client = ModbusClient(method=config[DOMAIN][CONF_METHOD], - port=config[DOMAIN][CONF_PORT], - baudrate=config[DOMAIN][CONF_BAUDRATE], - stopbits=config[DOMAIN][CONF_STOPBITS], - bytesize=config[DOMAIN][CONF_BYTESIZE], - parity=config[DOMAIN][CONF_PARITY], - timeout=config[DOMAIN][CONF_TIMEOUT]) - elif client_type == 'rtuovertcp': - from pymodbus.client.sync import ModbusTcpClient as ModbusClient - from pymodbus.transaction import ModbusRtuFramer as ModbusFramer - client = ModbusClient(host=config[DOMAIN][CONF_HOST], - port=config[DOMAIN][CONF_PORT], - framer=ModbusFramer, - timeout=config[DOMAIN][CONF_TIMEOUT]) - elif client_type == 'tcp': - from pymodbus.client.sync import ModbusTcpClient as ModbusClient - client = ModbusClient(host=config[DOMAIN][CONF_HOST], - port=config[DOMAIN][CONF_PORT], - timeout=config[DOMAIN][CONF_TIMEOUT]) - elif client_type == 'udp': - from pymodbus.client.sync import ModbusUdpClient as ModbusClient - client = ModbusClient(host=config[DOMAIN][CONF_HOST], - port=config[DOMAIN][CONF_PORT], - timeout=config[DOMAIN][CONF_TIMEOUT]) - else: - return False - + if client_type == "serial": + + return ModbusSerialClient( + method=client_config[CONF_METHOD], + port=client_config[CONF_PORT], + baudrate=client_config[CONF_BAUDRATE], + stopbits=client_config[CONF_STOPBITS], + bytesize=client_config[CONF_BYTESIZE], + parity=client_config[CONF_PARITY], + timeout=client_config[CONF_TIMEOUT], + ) + if client_type == "rtuovertcp": + + return ModbusTcpClient( + host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + framer=ModbusRtuFramer, + timeout=client_config[CONF_TIMEOUT], + ) + if client_type == "tcp": + return ModbusTcpClient( + host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + timeout=client_config[CONF_TIMEOUT], + ) + if client_type == "udp": + return ModbusUdpClient( + host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + timeout=client_config[CONF_TIMEOUT], + ) + + assert False + + +def setup(hass: Any, config: dict) -> bool: + """Set up Modbus component.""" + # Modbus connection type global HUB - HUB = ModbusHub(client) - def stop_modbus(event): + for client_config in config[DOMAIN]: + client = setup_client(client_config) + client_name = client_config[CONF_HUB_NAME] + HUB[client_name] = ModbusHub(client) + + def stop_modbus(event: Any) -> None: """Stop Modbus service.""" - HUB.close() + for client in HUB.values(): + client.close() - def start_modbus(event): + def start_modbus(event: Any) -> None: """Start Modbus service.""" - HUB.connect() + for client in HUB.values(): + client.connect() + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) # Register services for modbus hass.services.register( - DOMAIN, SERVICE_WRITE_REGISTER, write_register, - schema=SERVICE_WRITE_REGISTER_SCHEMA) + DOMAIN, + SERVICE_WRITE_REGISTER, + write_register, + schema=SERVICE_WRITE_REGISTER_SCHEMA, + ) hass.services.register( - DOMAIN, SERVICE_WRITE_COIL, write_coil, + DOMAIN, + SERVICE_WRITE_COIL, + write_coil, schema=SERVICE_WRITE_COIL_SCHEMA) - def write_register(service): + def write_register(service: Any) -> None: """Write modbus registers.""" unit = int(float(service.data.get(ATTR_UNIT))) address = int(float(service.data.get(ATTR_ADDRESS))) value = service.data.get(ATTR_VALUE) + client_name = service.data.get(CONF_HUB_NAME) if isinstance(value, list): - HUB.write_registers( - unit, - address, - [int(float(i)) for i in value]) + HUB[client_name].write_registers(unit, address, + [int(float(i)) for i in value]) else: - HUB.write_register( - unit, - address, - int(float(value))) + HUB[client_name].write_register(unit, address, int(float(value))) - def write_coil(service): + def write_coil(service: Any) -> None: """Write modbus coil.""" unit = service.data.get(ATTR_UNIT) address = service.data.get(ATTR_ADDRESS) state = service.data.get(ATTR_STATE) - HUB.write_coil(unit, address, state) + client_name = service.data.get(CONF_HUB_NAME) + HUB[client_name].write_coil(unit, address, state) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_modbus) @@ -160,71 +208,56 @@ def write_coil(service): class ModbusHub: """Thread safe wrapper class for pymodbus.""" - def __init__(self, modbus_client): + def __init__(self, modbus_client: "BaseModbusClient") -> None: """Initialize the modbus hub.""" self._client = modbus_client self._lock = threading.Lock() - def close(self): + def close(self) -> None: """Disconnect client.""" with self._lock: self._client.close() - def connect(self): + def connect(self) -> None: """Connect client.""" with self._lock: self._client.connect() - def read_coils(self, unit, address, count): + def read_coils(self, unit: int, address: int, count: int) -> Any: """Read coils.""" with self._lock: - kwargs = {'unit': unit} if unit else {} - return self._client.read_coils( - address, - count, - **kwargs) + kwargs = {"unit": unit} if unit else {} + return self._client.read_coils(address, count, **kwargs) - def read_input_registers(self, unit, address, count): + def read_input_registers(self, unit: int, address: int, count: int) -> Any: """Read input registers.""" with self._lock: - kwargs = {'unit': unit} if unit else {} - return self._client.read_input_registers( - address, - count, - **kwargs) + kwargs = {"unit": unit} if unit else {} + return self._client.read_input_registers(address, count, **kwargs) - def read_holding_registers(self, unit, address, count): + def read_holding_registers(self, unit: int, address: int, + count: int) -> Any: """Read holding registers.""" with self._lock: - kwargs = {'unit': unit} if unit else {} - return self._client.read_holding_registers( - address, - count, - **kwargs) + kwargs = {"unit": unit} if unit else {} + return self._client.read_holding_registers(address, count, + **kwargs) - def write_coil(self, unit, address, value): + def write_coil(self, unit: int, address: int, value: int) -> Any: """Write coil.""" with self._lock: - kwargs = {'unit': unit} if unit else {} - self._client.write_coil( - address, - value, - **kwargs) + kwargs = {"unit": unit} if unit else {} + self._client.write_coil(address, value, **kwargs) - def write_register(self, unit, address, value): + def write_register(self, unit: int, address: int, value: int) -> Any: """Write register.""" with self._lock: - kwargs = {'unit': unit} if unit else {} - self._client.write_register( - address, - value, - **kwargs) + kwargs = {"unit": unit} if unit else {} + self._client.write_register(address, value, **kwargs) - def write_registers(self, unit, address, values): + def write_registers(self, unit: int, address: int, + values: List[int]) -> Any: """Write registers.""" with self._lock: - kwargs = {'unit': unit} if unit else {} - self._client.write_registers( - address, - values, - **kwargs) + kwargs = {"unit": unit} if unit else {} + self._client.write_registers(address, values, **kwargs) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 833cb0c5a628e..98c7bd5394435 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -6,75 +6,104 @@ """ import logging import struct +from typing import TYPE_CHECKING, Any import voluptuous as vol from homeassistant.components import modbus +from homeassistant.components.modbus import CONF_HUB_NAME +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT, CONF_SLAVE, - CONF_STRUCTURE) -from homeassistant.helpers.entity import Entity + CONF_NAME, + CONF_OFFSET, + CONF_SLAVE, + CONF_STRUCTURE, + CONF_UNIT_OF_MEASUREMENT, +) from homeassistant.helpers import config_validation as cv -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.helpers.entity import Entity + +if TYPE_CHECKING: + from pymodbus.client.sync import BaseModbusClient _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['modbus'] +DEPENDENCIES = ["modbus"] -CONF_COUNT = 'count' -CONF_REVERSE_ORDER = 'reverse_order' -CONF_PRECISION = 'precision' -CONF_REGISTER = 'register' -CONF_REGISTERS = 'registers' -CONF_SCALE = 'scale' -CONF_DATA_TYPE = 'data_type' -CONF_REGISTER_TYPE = 'register_type' +CONF_COUNT = "count" +CONF_REVERSE_ORDER = "reverse_order" +CONF_PRECISION = "precision" +CONF_REGISTER = "register" +CONF_REGISTERS = "registers" +CONF_SCALE = "scale" +CONF_DATA_TYPE = "data_type" +CONF_REGISTER_TYPE = "register_type" -REGISTER_TYPE_HOLDING = 'holding' -REGISTER_TYPE_INPUT = 'input' +REGISTER_TYPE_HOLDING = "holding" +REGISTER_TYPE_INPUT = "input" -DATA_TYPE_INT = 'int' -DATA_TYPE_UINT = 'uint' -DATA_TYPE_FLOAT = 'float' -DATA_TYPE_CUSTOM = 'custom' +DATA_TYPE_INT = "int" +DATA_TYPE_UINT = "uint" +DATA_TYPE_FLOAT = "float" +DATA_TYPE_CUSTOM = "custom" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_REGISTERS): [{ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_REGISTER): cv.positive_int, + vol.Required(CONF_HUB_NAME, default="default"): + cv.string, + vol.Required(CONF_NAME): + cv.string, + vol.Required(CONF_REGISTER): + cv.positive_int, vol.Optional(CONF_REGISTER_TYPE, default=REGISTER_TYPE_HOLDING): vol.In([REGISTER_TYPE_HOLDING, REGISTER_TYPE_INPUT]), - vol.Optional(CONF_COUNT, default=1): cv.positive_int, - vol.Optional(CONF_REVERSE_ORDER, default=False): cv.boolean, - vol.Optional(CONF_OFFSET, default=0): vol.Coerce(float), - vol.Optional(CONF_PRECISION, default=0): cv.positive_int, - vol.Optional(CONF_SCALE, default=1): vol.Coerce(float), - vol.Optional(CONF_SLAVE): cv.positive_int, + vol.Optional(CONF_COUNT, default=1): + cv.positive_int, + vol.Optional(CONF_REVERSE_ORDER, default=False): + cv.boolean, + vol.Optional(CONF_OFFSET, default=0): + vol.Coerce(float), + vol.Optional(CONF_PRECISION, default=0): + cv.positive_int, + vol.Optional(CONF_SCALE, default=1): + vol.Coerce(float), + vol.Optional(CONF_SLAVE): + cv.positive_int, vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_INT): - vol.In([DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, - DATA_TYPE_CUSTOM]), - vol.Optional(CONF_STRUCTURE): cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string + vol.In([ + DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, + DATA_TYPE_CUSTOM + ]), + vol.Optional(CONF_STRUCTURE): + cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): + cv.string, }] }) -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform(hass: Any, + config: dict, + add_entities: Any, + discovery_info: Any = None) -> bool: """Set up the Modbus sensors.""" sensors = [] - data_types = {DATA_TYPE_INT: {1: 'h', 2: 'i', 4: 'q'}} - data_types[DATA_TYPE_UINT] = {1: 'H', 2: 'I', 4: 'Q'} - data_types[DATA_TYPE_FLOAT] = {1: 'e', 2: 'f', 4: 'd'} + data_types = {DATA_TYPE_INT: {1: "h", 2: "i", 4: "q"}} + data_types[DATA_TYPE_UINT] = {1: "H", 2: "I", 4: "Q"} + data_types[DATA_TYPE_FLOAT] = {1: "e", 2: "f", 4: "d"} for register in config.get(CONF_REGISTERS): - structure = '>i' + structure = ">i" if register.get(CONF_DATA_TYPE) != DATA_TYPE_CUSTOM: try: - structure = '>{}'.format(data_types[ - register.get(CONF_DATA_TYPE)][register.get(CONF_COUNT)]) + structure = ">{}".format(data_types[register.get( + CONF_DATA_TYPE)][register.get(CONF_COUNT)]) except KeyError: - _LOGGER.error("Unable to detect data type for %s sensor, " - "try a custom type.", register.get(CONF_NAME)) + _LOGGER.error( + "Unable to detect data type for %s sensor, " + "try a custom type.", + register.get(CONF_NAME), + ) continue else: structure = register.get(CONF_STRUCTURE) @@ -82,42 +111,49 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: size = struct.calcsize(structure) except struct.error as err: - _LOGGER.error( - "Error in sensor %s structure: %s", - register.get(CONF_NAME), err) + _LOGGER.error("Error in sensor %s structure: %s", + register.get(CONF_NAME), err) continue if register.get(CONF_COUNT) * 2 != size: _LOGGER.error( "Structure size (%d bytes) mismatch registers count " - "(%d words)", size, register.get(CONF_COUNT)) + "(%d words)", + size, + register.get(CONF_COUNT), + ) continue - sensors.append(ModbusRegisterSensor( - register.get(CONF_NAME), - register.get(CONF_SLAVE), - register.get(CONF_REGISTER), - register.get(CONF_REGISTER_TYPE), - register.get(CONF_UNIT_OF_MEASUREMENT), - register.get(CONF_COUNT), - register.get(CONF_REVERSE_ORDER), - register.get(CONF_SCALE), - register.get(CONF_OFFSET), - structure, - register.get(CONF_PRECISION))) + sensors.append( + ModbusRegisterSensor( + register.get(CONF_HUB_NAME), + register.get(CONF_NAME), + register.get(CONF_SLAVE), + register.get(CONF_REGISTER), + register.get(CONF_REGISTER_TYPE), + register.get(CONF_UNIT_OF_MEASUREMENT), + register.get(CONF_COUNT), + register.get(CONF_REVERSE_ORDER), + register.get(CONF_SCALE), + register.get(CONF_OFFSET), + structure, + register.get(CONF_PRECISION), + )) if not sensors: return False add_entities(sensors) + return True class ModbusRegisterSensor(Entity): """Modbus register sensor.""" - def __init__(self, name, slave, register, register_type, + def __init__(self, hub_name, name, slave, register, register_type, unit_of_measurement, count, reverse_order, scale, offset, structure, precision): """Initialize the modbus register sensor.""" + self._hub_name = hub_name self._name = name self._slave = int(slave) if slave else None self._register = int(register) @@ -129,35 +165,36 @@ def __init__(self, name, slave, register, register_type, self._offset = offset self._precision = precision self._structure = structure - self._value = None + self._value: str = None @property - def state(self): + def state(self) -> str: """Return the state of the sensor.""" return self._value @property - def name(self): + def name(self) -> str: """Return the name of the sensor.""" return self._name @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit of measurement.""" return self._unit_of_measurement - def update(self): + @property + def client(self) -> "BaseModbusClient": + """Find and return the client from modbus HUB.""" + return modbus.HUB[self._hub_name] + + def update(self) -> None: """Update the state of the sensor.""" if self._register_type == REGISTER_TYPE_INPUT: - result = modbus.HUB.read_input_registers( - self._slave, - self._register, - self._count) + result = self.client.read_input_registers( + self._slave, self._register, self._count) else: - result = modbus.HUB.read_holding_registers( - self._slave, - self._register, - self._count) + result = self.client.read_holding_registers( + self._slave, self._register, self._count) val = 0 try: @@ -165,12 +202,14 @@ def update(self): if self._reverse_order: registers.reverse() except AttributeError: - _LOGGER.error("No response from modbus slave %s, register %s", - self._slave, self._register) + _LOGGER.error( + "No response from modbus slave %s, register %s", + self._slave, + self._register, + ) return - byte_string = b''.join( - [x.to_bytes(2, byteorder='big') for x in registers] - ) + byte_string = b"".join( + [x.to_bytes(2, byteorder="big") for x in registers]) val = struct.unpack(self._structure, byte_string)[0] - self._value = format( - self._scale * val + self._offset, '.{}f'.format(self._precision)) + self._value = format(self._scale * val + self._offset, + ".{}f".format(self._precision)) diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index a8c8358f0cf19..28ed7f39f525c 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -5,14 +5,16 @@ https://home-assistant.io/components/switch.modbus/ """ import logging + import voluptuous as vol from homeassistant.components import modbus -from homeassistant.const import ( - CONF_NAME, CONF_SLAVE, CONF_COMMAND_ON, CONF_COMMAND_OFF) -from homeassistant.helpers.entity import ToggleEntity -from homeassistant.helpers import config_validation as cv +from homeassistant.components.modbus import CONF_HUB_NAME from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_NAME, + CONF_SLAVE) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] @@ -31,21 +33,32 @@ REGISTER_TYPE_INPUT = 'input' REGISTERS_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_SLAVE): cv.positive_int, - vol.Required(CONF_REGISTER): cv.positive_int, - vol.Required(CONF_COMMAND_ON): cv.positive_int, - vol.Required(CONF_COMMAND_OFF): cv.positive_int, - vol.Optional(CONF_VERIFY_STATE, default=True): cv.boolean, + vol.Required(CONF_HUB_NAME, default="default"): + cv.string, + vol.Required(CONF_NAME): + cv.string, + vol.Optional(CONF_SLAVE): + cv.positive_int, + vol.Required(CONF_REGISTER): + cv.positive_int, + vol.Required(CONF_COMMAND_ON): + cv.positive_int, + vol.Required(CONF_COMMAND_OFF): + cv.positive_int, + vol.Optional(CONF_VERIFY_STATE, default=True): + cv.boolean, vol.Optional(CONF_VERIFY_REGISTER): cv.positive_int, vol.Optional(CONF_REGISTER_TYPE, default=REGISTER_TYPE_HOLDING): vol.In([REGISTER_TYPE_HOLDING, REGISTER_TYPE_INPUT]), - vol.Optional(CONF_STATE_ON): cv.positive_int, - vol.Optional(CONF_STATE_OFF): cv.positive_int, + vol.Optional(CONF_STATE_ON): + cv.positive_int, + vol.Optional(CONF_STATE_OFF): + cv.positive_int, }) COILS_SCHEMA = vol.Schema({ + vol.Required(CONF_HUB_NAME, default="default"): cv.string, vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_NAME): cv.string, vol.Required(CONF_SLAVE): cv.positive_int, @@ -64,31 +77,31 @@ def setup_platform(hass, config, add_entities, discovery_info=None): switches = [] if CONF_COILS in config: for coil in config.get(CONF_COILS): - switches.append(ModbusCoilSwitch( - coil.get(CONF_NAME), - coil.get(CONF_SLAVE), - coil.get(CONF_COIL))) + switches.append( + ModbusCoilSwitch( + coil.get(CONF_HUB_NAME), coil.get(CONF_NAME), + coil.get(CONF_SLAVE), coil.get(CONF_COIL))) if CONF_REGISTERS in config: for register in config.get(CONF_REGISTERS): - switches.append(ModbusRegisterSwitch( - register.get(CONF_NAME), - register.get(CONF_SLAVE), - register.get(CONF_REGISTER), - register.get(CONF_COMMAND_ON), - register.get(CONF_COMMAND_OFF), - register.get(CONF_VERIFY_STATE), - register.get(CONF_VERIFY_REGISTER), - register.get(CONF_REGISTER_TYPE), - register.get(CONF_STATE_ON), - register.get(CONF_STATE_OFF))) + switches.append( + ModbusRegisterSwitch( + register.get(CONF_HUB_NAME), register.get(CONF_NAME), + register.get(CONF_SLAVE), register.get(CONF_REGISTER), + register.get(CONF_COMMAND_ON), + register.get(CONF_COMMAND_OFF), + register.get(CONF_VERIFY_STATE), + register.get(CONF_VERIFY_REGISTER), + register.get(CONF_REGISTER_TYPE), + register.get(CONF_STATE_ON), register.get(CONF_STATE_OFF))) add_entities(switches) class ModbusCoilSwitch(ToggleEntity): """Representation of a Modbus coil switch.""" - def __init__(self, name, slave, coil): + def __init__(self, hub_name, name, slave, coil): """Initialize the coil switch.""" + self._hub_name = hub_name self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) @@ -104,42 +117,46 @@ def name(self): """Return the name of the switch.""" return self._name + @property + def client(self) -> "BaseModbusClient": + """Find and return the client from modbus HUB.""" + return modbus.HUB[self._hub_name] + def turn_on(self, **kwargs): """Set switch on.""" - modbus.HUB.write_coil(self._slave, self._coil, True) + self.client.write_coil(self._slave, self._coil, True) def turn_off(self, **kwargs): """Set switch off.""" - modbus.HUB.write_coil(self._slave, self._coil, False) + self.client.write_coil(self._slave, self._coil, False) def update(self): """Update the state of the switch.""" - result = modbus.HUB.read_coils(self._slave, self._coil, 1) + result = self.client.read_coils(self._slave, self._coil, 1) try: self._is_on = bool(result.bits[0]) except AttributeError: - _LOGGER.error( - 'No response from modbus slave %s coil %s', - self._slave, - self._coil) + _LOGGER.error('No response from modbus slave %s coil %s', + self._slave, self._coil) class ModbusRegisterSwitch(ModbusCoilSwitch): """Representation of a Modbus register switch.""" # pylint: disable=super-init-not-called - def __init__(self, name, slave, register, command_on, - command_off, verify_state, verify_register, - register_type, state_on, state_off): + def __init__(self, hub_name, name, slave, register, command_on, + command_off, verify_state, verify_register, register_type, + state_on, state_off): """Initialize the register switch.""" + self._hub_name = hub_name self._name = name self._slave = slave self._register = register self._command_on = command_on self._command_off = command_off self._verify_state = verify_state - self._verify_register = ( - verify_register if verify_register else self._register) + self._verify_register = (verify_register + if verify_register else self._register) self._register_type = register_type if state_on is not None: @@ -154,21 +171,22 @@ def __init__(self, name, slave, register, command_on, self._is_on = None + @property + def client(self) -> "BaseModbusClient": + """Find and return the client from modbus HUB.""" + return modbus.HUB[self._hub_name] + def turn_on(self, **kwargs): """Set switch on.""" - modbus.HUB.write_register( - self._slave, - self._register, - self._command_on) + self.client.write_register(self._slave, self._register, + self._command_on) if not self._verify_state: self._is_on = True def turn_off(self, **kwargs): """Set switch off.""" - modbus.HUB.write_register( - self._slave, - self._register, - self._command_off) + self.client.write_register(self._slave, self._register, + self._command_off) if not self._verify_state: self._is_on = False @@ -179,23 +197,17 @@ def update(self): value = 0 if self._register_type == REGISTER_TYPE_INPUT: - result = modbus.HUB.read_input_registers( - self._slave, - self._register, - 1) + result = self.client.read_input_registers(self._slave, + self._register, 1) else: - result = modbus.HUB.read_holding_registers( - self._slave, - self._register, - 1) + result = self.client.read_holding_registers( + self._slave, self._register, 1) try: value = int(result.registers[0]) except AttributeError: - _LOGGER.error( - 'No response from modbus slave %s register %s', - self._slave, - self._verify_register) + _LOGGER.error('No response from modbus slave %s register %s', + self._slave, self._verify_register) if value == self._state_on: self._is_on = True @@ -204,7 +216,5 @@ def update(self): else: _LOGGER.error( 'Unexpected response from modbus slave %s ' - 'register %s, got 0x%2x', - self._slave, - self._verify_register, + 'register %s, got 0x%2x', self._slave, self._verify_register, value) From 4cbad0f6cae8e8593d229bb982863f73eff5af67 Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Sat, 27 Oct 2018 21:00:23 +0800 Subject: [PATCH 02/10] update data after entities added --- homeassistant/components/binary_sensor/modbus.py | 2 +- homeassistant/components/sensor/modbus.py | 2 +- homeassistant/components/switch/modbus.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py index b25d707f21e57..5633551bae432 100644 --- a/homeassistant/components/binary_sensor/modbus.py +++ b/homeassistant/components/binary_sensor/modbus.py @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ModbusCoilSensor( coil.get(CONF_HUB_NAME), coil.get(CONF_NAME), coil.get(CONF_SLAVE), coil.get(CONF_COIL))) - add_entities(sensors) + add_entities(sensors, True) class ModbusCoilSensor(BinarySensorDevice): diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 98c7bd5394435..20edb4fd69060 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -142,7 +142,7 @@ def setup_platform(hass: Any, if not sensors: return False - add_entities(sensors) + add_entities(sensors, True) return True diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index 28ed7f39f525c..259edcc17d9e7 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -93,7 +93,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): register.get(CONF_VERIFY_REGISTER), register.get(CONF_REGISTER_TYPE), register.get(CONF_STATE_ON), register.get(CONF_STATE_OFF))) - add_entities(switches) + add_entities(switches, True) class ModbusCoilSwitch(ToggleEntity): From 5c6749e59cac12ca45177d47802bea365c602fbb Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Sun, 28 Oct 2018 09:22:36 +0800 Subject: [PATCH 03/10] pass hub object to each entity. and save hub to hass.data but not in module level --- .../components/binary_sensor/modbus.py | 30 +++---- homeassistant/components/climate/modbus.py | 34 ++++---- homeassistant/components/modbus.py | 23 ++--- homeassistant/components/sensor/modbus.py | 36 ++++---- homeassistant/components/switch/modbus.py | 84 +++++++++---------- 5 files changed, 99 insertions(+), 108 deletions(-) diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py index 5633551bae432..494f2941e9ad5 100644 --- a/homeassistant/components/binary_sensor/modbus.py +++ b/homeassistant/components/binary_sensor/modbus.py @@ -5,16 +5,20 @@ https://home-assistant.io/components/binary_sensor.modbus/ """ import logging +from typing import TYPE_CHECKING import voluptuous as vol -from homeassistant.components import modbus from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.modbus import CONF_HUB_NAME +from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_SLAVE from homeassistant.helpers import config_validation as cv +if TYPE_CHECKING: + # pylint: disable=unused-import + from pymodbus.client.sync import BaseModbusClient + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] @@ -31,23 +35,24 @@ }) -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Modbus binary sensors.""" sensors = [] for coil in config.get(CONF_COILS): + hub_name = coil.get(CONF_HUB_NAME) + hub = hass.data[DOMAIN][hub_name] sensors.append( - ModbusCoilSensor( - coil.get(CONF_HUB_NAME), coil.get(CONF_NAME), - coil.get(CONF_SLAVE), coil.get(CONF_COIL))) - add_entities(sensors, True) + ModbusCoilSensor(hub, coil.get(CONF_NAME), coil.get(CONF_SLAVE), + coil.get(CONF_COIL))) + add_devices(sensors) class ModbusCoilSensor(BinarySensorDevice): """Modbus coil sensor.""" - def __init__(self, hub_name, name, slave, coil): + def __init__(self, hub, name, slave, coil): """Initialize the modbus coil sensor.""" - self._hub_name = hub_name + self._hub: "BaseModbusClient" = hub self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) @@ -63,14 +68,9 @@ def is_on(self): """Return the state of the sensor.""" return self._value - @property - def client(self) -> "BaseModbusClient": - """Find and return the client from modbus HUB.""" - return modbus.HUB[self._hub_name] - def update(self): """Update the state of the sensor.""" - result = self.client.read_coils(self._slave, self._coil, 1) + result = self._hub.read_coils(self._slave, self._coil, 1) try: self._value = result.bits[0] except AttributeError: diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/climate/modbus.py index ce4915184bc94..a6584dbc50f0f 100644 --- a/homeassistant/components/climate/modbus.py +++ b/homeassistant/components/climate/modbus.py @@ -10,16 +10,20 @@ """ import logging import struct +from typing import TYPE_CHECKING import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components import modbus -from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA, - SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.modbus import CONF_HUB_NAME +from homeassistant.components.climate import ( + PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) +from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, CONF_SLAVE +if TYPE_CHECKING: + # pylint: disable=unused-import + from pymodbus.client.sync import BaseModbusClient + DEPENDENCIES = ['modbus'] # Parameters not defined by homeassistant.const @@ -57,7 +61,7 @@ SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Modbus Thermostat Platform.""" name = config.get(CONF_NAME) modbus_slave = config.get(CONF_SLAVE) @@ -67,9 +71,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): count = config.get(CONF_COUNT) precision = config.get(CONF_PRECISION) hub_name = config.get(CONF_HUB_NAME) + hub = hass.data[DOMAIN][hub_name] - add_entities([ - ModbusThermostat(hub_name, name, modbus_slave, target_temp_register, + add_devices([ + ModbusThermostat(hub, name, modbus_slave, target_temp_register, current_temp_register, data_type, count, precision) ], True) @@ -77,10 +82,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class ModbusThermostat(ClimateDevice): """Representation of a Modbus Thermostat.""" - def __init__(self, hub_name, name, modbus_slave, target_temp_register, + def __init__(self, hub, name, modbus_slave, target_temp_register, current_temp_register, data_type, count, precision): """Initialize the unit.""" - self._hub_name = hub_name + self._hub: "BaseModbusClient" = hub self._name = name self._slave = modbus_slave self._target_temperature_register = target_temp_register @@ -140,11 +145,6 @@ def target_temperature(self): """Return the temperature we try to reach.""" return self._target_temperature - @property - def client(self) -> "BaseModbusClient": - """Find and return the client from modbus HUB.""" - return modbus.HUB[self._hub_name] - def set_temperature(self, **kwargs): """Set new target temperature.""" target_temperature = kwargs.get(ATTR_TEMPERATURE) @@ -162,8 +162,8 @@ def set_temperature(self, **kwargs): def read_register(self, register): """Read holding register using the modbus hub slave.""" try: - result = self.client.read_holding_registers( - self._slave, register, self._count) + result = self._hub.read_holding_registers(self._slave, register, + self._count) except AttributeError as ex: _LOGGER.error(ex) byte_string = b''.join( @@ -174,4 +174,4 @@ def read_register(self, register): def write_register(self, register, value): """Write register using the modbus hub slave.""" - self.client.write_registers(self._slave, register, [value, 0]) + self._hub.write_registers(self._slave, register, [value, 0]) diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index cb3b39e219125..25386172e652e 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -6,7 +6,7 @@ """ import logging import threading -from typing import TYPE_CHECKING, Any, Dict, List +from typing import TYPE_CHECKING, Any, List import voluptuous as vol @@ -16,6 +16,7 @@ EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) if TYPE_CHECKING: + # pylint: disable=unused-import from pymodbus.client.sync import BaseModbusClient DOMAIN = "modbus" @@ -61,6 +62,7 @@ def check_base_on_type(value: Any) -> Any: raise vol.Invalid("%s %s is not supported" % (CONF_TYPE, value[CONF_TYPE])) + CONFIG_SCHEMA = vol.Schema( { DOMAIN: @@ -93,8 +95,6 @@ def check_base_on_type(value: Any) -> Any: vol.Required(ATTR_STATE): cv.boolean, }) -HUB = {} # type: Dict[str, BaseModbusClient] - def setup_client(client_config: dict) -> "BaseModbusClient": """Setup pymodbus client.""" @@ -148,21 +148,21 @@ def setup_client(client_config: dict) -> "BaseModbusClient": def setup(hass: Any, config: dict) -> bool: """Set up Modbus component.""" # Modbus connection type - global HUB + hass.data[DOMAIN] = hub_collect = {} for client_config in config[DOMAIN]: client = setup_client(client_config) client_name = client_config[CONF_HUB_NAME] - HUB[client_name] = ModbusHub(client) + hub_collect[client_name] = ModbusHub(client) def stop_modbus(event: Any) -> None: """Stop Modbus service.""" - for client in HUB.values(): + for client in hub_collect.values(): client.close() def start_modbus(event: Any) -> None: """Start Modbus service.""" - for client in HUB.values(): + for client in hub_collect.values(): client.connect() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) @@ -187,10 +187,11 @@ def write_register(service: Any) -> None: value = service.data.get(ATTR_VALUE) client_name = service.data.get(CONF_HUB_NAME) if isinstance(value, list): - HUB[client_name].write_registers(unit, address, - [int(float(i)) for i in value]) + hub_collect[client_name].write_registers( + unit, address, [int(float(i)) for i in value]) else: - HUB[client_name].write_register(unit, address, int(float(value))) + hub_collect[client_name].write_register(unit, address, + int(float(value))) def write_coil(service: Any) -> None: """Write modbus coil.""" @@ -198,7 +199,7 @@ def write_coil(service: Any) -> None: address = service.data.get(ATTR_ADDRESS) state = service.data.get(ATTR_STATE) client_name = service.data.get(CONF_HUB_NAME) - HUB[client_name].write_coil(unit, address, state) + hub_collect[client_name].write_coil(unit, address, state) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_modbus) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 20edb4fd69060..d870f4343b70b 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -10,20 +10,15 @@ import voluptuous as vol -from homeassistant.components import modbus -from homeassistant.components.modbus import CONF_HUB_NAME +from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_NAME, - CONF_OFFSET, - CONF_SLAVE, - CONF_STRUCTURE, - CONF_UNIT_OF_MEASUREMENT, -) +from homeassistant.const import (CONF_NAME, CONF_OFFSET, CONF_SLAVE, + CONF_STRUCTURE, CONF_UNIT_OF_MEASUREMENT) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity if TYPE_CHECKING: + # pylint: disable=unused-import from pymodbus.client.sync import BaseModbusClient _LOGGER = logging.getLogger(__name__) @@ -84,7 +79,7 @@ def setup_platform(hass: Any, config: dict, - add_entities: Any, + add_devices: Any, discovery_info: Any = None) -> bool: """Set up the Modbus sensors.""" sensors = [] @@ -92,7 +87,7 @@ def setup_platform(hass: Any, data_types[DATA_TYPE_UINT] = {1: "H", 2: "I", 4: "Q"} data_types[DATA_TYPE_FLOAT] = {1: "e", 2: "f", 4: "d"} - for register in config.get(CONF_REGISTERS): + for register in config.get(CONF_REGISTERS, []): structure = ">i" if register.get(CONF_DATA_TYPE) != DATA_TYPE_CUSTOM: try: @@ -124,9 +119,11 @@ def setup_platform(hass: Any, ) continue + hub_name = register.get(CONF_HUB_NAME) + hub = hass.data[DOMAIN][hub_name] sensors.append( ModbusRegisterSensor( - register.get(CONF_HUB_NAME), + hub, register.get(CONF_NAME), register.get(CONF_SLAVE), register.get(CONF_REGISTER), @@ -142,18 +139,18 @@ def setup_platform(hass: Any, if not sensors: return False - add_entities(sensors, True) + add_devices(sensors) return True class ModbusRegisterSensor(Entity): """Modbus register sensor.""" - def __init__(self, hub_name, name, slave, register, register_type, + def __init__(self, hub, name, slave, register, register_type, unit_of_measurement, count, reverse_order, scale, offset, structure, precision): """Initialize the modbus register sensor.""" - self._hub_name = hub_name + self._hub: "BaseModbusClient" = hub self._name = name self._slave = int(slave) if slave else None self._register = int(register) @@ -182,18 +179,13 @@ def unit_of_measurement(self) -> str: """Return the unit of measurement.""" return self._unit_of_measurement - @property - def client(self) -> "BaseModbusClient": - """Find and return the client from modbus HUB.""" - return modbus.HUB[self._hub_name] - def update(self) -> None: """Update the state of the sensor.""" if self._register_type == REGISTER_TYPE_INPUT: - result = self.client.read_input_registers( + result = self._hub.read_input_registers( self._slave, self._register, self._count) else: - result = self.client.read_holding_registers( + result = self._hub.read_holding_registers( self._slave, self._register, self._count) val = 0 diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index 259edcc17d9e7..bd8e2abfb2bd3 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -5,17 +5,21 @@ https://home-assistant.io/components/switch.modbus/ """ import logging +from typing import TYPE_CHECKING import voluptuous as vol -from homeassistant.components import modbus -from homeassistant.components.modbus import CONF_HUB_NAME +from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import (CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_NAME, CONF_SLAVE) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import ToggleEntity +if TYPE_CHECKING: + # pylint: disable=unused-import + from pymodbus.client.sync import BaseModbusClient + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] @@ -72,36 +76,41 @@ })) -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Read configuration and create Modbus devices.""" switches = [] if CONF_COILS in config: for coil in config.get(CONF_COILS): + hub_name = coil.get(CONF_HUB_NAME) + hub = hass.data[DOMAIN][hub_name] switches.append( - ModbusCoilSwitch( - coil.get(CONF_HUB_NAME), coil.get(CONF_NAME), - coil.get(CONF_SLAVE), coil.get(CONF_COIL))) + ModbusCoilSwitch(hub, coil.get(CONF_NAME), + coil.get(CONF_SLAVE), coil.get(CONF_COIL))) if CONF_REGISTERS in config: for register in config.get(CONF_REGISTERS): + hub_name = register.get(CONF_HUB_NAME) + hub = hass.data[DOMAIN][hub_name] + switches.append( - ModbusRegisterSwitch( - register.get(CONF_HUB_NAME), register.get(CONF_NAME), - register.get(CONF_SLAVE), register.get(CONF_REGISTER), - register.get(CONF_COMMAND_ON), - register.get(CONF_COMMAND_OFF), - register.get(CONF_VERIFY_STATE), - register.get(CONF_VERIFY_REGISTER), - register.get(CONF_REGISTER_TYPE), - register.get(CONF_STATE_ON), register.get(CONF_STATE_OFF))) - add_entities(switches, True) + ModbusRegisterSwitch(hub, register.get(CONF_NAME), + register.get(CONF_SLAVE), + register.get(CONF_REGISTER), + register.get(CONF_COMMAND_ON), + register.get(CONF_COMMAND_OFF), + register.get(CONF_VERIFY_STATE), + register.get(CONF_VERIFY_REGISTER), + register.get(CONF_REGISTER_TYPE), + register.get(CONF_STATE_ON), + register.get(CONF_STATE_OFF))) + add_devices(switches) class ModbusCoilSwitch(ToggleEntity): """Representation of a Modbus coil switch.""" - def __init__(self, hub_name, name, slave, coil): + def __init__(self, hub, name, slave, coil): """Initialize the coil switch.""" - self._hub_name = hub_name + self._hub: "BaseModbusClient" = hub self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) @@ -117,22 +126,17 @@ def name(self): """Return the name of the switch.""" return self._name - @property - def client(self) -> "BaseModbusClient": - """Find and return the client from modbus HUB.""" - return modbus.HUB[self._hub_name] - def turn_on(self, **kwargs): """Set switch on.""" - self.client.write_coil(self._slave, self._coil, True) + self._hub.write_coil(self._slave, self._coil, True) def turn_off(self, **kwargs): """Set switch off.""" - self.client.write_coil(self._slave, self._coil, False) + self._hub.write_coil(self._slave, self._coil, False) def update(self): """Update the state of the switch.""" - result = self.client.read_coils(self._slave, self._coil, 1) + result = self._hub.read_coils(self._slave, self._coil, 1) try: self._is_on = bool(result.bits[0]) except AttributeError: @@ -144,11 +148,11 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): """Representation of a Modbus register switch.""" # pylint: disable=super-init-not-called - def __init__(self, hub_name, name, slave, register, command_on, - command_off, verify_state, verify_register, register_type, - state_on, state_off): + def __init__(self, hub, name, slave, register, command_on, command_off, + verify_state, verify_register, register_type, state_on, + state_off): """Initialize the register switch.""" - self._hub_name = hub_name + self._hub: "BaseModbusClient" = hub self._name = name self._slave = slave self._register = register @@ -171,22 +175,16 @@ def __init__(self, hub_name, name, slave, register, command_on, self._is_on = None - @property - def client(self) -> "BaseModbusClient": - """Find and return the client from modbus HUB.""" - return modbus.HUB[self._hub_name] - def turn_on(self, **kwargs): """Set switch on.""" - self.client.write_register(self._slave, self._register, - self._command_on) + self._hub.write_register(self._slave, self._register, self._command_on) if not self._verify_state: self._is_on = True def turn_off(self, **kwargs): """Set switch off.""" - self.client.write_register(self._slave, self._register, - self._command_off) + self._hub.write_register(self._slave, self._register, + self._command_off) if not self._verify_state: self._is_on = False @@ -197,11 +195,11 @@ def update(self): value = 0 if self._register_type == REGISTER_TYPE_INPUT: - result = self.client.read_input_registers(self._slave, - self._register, 1) + result = self._hub.read_input_registers(self._slave, + self._register, 1) else: - result = self.client.read_holding_registers( - self._slave, self._register, 1) + result = self._hub.read_holding_registers(self._slave, + self._register, 1) try: value = int(result.registers[0]) From 4e74bc5296004ab303e8269f6f941eb82409cf21 Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Sun, 28 Oct 2018 09:40:04 +0800 Subject: [PATCH 04/10] add hub_client setup log --- homeassistant/components/modbus.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 25386172e652e..84bb28d8c2415 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -19,6 +19,8 @@ # pylint: disable=unused-import from pymodbus.client.sync import BaseModbusClient +_LOGGER = logging.getLogger(__name__) + DOMAIN = "modbus" REQUIREMENTS = ["pymodbus==1.3.1"] @@ -154,6 +156,7 @@ def setup(hass: Any, config: dict) -> bool: client = setup_client(client_config) client_name = client_config[CONF_HUB_NAME] hub_collect[client_name] = ModbusHub(client) + _LOGGER.debug("Setting up hub_client: %s", client_config) def stop_modbus(event: Any) -> None: """Stop Modbus service.""" From fd943ca6a57ff52ea79f5267575bfc6b95829bde Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Sun, 28 Oct 2018 09:40:30 +0800 Subject: [PATCH 05/10] don't update when adding device, because hub_client is not ready right now --- homeassistant/components/climate/modbus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/climate/modbus.py index a6584dbc50f0f..9e2a308940b45 100644 --- a/homeassistant/components/climate/modbus.py +++ b/homeassistant/components/climate/modbus.py @@ -76,7 +76,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices([ ModbusThermostat(hub, name, modbus_slave, target_temp_register, current_temp_register, data_type, count, precision) - ], True) + ]) class ModbusThermostat(ClimateDevice): From b9eb3f15355f6bc9589815047d25ca7488e15a4e Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Sun, 28 Oct 2018 10:36:01 +0800 Subject: [PATCH 06/10] support restore last state --- homeassistant/components/sensor/modbus.py | 8 ++++++++ homeassistant/components/switch/modbus.py | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index d870f4343b70b..9c8d5fd76ad37 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -16,6 +16,7 @@ CONF_STRUCTURE, CONF_UNIT_OF_MEASUREMENT) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity +from homeassistant.helpers.restore_state import async_get_last_state if TYPE_CHECKING: # pylint: disable=unused-import @@ -164,6 +165,13 @@ def __init__(self, hub, name, slave, register, register_type, self._structure = structure self._value: str = None + async def async_added_to_hass(self): + """Handle entity which will be added.""" + state = await async_get_last_state(self.hass, self.entity_id) + if not state: + return + self._value = state.state + @property def state(self) -> str: """Return the state of the sensor.""" diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index bd8e2abfb2bd3..ccbe84e0af97a 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -12,9 +12,10 @@ from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import (CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_NAME, - CONF_SLAVE) + CONF_SLAVE, STATE_ON) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.restore_state import async_get_last_state if TYPE_CHECKING: # pylint: disable=unused-import @@ -116,6 +117,13 @@ def __init__(self, hub, name, slave, coil): self._coil = int(coil) self._is_on = None + async def async_added_to_hass(self): + """Handle entity which will be added.""" + state = await async_get_last_state(self.hass, self.entity_id) + if not state: + return + self._is_on = state.state == STATE_ON + @property def is_on(self): """Return true if switch is on.""" From ee659be65e117c5dd17714158ce4fd2ffbd1c64e Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Sun, 28 Oct 2018 10:42:41 +0800 Subject: [PATCH 07/10] remove useless func --- homeassistant/components/modbus.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 84bb28d8c2415..2dddf99cc20d6 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -54,17 +54,6 @@ vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, }) - -def check_base_on_type(value: Any) -> Any: - """Check modbus component schema base on "type".""" - if value[CONF_TYPE] == "serial": - return SERIAL_SCHEMA(value) - if value[CONF_TYPE] in ("tcp", "udp", "rtuovertcp"): - return ETHERNET_SCHEMA(value) - - raise vol.Invalid("%s %s is not supported" % (CONF_TYPE, value[CONF_TYPE])) - - CONFIG_SCHEMA = vol.Schema( { DOMAIN: From b8c6d60f4cec4e8f7de785d13194746f494c8ad2 Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Mon, 29 Oct 2018 14:21:24 +0800 Subject: [PATCH 08/10] compatible with python35 --- homeassistant/components/binary_sensor/modbus.py | 2 +- homeassistant/components/climate/modbus.py | 2 +- homeassistant/components/sensor/modbus.py | 2 +- homeassistant/components/switch/modbus.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py index 494f2941e9ad5..5c00e6e70f965 100644 --- a/homeassistant/components/binary_sensor/modbus.py +++ b/homeassistant/components/binary_sensor/modbus.py @@ -52,7 +52,7 @@ class ModbusCoilSensor(BinarySensorDevice): def __init__(self, hub, name, slave, coil): """Initialize the modbus coil sensor.""" - self._hub: "BaseModbusClient" = hub + self._hub = hub # type: BaseModbusClient self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/climate/modbus.py index 9e2a308940b45..059c6bf4a7cfc 100644 --- a/homeassistant/components/climate/modbus.py +++ b/homeassistant/components/climate/modbus.py @@ -85,7 +85,7 @@ class ModbusThermostat(ClimateDevice): def __init__(self, hub, name, modbus_slave, target_temp_register, current_temp_register, data_type, count, precision): """Initialize the unit.""" - self._hub: "BaseModbusClient" = hub + self._hub = hub # type: BaseModbusClient self._name = name self._slave = modbus_slave self._target_temperature_register = target_temp_register diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 9c8d5fd76ad37..b5801e5137671 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -151,7 +151,7 @@ def __init__(self, hub, name, slave, register, register_type, unit_of_measurement, count, reverse_order, scale, offset, structure, precision): """Initialize the modbus register sensor.""" - self._hub: "BaseModbusClient" = hub + self._hub = hub # type: BaseModbusClient self._name = name self._slave = int(slave) if slave else None self._register = int(register) diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index ccbe84e0af97a..cdf038ad558fa 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -111,7 +111,7 @@ class ModbusCoilSwitch(ToggleEntity): def __init__(self, hub, name, slave, coil): """Initialize the coil switch.""" - self._hub: "BaseModbusClient" = hub + self._hub = hub # type: BaseModbusClient self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) @@ -160,7 +160,7 @@ def __init__(self, hub, name, slave, register, command_on, command_off, verify_state, verify_register, register_type, state_on, state_off): """Initialize the register switch.""" - self._hub: "BaseModbusClient" = hub + self._hub = hub # type: BaseModbusClient self._name = name self._slave = slave self._register = register From f7191a00b9fbf6f71bc04371d6928c0bd782a4e7 Mon Sep 17 00:00:00 2001 From: Ben Van Mechelen Date: Wed, 19 Dec 2018 23:35:56 +0100 Subject: [PATCH 09/10] removed unrelated style changes --- .../components/binary_sensor/modbus.py | 26 +- homeassistant/components/climate/modbus.py | 71 ++---- homeassistant/components/modbus.py | 224 +++++++++--------- homeassistant/components/sensor/modbus.py | 189 +++++++-------- homeassistant/components/switch/modbus.py | 141 +++++------ 5 files changed, 296 insertions(+), 355 deletions(-) diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py index 5c00e6e70f965..673c6e2c54dcf 100644 --- a/homeassistant/components/binary_sensor/modbus.py +++ b/homeassistant/components/binary_sensor/modbus.py @@ -5,19 +5,13 @@ https://home-assistant.io/components/binary_sensor.modbus/ """ import logging -from typing import TYPE_CHECKING - import voluptuous as vol -from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN -from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_SLAVE +from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.helpers import config_validation as cv - -if TYPE_CHECKING: - # pylint: disable=unused-import - from pymodbus.client.sync import BaseModbusClient +from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] @@ -27,7 +21,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COILS): [{ - vol.Required(CONF_HUB_NAME, default="default"): cv.string, + vol.Required(CONF_HUB_NAME, default='default'): cv.string, vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_SLAVE): cv.positive_int @@ -35,16 +29,18 @@ }) -def setup_platform(hass, config, add_devices, discovery_info=None): +def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Modbus binary sensors.""" sensors = [] for coil in config.get(CONF_COILS): hub_name = coil.get(CONF_HUB_NAME) hub = hass.data[DOMAIN][hub_name] - sensors.append( - ModbusCoilSensor(hub, coil.get(CONF_NAME), coil.get(CONF_SLAVE), - coil.get(CONF_COIL))) - add_devices(sensors) + sensors.append(ModbusCoilSensor( + hub, + coil.get(CONF_NAME), + coil.get(CONF_SLAVE), + coil.get(CONF_COIL))) + add_entities(sensors) class ModbusCoilSensor(BinarySensorDevice): @@ -52,7 +48,7 @@ class ModbusCoilSensor(BinarySensorDevice): def __init__(self, hub, name, slave, coil): """Initialize the modbus coil sensor.""" - self._hub = hub # type: BaseModbusClient + self._hub = hub self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/climate/modbus.py index 059c6bf4a7cfc..76344caa50a76 100644 --- a/homeassistant/components/climate/modbus.py +++ b/homeassistant/components/climate/modbus.py @@ -10,19 +10,16 @@ """ import logging import struct -from typing import TYPE_CHECKING import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + CONF_NAME, CONF_SLAVE, ATTR_TEMPERATURE) from homeassistant.components.climate import ( - PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) -from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN -from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, CONF_SLAVE + ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) -if TYPE_CHECKING: - # pylint: disable=unused-import - from pymodbus.client.sync import BaseModbusClient +from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN +import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['modbus'] @@ -38,22 +35,15 @@ DATA_TYPE_FLOAT = 'float' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HUB_NAME, default="default"): - cv.string, - vol.Required(CONF_NAME): - cv.string, - vol.Required(CONF_SLAVE): - cv.positive_int, - vol.Required(CONF_TARGET_TEMP): - cv.positive_int, - vol.Required(CONF_CURRENT_TEMP): - cv.positive_int, + vol.Required(CONF_HUB_NAME, default="default"): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_SLAVE): cv.positive_int, + vol.Required(CONF_TARGET_TEMP): cv.positive_int, + vol.Required(CONF_CURRENT_TEMP): cv.positive_int, vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_FLOAT): vol.In([DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT]), - vol.Optional(CONF_COUNT, default=2): - cv.positive_int, - vol.Optional(CONF_PRECISION, default=1): - cv.positive_int + vol.Optional(CONF_COUNT, default=2): cv.positive_int, + vol.Optional(CONF_PRECISION, default=1): cv.positive_int }) _LOGGER = logging.getLogger(__name__) @@ -61,7 +51,7 @@ SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE -def setup_platform(hass, config, add_devices, discovery_info=None): +def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Modbus Thermostat Platform.""" name = config.get(CONF_NAME) modbus_slave = config.get(CONF_SLAVE) @@ -73,10 +63,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hub_name = config.get(CONF_HUB_NAME) hub = hass.data[DOMAIN][hub_name] - add_devices([ - ModbusThermostat(hub, name, modbus_slave, target_temp_register, - current_temp_register, data_type, count, precision) - ]) + add_entities([ModbusThermostat(hub, name, modbus_slave, + target_temp_register, current_temp_register, + data_type, count, precision)], True) class ModbusThermostat(ClimateDevice): @@ -85,7 +74,7 @@ class ModbusThermostat(ClimateDevice): def __init__(self, hub, name, modbus_slave, target_temp_register, current_temp_register, data_type, count, precision): """Initialize the unit.""" - self._hub = hub # type: BaseModbusClient + self._hub = hub self._name = name self._slave = modbus_slave self._target_temperature_register = target_temp_register @@ -97,26 +86,12 @@ def __init__(self, hub, name, modbus_slave, target_temp_register, self._precision = precision self._structure = '>f' - data_types = { - DATA_TYPE_INT: { - 1: 'h', - 2: 'i', - 4: 'q' - }, - DATA_TYPE_UINT: { - 1: 'H', - 2: 'I', - 4: 'Q' - }, - DATA_TYPE_FLOAT: { - 1: 'e', - 2: 'f', - 4: 'd' - } - } - - self._structure = '>{}'.format( - data_types[self._data_type][self._count]) + data_types = {DATA_TYPE_INT: {1: 'h', 2: 'i', 4: 'q'}, + DATA_TYPE_UINT: {1: 'H', 2: 'I', 4: 'Q'}, + DATA_TYPE_FLOAT: {1: 'e', 2: 'f', 4: 'd'}} + + self._structure = '>{}'.format(data_types[self._data_type] + [self._count]) @property def supported_features(self): diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 2dddf99cc20d6..2a808a67d83c0 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -6,137 +6,113 @@ """ import logging import threading -from typing import TYPE_CHECKING, Any, List import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - ATTR_STATE, CONF_HOST, CONF_METHOD, CONF_PORT, CONF_TIMEOUT, CONF_TYPE, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) - -if TYPE_CHECKING: - # pylint: disable=unused-import - from pymodbus.client.sync import BaseModbusClient + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + CONF_HOST, CONF_METHOD, CONF_PORT, CONF_TYPE, CONF_TIMEOUT, ATTR_STATE) _LOGGER = logging.getLogger(__name__) -DOMAIN = "modbus" +DOMAIN = 'modbus' -REQUIREMENTS = ["pymodbus==1.3.1"] +REQUIREMENTS = ['pymodbus==1.3.1'] # Type of network -CONF_BAUDRATE = "baudrate" -CONF_BYTESIZE = "bytesize" -CONF_STOPBITS = "stopbits" -CONF_PARITY = "parity" -CONF_HUB_NAME = "hub_name" +CONF_BAUDRATE = 'baudrate' +CONF_BYTESIZE = 'bytesize' +CONF_STOPBITS = 'stopbits' +CONF_PARITY = 'parity' +CONF_HUB_NAME = 'hub_name' BASE_SCHEMA = vol.Schema({ - vol.Optional(CONF_HUB_NAME, default="default"): cv.string + vol.Optional(CONF_HUB_NAME, default='default'): cv.string }) SERIAL_SCHEMA = BASE_SCHEMA.extend({ vol.Required(CONF_BAUDRATE): cv.positive_int, vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8), - vol.Required(CONF_METHOD): vol.Any("rtu", "ascii"), + vol.Required(CONF_METHOD): vol.Any('rtu', 'ascii'), vol.Required(CONF_PORT): cv.string, - vol.Required(CONF_PARITY): vol.Any("E", "O", "N"), + vol.Required(CONF_PARITY): vol.Any('E', 'O', 'N'), vol.Required(CONF_STOPBITS): vol.Any(1, 2), - vol.Required(CONF_TYPE): "serial", + vol.Required(CONF_TYPE): 'serial', vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, }) ETHERNET_SCHEMA = BASE_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port, - vol.Required(CONF_TYPE): vol.Any("tcp", "udp", "rtuovertcp"), + vol.Required(CONF_TYPE): vol.Any('tcp', 'udp', 'rtuovertcp'), vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, }) -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: - vol.All(cv.ensure_list, - vol.Schema([vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)])) - }, - extra=vol.ALLOW_EXTRA, -) +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: + vol.All(cv.ensure_list, + vol.Schema([vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)])) +}, extra=vol.ALLOW_EXTRA,) _LOGGER = logging.getLogger(__name__) -SERVICE_WRITE_REGISTER = "write_register" -SERVICE_WRITE_COIL = "write_coil" +SERVICE_WRITE_REGISTER = 'write_register' +SERVICE_WRITE_COIL = 'write_coil' -ATTR_ADDRESS = "address" -ATTR_UNIT = "unit" -ATTR_VALUE = "value" +ATTR_ADDRESS = 'address' +ATTR_UNIT = 'unit' +ATTR_VALUE = 'value' SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema({ - vol.Optional(CONF_HUB_NAME, default="default"): cv.string, + vol.Optional(CONF_HUB_NAME, default='default'): cv.string, vol.Required(ATTR_UNIT): cv.positive_int, vol.Required(ATTR_ADDRESS): cv.positive_int, - vol.Required(ATTR_VALUE): vol.All(cv.ensure_list, [cv.positive_int]), + vol.Required(ATTR_VALUE): vol.All(cv.ensure_list, [cv.positive_int]) }) SERVICE_WRITE_COIL_SCHEMA = vol.Schema({ - vol.Optional(CONF_HUB_NAME, default="default"): cv.string, + vol.Optional(CONF_HUB_NAME, default='default'): cv.string, vol.Required(ATTR_UNIT): cv.positive_int, vol.Required(ATTR_ADDRESS): cv.positive_int, - vol.Required(ATTR_STATE): cv.boolean, + vol.Required(ATTR_STATE): cv.boolean }) -def setup_client(client_config: dict) -> "BaseModbusClient": - """Setup pymodbus client.""" - from pymodbus.client.sync import ( - ModbusTcpClient, - ModbusUdpClient, - ModbusSerialClient, - ) - from pymodbus.transaction import ModbusRtuFramer - +def setup_client(client_config): + """Set up pymodbus client.""" client_type = client_config[CONF_TYPE] - # Connect to Modbus network - # pylint: disable=import-error - - if client_type == "serial": - - return ModbusSerialClient( - method=client_config[CONF_METHOD], - port=client_config[CONF_PORT], - baudrate=client_config[CONF_BAUDRATE], - stopbits=client_config[CONF_STOPBITS], - bytesize=client_config[CONF_BYTESIZE], - parity=client_config[CONF_PARITY], - timeout=client_config[CONF_TIMEOUT], - ) - if client_type == "rtuovertcp": - - return ModbusTcpClient( - host=client_config[CONF_HOST], - port=client_config[CONF_PORT], - framer=ModbusRtuFramer, - timeout=client_config[CONF_TIMEOUT], - ) - if client_type == "tcp": - return ModbusTcpClient( - host=client_config[CONF_HOST], - port=client_config[CONF_PORT], - timeout=client_config[CONF_TIMEOUT], - ) - if client_type == "udp": - return ModbusUdpClient( - host=client_config[CONF_HOST], - port=client_config[CONF_PORT], - timeout=client_config[CONF_TIMEOUT], - ) - + if client_type == 'serial': + from pymodbus.client.sync import ModbusSerialClient as ModbusClient + return ModbusClient(method=client_config[CONF_METHOD], + port=client_config[CONF_PORT], + baudrate=client_config[CONF_BAUDRATE], + stopbits=client_config[CONF_STOPBITS], + bytesize=client_config[CONF_BYTESIZE], + parity=client_config[CONF_PARITY], + timeout=client_config[CONF_TIMEOUT]) + if client_type == 'rtuovertcp': + from pymodbus.client.sync import ModbusTcpClient as ModbusClient + from pymodbus.transaction import ModbusRtuFramer + return ModbusClient(host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + framer=ModbusRtuFramer, + timeout=client_config[CONF_TIMEOUT]) + if client_type == 'tcp': + from pymodbus.client.sync import ModbusTcpClient as ModbusClient + return ModbusClient(host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + timeout=client_config[CONF_TIMEOUT]) + if client_type == 'udp': + from pymodbus.client.sync import ModbusUdpClient as ModbusClient + return ModbusClient(host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + timeout=client_config[CONF_TIMEOUT]) assert False -def setup(hass: Any, config: dict) -> bool: +def setup(hass, config): """Set up Modbus component.""" # Modbus connection type hass.data[DOMAIN] = hub_collect = {} @@ -145,14 +121,14 @@ def setup(hass: Any, config: dict) -> bool: client = setup_client(client_config) client_name = client_config[CONF_HUB_NAME] hub_collect[client_name] = ModbusHub(client) - _LOGGER.debug("Setting up hub_client: %s", client_config) + _LOGGER.debug('Setting up hub_client: %s', client_config) - def stop_modbus(event: Any) -> None: + def stop_modbus(event): """Stop Modbus service.""" for client in hub_collect.values(): client.close() - def start_modbus(event: Any) -> None: + def start_modbus(event): """Start Modbus service.""" for client in hub_collect.values(): client.connect() @@ -164,15 +140,14 @@ def start_modbus(event: Any) -> None: DOMAIN, SERVICE_WRITE_REGISTER, write_register, - schema=SERVICE_WRITE_REGISTER_SCHEMA, - ) + schema=SERVICE_WRITE_REGISTER_SCHEMA) hass.services.register( DOMAIN, SERVICE_WRITE_COIL, write_coil, schema=SERVICE_WRITE_COIL_SCHEMA) - def write_register(service: Any) -> None: + def write_register(service): """Write modbus registers.""" unit = int(float(service.data.get(ATTR_UNIT))) address = int(float(service.data.get(ATTR_ADDRESS))) @@ -180,12 +155,16 @@ def write_register(service: Any) -> None: client_name = service.data.get(CONF_HUB_NAME) if isinstance(value, list): hub_collect[client_name].write_registers( - unit, address, [int(float(i)) for i in value]) + unit, + address, + [int(float(i)) for i in value]) else: - hub_collect[client_name].write_register(unit, address, - int(float(value))) + hub_collect[client_name].write_register( + unit, + address, + int(float(value))) - def write_coil(service: Any) -> None: + def write_coil(service): """Write modbus coil.""" unit = service.data.get(ATTR_UNIT) address = service.data.get(ATTR_ADDRESS) @@ -201,56 +180,71 @@ def write_coil(service: Any) -> None: class ModbusHub: """Thread safe wrapper class for pymodbus.""" - def __init__(self, modbus_client: "BaseModbusClient") -> None: + def __init__(self, modbus_client): """Initialize the modbus hub.""" self._client = modbus_client self._lock = threading.Lock() - def close(self) -> None: + def close(self): """Disconnect client.""" with self._lock: self._client.close() - def connect(self) -> None: + def connect(self): """Connect client.""" with self._lock: self._client.connect() - def read_coils(self, unit: int, address: int, count: int) -> Any: + def read_coils(self, unit, address, count): """Read coils.""" with self._lock: - kwargs = {"unit": unit} if unit else {} - return self._client.read_coils(address, count, **kwargs) + kwargs = {'unit': unit} if unit else {} + return self._client.read_coils( + address, + count, + **kwargs) - def read_input_registers(self, unit: int, address: int, count: int) -> Any: + def read_input_registers(self, unit, address, count): """Read input registers.""" with self._lock: - kwargs = {"unit": unit} if unit else {} - return self._client.read_input_registers(address, count, **kwargs) + kwargs = {'unit': unit} if unit else {} + return self._client.read_input_registers( + address, + count, + **kwargs) - def read_holding_registers(self, unit: int, address: int, - count: int) -> Any: + def read_holding_registers(self, unit, address, count): """Read holding registers.""" with self._lock: - kwargs = {"unit": unit} if unit else {} - return self._client.read_holding_registers(address, count, - **kwargs) + kwargs = {'unit': unit} if unit else {} + return self._client.read_holding_registers( + address, + count, + **kwargs) - def write_coil(self, unit: int, address: int, value: int) -> Any: + def write_coil(self, unit, address, value): """Write coil.""" with self._lock: - kwargs = {"unit": unit} if unit else {} - self._client.write_coil(address, value, **kwargs) + kwargs = {'unit': unit} if unit else {} + self._client.write_coil( + address, + value, + **kwargs) - def write_register(self, unit: int, address: int, value: int) -> Any: + def write_register(self, unit, address, value): """Write register.""" with self._lock: - kwargs = {"unit": unit} if unit else {} - self._client.write_register(address, value, **kwargs) + kwargs = {'unit': unit} if unit else {} + self._client.write_register( + address, + value, + **kwargs) - def write_registers(self, unit: int, address: int, - values: List[int]) -> Any: + def write_registers(self, unit, address, values): """Write registers.""" with self._lock: - kwargs = {"unit": unit} if unit else {} - self._client.write_registers(address, values, **kwargs) + kwargs = {'unit': unit} if unit else {} + self._client.write_registers( + address, + values, + **kwargs) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index b5801e5137671..2fea95531206a 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -6,100 +6,76 @@ """ import logging import struct -from typing import TYPE_CHECKING, Any import voluptuous as vol from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_NAME, CONF_OFFSET, CONF_SLAVE, - CONF_STRUCTURE, CONF_UNIT_OF_MEASUREMENT) +from homeassistant.const import ( + CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT, CONF_SLAVE, + CONF_STRUCTURE) +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.restore_state import async_get_last_state - -if TYPE_CHECKING: - # pylint: disable=unused-import - from pymodbus.client.sync import BaseModbusClient +from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ["modbus"] +DEPENDENCIES = ['modbus'] -CONF_COUNT = "count" -CONF_REVERSE_ORDER = "reverse_order" -CONF_PRECISION = "precision" -CONF_REGISTER = "register" -CONF_REGISTERS = "registers" -CONF_SCALE = "scale" -CONF_DATA_TYPE = "data_type" -CONF_REGISTER_TYPE = "register_type" +CONF_COUNT = 'count' +CONF_REVERSE_ORDER = 'reverse_order' +CONF_PRECISION = 'precision' +CONF_REGISTER = 'register' +CONF_REGISTERS = 'registers' +CONF_SCALE = 'scale' +CONF_DATA_TYPE = 'data_type' +CONF_REGISTER_TYPE = 'register_type' -REGISTER_TYPE_HOLDING = "holding" -REGISTER_TYPE_INPUT = "input" +REGISTER_TYPE_HOLDING = 'holding' +REGISTER_TYPE_INPUT = 'input' -DATA_TYPE_INT = "int" -DATA_TYPE_UINT = "uint" -DATA_TYPE_FLOAT = "float" -DATA_TYPE_CUSTOM = "custom" +DATA_TYPE_INT = 'int' +DATA_TYPE_UINT = 'uint' +DATA_TYPE_FLOAT = 'float' +DATA_TYPE_CUSTOM = 'custom' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_REGISTERS): [{ - vol.Required(CONF_HUB_NAME, default="default"): - cv.string, - vol.Required(CONF_NAME): - cv.string, - vol.Required(CONF_REGISTER): - cv.positive_int, + vol.Required(CONF_HUB_NAME, default='default'): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_REGISTER): cv.positive_int, vol.Optional(CONF_REGISTER_TYPE, default=REGISTER_TYPE_HOLDING): vol.In([REGISTER_TYPE_HOLDING, REGISTER_TYPE_INPUT]), - vol.Optional(CONF_COUNT, default=1): - cv.positive_int, - vol.Optional(CONF_REVERSE_ORDER, default=False): - cv.boolean, - vol.Optional(CONF_OFFSET, default=0): - vol.Coerce(float), - vol.Optional(CONF_PRECISION, default=0): - cv.positive_int, - vol.Optional(CONF_SCALE, default=1): - vol.Coerce(float), - vol.Optional(CONF_SLAVE): - cv.positive_int, + vol.Optional(CONF_COUNT, default=1): cv.positive_int, + vol.Optional(CONF_REVERSE_ORDER, default=False): cv.boolean, + vol.Optional(CONF_OFFSET, default=0): vol.Coerce(float), + vol.Optional(CONF_PRECISION, default=0): cv.positive_int, + vol.Optional(CONF_SCALE, default=1): vol.Coerce(float), + vol.Optional(CONF_SLAVE): cv.positive_int, vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_INT): - vol.In([ - DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, - DATA_TYPE_CUSTOM - ]), - vol.Optional(CONF_STRUCTURE): - cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): - cv.string, + vol.In([DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, + DATA_TYPE_CUSTOM]), + vol.Optional(CONF_STRUCTURE): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string }] }) -def setup_platform(hass: Any, - config: dict, - add_devices: Any, - discovery_info: Any = None) -> bool: +def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Modbus sensors.""" sensors = [] - data_types = {DATA_TYPE_INT: {1: "h", 2: "i", 4: "q"}} - data_types[DATA_TYPE_UINT] = {1: "H", 2: "I", 4: "Q"} - data_types[DATA_TYPE_FLOAT] = {1: "e", 2: "f", 4: "d"} + data_types = {DATA_TYPE_INT: {1: 'h', 2: 'i', 4: 'q'}} + data_types[DATA_TYPE_UINT] = {1: 'H', 2: 'I', 4: 'Q'} + data_types[DATA_TYPE_FLOAT] = {1: 'e', 2: 'f', 4: 'd'} - for register in config.get(CONF_REGISTERS, []): - structure = ">i" + for register in config.get(CONF_REGISTERS): + structure = '>i' if register.get(CONF_DATA_TYPE) != DATA_TYPE_CUSTOM: try: - structure = ">{}".format(data_types[register.get( + structure = '>{}'.format(data_types[register.get( CONF_DATA_TYPE)][register.get(CONF_COUNT)]) except KeyError: - _LOGGER.error( - "Unable to detect data type for %s sensor, " - "try a custom type.", - register.get(CONF_NAME), - ) + _LOGGER.error("Unable to detect data type for %s sensor, " + "try a custom type.", register.get(CONF_NAME)) continue else: structure = register.get(CONF_STRUCTURE) @@ -107,51 +83,46 @@ def setup_platform(hass: Any, try: size = struct.calcsize(structure) except struct.error as err: - _LOGGER.error("Error in sensor %s structure: %s", - register.get(CONF_NAME), err) + _LOGGER.error( + "Error in sensor %s structure: %s", + register.get(CONF_NAME), err) continue if register.get(CONF_COUNT) * 2 != size: _LOGGER.error( "Structure size (%d bytes) mismatch registers count " - "(%d words)", - size, - register.get(CONF_COUNT), - ) + "(%d words)", size, register.get(CONF_COUNT)) continue hub_name = register.get(CONF_HUB_NAME) hub = hass.data[DOMAIN][hub_name] - sensors.append( - ModbusRegisterSensor( - hub, - register.get(CONF_NAME), - register.get(CONF_SLAVE), - register.get(CONF_REGISTER), - register.get(CONF_REGISTER_TYPE), - register.get(CONF_UNIT_OF_MEASUREMENT), - register.get(CONF_COUNT), - register.get(CONF_REVERSE_ORDER), - register.get(CONF_SCALE), - register.get(CONF_OFFSET), - structure, - register.get(CONF_PRECISION), - )) + sensors.append(ModbusRegisterSensor( + hub, + register.get(CONF_NAME), + register.get(CONF_SLAVE), + register.get(CONF_REGISTER), + register.get(CONF_REGISTER_TYPE), + register.get(CONF_UNIT_OF_MEASUREMENT), + register.get(CONF_COUNT), + register.get(CONF_REVERSE_ORDER), + register.get(CONF_SCALE), + register.get(CONF_OFFSET), + structure, + register.get(CONF_PRECISION))) if not sensors: return False - add_devices(sensors) - return True + add_entities(sensors) -class ModbusRegisterSensor(Entity): +class ModbusRegisterSensor(RestoreEntity): """Modbus register sensor.""" def __init__(self, hub, name, slave, register, register_type, unit_of_measurement, count, reverse_order, scale, offset, structure, precision): """Initialize the modbus register sensor.""" - self._hub = hub # type: BaseModbusClient + self._hub = hub self._name = name self._slave = int(slave) if slave else None self._register = int(register) @@ -163,38 +134,42 @@ def __init__(self, hub, name, slave, register, register_type, self._offset = offset self._precision = precision self._structure = structure - self._value: str = None + self._value = None async def async_added_to_hass(self): """Handle entity which will be added.""" - state = await async_get_last_state(self.hass, self.entity_id) + state = await self.async_get_last_state() if not state: return self._value = state.state @property - def state(self) -> str: + def state(self): """Return the state of the sensor.""" return self._value @property - def name(self) -> str: + def name(self): """Return the name of the sensor.""" return self._name @property - def unit_of_measurement(self) -> str: + def unit_of_measurement(self): """Return the unit of measurement.""" return self._unit_of_measurement - def update(self) -> None: + def update(self): """Update the state of the sensor.""" if self._register_type == REGISTER_TYPE_INPUT: result = self._hub.read_input_registers( - self._slave, self._register, self._count) + self._slave, + self._register, + self._count) else: result = self._hub.read_holding_registers( - self._slave, self._register, self._count) + self._slave, + self._register, + self._count) val = 0 try: @@ -202,14 +177,12 @@ def update(self) -> None: if self._reverse_order: registers.reverse() except AttributeError: - _LOGGER.error( - "No response from modbus slave %s, register %s", - self._slave, - self._register, - ) + _LOGGER.error("No response from modbus slave %s, register %s", + self._slave, self._register) return - byte_string = b"".join( - [x.to_bytes(2, byteorder="big") for x in registers]) + byte_string = b''.join( + [x.to_bytes(2, byteorder='big') for x in registers] + ) val = struct.unpack(self._structure, byte_string)[0] - self._value = format(self._scale * val + self._offset, - ".{}f".format(self._precision)) + self._value = format( + self._scale * val + self._offset, '.{}f'.format(self._precision)) diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index cdf038ad558fa..32e4b851786f7 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -5,21 +5,15 @@ https://home-assistant.io/components/switch.modbus/ """ import logging -from typing import TYPE_CHECKING - import voluptuous as vol from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_NAME, - CONF_SLAVE, STATE_ON) -from homeassistant.helpers import config_validation as cv +from homeassistant.const import ( + CONF_NAME, CONF_SLAVE, CONF_COMMAND_ON, CONF_COMMAND_OFF, STATE_ON) from homeassistant.helpers.entity import ToggleEntity -from homeassistant.helpers.restore_state import async_get_last_state - -if TYPE_CHECKING: - # pylint: disable=unused-import - from pymodbus.client.sync import BaseModbusClient +from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers import config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] @@ -38,32 +32,23 @@ REGISTER_TYPE_INPUT = 'input' REGISTERS_SCHEMA = vol.Schema({ - vol.Required(CONF_HUB_NAME, default="default"): - cv.string, - vol.Required(CONF_NAME): - cv.string, - vol.Optional(CONF_SLAVE): - cv.positive_int, - vol.Required(CONF_REGISTER): - cv.positive_int, - vol.Required(CONF_COMMAND_ON): - cv.positive_int, - vol.Required(CONF_COMMAND_OFF): - cv.positive_int, - vol.Optional(CONF_VERIFY_STATE, default=True): - cv.boolean, + vol.Required(CONF_HUB_NAME, default='default'): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_SLAVE): cv.positive_int, + vol.Required(CONF_REGISTER): cv.positive_int, + vol.Required(CONF_COMMAND_ON): cv.positive_int, + vol.Required(CONF_COMMAND_OFF): cv.positive_int, + vol.Optional(CONF_VERIFY_STATE, default=True): cv.boolean, vol.Optional(CONF_VERIFY_REGISTER): cv.positive_int, vol.Optional(CONF_REGISTER_TYPE, default=REGISTER_TYPE_HOLDING): vol.In([REGISTER_TYPE_HOLDING, REGISTER_TYPE_INPUT]), - vol.Optional(CONF_STATE_ON): - cv.positive_int, - vol.Optional(CONF_STATE_OFF): - cv.positive_int, + vol.Optional(CONF_STATE_ON): cv.positive_int, + vol.Optional(CONF_STATE_OFF): cv.positive_int, }) COILS_SCHEMA = vol.Schema({ - vol.Required(CONF_HUB_NAME, default="default"): cv.string, + vol.Required(CONF_HUB_NAME, default='default'): cv.string, vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_NAME): cv.string, vol.Required(CONF_SLAVE): cv.positive_int, @@ -77,41 +62,44 @@ })) -def setup_platform(hass, config, add_devices, discovery_info=None): +def setup_platform(hass, config, add_entities, discovery_info=None): """Read configuration and create Modbus devices.""" switches = [] if CONF_COILS in config: for coil in config.get(CONF_COILS): hub_name = coil.get(CONF_HUB_NAME) hub = hass.data[DOMAIN][hub_name] - switches.append( - ModbusCoilSwitch(hub, coil.get(CONF_NAME), - coil.get(CONF_SLAVE), coil.get(CONF_COIL))) + switches.append(ModbusCoilSwitch( + hub, + coil.get(CONF_NAME), + coil.get(CONF_SLAVE), + coil.get(CONF_COIL))) if CONF_REGISTERS in config: for register in config.get(CONF_REGISTERS): hub_name = register.get(CONF_HUB_NAME) hub = hass.data[DOMAIN][hub_name] - switches.append( - ModbusRegisterSwitch(hub, register.get(CONF_NAME), - register.get(CONF_SLAVE), - register.get(CONF_REGISTER), - register.get(CONF_COMMAND_ON), - register.get(CONF_COMMAND_OFF), - register.get(CONF_VERIFY_STATE), - register.get(CONF_VERIFY_REGISTER), - register.get(CONF_REGISTER_TYPE), - register.get(CONF_STATE_ON), - register.get(CONF_STATE_OFF))) - add_devices(switches) - - -class ModbusCoilSwitch(ToggleEntity): + switches.append(ModbusRegisterSwitch( + hub, + register.get(CONF_NAME), + register.get(CONF_SLAVE), + register.get(CONF_REGISTER), + register.get(CONF_COMMAND_ON), + register.get(CONF_COMMAND_OFF), + register.get(CONF_VERIFY_STATE), + register.get(CONF_VERIFY_REGISTER), + register.get(CONF_REGISTER_TYPE), + register.get(CONF_STATE_ON), + register.get(CONF_STATE_OFF))) + add_entities(switches) + + +class ModbusCoilSwitch(ToggleEntity, RestoreEntity): """Representation of a Modbus coil switch.""" def __init__(self, hub, name, slave, coil): """Initialize the coil switch.""" - self._hub = hub # type: BaseModbusClient + self._hub = hub self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) @@ -119,7 +107,7 @@ def __init__(self, hub, name, slave, coil): async def async_added_to_hass(self): """Handle entity which will be added.""" - state = await async_get_last_state(self.hass, self.entity_id) + state = await self.async_get_last_state() if not state: return self._is_on = state.state == STATE_ON @@ -148,27 +136,29 @@ def update(self): try: self._is_on = bool(result.bits[0]) except AttributeError: - _LOGGER.error('No response from modbus slave %s coil %s', - self._slave, self._coil) + _LOGGER.error( + 'No response from modbus slave %s coil %s', + self._slave, + self._coil) class ModbusRegisterSwitch(ModbusCoilSwitch): """Representation of a Modbus register switch.""" # pylint: disable=super-init-not-called - def __init__(self, hub, name, slave, register, command_on, command_off, - verify_state, verify_register, register_type, state_on, - state_off): + def __init__(self, hub, name, slave, register, command_on, + command_off, verify_state, verify_register, + register_type, state_on, state_off): """Initialize the register switch.""" - self._hub = hub # type: BaseModbusClient + self._hub = hub self._name = name self._slave = slave self._register = register self._command_on = command_on self._command_off = command_off self._verify_state = verify_state - self._verify_register = (verify_register - if verify_register else self._register) + self._verify_register = ( + verify_register if verify_register else self._register) self._register_type = register_type if state_on is not None: @@ -185,14 +175,19 @@ def __init__(self, hub, name, slave, register, command_on, command_off, def turn_on(self, **kwargs): """Set switch on.""" - self._hub.write_register(self._slave, self._register, self._command_on) + self._hub.write_register( + self._slave, + self._register, + self._command_on) if not self._verify_state: self._is_on = True def turn_off(self, **kwargs): """Set switch off.""" - self._hub.write_register(self._slave, self._register, - self._command_off) + self._hub.write_register( + self._slave, + self._register, + self._command_off) if not self._verify_state: self._is_on = False @@ -203,17 +198,23 @@ def update(self): value = 0 if self._register_type == REGISTER_TYPE_INPUT: - result = self._hub.read_input_registers(self._slave, - self._register, 1) + result = self._hub.read_input_registers( + self._slave, + self._register, + 1) else: - result = self._hub.read_holding_registers(self._slave, - self._register, 1) + result = self._hub.read_holding_registers( + self._slave, + self._register, + 1) try: value = int(result.registers[0]) except AttributeError: - _LOGGER.error('No response from modbus slave %s register %s', - self._slave, self._verify_register) + _LOGGER.error( + 'No response from modbus slave %s register %s', + self._slave, + self._verify_register) if value == self._state_on: self._is_on = True @@ -222,5 +223,7 @@ def update(self): else: _LOGGER.error( 'Unexpected response from modbus slave %s ' - 'register %s, got 0x%2x', self._slave, self._verify_register, + 'register %s, got 0x%2x', + self._slave, + self._verify_register, value) From 69acf214b93404128bb4533a7b13ba11e1ac2ab7 Mon Sep 17 00:00:00 2001 From: Ben Van Mechelen Date: Thu, 20 Dec 2018 01:51:57 +0100 Subject: [PATCH 10/10] Update flexit for multi-device modbus --- homeassistant/components/climate/flexit.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/climate/flexit.py b/homeassistant/components/climate/flexit.py index de74d2facb57b..54c097aa66d7e 100644 --- a/homeassistant/components/climate/flexit.py +++ b/homeassistant/components/climate/flexit.py @@ -20,13 +20,14 @@ from homeassistant.components.climate import ( ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE) -from homeassistant.components import modbus +from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pyflexit==0.3'] DEPENDENCIES = ['modbus'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HUB_NAME, default="default"): cv.string, vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)), vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string }) @@ -40,15 +41,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Flexit Platform.""" modbus_slave = config.get(CONF_SLAVE, None) name = config.get(CONF_NAME, None) - add_entities([Flexit(modbus_slave, name)], True) + hub_name = config.get(CONF_HUB_NAME) + hub = hass.data[DOMAIN][hub_name] + add_entities([Flexit(hub, modbus_slave, name)], True) class Flexit(ClimateDevice): """Representation of a Flexit AC unit.""" - def __init__(self, modbus_slave, name): + def __init__(self, hub, modbus_slave, name): """Initialize the unit.""" from pyflexit import pyflexit + self._hub = hub self._name = name self._slave = modbus_slave self._target_temperature = None @@ -64,7 +68,7 @@ def __init__(self, modbus_slave, name): self._heating = None self._cooling = None self._alarm = False - self.unit = pyflexit.pyflexit(modbus.HUB, modbus_slave) + self.unit = pyflexit.pyflexit(hub, modbus_slave) @property def supported_features(self):