Support for Multiple modbus hubs (#19726)

* modbus: support multiple modbus hub

* update data after entities added

* pass hub object to each entity. and save hub to hass.data but not in module level

* add hub_client setup log

* don't update when adding device, because hub_client is not ready right now

* support restore last state

* remove useless func

* compatible with python35

* removed unrelated style changes

* Update flexit for multi-device modbus

* change how hubs are referenced in the configuration

* Also update climate/modbus.py

* Remove unwanted whitescapce

* Defined common constants centrally

* Update DOMAIN in climate and switch components

* Removed unnecessary vol.schema

* Make hub name optional

* Add name property to ModbusHub
This commit is contained in:
Ben Van Mechelen 2019-02-11 20:00:37 +01:00 committed by Charles Garwood
parent 49ecca9cb9
commit 861d58f58f
6 changed files with 163 additions and 94 deletions

View file

@ -20,13 +20,15 @@ from homeassistant.const import (
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_FAN_MODE) SUPPORT_FAN_MODE)
from homeassistant.components import modbus from homeassistant.components.modbus import (
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyflexit==0.3'] REQUIREMENTS = ['pyflexit==0.3']
DEPENDENCIES = ['modbus'] DEPENDENCIES = ['modbus']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)), vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)),
vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string
}) })
@ -40,15 +42,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Flexit Platform.""" """Set up the Flexit Platform."""
modbus_slave = config.get(CONF_SLAVE, None) modbus_slave = config.get(CONF_SLAVE, None)
name = config.get(CONF_NAME, None) name = config.get(CONF_NAME, None)
add_entities([Flexit(modbus_slave, name)], True) hub = hass.data[MODBUS_DOMAIN][config.get(CONF_HUB)]
add_entities([Flexit(hub, modbus_slave, name)], True)
class Flexit(ClimateDevice): class Flexit(ClimateDevice):
"""Representation of a Flexit AC unit.""" """Representation of a Flexit AC unit."""
def __init__(self, modbus_slave, name): def __init__(self, hub, modbus_slave, name):
"""Initialize the unit.""" """Initialize the unit."""
from pyflexit import pyflexit from pyflexit import pyflexit
self._hub = hub
self._name = name self._name = name
self._slave = modbus_slave self._slave = modbus_slave
self._target_temperature = None self._target_temperature = None
@ -64,7 +68,7 @@ class Flexit(ClimateDevice):
self._heating = None self._heating = None
self._cooling = None self._cooling = None
self._alarm = False self._alarm = False
self.unit = pyflexit.pyflexit(modbus.HUB, modbus_slave) self.unit = pyflexit.pyflexit(hub, modbus_slave)
@property @property
def supported_features(self): def supported_features(self):

View file

@ -12,19 +12,27 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
CONF_HOST, CONF_METHOD, CONF_PORT, CONF_TYPE, CONF_TIMEOUT, ATTR_STATE) CONF_HOST, CONF_METHOD, CONF_NAME, CONF_PORT, CONF_TYPE, CONF_TIMEOUT,
ATTR_STATE)
DOMAIN = 'modbus' DOMAIN = 'modbus'
REQUIREMENTS = ['pymodbus==1.5.2'] REQUIREMENTS = ['pymodbus==1.5.2']
CONF_HUB = 'hub'
# Type of network # Type of network
CONF_BAUDRATE = 'baudrate' CONF_BAUDRATE = 'baudrate'
CONF_BYTESIZE = 'bytesize' CONF_BYTESIZE = 'bytesize'
CONF_STOPBITS = 'stopbits' CONF_STOPBITS = 'stopbits'
CONF_PARITY = 'parity' CONF_PARITY = 'parity'
SERIAL_SCHEMA = { DEFAULT_HUB = 'default'
BASE_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string
})
SERIAL_SCHEMA = BASE_SCHEMA.extend({
vol.Required(CONF_BAUDRATE): cv.positive_int, vol.Required(CONF_BAUDRATE): cv.positive_int,
vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8), 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'),
@ -33,20 +41,18 @@ SERIAL_SCHEMA = {
vol.Required(CONF_STOPBITS): vol.Any(1, 2), 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, 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_HOST): cv.string,
vol.Required(CONF_PORT): cv.port, 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, vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
} })
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA) DOMAIN: vol.All(cv.ensure_list, [vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)])
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA,)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,71 +60,79 @@ SERVICE_WRITE_REGISTER = 'write_register'
SERVICE_WRITE_COIL = 'write_coil' SERVICE_WRITE_COIL = 'write_coil'
ATTR_ADDRESS = 'address' ATTR_ADDRESS = 'address'
ATTR_HUB = 'hub'
ATTR_UNIT = 'unit' ATTR_UNIT = 'unit'
ATTR_VALUE = 'value' ATTR_VALUE = 'value'
SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema({ SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema({
vol.Optional(ATTR_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(ATTR_UNIT): cv.positive_int, vol.Required(ATTR_UNIT): cv.positive_int,
vol.Required(ATTR_ADDRESS): 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({ SERVICE_WRITE_COIL_SCHEMA = vol.Schema({
vol.Optional(ATTR_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(ATTR_UNIT): cv.positive_int, vol.Required(ATTR_UNIT): cv.positive_int,
vol.Required(ATTR_ADDRESS): cv.positive_int, vol.Required(ATTR_ADDRESS): cv.positive_int,
vol.Required(ATTR_STATE): cv.boolean vol.Required(ATTR_STATE): cv.boolean
}) })
HUB = None
def setup_client(client_config):
"""Set up pymodbus client."""
client_type = client_config[CONF_TYPE]
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, config): def setup(hass, config):
"""Set up Modbus component.""" """Set up Modbus component."""
# Modbus connection type # Modbus connection type
client_type = config[DOMAIN][CONF_TYPE] hass.data[DOMAIN] = hub_collect = {}
# Connect to Modbus network for client_config in config[DOMAIN]:
# pylint: disable=import-error client = setup_client(client_config)
name = client_config[CONF_NAME]
if client_type == 'serial': hub_collect[name] = ModbusHub(client, name)
from pymodbus.client.sync import ModbusSerialClient as ModbusClient _LOGGER.debug('Setting up hub: %s', client_config)
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
global HUB
HUB = ModbusHub(client)
def stop_modbus(event): def stop_modbus(event):
"""Stop Modbus service.""" """Stop Modbus service."""
HUB.close() for client in hub_collect.values():
client.close()
def start_modbus(event): def start_modbus(event):
"""Start Modbus service.""" """Start Modbus service."""
HUB.connect() for client in hub_collect.values():
client.connect()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus)
# Register services for modbus # Register services for modbus
@ -134,13 +148,14 @@ def setup(hass, config):
unit = int(float(service.data.get(ATTR_UNIT))) unit = int(float(service.data.get(ATTR_UNIT)))
address = int(float(service.data.get(ATTR_ADDRESS))) address = int(float(service.data.get(ATTR_ADDRESS)))
value = service.data.get(ATTR_VALUE) value = service.data.get(ATTR_VALUE)
client_name = service.data.get(ATTR_HUB)
if isinstance(value, list): if isinstance(value, list):
HUB.write_registers( hub_collect[client_name].write_registers(
unit, unit,
address, address,
[int(float(i)) for i in value]) [int(float(i)) for i in value])
else: else:
HUB.write_register( hub_collect[client_name].write_register(
unit, unit,
address, address,
int(float(value))) int(float(value)))
@ -150,7 +165,8 @@ def setup(hass, config):
unit = service.data.get(ATTR_UNIT) unit = service.data.get(ATTR_UNIT)
address = service.data.get(ATTR_ADDRESS) address = service.data.get(ATTR_ADDRESS)
state = service.data.get(ATTR_STATE) state = service.data.get(ATTR_STATE)
HUB.write_coil(unit, address, state) client_name = service.data.get(ATTR_HUB)
hub_collect[client_name].write_coil(unit, address, state)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_modbus) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_modbus)
@ -160,10 +176,16 @@ def setup(hass, config):
class ModbusHub: class ModbusHub:
"""Thread safe wrapper class for pymodbus.""" """Thread safe wrapper class for pymodbus."""
def __init__(self, modbus_client): def __init__(self, modbus_client, name):
"""Initialize the modbus hub.""" """Initialize the modbus hub."""
self._client = modbus_client self._client = modbus_client
self._lock = threading.Lock() self._lock = threading.Lock()
self._name = name
@property
def name(self):
"""Return the name of this hub."""
return self._name
def close(self): def close(self):
"""Disconnect client.""" """Disconnect client."""

View file

@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.modbus/
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components import modbus from homeassistant.components.modbus import (
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
from homeassistant.const import CONF_NAME, CONF_SLAVE from homeassistant.const import CONF_NAME, CONF_SLAVE
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
@ -21,6 +22,7 @@ CONF_COILS = 'coils'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COILS): [{ vol.Required(CONF_COILS): [{
vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_COIL): cv.positive_int,
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_SLAVE): cv.positive_int vol.Optional(CONF_SLAVE): cv.positive_int
@ -32,7 +34,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Modbus binary sensors.""" """Set up the Modbus binary sensors."""
sensors = [] sensors = []
for coil in config.get(CONF_COILS): for coil in config.get(CONF_COILS):
hub = hass.data[MODBUS_DOMAIN][coil.get(CONF_HUB)]
sensors.append(ModbusCoilSensor( sensors.append(ModbusCoilSensor(
hub,
coil.get(CONF_NAME), coil.get(CONF_NAME),
coil.get(CONF_SLAVE), coil.get(CONF_SLAVE),
coil.get(CONF_COIL))) coil.get(CONF_COIL)))
@ -42,8 +46,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class ModbusCoilSensor(BinarySensorDevice): class ModbusCoilSensor(BinarySensorDevice):
"""Modbus coil sensor.""" """Modbus coil sensor."""
def __init__(self, name, slave, coil): def __init__(self, hub, name, slave, coil):
"""Initialize the modbus coil sensor.""" """Initialize the modbus coil sensor."""
self._hub = hub
self._name = name self._name = name
self._slave = int(slave) if slave else None self._slave = int(slave) if slave else None
self._coil = int(coil) self._coil = int(coil)
@ -61,11 +66,9 @@ class ModbusCoilSensor(BinarySensorDevice):
def update(self): def update(self):
"""Update the state of the sensor.""" """Update the state of the sensor."""
result = modbus.HUB.read_coils(self._slave, self._coil, 1) result = self._hub.read_coils(self._slave, self._coil, 1)
try: try:
self._value = result.bits[0] self._value = result.bits[0]
except AttributeError: except AttributeError:
_LOGGER.error( _LOGGER.error('No response from hub %s, slave %s, coil %s',
'No response from modbus slave %s coil %s', self._hub.name, self._slave, self._coil)
self._slave,
self._coil)

