Modbus component refactoring - sensors and switches (#3297)

This commit is contained in:
Per Sandström 2016-09-13 22:47:44 +02:00 committed by Johann Kellerman
parent e4f4e91096
commit ca646c08c2
4 changed files with 213 additions and 202 deletions

View File

@ -0,0 +1,61 @@
"""
Support for Modbus Coil sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.modbus/
"""
import logging
import voluptuous as vol
import homeassistant.components.modbus as modbus
from homeassistant.const import CONF_NAME
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['modbus']
CONF_COIL = "coil"
CONF_COILS = "coils"
CONF_SLAVE = "slave"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COILS): [{
vol.Required(CONF_COIL): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_SLAVE): cv.positive_int
}]
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup 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)))
add_devices(sensors)
class ModbusCoilSensor(BinarySensorDevice):
"""Modbus coil sensor."""
def __init__(self, name, slave, coil):
"""Initialize the modbus coil sensor."""
self._name = name
self._slave = int(slave) if slave else None
self._coil = int(coil)
self._value = None
@property
def is_on(self):
"""Return the state of the sensor."""
return self._value
def update(self):
"""Update the state of the sensor."""
result = modbus.HUB.read_coils(self._slave, self._coil, 1)
self._value = result.bits[0]

View File

@ -7,8 +7,12 @@ https://home-assistant.io/components/modbus/
import logging import logging
import threading import threading
import voluptuous as vol
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)
import homeassistant.helpers.config_validation as cv
DOMAIN = "modbus" DOMAIN = "modbus"
@ -16,19 +20,33 @@ REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/'
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0'] 'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0']
# Type of network # Type of network
MEDIUM = "type" CONF_BAUDRATE = "baudrate"
CONF_BYTESIZE = "bytesize"
CONF_STOPBITS = "stopbits"
CONF_TYPE = "type"
CONF_PARITY = "parity"
# if MEDIUM == "serial" SERIAL_SCHEMA = {
METHOD = "method" vol.Required(CONF_BAUDRATE): cv.positive_int,
SERIAL_PORT = "port" vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8),
BAUDRATE = "baudrate" vol.Required(CONF_METHOD): vol.Any('rtu', 'ascii'),
STOPBITS = "stopbits" vol.Required(CONF_PORT): cv.string,
BYTESIZE = "bytesize" vol.Required(CONF_PARITY): vol.Any('E', 'O', 'N'),
PARITY = "parity" vol.Required(CONF_STOPBITS): vol.Any(1, 2),
vol.Required(CONF_TYPE): 'serial',
}
ETHERNET_SCHEMA = {
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): cv.positive_int,
vol.Required(CONF_TYPE): vol.Any('tcp', 'udp'),
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)
}, extra=vol.ALLOW_EXTRA)
# if MEDIUM == "tcp" or "udp"
HOST = "host"
IP_PORT = "port"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -38,36 +56,41 @@ ATTR_ADDRESS = "address"
ATTR_UNIT = "unit" ATTR_UNIT = "unit"
ATTR_VALUE = "value" ATTR_VALUE = "value"
SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema({
vol.Required(ATTR_UNIT): cv.positive_int,
vol.Required(ATTR_ADDRESS): cv.positive_int,
vol.Required(ATTR_VALUE): cv.positive_int
})
HUB = None HUB = None
TYPE = None
def setup(hass, config): def setup(hass, config):
"""Setup Modbus component.""" """Setup Modbus component."""
# Modbus connection type # Modbus connection type
# pylint: disable=global-statement, import-error # pylint: disable=global-statement, import-error
global TYPE client_type = config[DOMAIN][CONF_TYPE]
TYPE = config[DOMAIN][MEDIUM]
# Connect to Modbus network # Connect to Modbus network
# pylint: disable=global-statement, import-error # pylint: disable=global-statement, import-error
if TYPE == "serial": if client_type == "serial":
from pymodbus.client.sync import ModbusSerialClient as ModbusClient from pymodbus.client.sync import ModbusSerialClient as ModbusClient
client = ModbusClient(method=config[DOMAIN][METHOD], client = ModbusClient(method=config[DOMAIN][CONF_METHOD],
port=config[DOMAIN][SERIAL_PORT], port=config[DOMAIN][CONF_PORT],
baudrate=config[DOMAIN][BAUDRATE], baudrate=config[DOMAIN][CONF_BAUDRATE],
stopbits=config[DOMAIN][STOPBITS], stopbits=config[DOMAIN][CONF_STOPBITS],
bytesize=config[DOMAIN][BYTESIZE], bytesize=config[DOMAIN][CONF_BYTESIZE],
parity=config[DOMAIN][PARITY]) parity=config[DOMAIN][CONF_PARITY])
elif TYPE == "tcp": elif client_type == "tcp":
from pymodbus.client.sync import ModbusTcpClient as ModbusClient from pymodbus.client.sync import ModbusTcpClient as ModbusClient
client = ModbusClient(host=config[DOMAIN][HOST], client = ModbusClient(host=config[DOMAIN][CONF_HOST],
port=config[DOMAIN][IP_PORT]) port=config[DOMAIN][CONF_PORT])
elif TYPE == "udp": elif client_type == "udp":
from pymodbus.client.sync import ModbusUdpClient as ModbusClient from pymodbus.client.sync import ModbusUdpClient as ModbusClient
client = ModbusClient(host=config[DOMAIN][HOST], client = ModbusClient(host=config[DOMAIN][CONF_HOST],
port=config[DOMAIN][IP_PORT]) port=config[DOMAIN][CONF_PORT])
else: else:
return False return False
@ -84,7 +107,8 @@ def setup(hass, config):
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
hass.services.register(DOMAIN, SERVICE_WRITE_REGISTER, write_register) hass.services.register(DOMAIN, SERVICE_WRITE_REGISTER, write_register,
schema=SERVICE_WRITE_REGISTER_SCHEMA)
def write_register(service): def write_register(service):
"""Write modbus registers.""" """Write modbus registers."""
@ -128,39 +152,44 @@ class ModbusHub(object):
def read_coils(self, unit, address, count): def read_coils(self, unit, address, count):
"""Read coils.""" """Read coils."""
with self._lock: with self._lock:
kwargs = {'unit': unit} if unit else {}
return self._client.read_coils( return self._client.read_coils(
address, address,
count, count,
unit=unit) **kwargs)
def read_holding_registers(self, unit, address, count): def read_holding_registers(self, unit, address, count):
"""Read holding registers.""" """Read holding registers."""
with self._lock: with self._lock:
kwargs = {'unit': unit} if unit else {}
return self._client.read_holding_registers( return self._client.read_holding_registers(
address, address,
count, count,
unit=unit) **kwargs)
def write_coil(self, unit, address, value): def write_coil(self, unit, address, value):
"""Write coil.""" """Write coil."""
with self._lock: with self._lock:
kwargs = {'unit': unit} if unit else {}
self._client.write_coil( self._client.write_coil(
address, address,
value, value,
unit=unit) **kwargs)
def write_register(self, unit, address, value): def write_register(self, unit, address, value):
"""Write register.""" """Write register."""
with self._lock: with self._lock:
kwargs = {'unit': unit} if unit else {}
self._client.write_register( self._client.write_register(
address, address,
value, value,
unit=unit) **kwargs)
def write_registers(self, unit, address, values): def write_registers(self, unit, address, values):
"""Write registers.""" """Write registers."""
with self._lock: with self._lock:
kwargs = {'unit': unit} if unit else {}
self._client.write_registers( self._client.write_registers(
address, address,
values, values,
unit=unit) **kwargs)

