Add multiple gateways

* Add support for multiple serial gateways.
* Fix serialization of python objects by adding dict representation of
    classes.
* Add support for showing more than one child value type per entity.
    The entity state is always only one value type. This is defined by
    the platform value types. Value types that are not defined as the
    platform value type are shown as state_attributes.
* Add more unit of measurement types.
* Clean up code.
This commit is contained in:
MartinHjelmare 2015-12-06 00:29:03 +01:00
parent 1d141566bd
commit 59524c7933
4 changed files with 240 additions and 139 deletions

View file

@ -23,27 +23,25 @@ CONF_VERSION = 'version'
DOMAIN = 'mysensors'
DEPENDENCIES = []
REQUIREMENTS = ['file:///home/martin/Dev/pymysensors-fifo_queue.zip'
REQUIREMENTS = [
'https://github.com/MartinHjelmare/pymysensors/archive/fifo_queue.zip'
'#pymysensors==0.3']
_LOGGER = logging.getLogger(__name__)
ATTR_PORT = 'port'
ATTR_DEVICES = 'devices'
ATTR_NODE_ID = 'node_id'
ATTR_CHILD_ID = 'child_id'
ATTR_UPDATE_TYPE = 'update_type'
PLATFORM_FORMAT = '{}.{}'
IS_METRIC = None
DEVICES = None
GATEWAY = None
EVENT_MYSENSORS_NODE_UPDATE = 'MYSENSORS_NODE_UPDATE'
UPDATE_TYPE = 'update_type'
NODE_ID = 'nid'
CONST = None
GATEWAYS = None
EVENT_MYSENSORS_NODE_UPDATE = 'MYSENSORS_NODE_UPDATE'
def setup(hass, config):
def setup(hass, config): # noqa
""" Setup the MySensors component. """
# pylint:disable=no-name-in-module
import mysensors.mysensors as mysensors
if not validate_config(config,
@ -57,53 +55,83 @@ def setup(hass, config):
if version == '1.4':
import mysensors.const_14 as const
CONST = const
_LOGGER.info('CONST = %s, 1.4', const)
elif version == '1.5':
import mysensors.const_15 as const
CONST = const
_LOGGER.info('CONST = %s, 1.5', const)
else:
import mysensors.const_14 as const
CONST = const
_LOGGER.info('CONST = %s, 1.4 default', const)
global IS_METRIC
# Just assume celcius means that the user wants metric for now.
# It may make more sense to make this a global config option in the future.
global IS_METRIC
IS_METRIC = (hass.config.temperature_unit == TEMP_CELCIUS)
global DEVICES
DEVICES = {} # keep track of devices added to HA
def callback_generator(port, devices):
"""
Generator of callback, should be run once per gateway setup.
"""
def node_update(update_type, nid):
""" Callback for node updates from the MySensors gateway. """
_LOGGER.info('update %s: node %s', update_type, nid)
hass.bus.fire(EVENT_MYSENSORS_NODE_UPDATE, {
UPDATE_TYPE: update_type,
NODE_ID: nid
ATTR_PORT: port,
ATTR_DEVICES: devices,
ATTR_UPDATE_TYPE: update_type,
ATTR_NODE_ID: nid
})
return
return node_update
port = config[DOMAIN].get(CONF_PORT)
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
persistence_file = config[DOMAIN].get(
CONF_PERSISTENCE_FILE, hass.config.path('mysensors.pickle'))
global GATEWAY
GATEWAY = mysensors.SerialGateway(port, node_update,
def setup_gateway(port, persistence, persistence_file):
"""
Instantiate gateway, set gateway attributes and start gateway.
If persistence is true, update all nodes.
Listen for stop of home-assistant, then stop gateway.
"""
devices = {} # keep track of devices added to HA
gateway = mysensors.SerialGateway(port,
persistence=persistence,
persistence_file=persistence_file,
protocol_version=version)
GATEWAY.metric = IS_METRIC
GATEWAY.debug = config[DOMAIN].get(CONF_DEBUG, False)
GATEWAY.start()
gateway.event_callback = callback_generator(port, devices)
gateway.metric = IS_METRIC
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
gateway.start()
if persistence:
for nid in GATEWAY.sensors:
node_update('node_update', nid)
for nid in gateway.sensors:
gateway.event_callback('node_update', nid)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: GATEWAY.stop())
lambda event: gateway.stop())
return gateway
port = config[DOMAIN].get(CONF_PORT)
persistence_file = config[DOMAIN].get(
CONF_PERSISTENCE_FILE, hass.config.path('mysensors.pickle'))
if isinstance(port, str):
port = [port]
if isinstance(persistence_file, str):
persistence_file = [persistence_file]
# Setup all ports from config
global GATEWAYS
GATEWAYS = {}
for index, port_item in enumerate(port):
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
try:
persistence_f_item = persistence_file[index]
except IndexError:
_LOGGER.exception(
'No persistence_file is set for port %s,'
' disabling persistence', port_item)
persistence = False
persistence_f_item = None
GATEWAYS[port_item] = setup_gateway(
port_item, persistence, persistence_f_item)
return True
@ -113,7 +141,7 @@ def mysensors_update(platform_type):
Decorator for callback function for sensor updates from the MySensors
component.
"""
def wrapper(gateway, devices, nid):
def wrapper(gateway, port, devices, nid):
"""Wrapper function in the decorator."""
sensor = gateway.sensors[nid]
if sensor.sketch_name is None:
@ -123,26 +151,23 @@ def mysensors_update(platform_type):
devices[nid] = {}
node = devices[nid]
new_devices = []
platform_def = platform_type(gateway, devices, nid)
platform_object = platform_def['platform_class']
platform_v_types = platform_def['types_to_handle']
add_devices = platform_def['add_devices']
# Get platform specific V_TYPES, class and add_devices function.
platform_v_types, platform_class, add_devices = platform_type(
gateway, port, devices, nid)
for child_id, child in sensor.children.items():
if child_id not in node:
node[child_id] = {}
for value_type, value in child.values.items():
if value_type not in node[child_id]:
for value_type, _ in child.values.items():
if ((value_type not in node[child_id]) and
(value_type in platform_v_types)):
name = '{} {}.{}'.format(
sensor.sketch_name, nid, child.id)
if value_type in platform_v_types:
node[child_id][value_type] = \
platform_object(
gateway, nid, child_id, name, value_type)
node[child_id][value_type] = platform_class(
port, nid, child_id, name, value_type)
new_devices.append(node[child_id][value_type])
else:
elif value_type in platform_v_types:
node[child_id][value_type].update_sensor(
value, sensor.battery_level)
_LOGGER.info('sensor_update: %s', new_devices)
child.values, sensor.battery_level)
if new_devices:
_LOGGER.info('adding new devices: %s', new_devices)
add_devices(new_devices)

View file

@ -17,9 +17,6 @@ from homeassistant.const import (
import homeassistant.components.mysensors as mysensors
ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mysensors']
@ -27,28 +24,29 @@ DEPENDENCIES = ['mysensors']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the mysensors platform for sensors. """
# Define the V_TYPES that the platform should handle as states.
v_types = []
for _, member in mysensors.CONST.SetReq.__members__.items():
if (member.value != mysensors.CONST.SetReq.V_STATUS and
if (member.value != mysensors.CONST.SetReq.V_ARMED and
member.value != mysensors.CONST.SetReq.V_STATUS and
member.value != mysensors.CONST.SetReq.V_LIGHT and
member.value != mysensors.CONST.SetReq.V_LOCK_STATUS):
v_types.append(member)
@mysensors.mysensors_update
def _sensor_update(gateway, devices, nid):
def _sensor_update(gateway, port, devices, nid):
"""Internal callback for sensor updates."""
_LOGGER.info("sensor update = %s", devices)
return {'types_to_handle': v_types,
'platform_class': MySensorsSensor,
'add_devices': add_devices}
return (v_types, MySensorsSensor, add_devices)
def sensor_update(event):
""" Callback for sensor updates from the MySensors component. """
_LOGGER.info(
'update %s: node %s', event.data[mysensors.UPDATE_TYPE],
event.data[mysensors.NODE_ID])
_sensor_update(mysensors.GATEWAY, mysensors.DEVICES,
event.data[mysensors.NODE_ID])
'update %s: node %s', event.data[mysensors.ATTR_UPDATE_TYPE],
event.data[mysensors.ATTR_NODE_ID])
_sensor_update(mysensors.GATEWAYS[event.data[mysensors.ATTR_PORT]],
event.data[mysensors.ATTR_PORT],
event.data[mysensors.ATTR_DEVICES],
event.data[mysensors.ATTR_NODE_ID])
hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, sensor_update)
@ -58,16 +56,26 @@ class MySensorsSensor(Entity):
""" Represents the value of a MySensors child node. """
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, gateway, node_id, child_id, name, value_type):
self.gateway = gateway
def __init__(self, port, node_id, child_id, name, value_type):
self.port = port
self._name = name
self.node_id = node_id
self.child_id = child_id
self.battery_level = 0
self.value_type = value_type
self.metric = mysensors.IS_METRIC
self._value = ''
self.const = mysensors.CONST
self._values = {}
def as_dict(self):
""" Returns a dict representation of this Entity. """
return {
'port': self.port,
'name': self._name,
'node_id': self.node_id,
'child_id': self.child_id,
'battery_level': self.battery_level,
'value_type': self.value_type,
'values': self._values,
}
@property
def should_poll(self):
@ -82,35 +90,69 @@ class MySensorsSensor(Entity):
@property
def state(self):
""" Returns the state of the device. """
return self._value
if not self._values:
return ''
return self._values[self.value_type]
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity. """
if self.value_type == self.const.SetReq.V_TEMP:
return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
elif self.value_type == self.const.SetReq.V_HUM or \
self.value_type == self.const.SetReq.V_DIMMER or \
self.value_type == self.const.SetReq.V_LIGHT_LEVEL:
# pylint:disable=too-many-return-statements
if self.value_type == mysensors.CONST.SetReq.V_TEMP:
return TEMP_CELCIUS if mysensors.IS_METRIC else TEMP_FAHRENHEIT
elif self.value_type == mysensors.CONST.SetReq.V_HUM or \
self.value_type == mysensors.CONST.SetReq.V_DIMMER or \
self.value_type == mysensors.CONST.SetReq.V_PERCENTAGE or \
self.value_type == mysensors.CONST.SetReq.V_LIGHT_LEVEL:
return '%'
elif self.value_type == mysensors.CONST.SetReq.V_WATT:
return 'W'
elif self.value_type == mysensors.CONST.SetReq.V_KWH:
return 'kWh'
elif self.value_type == mysensors.CONST.SetReq.V_VOLTAGE:
return 'V'
elif self.value_type == mysensors.CONST.SetReq.V_CURRENT:
return 'A'
elif self.value_type == mysensors.CONST.SetReq.V_IMPEDANCE:
return 'ohm'
elif mysensors.CONST.SetReq.V_UNIT_PREFIX in self._values:
return self._values[mysensors.CONST.SetReq.V_UNIT_PREFIX]
return None
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """
device_attr = dict(self._values)
device_attr.pop(self.value_type, None)
return device_attr
@property
def state_attributes(self):
""" Returns the state attributes. """
return {
ATTR_NODE_ID: self.node_id,
ATTR_CHILD_ID: self.child_id,
data = {
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
def update_sensor(self, value, battery_level):
""" Update the controller with the latest value from a sensor. """
_LOGGER.info("%s value = %s", self._name, value)
if self.value_type == self.const.SetReq.V_TRIPPED or \
self.value_type == self.const.SetReq.V_ARMED:
self._value = STATE_ON if int(value) == 1 else STATE_OFF
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data
def update_sensor(self, values, battery_level):
""" Update the controller with the latest values from a sensor. """
for value_type, value in values.items():
_LOGGER.info(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == mysensors.CONST.SetReq.V_TRIPPED:
self._values[value_type] = STATE_ON if int(
value) == 1 else STATE_OFF
else:
self._value = value
self._values[value_type] = value
self.battery_level = battery_level
self.update_ha_state()

View file

@ -1,5 +1,5 @@
"""
homeassistant.components.sensor.mysensors
homeassistant.components.switch.mysensors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors switches.
@ -17,9 +17,6 @@ from homeassistant.const import (
import homeassistant.components.mysensors as mysensors
ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mysensors']
@ -27,28 +24,29 @@ DEPENDENCIES = ['mysensors']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the mysensors platform for switches. """
# Define the V_TYPES that the platform should handle as states.
v_types = []
for _, member in mysensors.CONST.SetReq.__members__.items():
if (member.value == mysensors.CONST.SetReq.V_STATUS or
if (member.value == mysensors.CONST.SetReq.V_ARMED or
member.value == mysensors.CONST.SetReq.V_STATUS or
member.value == mysensors.CONST.SetReq.V_LIGHT or
member.value == mysensors.CONST.SetReq.V_LOCK_STATUS):
v_types.append(member)
@mysensors.mysensors_update
def _sensor_update(gateway, devices, nid):
def _sensor_update(gateway, port, devices, nid):
"""Internal callback for sensor updates."""
_LOGGER.info("sensor update = %s", devices)
return {'types_to_handle': v_types,
'platform_class': MySensorsSwitch,
'add_devices': add_devices}
return (v_types, MySensorsSwitch, add_devices)
def sensor_update(event):
""" Callback for sensor updates from the MySensors component. """
_LOGGER.info(
'update %s: node %s', event.data[mysensors.UPDATE_TYPE],
event.data[mysensors.NODE_ID])
_sensor_update(mysensors.GATEWAY, mysensors.DEVICES,
event.data[mysensors.NODE_ID])
'update %s: node %s', event.data[mysensors.ATTR_UPDATE_TYPE],
event.data[mysensors.ATTR_NODE_ID])
_sensor_update(mysensors.GATEWAYS[event.data[mysensors.ATTR_PORT]],
event.data[mysensors.ATTR_PORT],
event.data[mysensors.ATTR_DEVICES],
event.data[mysensors.ATTR_NODE_ID])
hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, sensor_update)
@ -58,16 +56,26 @@ class MySensorsSwitch(SwitchDevice):
""" Represents the value of a MySensors child node. """
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, gateway, node_id, child_id, name, value_type):
self.gateway = gateway
def __init__(self, port, node_id, child_id, name, value_type):
self.port = port
self._name = name
self.node_id = node_id
self.child_id = child_id
self.battery_level = 0
self.value_type = value_type
self.metric = mysensors.IS_METRIC
self._value = STATE_OFF
self.const = mysensors.CONST
self._values = {}
def as_dict(self):
""" Returns a dict representation of this Entity. """
return {
'port': self.port,
'name': self._name,
'node_id': self.node_id,
'child_id': self.child_id,
'battery_level': self.battery_level,
'value_type': self.value_type,
'values': self._values,
}
@property
def should_poll(self):
@ -79,60 +87,86 @@ class MySensorsSwitch(SwitchDevice):
""" The name of this sensor. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._value
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity. """
if self.value_type == self.const.SetReq.V_TEMP:
return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
elif self.value_type == self.const.SetReq.V_HUM or \
self.value_type == self.const.SetReq.V_DIMMER or \
self.value_type == self.const.SetReq.V_LIGHT_LEVEL:
# pylint:disable=too-many-return-statements
if self.value_type == mysensors.CONST.SetReq.V_TEMP:
return TEMP_CELCIUS if mysensors.IS_METRIC else TEMP_FAHRENHEIT
elif self.value_type == mysensors.CONST.SetReq.V_HUM or \
self.value_type == mysensors.CONST.SetReq.V_DIMMER or \
self.value_type == mysensors.CONST.SetReq.V_PERCENTAGE or \
self.value_type == mysensors.CONST.SetReq.V_LIGHT_LEVEL:
return '%'
elif self.value_type == mysensors.CONST.SetReq.V_WATT:
return 'W'
elif self.value_type == mysensors.CONST.SetReq.V_KWH:
return 'kWh'
elif self.value_type == mysensors.CONST.SetReq.V_VOLTAGE:
return 'V'
elif self.value_type == mysensors.CONST.SetReq.V_CURRENT:
return 'A'
elif self.value_type == mysensors.CONST.SetReq.V_IMPEDANCE:
return 'ohm'
elif mysensors.CONST.SetReq.V_UNIT_PREFIX in self._values:
return self._values[mysensors.CONST.SetReq.V_UNIT_PREFIX]
return None
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """
device_attr = dict(self._values)
device_attr.pop(self.value_type, None)
return device_attr
@property
def state_attributes(self):
""" Returns the state attributes. """
return {
ATTR_NODE_ID: self.node_id,
ATTR_CHILD_ID: self.child_id,
data = {
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data
@property
def is_on(self):
""" Returns True if switch is on. """
return self._value == STATE_ON
return self._values[self.value_type] == STATE_ON
def turn_on(self):
""" Turns the switch on. """
self.gateway.set_child_value(
mysensors.GATEWAYS[self.port].set_child_value(
self.node_id, self.child_id, self.value_type, 1)
self._value = STATE_ON
self._values[self.value_type] = STATE_ON
self.update_ha_state()
def turn_off(self):
""" Turns the pin to low/off. """
self.gateway.set_child_value(
mysensors.GATEWAYS[self.port].set_child_value(
self.node_id, self.child_id, self.value_type, 0)
self._value = STATE_OFF
self._values[self.value_type] = STATE_OFF
self.update_ha_state()
def update_sensor(self, value, battery_level):
def update_sensor(self, values, battery_level):
""" Update the controller with the latest value from a sensor. """
_LOGGER.info("%s value = %s", self._name, value)
if self.value_type == self.const.SetReq.V_TRIPPED or \
self.value_type == self.const.SetReq.V_ARMED or \
self.value_type == self.const.SetReq.V_STATUS or \
self.value_type == self.const.SetReq.V_LIGHT or \
self.value_type == self.const.SetReq.V_LOCK_STATUS:
self._value = STATE_ON if int(value) == 1 else STATE_OFF
for value_type, value in values.items():
_LOGGER.info(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == mysensors.CONST.SetReq.V_ARMED or \
value_type == mysensors.CONST.SetReq.V_STATUS or \
value_type == mysensors.CONST.SetReq.V_LIGHT or \
value_type == mysensors.CONST.SetReq.V_LOCK_STATUS:
self._values[value_type] = (
STATE_ON if int(value) == 1 else STATE_OFF)
else:
self._value = value
self._values[value_type] = value
self.battery_level = battery_level
self.update_ha_state()

View file

@ -119,7 +119,7 @@ py-cpuinfo==0.1.6
python-forecastio==1.3.3
# homeassistant.components.sensor.mysensors
https://github.com/theolind/pymysensors/archive/d4b809c2167650691058d1e29bfd2c4b1792b4b0.zip#pymysensors==0.3
https://github.com/MartinHjelmare/pymysensors/archive/fifo_queue.zip#pymysensors==0.3
# homeassistant.components.sensor.openweathermap
pyowm==2.2.1