View file

@ -17,8 +17,8 @@ from homeassistant.const import (
CONF_NAME, CONF_SLAVE, ATTR_TEMPERATURE) CONF_NAME, CONF_SLAVE, ATTR_TEMPERATURE)
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.components.modbus import (
from homeassistant.components import modbus CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['modbus'] DEPENDENCIES = ['modbus']
@ -35,6 +35,7 @@ DATA_TYPE_UINT = 'uint'
DATA_TYPE_FLOAT = 'float' DATA_TYPE_FLOAT = 'float'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_SLAVE): cv.positive_int, vol.Required(CONF_SLAVE): cv.positive_int,
vol.Required(CONF_TARGET_TEMP): cv.positive_int, vol.Required(CONF_TARGET_TEMP): cv.positive_int,
@ -59,8 +60,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
data_type = config.get(CONF_DATA_TYPE) data_type = config.get(CONF_DATA_TYPE)
count = config.get(CONF_COUNT) count = config.get(CONF_COUNT)
precision = config.get(CONF_PRECISION) precision = config.get(CONF_PRECISION)
hub_name = config.get(CONF_HUB)
hub = hass.data[MODBUS_DOMAIN][hub_name]
add_entities([ModbusThermostat(name, modbus_slave, add_entities([ModbusThermostat(hub, name, modbus_slave,
target_temp_register, current_temp_register, target_temp_register, current_temp_register,
data_type, count, precision)], True) data_type, count, precision)], True)
@ -68,9 +71,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class ModbusThermostat(ClimateDevice): class ModbusThermostat(ClimateDevice):
"""Representation of a Modbus Thermostat.""" """Representation of a Modbus Thermostat."""
def __init__(self, name, modbus_slave, target_temp_register, def __init__(self, hub, name, modbus_slave, target_temp_register,
current_temp_register, data_type, count, precision): current_temp_register, data_type, count, precision):
"""Initialize the unit.""" """Initialize the unit."""
self._hub = hub
self._name = name self._name = name
self._slave = modbus_slave self._slave = modbus_slave
self._target_temperature_register = target_temp_register self._target_temperature_register = target_temp_register
@ -133,7 +137,7 @@ class ModbusThermostat(ClimateDevice):
def read_register(self, register): def read_register(self, register):
"""Read holding register using the modbus hub slave.""" """Read holding register using the modbus hub slave."""
try: try:
result = modbus.HUB.read_holding_registers(self._slave, register, result = self._hub.read_holding_registers(self._slave, register,
self._count) self._count)
except AttributeError as ex: except AttributeError as ex:
_LOGGER.error(ex) _LOGGER.error(ex)
@ -145,4 +149,4 @@ class ModbusThermostat(ClimateDevice):
def write_register(self, register, value): def write_register(self, register, value):
"""Write register using the modbus hub slave.""" """Write register using the modbus hub slave."""
modbus.HUB.write_registers(self._slave, register, [value, 0]) self._hub.write_registers(self._slave, register, [value, 0])