View File

@ -1,100 +1,80 @@
""" """
Support for Modbus sensors. Support for Modbus Register sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.modbus/ https://home-assistant.io/components/sensor.modbus/
""" """
import logging import logging
import voluptuous as vol
import homeassistant.components.modbus as modbus import homeassistant.components.modbus as modbus
from homeassistant.const import ( from homeassistant.const import (
STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT) CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['modbus'] DEPENDENCIES = ['modbus']
CONF_COUNT = "count"
CONF_PRECISION = "precision"
CONF_REGISTER = "register"
CONF_REGISTERS = "registers"
CONF_SCALE = "scale"
CONF_SLAVE = "slave"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_REGISTERS): [{
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_REGISTER): cv.positive_int,
vol.Optional(CONF_COUNT, default=1): cv.positive_int,
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_UNIT_OF_MEASUREMENT): cv.string
}]
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Modbus devices.""" """Setup Modbus sensors."""
sensors = [] sensors = []
slave = config.get("slave", None) for register in config.get(CONF_REGISTERS):
if modbus.TYPE == "serial" and not slave: sensors.append(ModbusRegisterSensor(
_LOGGER.error("No slave number provided for serial Modbus") register.get(CONF_NAME),
return False register.get(CONF_SLAVE),
registers = config.get("registers") register.get(CONF_REGISTER),
if registers: register.get(CONF_UNIT_OF_MEASUREMENT),
for regnum, register in registers.items(): register.get(CONF_COUNT),
if register.get("name"): register.get(CONF_SCALE),
sensors.append( register.get(CONF_OFFSET),
ModbusSensor(register.get("name"), register.get(CONF_PRECISION)))
slave,
regnum,
None,
register.get("unit"),
scale=register.get("scale", 1),
offset=register.get("offset", 0),
precision=register.get("precision", 0)))
if register.get("bits"):
bits = register.get("bits")
for bitnum, bit in bits.items():
if bit.get("name"):
sensors.append(ModbusSensor(bit.get("name"),
slave,
regnum,
bitnum))
coils = config.get("coils")
if coils:
for coilnum, coil in coils.items():
sensors.append(ModbusSensor(coil.get("name"),
slave,
coilnum,
coil=True))
add_devices(sensors) add_devices(sensors)
class ModbusSensor(Entity): class ModbusRegisterSensor(Entity):
"""Representation of a Modbus Sensor.""" """Modbus resgister sensor."""
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-instance-attributes, too-many-arguments
def __init__(self, name, slave, register, bit=None, unit=None, coil=False, def __init__(self, name, slave, register, unit_of_measurement, count,
scale=1, offset=0, precision=0): scale, offset, precision):
"""Initialize the sensor.""" """Initialize the modbus register sensor."""
self._name = name self._name = name
self.slave = int(slave) if slave else 1 self._slave = int(slave) if slave else None
self.register = int(register) self._register = int(register)
self.bit = int(bit) if bit else None self._unit_of_measurement = unit_of_measurement
self._value = None self._count = int(count)
self._unit = unit
self._coil = coil
self._scale = scale self._scale = scale
self._offset = offset self._offset = offset
self._precision = precision self._precision = precision
self._value = None
def __str__(self):
"""Return the name and the state of the sensor."""
return "%s: %s" % (self.name, self.state)
@property
def should_poll(self):
"""Polling needed."""
return True
@property
def unique_id(self):
"""Return a unique id."""
return "MODBUS-SENSOR-{}-{}-{}".format(self.slave,
self.register,
self.bit)
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
if self.bit: return self._value
return STATE_ON if self._value else STATE_OFF
else:
return self._value
@property @property
def name(self): def name(self):
@ -103,28 +83,18 @@ class ModbusSensor(Entity):
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any.""" """Return the unit of measurement."""
if self._unit == "C": return self._unit_of_measurement
return TEMP_CELSIUS
elif self._unit == "F":
return TEMP_FAHRENHEIT
else:
return self._unit
def update(self): def update(self):
"""Update the state of the sensor.""" """Update the state of the sensor."""
if self._coil: result = modbus.HUB.read_holding_registers(
result = modbus.HUB.read_coils(self.slave, self.register, 1) self._slave,
self._value = result.bits[0] self._register,
else: self._count)
result = modbus.HUB.read_holding_registers( val = 0
self.slave, self.register, 1) for i, res in enumerate(result.registers):
val = 0 val += res * (2**(i*16))
for i, res in enumerate(result.registers): self._value = format(
val += res * (2**(i*16)) self._scale * val + self._offset,
if self.bit: ".{}f".format(self._precision))
self._value = val & (0x0001 << self.bit)
else:
self._value = format(
self._scale * val + self._offset,
".{}f".format(self._precision))

