Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]Support for Multi modbus hub #17901

Closed
wants to merge 10 commits into from
17 changes: 10 additions & 7 deletions homeassistant/components/binary_sensor/modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging
import voluptuous as vol

from homeassistant.components import modbus
from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN
from homeassistant.const import CONF_NAME, CONF_SLAVE
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv
Expand All @@ -21,6 +21,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
Expand All @@ -32,7 +33,10 @@ 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)))
Expand All @@ -42,8 +46,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class ModbusCoilSensor(BinarySensorDevice):
"""Modbus coil sensor."""

def __init__(self, name, slave, coil):
def __init__(self, hub, name, slave, coil):
"""Initialize the modbus coil sensor."""
self._hub = hub
self._name = name
self._slave = int(slave) if slave else None
self._coil = int(coil)
Expand All @@ -61,11 +66,9 @@ def is_on(self):

def update(self):
"""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:
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)
12 changes: 8 additions & 4 deletions homeassistant/components/climate/flexit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
Expand All @@ -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
Expand All @@ -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):
Expand Down
16 changes: 10 additions & 6 deletions homeassistant/components/climate/modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE)

from homeassistant.components import modbus
from homeassistant.components.modbus import CONF_HUB_NAME, DOMAIN
import homeassistant.helpers.config_validation as cv

DEPENDENCIES = ['modbus']
Expand All @@ -35,6 +35,7 @@
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,
Expand All @@ -59,18 +60,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)
hub = hass.data[DOMAIN][hub_name]

add_entities([ModbusThermostat(name, modbus_slave,
add_entities([ModbusThermostat(hub, 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, modbus_slave, target_temp_register,
current_temp_register, data_type, count, precision):
"""Initialize the unit."""
self._hub = hub
self._name = name
self._slave = modbus_slave
self._target_temperature_register = target_temp_register
Expand Down Expand Up @@ -133,8 +137,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._hub.read_holding_registers(self._slave, register,
self._count)
except AttributeError as ex:
_LOGGER.error(ex)
byte_string = b''.join(
Expand All @@ -145,4 +149,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._hub.write_registers(self._slave, register, [value, 0])
118 changes: 69 additions & 49 deletions homeassistant/components/modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
CONF_HOST, CONF_METHOD, CONF_PORT, CONF_TYPE, CONF_TIMEOUT, ATTR_STATE)

_LOGGER = logging.getLogger(__name__)

DOMAIN = 'modbus'

REQUIREMENTS = ['pymodbus==1.3.1']
Expand All @@ -23,8 +25,13 @@
CONF_BYTESIZE = 'bytesize'
CONF_STOPBITS = 'stopbits'
CONF_PARITY = 'parity'
CONF_HUB_NAME = 'hub_name'

SERIAL_SCHEMA = {
BASE_SCHEMA = vol.Schema({
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'),
Expand All @@ -33,20 +40,20 @@
vol.Required(CONF_STOPBITS): vol.Any(1, 2),
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.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
}

})

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)
}, extra=vol.ALLOW_EXTRA)

DOMAIN:
vol.All(cv.ensure_list,
vol.Schema([vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)]))
}, extra=vol.ALLOW_EXTRA,)

_LOGGER = logging.getLogger(__name__)

Expand All @@ -58,89 +65,101 @@
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])
})

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
})

HUB = None


def setup(hass, config):
"""Set up Modbus component."""
# Modbus connection type
client_type = config[DOMAIN][CONF_TYPE]

# Connect to Modbus network
# pylint: disable=import-error
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
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':
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 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.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
client = ModbusClient(host=config[DOMAIN][CONF_HOST],
port=config[DOMAIN][CONF_PORT],
timeout=config[DOMAIN][CONF_TIMEOUT])
elif client_type == 'udp':
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
client = ModbusClient(host=config[DOMAIN][CONF_HOST],
port=config[DOMAIN][CONF_PORT],
timeout=config[DOMAIN][CONF_TIMEOUT])
else:
return False
return ModbusClient(host=client_config[CONF_HOST],
port=client_config[CONF_PORT],
timeout=client_config[CONF_TIMEOUT])
assert False

global HUB
HUB = ModbusHub(client)

def setup(hass, config):
"""Set up Modbus component."""
# Modbus connection type
hass.data[DOMAIN] = hub_collect = {}

for client_config in config[DOMAIN]:
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):
"""Stop Modbus service."""
HUB.close()
for client in hub_collect.values():
client.close()

def start_modbus(event):
"""Start Modbus service."""
HUB.connect()
for client in hub_collect.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,
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):
"""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(
hub_collect[client_name].write_registers(
unit,
address,
[int(float(i)) for i in value])
else:
HUB.write_register(
hub_collect[client_name].write_register(
unit,
address,
int(float(value)))
Expand All @@ -150,7 +169,8 @@ def write_coil(service):
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_collect[client_name].write_coil(unit, address, state)

hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_modbus)

Expand Down