View file

@ -9,11 +9,12 @@ import struct
import voluptuous as vol import voluptuous as vol
from homeassistant.components import modbus from homeassistant.components.modbus import (
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT, CONF_SLAVE, CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT, CONF_SLAVE,
CONF_STRUCTURE) CONF_STRUCTURE)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.sensor import PLATFORM_SCHEMA
@ -40,6 +41,7 @@ DATA_TYPE_CUSTOM = 'custom'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_REGISTERS): [{ vol.Required(CONF_REGISTERS): [{
vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_REGISTER): cv.positive_int, vol.Required(CONF_REGISTER): cv.positive_int,
vol.Optional(CONF_REGISTER_TYPE, default=REGISTER_TYPE_HOLDING): vol.Optional(CONF_REGISTER_TYPE, default=REGISTER_TYPE_HOLDING):
@ -70,8 +72,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
structure = '>i' structure = '>i'
if register.get(CONF_DATA_TYPE) != DATA_TYPE_CUSTOM: if register.get(CONF_DATA_TYPE) != DATA_TYPE_CUSTOM:
try: try:
structure = '>{}'.format(data_types[ structure = '>{}'.format(data_types[register.get(
register.get(CONF_DATA_TYPE)][register.get(CONF_COUNT)]) CONF_DATA_TYPE)][register.get(CONF_COUNT)])
except KeyError: except KeyError:
_LOGGER.error("Unable to detect data type for %s sensor, " _LOGGER.error("Unable to detect data type for %s sensor, "
"try a custom type.", register.get(CONF_NAME)) "try a custom type.", register.get(CONF_NAME))
@ -93,7 +95,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"(%d words)", size, register.get(CONF_COUNT)) "(%d words)", size, register.get(CONF_COUNT))
continue continue
hub_name = register.get(CONF_HUB)
hub = hass.data[MODBUS_DOMAIN][hub_name]
sensors.append(ModbusRegisterSensor( sensors.append(ModbusRegisterSensor(
hub,
register.get(CONF_NAME), register.get(CONF_NAME),
register.get(CONF_SLAVE), register.get(CONF_SLAVE),
register.get(CONF_REGISTER), register.get(CONF_REGISTER),
@ -111,13 +116,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(sensors) add_entities(sensors)
class ModbusRegisterSensor(Entity): class ModbusRegisterSensor(RestoreEntity):
"""Modbus register sensor.""" """Modbus register sensor."""
def __init__(self, name, slave, register, register_type, def __init__(self, hub, name, slave, register, register_type,
unit_of_measurement, count, reverse_order, scale, offset, unit_of_measurement, count, reverse_order, scale, offset,
structure, precision): structure, precision):
"""Initialize the modbus register sensor.""" """Initialize the modbus register sensor."""
self._hub = hub
self._name = name self._name = name
self._slave = int(slave) if slave else None self._slave = int(slave) if slave else None
self._register = int(register) self._register = int(register)
@ -131,6 +137,13 @@ class ModbusRegisterSensor(Entity):
self._structure = structure self._structure = structure
self._value = None self._value = None
async def async_added_to_hass(self):
"""Handle entity which will be added."""
state = await self.async_get_last_state()
if not state:
return
self._value = state.state
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
@ -149,12 +162,12 @@ class ModbusRegisterSensor(Entity):
def update(self): def update(self):
"""Update the state of the sensor.""" """Update the state of the sensor."""
if self._register_type == REGISTER_TYPE_INPUT: if self._register_type == REGISTER_TYPE_INPUT:
result = modbus.HUB.read_input_registers( result = self._hub.read_input_registers(
self._slave, self._slave,
self._register, self._register,
self._count) self._count)
else: else:
result = modbus.HUB.read_holding_registers( result = self._hub.read_holding_registers(
self._slave, self._slave,
self._register, self._register,
self._count) self._count)
@ -165,8 +178,8 @@ class ModbusRegisterSensor(Entity):
if self._reverse_order: if self._reverse_order:
registers.reverse() registers.reverse()
except AttributeError: except AttributeError:
_LOGGER.error("No response from modbus slave %s, register %s", _LOGGER.error("No response from hub %s, slave %s, register %s",
self._slave, self._register) self._hub.name, self._slave, self._register)
return return
byte_string = b''.join( byte_string = b''.join(
[x.to_bytes(2, byteorder='big') for x in registers] [x.to_bytes(2, byteorder='big') for x in registers]

View file

@ -7,10 +7,12 @@ https://home-assistant.io/components/switch.modbus/
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components import modbus from homeassistant.components.modbus import (
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_SLAVE, CONF_COMMAND_ON, CONF_COMMAND_OFF) CONF_NAME, CONF_SLAVE, CONF_COMMAND_ON, CONF_COMMAND_OFF, STATE_ON)
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.sensor import PLATFORM_SCHEMA
@ -31,6 +33,7 @@ REGISTER_TYPE_HOLDING = 'holding'
REGISTER_TYPE_INPUT = 'input' REGISTER_TYPE_INPUT = 'input'
REGISTERS_SCHEMA = vol.Schema({ REGISTERS_SCHEMA = vol.Schema({
vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_SLAVE): cv.positive_int, vol.Optional(CONF_SLAVE): cv.positive_int,
vol.Required(CONF_REGISTER): cv.positive_int, vol.Required(CONF_REGISTER): cv.positive_int,
@ -46,6 +49,7 @@ REGISTERS_SCHEMA = vol.Schema({
}) })
COILS_SCHEMA = vol.Schema({ COILS_SCHEMA = vol.Schema({
vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_COIL): cv.positive_int,
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_SLAVE): cv.positive_int, vol.Required(CONF_SLAVE): cv.positive_int,
@ -64,13 +68,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
switches = [] switches = []
if CONF_COILS in config: if CONF_COILS in config:
for coil in config.get(CONF_COILS): for coil in config.get(CONF_COILS):
hub_name = coil.get(CONF_HUB)
hub = hass.data[MODBUS_DOMAIN][hub_name]
switches.append(ModbusCoilSwitch( switches.append(ModbusCoilSwitch(
hub,
coil.get(CONF_NAME), coil.get(CONF_NAME),
coil.get(CONF_SLAVE), coil.get(CONF_SLAVE),
coil.get(CONF_COIL))) coil.get(CONF_COIL)))
if CONF_REGISTERS in config: if CONF_REGISTERS in config:
for register in config.get(CONF_REGISTERS): for register in config.get(CONF_REGISTERS):
hub_name = register.get(CONF_HUB)
hub = hass.data[MODBUS_DOMAIN][hub_name]
switches.append(ModbusRegisterSwitch( switches.append(ModbusRegisterSwitch(
hub,
register.get(CONF_NAME), register.get(CONF_NAME),
register.get(CONF_SLAVE), register.get(CONF_SLAVE),
register.get(CONF_REGISTER), register.get(CONF_REGISTER),
@ -84,16 +95,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(switches) add_entities(switches)
class ModbusCoilSwitch(ToggleEntity): class ModbusCoilSwitch(ToggleEntity, RestoreEntity):
"""Representation of a Modbus coil switch.""" """Representation of a Modbus coil switch."""
def __init__(self, name, slave, coil): def __init__(self, hub, name, slave, coil):
"""Initialize the coil switch.""" """Initialize the coil switch."""
self._hub = hub
self._name = name self._name = name
self._slave = int(slave) if slave else None self._slave = int(slave) if slave else None
self._coil = int(coil) self._coil = int(coil)
self._is_on = None self._is_on = None
async def async_added_to_hass(self):
"""Handle entity which will be added."""
state = await self.async_get_last_state()
if not state:
return
self._is_on = state.state == STATE_ON
@property @property
def is_on(self): def is_on(self):
"""Return true if switch is on.""" """Return true if switch is on."""
@ -106,20 +125,21 @@ class ModbusCoilSwitch(ToggleEntity):
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Set switch on.""" """Set switch on."""
modbus.HUB.write_coil(self._slave, self._coil, True) self._hub.write_coil(self._slave, self._coil, True)
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Set switch off.""" """Set switch off."""
modbus.HUB.write_coil(self._slave, self._coil, False) self._hub.write_coil(self._slave, self._coil, False)
def update(self): def update(self):
"""Update the state of the switch.""" """Update the state of the switch."""
result = modbus.HUB.read_coils(self._slave, self._coil, 1) result = self._hub.read_coils(self._slave, self._coil, 1)
try: try:
self._is_on = bool(result.bits[0]) self._is_on = bool(result.bits[0])
except AttributeError: except AttributeError:
_LOGGER.error( _LOGGER.error(
'No response from modbus slave %s coil %s', 'No response from hub %s, slave %s, coil %s',
self._hub.name,
self._slave, self._slave,
self._coil) self._coil)
@ -128,10 +148,11 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
"""Representation of a Modbus register switch.""" """Representation of a Modbus register switch."""
# pylint: disable=super-init-not-called # pylint: disable=super-init-not-called
def __init__(self, name, slave, register, command_on, def __init__(self, hub, name, slave, register, command_on,
command_off, verify_state, verify_register, command_off, verify_state, verify_register,
register_type, state_on, state_off): register_type, state_on, state_off):
"""Initialize the register switch.""" """Initialize the register switch."""
self._hub = hub
self._name = name self._name = name
self._slave = slave self._slave = slave
self._register = register self._register = register
@ -156,7 +177,7 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Set switch on.""" """Set switch on."""
modbus.HUB.write_register( self._hub.write_register(
self._slave, self._slave,
self._register, self._register,
self._command_on) self._command_on)
@ -165,7 +186,7 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Set switch off.""" """Set switch off."""
modbus.HUB.write_register( self._hub.write_register(
self._slave, self._slave,
self._register, self._register,
self._command_off) self._command_off)
@ -179,12 +200,12 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
value = 0 value = 0
if self._register_type == REGISTER_TYPE_INPUT: if self._register_type == REGISTER_TYPE_INPUT:
result = modbus.HUB.read_input_registers( result = self._hub.read_input_registers(
self._slave, self._slave,
self._register, self._register,
1) 1)
else: else:
result = modbus.HUB.read_holding_registers( result = self._hub.read_holding_registers(
self._slave, self._slave,
self._register, self._register,
1) 1)
@ -193,7 +214,8 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
value = int(result.registers[0]) value = int(result.registers[0])
except AttributeError: except AttributeError:
_LOGGER.error( _LOGGER.error(
'No response from modbus slave %s register %s', 'No response from hub %s, slave %s, register %s',
self._hub.name,
self._slave, self._slave,
self._verify_register) self._verify_register)
@ -203,8 +225,9 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
self._is_on = False self._is_on = False
else: else:
_LOGGER.error( _LOGGER.error(
'Unexpected response from modbus slave %s ' 'Unexpected response from hub %s, slave %s '
'register %s, got 0x%2x', 'register %s, got 0x%2x',
self._hub.name,
self._slave, self._slave,
self._verify_register, self._verify_register,
value) value)