View File

@ -5,74 +5,51 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.modbus/ https://home-assistant.io/components/switch.modbus/
""" """
import logging import logging
import voluptuous as vol
import homeassistant.components.modbus as modbus import homeassistant.components.modbus as modbus
from homeassistant.const import CONF_NAME
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['modbus'] DEPENDENCIES = ['modbus']
CONF_COIL = "coil"
CONF_COILS = "coils"
CONF_SLAVE = "slave"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COILS): [{
vol.Required(CONF_COIL): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_SLAVE): cv.positive_int,
}]
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Read configuration and create Modbus devices.""" """Read configuration and create Modbus devices."""
switches = [] switches = []
slave = config.get("slave", None) for coil in config.get("coils"):
if modbus.TYPE == "serial" and not slave: switches.append(ModbusCoilSwitch(
_LOGGER.error("No slave number provided for serial Modbus") coil.get(CONF_NAME),
return False coil.get(CONF_SLAVE),
registers = config.get("registers") coil.get(CONF_COIL)))
if registers:
for regnum, register in registers.items():
bits = register.get("bits")
for bitnum, bit in bits.items():
if bit.get("name"):
switches.append(ModbusSwitch(bit.get("name"),
slave,
regnum,
bitnum))
coils = config.get("coils")
if coils:
for coilnum, coil in coils.items():
switches.append(ModbusSwitch(coil.get("name"),
slave,
coilnum,
0,
coil=True))
add_devices(switches) add_devices(switches)
class ModbusSwitch(ToggleEntity): class ModbusCoilSwitch(ToggleEntity):
"""Representation of a Modbus switch.""" """Representation of a Modbus switch."""
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, name, slave, register, bit, coil=False): def __init__(self, name, slave, coil):
"""Initialize the switch.""" """Initialize the switch."""
self._name = name self._name = name
self.slave = int(slave) if slave else 1 self._slave = int(slave) if slave else None
self.register = int(register) self._coil = int(coil)
self.bit = int(bit)
self._coil = coil
self._is_on = None self._is_on = None
self.register_value = None
def __str__(self):
"""String representation of Modbus switch."""
return "%s: %s" % (self.name, self.state)
@property
def should_poll(self):
"""Poling needed.
Slaves are not allowed to initiate communication on Modbus networks.
"""
return True
@property
def unique_id(self):
"""Return a unique ID."""
return "MODBUS-SWITCH-{}-{}-{}".format(self.slave,
self.register,
self.bit)
@property @property
def is_on(self): def is_on(self):
@ -86,39 +63,13 @@ class ModbusSwitch(ToggleEntity):
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Set switch on.""" """Set switch on."""
if self.register_value is None: modbus.HUB.write_coil(self._slave, self._coil, True)
self.update()
if self._coil:
modbus.HUB.write_coil(self.slave, self.register, True)
else:
val = self.register_value | (0x0001 << self.bit)
modbus.HUB.write_register(self.slave, self.register, val)
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Set switch off.""" """Set switch off."""
if self.register_value is None: modbus.HUB.write_coil(self._slave, self._coil, False)
self.update()
if self._coil:
modbus.HUB.write_coil(self.slave, self.register, False)
else:
val = self.register_value & ~(0x0001 << self.bit)
modbus.HUB.write_register(self.slave, self.register, val)
def update(self): def update(self):
"""Update the state of the switch.""" """Update the state of the switch."""
if self._coil: result = modbus.HUB.read_coils(self._slave, self._coil, 1)
result = modbus.HUB.read_coils(self.slave, self.register, 1) self._is_on = bool(result.bits[0])
self.register_value = result.bits[0]
self._is_on = self.register_value
else:
result = modbus.HUB.read_holding_registers(
self.slave,
self.register,
1)
val = 0
for i, res in enumerate(result.registers):
val += res * (2**(i*16))
self.register_value = val
self._is_on = (val & (0x0001 << self.bit) > 0)