From 45fe37a301e4932f0ba2859e8263642b9b7bc5ed Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Wed, 4 Nov 2015 04:53:59 +0100 Subject: [PATCH 01/12] Add mysensors component and switch platform * Add a general mysensors component. This sets up the serial comm with the gateway through pymysensors. The component also contains a decorator function for the callback function of mysensors platforms. Mysensors platforms should create a function that listens for the node update event fired by the mysensors component. This function should call another function, that uses the decorator, and returns a dict. The dict should contain a list of which mysensors V_TYPE values the platform handles, the platfrom class and the add_devices function (from setup_platform). * Change existing mysensors sensor platform to depend on the new mysensors component. * Add a mysensors switch platform. The switch platform takes advantage of new functionality from the the fork of pymysensors https://github.com/MartinHjelmare/pymysensors, that enables the gateway to send commands to change node child values. * Change const and is_metric to global constants, in the mysensors component and import const depending on the mysensors version used. * Change variables devices and gateway to global variables. * Add some debug logging at INFO log level. --- homeassistant/components/mysensors.py | 150 +++++++++++++++++++ homeassistant/components/sensor/mysensors.py | 105 ++++--------- homeassistant/components/switch/mysensors.py | 138 +++++++++++++++++ 3 files changed, 321 insertions(+), 72 deletions(-) create mode 100644 homeassistant/components/mysensors.py create mode 100644 homeassistant/components/switch/mysensors.py diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py new file mode 100644 index 00000000000..6bffe9afd2c --- /dev/null +++ b/homeassistant/components/mysensors.py @@ -0,0 +1,150 @@ +""" +homeassistant.components.mysensors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +MySensors component that connects to a MySensors gateway via pymysensors +API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.mysensors.html +""" +import logging + +from homeassistant.helpers import (validate_config) + +from homeassistant.const import ( + EVENT_HOMEASSISTANT_STOP, + TEMP_CELCIUS) + +CONF_PORT = 'port' +CONF_DEBUG = 'debug' +CONF_PERSISTENCE = 'persistence' +CONF_PERSISTENCE_FILE = 'persistence_file' +CONF_VERSION = 'version' + +DOMAIN = 'mysensors' +DEPENDENCIES = [] +REQUIREMENTS = ['file:///home/martin/Dev/pymysensors-fifo_queue.zip' + '#pymysensors==0.3'] +_LOGGER = logging.getLogger(__name__) +ATTR_NODE_ID = 'node_id' +ATTR_CHILD_ID = 'child_id' + +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 + + +def setup(hass, config): + """ Setup the MySensors component. """ + + import mysensors.mysensors as mysensors + + if not validate_config(config, + {DOMAIN: [CONF_PORT]}, + _LOGGER): + return False + + version = config[DOMAIN].get(CONF_VERSION, '1.4') + + global CONST + 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. + IS_METRIC = (hass.config.temperature_unit == TEMP_CELCIUS) + global DEVICES + DEVICES = {} # keep track of devices added to HA + + 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 + }) + + 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, + persistence=persistence, + persistence_file=persistence_file, + protocol_version=version) + 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) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + lambda event: GATEWAY.stop()) + + return True + + +def mysensors_update(platform_type): + """ + Decorator for callback function for sensor updates from the MySensors + component. + """ + def wrapper(gateway, devices, nid): + """Wrapper function in the decorator.""" + sensor = gateway.sensors[nid] + if sensor.sketch_name is None: + _LOGGER.info('No sketch_name: node %s', nid) + return + if nid not in devices: + 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'] + 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]: + 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) + new_devices.append(node[child_id][value_type]) + else: + node[child_id][value_type].update_sensor( + value, sensor.battery_level) + _LOGGER.info('sensor_update: %s', new_devices) + if new_devices: + _LOGGER.info('adding new devices: %s', new_devices) + add_devices(new_devices) + return + return wrapper diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index cb959522134..b49fe706f78 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -11,102 +11,63 @@ import logging from homeassistant.helpers.entity import Entity from homeassistant.const import ( - ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP, + ATTR_BATTERY_LEVEL, TEMP_CELCIUS, TEMP_FAHRENHEIT, STATE_ON, STATE_OFF) -CONF_PORT = "port" -CONF_DEBUG = "debug" -CONF_PERSISTENCE = "persistence" -CONF_PERSISTENCE_FILE = "persistence_file" -CONF_VERSION = "version" +import homeassistant.components.mysensors as mysensors ATTR_NODE_ID = "node_id" ATTR_CHILD_ID = "child_id" _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/' - 'd4b809c2167650691058d1e29bfd2c4b1792b4b0.zip' - '#pymysensors==0.3'] +DEPENDENCIES = ['mysensors'] def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup the mysensors platform. """ + """ Setup the mysensors platform for sensors. """ - import mysensors.mysensors as mysensors - import mysensors.const_14 as const + v_types = [] + for _, member in mysensors.CONST.SetReq.__members__.items(): + if (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) - devices = {} # keep track of devices added to HA - # 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. - is_metric = (hass.config.temperature_unit == TEMP_CELCIUS) + @mysensors.mysensors_update + def _sensor_update(gateway, 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} - def sensor_update(update_type, nid): - """ Callback for sensor updates from the MySensors gateway. """ - _LOGGER.info("sensor_update %s: node %s", update_type, nid) - sensor = gateway.sensors[nid] - if sensor.sketch_name is None: - return - if nid not in devices: - devices[nid] = {} + 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]) - node = devices[nid] - new_devices = [] - 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]: - name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id) - node[child_id][value_type] = \ - MySensorsNodeValue( - nid, child_id, name, value_type, is_metric, const) - new_devices.append(node[child_id][value_type]) - else: - node[child_id][value_type].update_sensor( - value, sensor.battery_level) - - if new_devices: - _LOGGER.info("adding new devices: %s", new_devices) - add_devices(new_devices) - - port = config.get(CONF_PORT) - if port is None: - _LOGGER.error("Missing required key 'port'") - return False - - persistence = config.get(CONF_PERSISTENCE, True) - persistence_file = config.get(CONF_PERSISTENCE_FILE, 'mysensors.pickle') - version = config.get(CONF_VERSION, '1.4') - - gateway = mysensors.SerialGateway(port, sensor_update, - persistence=persistence, - persistence_file=persistence_file, - protocol_version=version) - gateway.metric = is_metric - gateway.debug = config.get(CONF_DEBUG, False) - gateway.start() - - if persistence: - for nid in gateway.sensors: - sensor_update('sensor_update', nid) - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - lambda event: gateway.stop()) + hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, sensor_update) -class MySensorsNodeValue(Entity): +class MySensorsSensor(Entity): + """ Represents the value of a MySensors child node. """ # pylint: disable=too-many-arguments, too-many-instance-attributes - def __init__(self, node_id, child_id, name, value_type, metric, const): + + def __init__(self, gateway, node_id, child_id, name, value_type): + self.gateway = gateway self._name = name self.node_id = node_id self.child_id = child_id self.battery_level = 0 self.value_type = value_type - self.metric = metric + self.metric = mysensors.IS_METRIC self._value = '' - self.const = const + self.const = mysensors.CONST @property def should_poll(self): @@ -144,7 +105,7 @@ class MySensorsNodeValue(Entity): } def update_sensor(self, value, battery_level): - """ Update a sensor with the latest value from the controller. """ + """ 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: diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py new file mode 100644 index 00000000000..9c67ed44b93 --- /dev/null +++ b/homeassistant/components/switch/mysensors.py @@ -0,0 +1,138 @@ +""" +homeassistant.components.sensor.mysensors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for MySensors switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.mysensors.html +""" +import logging + +from homeassistant.components.switch import SwitchDevice + +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, + TEMP_CELCIUS, TEMP_FAHRENHEIT, + STATE_ON, STATE_OFF) + +import homeassistant.components.mysensors as mysensors + +ATTR_NODE_ID = "node_id" +ATTR_CHILD_ID = "child_id" + +_LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['mysensors'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Setup the mysensors platform for switches. """ + + v_types = [] + for _, member in mysensors.CONST.SetReq.__members__.items(): + if (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): + """Internal callback for sensor updates.""" + _LOGGER.info("sensor update = %s", devices) + return {'types_to_handle': v_types, + 'platform_class': MySensorsSwitch, + 'add_devices': 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]) + + hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, sensor_update) + + +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 + 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 + + @property + def should_poll(self): + """ MySensor gateway pushes its state to HA. """ + return False + + @property + def name(self): + """ 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: + return '%' + return None + + @property + def state_attributes(self): + """ Returns the state attributes. """ + return { + ATTR_NODE_ID: self.node_id, + ATTR_CHILD_ID: self.child_id, + ATTR_BATTERY_LEVEL: self.battery_level, + } + + @property + def is_on(self): + """ Returns True if switch is on. """ + return self._value == STATE_ON + + def turn_on(self): + """ Turns the switch on. """ + self.gateway.set_child_value( + self.node_id, self.child_id, self.value_type, 1) + self._value = STATE_ON + self.update_ha_state() + + def turn_off(self): + """ Turns the pin to low/off. """ + self.gateway.set_child_value( + self.node_id, self.child_id, self.value_type, 0) + self._value = STATE_OFF + self.update_ha_state() + + 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 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 + else: + self._value = value + self.battery_level = battery_level + self.update_ha_state() From 59524c7933c84680c5fb1b164814cd9b363d5727 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Sun, 6 Dec 2015 00:29:03 +0100 Subject: [PATCH 02/12] 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. --- homeassistant/components/mysensors.py | 137 +++++++++++-------- homeassistant/components/sensor/mysensors.py | 112 ++++++++++----- homeassistant/components/switch/mysensors.py | 128 ++++++++++------- requirements_all.txt | 2 +- 4 files changed, 240 insertions(+), 139 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 6bffe9afd2c..59724a7d810 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -23,27 +23,25 @@ CONF_VERSION = 'version' DOMAIN = 'mysensors' DEPENDENCIES = [] -REQUIREMENTS = ['file:///home/martin/Dev/pymysensors-fifo_queue.zip' - '#pymysensors==0.3'] +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 node_update(update_type, nid): - """ Callback for node updates from the MySensors gateway. """ - _LOGGER.info('update %s: node %s', update_type, nid) + 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 - }) + hass.bus.fire(EVENT_MYSENSORS_NODE_UPDATE, { + ATTR_PORT: port, + ATTR_DEVICES: devices, + ATTR_UPDATE_TYPE: update_type, + ATTR_NODE_ID: nid + }) + return + return 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.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: + gateway.event_callback('node_update', nid) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + lambda event: gateway.stop()) + return gateway 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, - persistence=persistence, - persistence_file=persistence_file, - protocol_version=version) - GATEWAY.metric = IS_METRIC - GATEWAY.debug = config[DOMAIN].get(CONF_DEBUG, False) - GATEWAY.start() + if isinstance(port, str): + port = [port] + if isinstance(persistence_file, str): + persistence_file = [persistence_file] - if persistence: - for nid in GATEWAY.sensors: - node_update('node_update', nid) - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - lambda event: GATEWAY.stop()) + # 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) - new_devices.append(node[child_id][value_type]) - else: + node[child_id][value_type] = platform_class( + port, nid, child_id, name, value_type) + new_devices.append(node[child_id][value_type]) + 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) diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index 4e9e03da0d0..c16980f7587 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -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 - else: - self._value = value + 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._values[value_type] = value + self.battery_level = battery_level self.update_ha_state() diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 9c67ed44b93..5db5b9d25fc 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -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 - else: - self._value = value + 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._values[value_type] = value self.battery_level = battery_level self.update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index 2715ca3288d..87e7555b395 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 From 7cc707f1ce28e6810cb4cc30c4e9f08f2f25d170 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Tue, 8 Dec 2015 01:03:07 +0100 Subject: [PATCH 03/12] Fix docstrings to conform to pep --- homeassistant/components/mysensors.py | 17 ++---- homeassistant/components/sensor/mysensors.py | 46 +++++++++++------ homeassistant/components/switch/mysensors.py | 54 +++++++++++++------- 3 files changed, 71 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 59724a7d810..1dcd65ab8e6 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -68,11 +68,9 @@ def setup(hass, config): # noqa IS_METRIC = (hass.config.temperature_unit == TEMP_CELCIUS) def callback_generator(port, devices): - """ - Generator of callback, should be run once per gateway setup. - """ + """Return a new callback function. Run once per gateway setup.""" def node_update(update_type, nid): - """ Callback for node updates from the MySensors gateway. """ + """Callback for node updates from the MySensors gateway.""" _LOGGER.info('update %s: node %s', update_type, nid) hass.bus.fire(EVENT_MYSENSORS_NODE_UPDATE, { @@ -85,11 +83,7 @@ def setup(hass, config): # noqa return 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. - """ + """Return gateway after setup of the gateway.""" devices = {} # keep track of devices added to HA gateway = mysensors.SerialGateway(port, persistence=persistence, @@ -137,10 +131,7 @@ def setup(hass, config): # noqa def mysensors_update(platform_type): - """ - Decorator for callback function for sensor updates from the MySensors - component. - """ + """Decorator for callback function for mysensor updates.""" def wrapper(gateway, port, devices, nid): """Wrapper function in the decorator.""" sensor = gateway.sensors[nid] diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index c16980f7587..f1ce4f38271 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -22,8 +22,7 @@ DEPENDENCIES = ['mysensors'] def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup the mysensors platform for sensors. """ - + """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(): @@ -39,7 +38,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return (v_types, MySensorsSensor, add_devices) def sensor_update(event): - """ Callback for sensor updates from the MySensors component. """ + """Callback for sensor updates from the MySensors component.""" _LOGGER.info( 'update %s: node %s', event.data[mysensors.ATTR_UPDATE_TYPE], event.data[mysensors.ATTR_NODE_ID]) @@ -52,21 +51,39 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MySensorsSensor(Entity): + """Represent the value of a MySensors child node.""" - """ Represents the value of a MySensors child node. """ # pylint: disable=too-many-arguments, too-many-instance-attributes def __init__(self, port, node_id, child_id, name, value_type): + """Setup class attributes on instantiation. + + Args: + port (str): Gateway port. + node_id (str): Id of node. + child_id (str): Id of child. + name (str): Sketch name. + value_type (str): Value type of child. Value is entity state. + + Attributes: + port (str): Gateway port. + node_id (str): Id of node. + child_id (str): Id of child. + _name (str): Sketch name. + value_type (str): Value type of child. Value is entity state. + battery_level (int): Node battery level. + _values (dict): Child values. Non state values set as state attributes. + """ self.port = port - self._name = name self.node_id = node_id self.child_id = child_id - self.battery_level = 0 + self._name = name self.value_type = value_type + self.battery_level = 0 self._values = {} def as_dict(self): - """ Returns a dict representation of this Entity. """ + """Return a dict representation of this Entity.""" return { 'port': self.port, 'name': self._name, @@ -79,24 +96,24 @@ class MySensorsSensor(Entity): @property def should_poll(self): - """ MySensor gateway pushes its state to HA. """ + """MySensor gateway pushes its state to HA.""" return False @property def name(self): - """ The name of this sensor. """ + """The name of this sensor.""" return self._name @property def state(self): - """ Returns the state of the device. """ + """Return the state of the device.""" if not self._values: return '' return self._values[self.value_type] @property def unit_of_measurement(self): - """ Unit of measurement of this entity. """ + """Unit of measurement of this entity.""" # 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 @@ -121,15 +138,14 @@ class MySensorsSensor(Entity): @property def device_state_attributes(self): - """ Returns device specific state attributes. """ + """Return 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 the state attributes.""" data = { mysensors.ATTR_NODE_ID: self.node_id, mysensors.ATTR_CHILD_ID: self.child_id, @@ -144,7 +160,7 @@ class MySensorsSensor(Entity): return data def update_sensor(self, values, battery_level): - """ Update the controller with the latest values from a sensor. """ + """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) diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 5db5b9d25fc..541c305fafa 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -22,8 +22,7 @@ DEPENDENCIES = ['mysensors'] def setup_platform(hass, config, add_devices, discovery_info=None): - """ Setup the mysensors platform for switches. """ - + """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(): @@ -39,7 +38,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return (v_types, MySensorsSwitch, add_devices) def sensor_update(event): - """ Callback for sensor updates from the MySensors component. """ + """Callback for sensor updates from the MySensors component.""" _LOGGER.info( 'update %s: node %s', event.data[mysensors.ATTR_UPDATE_TYPE], event.data[mysensors.ATTR_NODE_ID]) @@ -52,21 +51,39 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MySensorsSwitch(SwitchDevice): + """Represent the value of a MySensors child node.""" - """ Represents the value of a MySensors child node. """ # pylint: disable=too-many-arguments, too-many-instance-attributes def __init__(self, port, node_id, child_id, name, value_type): + """Setup class attributes on instantiation. + + Args: + port (str): Gateway port. + node_id (str): Id of node. + child_id (str): Id of child. + name (str): Sketch name. + value_type (str): Value type of child. Value is entity state. + + Attributes: + port (str): Gateway port. + node_id (str): Id of node. + child_id (str): Id of child. + _name (str): Sketch name. + value_type (str): Value type of child. Value is entity state. + battery_level (int): Node battery level. + _values (dict): Child values. Non state values set as state attributes. + """ self.port = port - self._name = name self.node_id = node_id self.child_id = child_id - self.battery_level = 0 + self._name = name self.value_type = value_type + self.battery_level = 0 self._values = {} def as_dict(self): - """ Returns a dict representation of this Entity. """ + """Return a dict representation of this Entity.""" return { 'port': self.port, 'name': self._name, @@ -79,17 +96,17 @@ class MySensorsSwitch(SwitchDevice): @property def should_poll(self): - """ MySensor gateway pushes its state to HA. """ + """MySensor gateway pushes its state to HA.""" return False @property def name(self): - """ The name of this sensor. """ + """The name of this sensor.""" return self._name @property def unit_of_measurement(self): - """ Unit of measurement of this entity. """ + """Unit of measurement of this entity.""" # 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 @@ -114,15 +131,14 @@ class MySensorsSwitch(SwitchDevice): @property def device_state_attributes(self): - """ Returns device specific state attributes. """ + """Return 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 the state attributes.""" data = { mysensors.ATTR_NODE_ID: self.node_id, mysensors.ATTR_CHILD_ID: self.child_id, @@ -138,25 +154,27 @@ class MySensorsSwitch(SwitchDevice): @property def is_on(self): - """ Returns True if switch is on. """ - return self._values[self.value_type] == STATE_ON + """Return True if switch is on.""" + if self.value_type in self._values: + return self._values[self.value_type] == STATE_ON + return False def turn_on(self): - """ Turns the switch on. """ + """Turn the switch on.""" mysensors.GATEWAYS[self.port].set_child_value( self.node_id, self.child_id, self.value_type, 1) self._values[self.value_type] = STATE_ON self.update_ha_state() def turn_off(self): - """ Turns the pin to low/off. """ + """Turn the pin to low/off.""" mysensors.GATEWAYS[self.port].set_child_value( self.node_id, self.child_id, self.value_type, 0) self._values[self.value_type] = STATE_OFF self.update_ha_state() def update_sensor(self, values, battery_level): - """ Update the controller with the latest value from a sensor. """ + """Update the controller with the latest value from a sensor.""" for value_type, value in values.items(): _LOGGER.info( "%s: value_type %s, value = %s", self._name, value_type, value) From 9463c84603a9e7310b98dd9b9727f9ebd1be14b0 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Tue, 8 Dec 2015 02:47:15 +0100 Subject: [PATCH 04/12] Clean up --- homeassistant/components/sensor/mysensors.py | 8 ++--- homeassistant/components/switch/mysensors.py | 36 +++----------------- 2 files changed, 9 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index f1ce4f38271..9c4d3d3fcc4 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -62,14 +62,14 @@ class MySensorsSensor(Entity): port (str): Gateway port. node_id (str): Id of node. child_id (str): Id of child. - name (str): Sketch name. + name (str): Entity name. value_type (str): Value type of child. Value is entity state. Attributes: port (str): Gateway port. node_id (str): Id of node. child_id (str): Id of child. - _name (str): Sketch name. + _name (str): Entity name. value_type (str): Value type of child. Value is entity state. battery_level (int): Node battery level. _values (dict): Child values. Non state values set as state attributes. @@ -83,7 +83,7 @@ class MySensorsSensor(Entity): self._values = {} def as_dict(self): - """Return a dict representation of this Entity.""" + """Return a dict representation of this entity.""" return { 'port': self.port, 'name': self._name, @@ -101,7 +101,7 @@ class MySensorsSensor(Entity): @property def name(self): - """The name of this sensor.""" + """The name of this entity.""" return self._name @property diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 541c305fafa..a2557900141 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -12,7 +12,6 @@ from homeassistant.components.switch import SwitchDevice from homeassistant.const import ( ATTR_BATTERY_LEVEL, - TEMP_CELCIUS, TEMP_FAHRENHEIT, STATE_ON, STATE_OFF) import homeassistant.components.mysensors as mysensors @@ -62,14 +61,14 @@ class MySensorsSwitch(SwitchDevice): port (str): Gateway port. node_id (str): Id of node. child_id (str): Id of child. - name (str): Sketch name. + name (str): Entity name. value_type (str): Value type of child. Value is entity state. Attributes: port (str): Gateway port. node_id (str): Id of node. child_id (str): Id of child. - _name (str): Sketch name. + _name (str): Entity name. value_type (str): Value type of child. Value is entity state. battery_level (int): Node battery level. _values (dict): Child values. Non state values set as state attributes. @@ -83,7 +82,7 @@ class MySensorsSwitch(SwitchDevice): self._values = {} def as_dict(self): - """Return a dict representation of this Entity.""" + """Return a dict representation of this entity.""" return { 'port': self.port, 'name': self._name, @@ -101,34 +100,9 @@ class MySensorsSwitch(SwitchDevice): @property def name(self): - """The name of this sensor.""" + """The name of this entity.""" return self._name - @property - def unit_of_measurement(self): - """Unit of measurement of this entity.""" - # 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): """Return device specific state attributes.""" @@ -167,7 +141,7 @@ class MySensorsSwitch(SwitchDevice): self.update_ha_state() def turn_off(self): - """Turn the pin to low/off.""" + """Turn the switch off.""" mysensors.GATEWAYS[self.port].set_child_value( self.node_id, self.child_id, self.value_type, 0) self._values[self.value_type] = STATE_OFF From 1e52d5c7f22122df1a869e1ee107d741b692e95b Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Wed, 9 Dec 2015 04:43:18 +0100 Subject: [PATCH 05/12] Add S_TYPES to platform type and fix persistence * Add S_TYPES to platform type. * Fix persistence update on startup. * Clean up code. --- homeassistant/components/mysensors.py | 52 +++++++++++++++----- homeassistant/components/sensor/mysensors.py | 50 +++++++++++++------ homeassistant/components/switch/mysensors.py | 45 ++++++++++------- 3 files changed, 99 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 1dcd65ab8e6..3ab1a96d80f 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -12,6 +12,7 @@ import logging from homeassistant.helpers import (validate_config) from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, TEMP_CELCIUS) @@ -94,9 +95,15 @@ def setup(hass, config): # noqa gateway.debug = config[DOMAIN].get(CONF_DEBUG, False) gateway.start() + def persistence_update(event): + """Callback to trigger update from persistence file.""" + for _ in range(2): + for nid in gateway.sensors: + gateway.event_callback('persistence', nid) + if persistence: - for nid in gateway.sensors: - gateway.event_callback('node_update', nid) + hass.bus.listen_once( + EVENT_HOMEASSISTANT_START, persistence_update) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: gateway.stop()) @@ -134,33 +141,52 @@ def mysensors_update(platform_type): """Decorator for callback function for mysensor updates.""" def wrapper(gateway, port, devices, nid): """Wrapper function in the decorator.""" - sensor = gateway.sensors[nid] - if sensor.sketch_name is None: + if gateway.sensors[nid].sketch_name is None: _LOGGER.info('No sketch_name: node %s', nid) return if nid not in devices: devices[nid] = {} node = devices[nid] new_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(): + # Get platform specific S_TYPES, V_TYPES, class and add_devices. + (platform_s_types, + platform_v_types, + platform_class, + add_devices) = platform_type(gateway, port, devices, nid) + for child_id, child in gateway.sensors[nid].children.items(): if child_id not in node: 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)): + if (value_type not in node[child_id] and + child.type in platform_s_types and + value_type in platform_v_types): name = '{} {}.{}'.format( - sensor.sketch_name, nid, child.id) + gateway.sensors[nid].sketch_name, nid, child.id) node[child_id][value_type] = platform_class( port, nid, child_id, name, value_type) new_devices.append(node[child_id][value_type]) - elif value_type in platform_v_types: + elif (child.type in platform_s_types and + value_type in platform_v_types): node[child_id][value_type].update_sensor( - child.values, sensor.battery_level) + child.values, gateway.sensors[nid].battery_level) if new_devices: _LOGGER.info('adding new devices: %s', new_devices) add_devices(new_devices) return return wrapper + + +def event_update(update): + """Decorator for callback function for mysensor event updates.""" + def wrapper(event): + """Wrapper function in the decorator.""" + _LOGGER.info( + 'update %s: node %s', event.data[ATTR_UPDATE_TYPE], + event.data[ATTR_NODE_ID]) + sensor_update = update(event) + sensor_update(GATEWAYS[event.data[ATTR_PORT]], + event.data[ATTR_PORT], + event.data[ATTR_DEVICES], + event.data[ATTR_NODE_ID]) + return + return wrapper diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index 9c4d3d3fcc4..e3843448763 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -23,31 +23,49 @@ 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. + # Define the S_TYPES and V_TYPES that the platform should handle as states. + s_types = [ + mysensors.CONST.Presentation.S_TEMP, + mysensors.CONST.Presentation.S_HUM, + mysensors.CONST.Presentation.S_BARO, + mysensors.CONST.Presentation.S_WIND, + mysensors.CONST.Presentation.S_RAIN, + mysensors.CONST.Presentation.S_UV, + mysensors.CONST.Presentation.S_WEIGHT, + mysensors.CONST.Presentation.S_POWER, + mysensors.CONST.Presentation.S_DISTANCE, + mysensors.CONST.Presentation.S_LIGHT_LEVEL, + mysensors.CONST.Presentation.S_IR, + mysensors.CONST.Presentation.S_WATER, + mysensors.CONST.Presentation.S_AIR_QUALITY, + mysensors.CONST.Presentation.S_CUSTOM, + mysensors.CONST.Presentation.S_DUST, + mysensors.CONST.Presentation.S_SCENE_CONTROLLER, + mysensors.CONST.Presentation.S_COLOR_SENSOR, + mysensors.CONST.Presentation.S_MULTIMETER, + ] + not_v_types = [ + mysensors.CONST.SetReq.V_ARMED, + mysensors.CONST.SetReq.V_STATUS, + mysensors.CONST.SetReq.V_LIGHT, + mysensors.CONST.SetReq.V_LOCK_STATUS, + ] v_types = [] for _, member in mysensors.CONST.SetReq.__members__.items(): - 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): + if all(test != member.value for test in not_v_types): v_types.append(member) @mysensors.mysensors_update def _sensor_update(gateway, port, devices, nid): """Internal callback for sensor updates.""" - return (v_types, MySensorsSensor, add_devices) + return (s_types, 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.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]) + @mysensors.event_update + def event_update(event): + """Callback for event updates from the MySensors component.""" + return _sensor_update - hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, sensor_update) + hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, event_update) class MySensorsSensor(Entity): diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index a2557900141..792502aef07 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -22,31 +22,38 @@ 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_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) + # Define the S_TYPES and V_TYPES that the platform should handle as states. + s_types = [ + mysensors.CONST.Presentation.S_DOOR, + mysensors.CONST.Presentation.S_MOTION, + mysensors.CONST.Presentation.S_SMOKE, + mysensors.CONST.Presentation.S_LIGHT, + mysensors.CONST.Presentation.S_BINARY, + mysensors.CONST.Presentation.S_LOCK, + mysensors.CONST.Presentation.S_SPRINKLER, + mysensors.CONST.Presentation.S_WATER_LEAK, + mysensors.CONST.Presentation.S_SOUND, + mysensors.CONST.Presentation.S_VIBRATION, + mysensors.CONST.Presentation.S_MOISTURE, + ] + v_types = [ + mysensors.CONST.SetReq.V_ARMED, + mysensors.CONST.SetReq.V_STATUS, + mysensors.CONST.SetReq.V_LIGHT, + mysensors.CONST.SetReq.V_LOCK_STATUS, + ] @mysensors.mysensors_update def _sensor_update(gateway, port, devices, nid): """Internal callback for sensor updates.""" - return (v_types, MySensorsSwitch, add_devices) + return (s_types, 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.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]) + @mysensors.event_update + def event_update(event): + """Callback for event updates from the MySensors component.""" + return _sensor_update - hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, sensor_update) + hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, event_update) class MySensorsSwitch(SwitchDevice): From 659226886f471be446c903b8ae00bce22caf59b5 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Fri, 18 Dec 2015 03:37:49 +0100 Subject: [PATCH 06/12] Update .coveragerc and requirements --- .coveragerc | 4 +++- homeassistant/components/mysensors.py | 4 ++-- requirements_all.txt | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.coveragerc b/.coveragerc index 46a945e760b..bf5582c2115 100644 --- a/.coveragerc +++ b/.coveragerc @@ -32,6 +32,9 @@ omit = homeassistant/components/rfxtrx.py homeassistant/components/*/rfxtrx.py + homeassistant/components/mysensors.py + homeassistant/components/*/mysensors.py + homeassistant/components/binary_sensor/arest.py homeassistant/components/browser.py homeassistant/components/camera/* @@ -86,7 +89,6 @@ omit = homeassistant/components/sensor/efergy.py homeassistant/components/sensor/forecast.py homeassistant/components/sensor/glances.py - homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/rest.py homeassistant/components/sensor/rpi_gpio.py diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 3ab1a96d80f..a1491bbb1db 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -25,8 +25,8 @@ CONF_VERSION = 'version' DOMAIN = 'mysensors' DEPENDENCIES = [] REQUIREMENTS = [ - 'https://github.com/MartinHjelmare/pymysensors/archive/fifo_queue.zip' - '#pymysensors==0.3'] + 'https://github.com/theolind/pymysensors/archive/' + '2aa8f32908e8c5bb3e5c77c5851db778f8635792.zip#pymysensors==0.3'] _LOGGER = logging.getLogger(__name__) ATTR_PORT = 'port' ATTR_DEVICES = 'devices' diff --git a/requirements_all.txt b/requirements_all.txt index 87e7555b395..25e1fd50618 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -118,8 +118,8 @@ py-cpuinfo==0.1.6 # homeassistant.components.sensor.forecast python-forecastio==1.3.3 -# homeassistant.components.sensor.mysensors -https://github.com/MartinHjelmare/pymysensors/archive/fifo_queue.zip#pymysensors==0.3 +# homeassistant.components.mysensors +https://github.com/theolind/pymysensors/archive/2aa8f32908e8c5bb3e5c77c5851db778f8635792.zip#pymysensors==0.3 # homeassistant.components.sensor.openweathermap pyowm==2.2.1 From 845926236ef62a01cb0ac362208dabe17f5dc3c8 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Fri, 18 Dec 2015 03:58:21 +0100 Subject: [PATCH 07/12] Add config sample and fix requirements_all --- homeassistant/components/mysensors.py | 27 +++++++++++++++++++++++++++ requirements_all.txt | 6 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index a1491bbb1db..0e2ba92627f 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -6,6 +6,33 @@ API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mysensors.html + + +New features: + +New MySensors component. +Updated MySensors Sensor platform. +New MySensors Switch platform. +Multiple gateways are now supported. + +Configuration.yaml: + +mysensors: + port: + - '/dev/ttyUSB0' + - '/dev/ttyACM1' + debug: true + persistence: true + persistence_file: + - 'path/to/.homeassistant/mysensors.json' + - 'path/to/.homeassistant/mysensors2.json' + version: '1.5' + +sensor: + platform: mysensors + +switch: + platform: mysensors """ import logging diff --git a/requirements_all.txt b/requirements_all.txt index 5852cc45dee..303c51bd92a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -85,6 +85,9 @@ paho-mqtt==1.1 # homeassistant.components.mqtt jsonpath-rw==1.4.0 +# homeassistant.components.mysensors +https://github.com/theolind/pymysensors/archive/2aa8f32908e8c5bb3e5c77c5851db778f8635792.zip#pymysensors==0.3 + # homeassistant.components.notify.pushbullet pushbullet.py==0.9.0 @@ -121,9 +124,6 @@ py-cpuinfo==0.1.6 # homeassistant.components.sensor.forecast python-forecastio==1.3.3 -# homeassistant.components.mysensors -https://github.com/theolind/pymysensors/archive/2aa8f32908e8c5bb3e5c77c5851db778f8635792.zip#pymysensors==0.3 - # homeassistant.components.sensor.openweathermap pyowm==2.2.1 From 9f54bcc21b1744f2850a35e54903f3581d14badb Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Wed, 23 Dec 2015 23:20:39 +0100 Subject: [PATCH 08/12] Fix comments for pull request * Fix cleaner user config. * Remove bad disabling of linting. * Extract default mysensors version into constant. * Clean up selection of mysensors.CONST from version. * Update mysensors update decorator to add devices and update values in one go. * Fix persistence update. * Clean up setup of ports. * Setup of mysensors platforms from main mysensors component. * Clean up v_types selection in mysensors sensor platform. * Fix s_types and v_types selection version dependency in platforms. --- homeassistant/components/mysensors.py | 108 ++++++++++--------- homeassistant/components/sensor/mysensors.py | 17 +-- homeassistant/components/switch/mysensors.py | 17 +-- 3 files changed, 78 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 0e2ba92627f..89bc14a4ef8 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -17,6 +17,16 @@ Multiple gateways are now supported. Configuration.yaml: +mysensors: + gateways: + - port: '/dev/ttyUSB0' + persistence_file: 'path/mysensors.json' + - port: '/dev/ttyACM1' + persistence_file: 'path/mysensors2.json' + debug: true + persistence: true + version: '1.5' + mysensors: port: - '/dev/ttyUSB0' @@ -36,18 +46,23 @@ switch: """ import logging -from homeassistant.helpers import (validate_config) +from homeassistant.helpers import validate_config +import homeassistant.bootstrap as bootstrap from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - TEMP_CELCIUS) + TEMP_CELCIUS, + CONF_PLATFORM) +CONF_GATEWAYS = 'gateways' CONF_PORT = 'port' CONF_DEBUG = 'debug' CONF_PERSISTENCE = 'persistence' CONF_PERSISTENCE_FILE = 'persistence_file' CONF_VERSION = 'version' +DEFAULT_VERSION = '1.4' +VERSION = None DOMAIN = 'mysensors' DEPENDENCIES = [] @@ -61,29 +76,31 @@ ATTR_NODE_ID = 'node_id' ATTR_CHILD_ID = 'child_id' ATTR_UPDATE_TYPE = 'update_type' +COMPONENTS_WITH_MYSENSORS_PLATFORM = [ + 'sensor', + 'switch', +] + IS_METRIC = None CONST = None GATEWAYS = None EVENT_MYSENSORS_NODE_UPDATE = 'MYSENSORS_NODE_UPDATE' -def setup(hass, config): # noqa - """ Setup the MySensors component. """ - # pylint:disable=no-name-in-module +def setup(hass, config): + """Setup the MySensors component.""" import mysensors.mysensors as mysensors if not validate_config(config, - {DOMAIN: [CONF_PORT]}, + {DOMAIN: [CONF_GATEWAYS]}, _LOGGER): return False - version = config[DOMAIN].get(CONF_VERSION, '1.4') + global VERSION + VERSION = config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION) global CONST - if version == '1.4': - import mysensors.const_14 as const - CONST = const - elif version == '1.5': + if VERSION == '1.5': import mysensors.const_15 as const CONST = const else: @@ -95,7 +112,14 @@ def setup(hass, config): # noqa global IS_METRIC IS_METRIC = (hass.config.temperature_unit == TEMP_CELCIUS) - def callback_generator(port, devices): + # Setup mysensors platforms + mysensors_config = config.copy() + for component in COMPONENTS_WITH_MYSENSORS_PLATFORM: + mysensors_config[component] = {CONF_PLATFORM: 'mysensors'} + if not bootstrap.setup_component(hass, component, mysensors_config): + return False + + def callback_factory(port, devices): """Return a new callback function. Run once per gateway setup.""" def node_update(update_type, nid): """Callback for node updates from the MySensors gateway.""" @@ -107,7 +131,7 @@ def setup(hass, config): # noqa ATTR_UPDATE_TYPE: update_type, ATTR_NODE_ID: nid }) - return + return node_update def setup_gateway(port, persistence, persistence_file): @@ -116,17 +140,16 @@ def setup(hass, config): # noqa gateway = mysensors.SerialGateway(port, persistence=persistence, persistence_file=persistence_file, - protocol_version=version) - gateway.event_callback = callback_generator(port, devices) + protocol_version=VERSION) + gateway.event_callback = callback_factory(port, devices) gateway.metric = IS_METRIC gateway.debug = config[DOMAIN].get(CONF_DEBUG, False) gateway.start() def persistence_update(event): """Callback to trigger update from persistence file.""" - for _ in range(2): - for nid in gateway.sensors: - gateway.event_callback('persistence', nid) + for nid in gateway.sensors: + gateway.event_callback('persistence', nid) if persistence: hass.bus.listen_once( @@ -134,32 +157,23 @@ def setup(hass, config): # noqa hass.bus.listen_once(EVENT_HOMEASSISTANT_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) + conf_gateways = config[DOMAIN][CONF_GATEWAYS] + if isinstance(conf_gateways, dict): + conf_gateways = [conf_gateways] + persistence = config[DOMAIN].get(CONF_PERSISTENCE, True) + for index, gway in enumerate(conf_gateways): + port = gway[CONF_PORT] + persistence_file = gway.get( + CONF_PERSISTENCE_FILE, + hass.config.path('mysensors{}.pickle'.format(index + 1))) + GATEWAYS[port] = setup_gateway( + port, persistence, persistence_file) return True @@ -174,7 +188,6 @@ def mysensors_update(platform_type): if nid not in devices: devices[nid] = {} node = devices[nid] - new_devices = [] # Get platform specific S_TYPES, V_TYPES, class and add_devices. (platform_s_types, platform_v_types, @@ -183,7 +196,7 @@ def mysensors_update(platform_type): for child_id, child in gateway.sensors[nid].children.items(): if child_id not in node: node[child_id] = {} - for value_type, _ in child.values.items(): + for value_type in child.values.keys(): if (value_type not in node[child_id] and child.type in platform_s_types and value_type in platform_v_types): @@ -191,15 +204,13 @@ def mysensors_update(platform_type): gateway.sensors[nid].sketch_name, nid, child.id) node[child_id][value_type] = platform_class( port, nid, child_id, name, value_type) - new_devices.append(node[child_id][value_type]) - elif (child.type in platform_s_types and - value_type in platform_v_types): + _LOGGER.info('adding new device: %s', + node[child_id][value_type]) + add_devices([node[child_id][value_type]]) + if (child.type in platform_s_types and + value_type in platform_v_types): node[child_id][value_type].update_sensor( child.values, gateway.sensors[nid].battery_level) - if new_devices: - _LOGGER.info('adding new devices: %s', new_devices) - add_devices(new_devices) - return return wrapper @@ -215,5 +226,4 @@ def event_update(update): event.data[ATTR_PORT], event.data[ATTR_DEVICES], event.data[ATTR_NODE_ID]) - return return wrapper diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index e3843448763..16f047beaf4 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -18,7 +18,7 @@ from homeassistant.const import ( import homeassistant.components.mysensors as mysensors _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['mysensors'] +DEPENDENCIES = [] def setup_platform(hass, config, add_devices, discovery_info=None): @@ -41,19 +41,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None): mysensors.CONST.Presentation.S_CUSTOM, mysensors.CONST.Presentation.S_DUST, mysensors.CONST.Presentation.S_SCENE_CONTROLLER, - mysensors.CONST.Presentation.S_COLOR_SENSOR, - mysensors.CONST.Presentation.S_MULTIMETER, ] not_v_types = [ mysensors.CONST.SetReq.V_ARMED, - mysensors.CONST.SetReq.V_STATUS, mysensors.CONST.SetReq.V_LIGHT, mysensors.CONST.SetReq.V_LOCK_STATUS, ] - v_types = [] - for _, member in mysensors.CONST.SetReq.__members__.items(): - if all(test != member.value for test in not_v_types): - v_types.append(member) + if float(mysensors.VERSION) >= 1.5: + s_types.extend([ + mysensors.CONST.Presentation.S_COLOR_SENSOR, + mysensors.CONST.Presentation.S_MULTIMETER, + ]) + not_v_types.extend([mysensors.CONST.SetReq.V_STATUS, ]) + v_types = [member for member in mysensors.CONST.SetReq + if member.value not in not_v_types] @mysensors.mysensors_update def _sensor_update(gateway, port, devices, nid): diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 792502aef07..efa3bd7f7c4 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -17,7 +17,7 @@ from homeassistant.const import ( import homeassistant.components.mysensors as mysensors _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['mysensors'] +DEPENDENCIES = [] def setup_platform(hass, config, add_devices, discovery_info=None): @@ -30,18 +30,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): mysensors.CONST.Presentation.S_LIGHT, mysensors.CONST.Presentation.S_BINARY, mysensors.CONST.Presentation.S_LOCK, - mysensors.CONST.Presentation.S_SPRINKLER, - mysensors.CONST.Presentation.S_WATER_LEAK, - mysensors.CONST.Presentation.S_SOUND, - mysensors.CONST.Presentation.S_VIBRATION, - mysensors.CONST.Presentation.S_MOISTURE, ] v_types = [ mysensors.CONST.SetReq.V_ARMED, - mysensors.CONST.SetReq.V_STATUS, mysensors.CONST.SetReq.V_LIGHT, mysensors.CONST.SetReq.V_LOCK_STATUS, ] + if float(mysensors.VERSION) >= 1.5: + s_types.extend([ + mysensors.CONST.Presentation.S_SPRINKLER, + mysensors.CONST.Presentation.S_WATER_LEAK, + mysensors.CONST.Presentation.S_SOUND, + mysensors.CONST.Presentation.S_VIBRATION, + mysensors.CONST.Presentation.S_MOISTURE, + ]) + v_types.extend([mysensors.CONST.SetReq.V_STATUS, ]) @mysensors.mysensors_update def _sensor_update(gateway, port, devices, nid): From be25ea4f09c246a8317fb8b138d90a2fe728e5a9 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Thu, 24 Dec 2015 02:14:58 +0100 Subject: [PATCH 09/12] Fix avoid event bus for updates --- homeassistant/components/mysensors.py | 52 +++----------------- homeassistant/components/sensor/mysensors.py | 44 +++++++---------- homeassistant/components/switch/mysensors.py | 46 +++++++---------- 3 files changed, 43 insertions(+), 99 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 89bc14a4ef8..6c3b1854b02 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -26,23 +26,6 @@ mysensors: debug: true persistence: true version: '1.5' - -mysensors: - port: - - '/dev/ttyUSB0' - - '/dev/ttyACM1' - debug: true - persistence: true - persistence_file: - - 'path/to/.homeassistant/mysensors.json' - - 'path/to/.homeassistant/mysensors2.json' - version: '1.5' - -sensor: - platform: mysensors - -switch: - platform: mysensors """ import logging @@ -70,11 +53,8 @@ REQUIREMENTS = [ 'https://github.com/theolind/pymysensors/archive/' '2aa8f32908e8c5bb3e5c77c5851db778f8635792.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' COMPONENTS_WITH_MYSENSORS_PLATFORM = [ 'sensor', @@ -84,11 +64,11 @@ COMPONENTS_WITH_MYSENSORS_PLATFORM = [ IS_METRIC = None CONST = None GATEWAYS = None -EVENT_MYSENSORS_NODE_UPDATE = 'MYSENSORS_NODE_UPDATE' def setup(hass, config): """Setup the MySensors component.""" + # pylint: disable=too-many-locals import mysensors.mysensors as mysensors if not validate_config(config, @@ -119,18 +99,17 @@ def setup(hass, config): if not bootstrap.setup_component(hass, component, mysensors_config): return False - def callback_factory(port, devices): + import homeassistant.components.sensor.mysensors as mysensors_sensor + import homeassistant.components.switch.mysensors as mysensors_switch + + def callback_factory(gateway, port, devices): """Return a new callback function. 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, { - ATTR_PORT: port, - ATTR_DEVICES: devices, - ATTR_UPDATE_TYPE: update_type, - ATTR_NODE_ID: nid - }) + mysensors_sensor.sensor_update(gateway, port, devices, nid) + mysensors_switch.sensor_update(gateway, port, devices, nid) return node_update @@ -141,7 +120,7 @@ def setup(hass, config): persistence=persistence, persistence_file=persistence_file, protocol_version=VERSION) - gateway.event_callback = callback_factory(port, devices) + gateway.event_callback = callback_factory(gateway, port, devices) gateway.metric = IS_METRIC gateway.debug = config[DOMAIN].get(CONF_DEBUG, False) gateway.start() @@ -212,18 +191,3 @@ def mysensors_update(platform_type): node[child_id][value_type].update_sensor( child.values, gateway.sensors[nid].battery_level) return wrapper - - -def event_update(update): - """Decorator for callback function for mysensor event updates.""" - def wrapper(event): - """Wrapper function in the decorator.""" - _LOGGER.info( - 'update %s: node %s', event.data[ATTR_UPDATE_TYPE], - event.data[ATTR_NODE_ID]) - sensor_update = update(event) - sensor_update(GATEWAYS[event.data[ATTR_PORT]], - event.data[ATTR_PORT], - event.data[ATTR_DEVICES], - event.data[ATTR_NODE_ID]) - return wrapper diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index 16f047beaf4..eb8d4c57161 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -20,11 +20,24 @@ import homeassistant.components.mysensors as mysensors _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] +ADD_DEVICES = None +S_TYPES = None +V_TYPES = None + + +@mysensors.mysensors_update +def sensor_update(gateway, port, devices, nid): + """Internal callback for sensor updates.""" + return (S_TYPES, V_TYPES, MySensorsSensor, ADD_DEVICES) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the mysensors platform for sensors.""" # Define the S_TYPES and V_TYPES that the platform should handle as states. - s_types = [ + global ADD_DEVICES + ADD_DEVICES = add_devices + global S_TYPES + S_TYPES = [ mysensors.CONST.Presentation.S_TEMP, mysensors.CONST.Presentation.S_HUM, mysensors.CONST.Presentation.S_BARO, @@ -48,26 +61,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): mysensors.CONST.SetReq.V_LOCK_STATUS, ] if float(mysensors.VERSION) >= 1.5: - s_types.extend([ + S_TYPES.extend([ mysensors.CONST.Presentation.S_COLOR_SENSOR, mysensors.CONST.Presentation.S_MULTIMETER, ]) not_v_types.extend([mysensors.CONST.SetReq.V_STATUS, ]) - v_types = [member for member in mysensors.CONST.SetReq + global V_TYPES + V_TYPES = [member for member in mysensors.CONST.SetReq if member.value not in not_v_types] - @mysensors.mysensors_update - def _sensor_update(gateway, port, devices, nid): - """Internal callback for sensor updates.""" - return (s_types, v_types, MySensorsSensor, add_devices) - - @mysensors.event_update - def event_update(event): - """Callback for event updates from the MySensors component.""" - return _sensor_update - - hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, event_update) - class MySensorsSensor(Entity): """Represent the value of a MySensors child node.""" @@ -101,18 +103,6 @@ class MySensorsSensor(Entity): self.battery_level = 0 self._values = {} - def as_dict(self): - """Return 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): """MySensor gateway pushes its state to HA.""" diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index efa3bd7f7c4..4ca14cae27c 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -19,11 +19,24 @@ import homeassistant.components.mysensors as mysensors _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] +ADD_DEVICES = None +S_TYPES = None +V_TYPES = None + + +@mysensors.mysensors_update +def sensor_update(gateway, port, devices, nid): + """Internal callback for sensor updates.""" + return (S_TYPES, V_TYPES, MySensorsSwitch, ADD_DEVICES) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the mysensors platform for switches.""" # Define the S_TYPES and V_TYPES that the platform should handle as states. - s_types = [ + global ADD_DEVICES + ADD_DEVICES = add_devices + global S_TYPES + S_TYPES = [ mysensors.CONST.Presentation.S_DOOR, mysensors.CONST.Presentation.S_MOTION, mysensors.CONST.Presentation.S_SMOKE, @@ -31,32 +44,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): mysensors.CONST.Presentation.S_BINARY, mysensors.CONST.Presentation.S_LOCK, ] - v_types = [ + global V_TYPES + V_TYPES = [ mysensors.CONST.SetReq.V_ARMED, mysensors.CONST.SetReq.V_LIGHT, mysensors.CONST.SetReq.V_LOCK_STATUS, ] if float(mysensors.VERSION) >= 1.5: - s_types.extend([ + S_TYPES.extend([ mysensors.CONST.Presentation.S_SPRINKLER, mysensors.CONST.Presentation.S_WATER_LEAK, mysensors.CONST.Presentation.S_SOUND, mysensors.CONST.Presentation.S_VIBRATION, mysensors.CONST.Presentation.S_MOISTURE, ]) - v_types.extend([mysensors.CONST.SetReq.V_STATUS, ]) - - @mysensors.mysensors_update - def _sensor_update(gateway, port, devices, nid): - """Internal callback for sensor updates.""" - return (s_types, v_types, MySensorsSwitch, add_devices) - - @mysensors.event_update - def event_update(event): - """Callback for event updates from the MySensors component.""" - return _sensor_update - - hass.bus.listen(mysensors.EVENT_MYSENSORS_NODE_UPDATE, event_update) + V_TYPES.extend([mysensors.CONST.SetReq.V_STATUS, ]) class MySensorsSwitch(SwitchDevice): @@ -91,18 +93,6 @@ class MySensorsSwitch(SwitchDevice): self.battery_level = 0 self._values = {} - def as_dict(self): - """Return 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): """MySensor gateway pushes its state to HA.""" From 69ed6fe6e7abaa07be3be4d57530a3be8d1c88bf Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Thu, 31 Dec 2015 05:48:23 +0100 Subject: [PATCH 10/12] Add gateway wrapper, fix discovery and callbacks * Add gateway wrapper by subclassing serial gateway. * Fix platform setup with discovery service. * Fix platform callback functions with callback factory. --- homeassistant/components/mysensors.py | 219 ++++++++++--------- homeassistant/components/sensor/__init__.py | 6 +- homeassistant/components/sensor/mysensors.py | 137 ++++++------ homeassistant/components/switch/__init__.py | 3 +- homeassistant/components/switch/mysensors.py | 95 ++++---- 5 files changed, 244 insertions(+), 216 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 6c3b1854b02..a0601850fa7 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -29,14 +29,19 @@ mysensors: """ import logging +try: + import mysensors.mysensors as mysensors +except ImportError: + mysensors = None + from homeassistant.helpers import validate_config import homeassistant.bootstrap as bootstrap from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - TEMP_CELCIUS, - CONF_PLATFORM) + EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, + TEMP_CELCIUS,) CONF_GATEWAYS = 'gateways' CONF_PORT = 'port' @@ -45,7 +50,6 @@ CONF_PERSISTENCE = 'persistence' CONF_PERSISTENCE_FILE = 'persistence_file' CONF_VERSION = 'version' DEFAULT_VERSION = '1.4' -VERSION = None DOMAIN = 'mysensors' DEPENDENCIES = [] @@ -56,86 +60,54 @@ _LOGGER = logging.getLogger(__name__) ATTR_NODE_ID = 'node_id' ATTR_CHILD_ID = 'child_id' -COMPONENTS_WITH_MYSENSORS_PLATFORM = [ - 'sensor', - 'switch', -] - -IS_METRIC = None -CONST = None GATEWAYS = None +SCAN_INTERVAL = 30 + +DISCOVER_SENSORS = "mysensors.sensors" +DISCOVER_SWITCHES = "mysensors.switches" + +# Maps discovered services to their platforms +DISCOVERY_COMPONENTS = [ + ('sensor', DISCOVER_SENSORS), + ('switch', DISCOVER_SWITCHES), +] def setup(hass, config): """Setup the MySensors component.""" # pylint: disable=too-many-locals - import mysensors.mysensors as mysensors if not validate_config(config, {DOMAIN: [CONF_GATEWAYS]}, _LOGGER): return False - global VERSION - VERSION = config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION) + global mysensors # pylint: disable=invalid-name + if mysensors is None: + import mysensors.mysensors as _mysensors + mysensors = _mysensors - global CONST - if VERSION == '1.5': - import mysensors.const_15 as const - CONST = const - else: - import mysensors.const_14 as const - CONST = const + version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION)) + is_metric = (hass.config.temperature_unit == TEMP_CELCIUS) - # 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) - - # Setup mysensors platforms - mysensors_config = config.copy() - for component in COMPONENTS_WITH_MYSENSORS_PLATFORM: - mysensors_config[component] = {CONF_PLATFORM: 'mysensors'} - if not bootstrap.setup_component(hass, component, mysensors_config): - return False - - import homeassistant.components.sensor.mysensors as mysensors_sensor - import homeassistant.components.switch.mysensors as mysensors_switch - - def callback_factory(gateway, port, devices): - """Return a new callback function. 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) - - mysensors_sensor.sensor_update(gateway, port, devices, nid) - mysensors_switch.sensor_update(gateway, port, devices, nid) - - return node_update - - def setup_gateway(port, persistence, persistence_file): + def setup_gateway(port, persistence, persistence_file, version): """Return gateway after setup of the gateway.""" - devices = {} # keep track of devices added to HA - gateway = mysensors.SerialGateway(port, - persistence=persistence, - persistence_file=persistence_file, - protocol_version=VERSION) - gateway.event_callback = callback_factory(gateway, port, devices) - gateway.metric = IS_METRIC + gateway = GatewayWrapper( + port, persistence, persistence_file, version) + # pylint: disable=attribute-defined-outside-init + gateway.metric = is_metric gateway.debug = config[DOMAIN].get(CONF_DEBUG, False) - gateway.start() - def persistence_update(event): - """Callback to trigger update from persistence file.""" - for nid in gateway.sensors: - gateway.event_callback('persistence', nid) + def gw_start(event): + """Callback to trigger start of gateway and any persistence.""" + gateway.start() + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + lambda event: gateway.stop()) + if persistence: + for node_id in gateway.sensors: + gateway.event_callback('persistence', node_id) - if persistence: - hass.bus.listen_once( - EVENT_HOMEASSISTANT_START, persistence_update) - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - lambda event: gateway.stop()) + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, gw_start) return gateway @@ -146,48 +118,99 @@ def setup(hass, config): if isinstance(conf_gateways, dict): conf_gateways = [conf_gateways] persistence = config[DOMAIN].get(CONF_PERSISTENCE, True) + for index, gway in enumerate(conf_gateways): port = gway[CONF_PORT] persistence_file = gway.get( CONF_PERSISTENCE_FILE, hass.config.path('mysensors{}.pickle'.format(index + 1))) GATEWAYS[port] = setup_gateway( - port, persistence, persistence_file) + port, persistence, persistence_file, version) + + for (component, discovery_service) in DISCOVERY_COMPONENTS: + # Ensure component is loaded + if not bootstrap.setup_component(hass, component, config): + return False + # Fire discovery event + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { + ATTR_SERVICE: discovery_service, + ATTR_DISCOVERED: {}}) return True -def mysensors_update(platform_type): - """Decorator for callback function for mysensor updates.""" - def wrapper(gateway, port, devices, nid): - """Wrapper function in the decorator.""" - if gateway.sensors[nid].sketch_name is None: - _LOGGER.info('No sketch_name: node %s', nid) +def pf_callback_factory( + s_types, v_types, devices, add_devices, entity_class): + """Return a new callback for the platform.""" + def mysensors_callback(gateway, node_id): + """Callback for mysensors platform.""" + if gateway.sensors[node_id].sketch_name is None: + _LOGGER.info('No sketch_name: node %s', node_id) return - if nid not in devices: - devices[nid] = {} - node = devices[nid] - # Get platform specific S_TYPES, V_TYPES, class and add_devices. - (platform_s_types, - platform_v_types, - platform_class, - add_devices) = platform_type(gateway, port, devices, nid) - for child_id, child in gateway.sensors[nid].children.items(): - if child_id not in node: - node[child_id] = {} + # previously discovered, just update state with latest info + if node_id in devices: + for entity in devices[node_id]: + entity.update_ha_state(True) + return + + # First time we see this node, detect sensors + for child in gateway.sensors[node_id].children.values(): + name = '{} {}.{}'.format( + gateway.sensors[node_id].sketch_name, node_id, child.id) + for value_type in child.values.keys(): - if (value_type not in node[child_id] and - child.type in platform_s_types and - value_type in platform_v_types): - name = '{} {}.{}'.format( - gateway.sensors[nid].sketch_name, nid, child.id) - node[child_id][value_type] = platform_class( - port, nid, child_id, name, value_type) - _LOGGER.info('adding new device: %s', - node[child_id][value_type]) - add_devices([node[child_id][value_type]]) - if (child.type in platform_s_types and - value_type in platform_v_types): - node[child_id][value_type].update_sensor( - child.values, gateway.sensors[nid].battery_level) - return wrapper + if child.type not in s_types or value_type not in v_types: + continue + + devices[node_id].append( + entity_class(gateway, node_id, child.id, name, value_type)) + if devices[node_id]: + _LOGGER.info('adding new devices: %s', devices[node_id]) + add_devices(devices[node_id]) + for entity in devices[node_id]: + entity.update_ha_state(True) + return mysensors_callback + + +class GatewayWrapper(mysensors.SerialGateway): + """Gateway wrapper class, by subclassing serial gateway.""" + + def __init__(self, port, persistence, persistence_file, version): + """Setup class attributes on instantiation. + + Args: + port: Port of gateway to wrap. + persistence: Persistence, true or false. + persistence_file: File to store persistence info. + version: Version of mysensors API. + + Attributes: + version (str): Version of mysensors API. + platform_callbacks (list): Callback functions, one per platform. + const (module): Mysensors API constants. + """ + super().__init__(port, event_callback=self.callback_factory(), + persistence=persistence, + persistence_file=persistence_file, + protocol_version=version) + self.version = version + self.platform_callbacks = [] + self.const = self.get_const() + + def get_const(self): + """Get mysensors API constants.""" + if self.version == '1.5': + import mysensors.const_15 as const + else: + import mysensors.const_14 as const + return const + + def callback_factory(self): + """Return a new callback function.""" + def node_update(update_type, node_id): + """Callback for node updates from the MySensors gateway.""" + _LOGGER.info('update %s: node %s', update_type, node_id) + for callback in self.platform_callbacks: + callback(self, node_id) + + return node_update diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 04770ced241..1689f7a8889 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -9,7 +9,8 @@ https://home-assistant.io/components/sensor/ import logging from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.components import wink, zwave, isy994, verisure, ecobee +from homeassistant.components import ( + wink, zwave, isy994, verisure, ecobee, mysensors) DOMAIN = 'sensor' SCAN_INTERVAL = 30 @@ -22,7 +23,8 @@ DISCOVERY_PLATFORMS = { zwave.DISCOVER_SENSORS: 'zwave', isy994.DISCOVER_SENSORS: 'isy994', verisure.DISCOVER_SENSORS: 'verisure', - ecobee.DISCOVER_SENSORS: 'ecobee' + ecobee.DISCOVER_SENSORS: 'ecobee', + mysensors.DISCOVER_SENSORS: 'mysensors', } diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index eb8d4c57161..3944cf4f982 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -7,6 +7,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mysensors/ """ import logging +from collections import defaultdict from homeassistant.helpers.entity import Entity @@ -20,74 +21,71 @@ import homeassistant.components.mysensors as mysensors _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] -ADD_DEVICES = None -S_TYPES = None -V_TYPES = None - - -@mysensors.mysensors_update -def sensor_update(gateway, port, devices, nid): - """Internal callback for sensor updates.""" - return (S_TYPES, V_TYPES, MySensorsSensor, ADD_DEVICES) - def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the mysensors platform for sensors.""" - # Define the S_TYPES and V_TYPES that the platform should handle as states. - global ADD_DEVICES - ADD_DEVICES = add_devices - global S_TYPES - S_TYPES = [ - mysensors.CONST.Presentation.S_TEMP, - mysensors.CONST.Presentation.S_HUM, - mysensors.CONST.Presentation.S_BARO, - mysensors.CONST.Presentation.S_WIND, - mysensors.CONST.Presentation.S_RAIN, - mysensors.CONST.Presentation.S_UV, - mysensors.CONST.Presentation.S_WEIGHT, - mysensors.CONST.Presentation.S_POWER, - mysensors.CONST.Presentation.S_DISTANCE, - mysensors.CONST.Presentation.S_LIGHT_LEVEL, - mysensors.CONST.Presentation.S_IR, - mysensors.CONST.Presentation.S_WATER, - mysensors.CONST.Presentation.S_AIR_QUALITY, - mysensors.CONST.Presentation.S_CUSTOM, - mysensors.CONST.Presentation.S_DUST, - mysensors.CONST.Presentation.S_SCENE_CONTROLLER, - ] - not_v_types = [ - mysensors.CONST.SetReq.V_ARMED, - mysensors.CONST.SetReq.V_LIGHT, - mysensors.CONST.SetReq.V_LOCK_STATUS, - ] - if float(mysensors.VERSION) >= 1.5: - S_TYPES.extend([ - mysensors.CONST.Presentation.S_COLOR_SENSOR, - mysensors.CONST.Presentation.S_MULTIMETER, - ]) - not_v_types.extend([mysensors.CONST.SetReq.V_STATUS, ]) - global V_TYPES - V_TYPES = [member for member in mysensors.CONST.SetReq - if member.value not in not_v_types] + # Only act if loaded via mysensors by discovery event. + # Otherwise gateway is not setup. + if discovery_info is None: + return + + for gateway in mysensors.GATEWAYS.values(): + # Define the S_TYPES and V_TYPES that the platform should handle as + # states. + s_types = [ + gateway.const.Presentation.S_TEMP, + gateway.const.Presentation.S_HUM, + gateway.const.Presentation.S_BARO, + gateway.const.Presentation.S_WIND, + gateway.const.Presentation.S_RAIN, + gateway.const.Presentation.S_UV, + gateway.const.Presentation.S_WEIGHT, + gateway.const.Presentation.S_POWER, + gateway.const.Presentation.S_DISTANCE, + gateway.const.Presentation.S_LIGHT_LEVEL, + gateway.const.Presentation.S_IR, + gateway.const.Presentation.S_WATER, + gateway.const.Presentation.S_AIR_QUALITY, + gateway.const.Presentation.S_CUSTOM, + gateway.const.Presentation.S_DUST, + gateway.const.Presentation.S_SCENE_CONTROLLER, + ] + not_v_types = [ + gateway.const.SetReq.V_ARMED, + gateway.const.SetReq.V_LIGHT, + gateway.const.SetReq.V_LOCK_STATUS, + ] + if float(gateway.version) >= 1.5: + s_types.extend([ + gateway.const.Presentation.S_COLOR_SENSOR, + gateway.const.Presentation.S_MULTIMETER, + ]) + not_v_types.extend([gateway.const.SetReq.V_STATUS, ]) + v_types = [member for member in gateway.const.SetReq + if member.value not in not_v_types] + + devices = defaultdict(list) + gateway.platform_callbacks.append(mysensors.pf_callback_factory( + s_types, v_types, devices, add_devices, MySensorsSensor)) class MySensorsSensor(Entity): """Represent the value of a MySensors child node.""" - # pylint: disable=too-many-arguments, too-many-instance-attributes + # pylint: disable=too-many-arguments - def __init__(self, port, node_id, child_id, name, value_type): + def __init__(self, gateway, node_id, child_id, name, value_type): """Setup class attributes on instantiation. Args: - port (str): Gateway port. + gateway (str): Gateway. node_id (str): Id of node. child_id (str): Id of child. name (str): Entity name. value_type (str): Value type of child. Value is entity state. Attributes: - port (str): Gateway port. + gateway (str): Gateway. node_id (str): Id of node. child_id (str): Id of child. _name (str): Entity name. @@ -95,7 +93,7 @@ class MySensorsSensor(Entity): battery_level (int): Node battery level. _values (dict): Child values. Non state values set as state attributes. """ - self.port = port + self.gateway = gateway self.node_id = node_id self.child_id = child_id self._name = name @@ -124,25 +122,25 @@ class MySensorsSensor(Entity): def unit_of_measurement(self): """Unit of measurement of this entity.""" # 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: + if self.value_type == self.gateway.const.SetReq.V_TEMP: + return TEMP_CELCIUS if self.gateway.metric else TEMP_FAHRENHEIT + elif self.value_type == self.gateway.const.SetReq.V_HUM or \ + self.value_type == self.gateway.const.SetReq.V_DIMMER or \ + self.value_type == self.gateway.const.SetReq.V_PERCENTAGE or \ + self.value_type == self.gateway.const.SetReq.V_LIGHT_LEVEL: return '%' - elif self.value_type == mysensors.CONST.SetReq.V_WATT: + elif self.value_type == self.gateway.const.SetReq.V_WATT: return 'W' - elif self.value_type == mysensors.CONST.SetReq.V_KWH: + elif self.value_type == self.gateway.const.SetReq.V_KWH: return 'kWh' - elif self.value_type == mysensors.CONST.SetReq.V_VOLTAGE: + elif self.value_type == self.gateway.const.SetReq.V_VOLTAGE: return 'V' - elif self.value_type == mysensors.CONST.SetReq.V_CURRENT: + elif self.value_type == self.gateway.const.SetReq.V_CURRENT: return 'A' - elif self.value_type == mysensors.CONST.SetReq.V_IMPEDANCE: + elif self.value_type == self.gateway.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] + elif self.gateway.const.SetReq.V_UNIT_PREFIX in self._values: + return self._values[self.gateway.const.SetReq.V_UNIT_PREFIX] return None @property @@ -168,16 +166,17 @@ class MySensorsSensor(Entity): return data - def update_sensor(self, values, battery_level): + def update(self): """Update the controller with the latest values from a sensor.""" - for value_type, value in values.items(): + node = self.gateway.sensors[self.node_id] + child = node.children[self.child_id] + for value_type, value in child.values.items(): _LOGGER.info( "%s: value_type %s, value = %s", self._name, value_type, value) - if value_type == mysensors.CONST.SetReq.V_TRIPPED: + if value_type == self.gateway.const.SetReq.V_TRIPPED: self._values[value_type] = STATE_ON if int( value) == 1 else STATE_OFF else: self._values[value_type] = value - self.battery_level = battery_level - self.update_ha_state() + self.battery_level = node.battery_level diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index e7b3c629f39..9f9bcc18604 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -17,7 +17,7 @@ from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) from homeassistant.components import ( - group, discovery, wink, isy994, verisure, zwave) + group, discovery, wink, isy994, verisure, zwave, mysensors) DOMAIN = 'switch' SCAN_INTERVAL = 30 @@ -40,6 +40,7 @@ DISCOVERY_PLATFORMS = { isy994.DISCOVER_SWITCHES: 'isy994', verisure.DISCOVER_SWITCHES: 'verisure', zwave.DISCOVER_SWITCHES: 'zwave', + mysensors.DISCOVER_SWITCHES: 'mysensors', } PROP_TO_ATTR = { diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 4ca14cae27c..2b886153d8f 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -7,6 +7,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mysensors.html """ import logging +from collections import defaultdict from homeassistant.components.switch import SwitchDevice @@ -24,49 +25,50 @@ S_TYPES = None V_TYPES = None -@mysensors.mysensors_update -def sensor_update(gateway, port, devices, nid): - """Internal callback for sensor updates.""" - return (S_TYPES, V_TYPES, MySensorsSwitch, ADD_DEVICES) - - def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the mysensors platform for switches.""" - # Define the S_TYPES and V_TYPES that the platform should handle as states. - global ADD_DEVICES - ADD_DEVICES = add_devices - global S_TYPES - S_TYPES = [ - mysensors.CONST.Presentation.S_DOOR, - mysensors.CONST.Presentation.S_MOTION, - mysensors.CONST.Presentation.S_SMOKE, - mysensors.CONST.Presentation.S_LIGHT, - mysensors.CONST.Presentation.S_BINARY, - mysensors.CONST.Presentation.S_LOCK, - ] - global V_TYPES - V_TYPES = [ - mysensors.CONST.SetReq.V_ARMED, - mysensors.CONST.SetReq.V_LIGHT, - mysensors.CONST.SetReq.V_LOCK_STATUS, - ] - if float(mysensors.VERSION) >= 1.5: - S_TYPES.extend([ - mysensors.CONST.Presentation.S_SPRINKLER, - mysensors.CONST.Presentation.S_WATER_LEAK, - mysensors.CONST.Presentation.S_SOUND, - mysensors.CONST.Presentation.S_VIBRATION, - mysensors.CONST.Presentation.S_MOISTURE, - ]) - V_TYPES.extend([mysensors.CONST.SetReq.V_STATUS, ]) + # Only act if loaded via mysensors by discovery event. + # Otherwise gateway is not setup. + if discovery_info is None: + return + + for gateway in mysensors.GATEWAYS.values(): + # Define the S_TYPES and V_TYPES that the platform should handle as + # states. + s_types = [ + gateway.const.Presentation.S_DOOR, + gateway.const.Presentation.S_MOTION, + gateway.const.Presentation.S_SMOKE, + gateway.const.Presentation.S_LIGHT, + gateway.const.Presentation.S_BINARY, + gateway.const.Presentation.S_LOCK, + ] + v_types = [ + gateway.const.SetReq.V_ARMED, + gateway.const.SetReq.V_LIGHT, + gateway.const.SetReq.V_LOCK_STATUS, + ] + if float(gateway.version) >= 1.5: + s_types.extend([ + gateway.const.Presentation.S_SPRINKLER, + gateway.const.Presentation.S_WATER_LEAK, + gateway.const.Presentation.S_SOUND, + gateway.const.Presentation.S_VIBRATION, + gateway.const.Presentation.S_MOISTURE, + ]) + v_types.extend([gateway.const.SetReq.V_STATUS, ]) + + devices = defaultdict(list) + gateway.platform_callbacks.append(mysensors.pf_callback_factory( + s_types, v_types, devices, add_devices, MySensorsSwitch)) class MySensorsSwitch(SwitchDevice): """Represent the value of a MySensors child node.""" - # pylint: disable=too-many-arguments, too-many-instance-attributes + # pylint: disable=too-many-arguments - def __init__(self, port, node_id, child_id, name, value_type): + def __init__(self, gateway, node_id, child_id, name, value_type): """Setup class attributes on instantiation. Args: @@ -85,7 +87,7 @@ class MySensorsSwitch(SwitchDevice): battery_level (int): Node battery level. _values (dict): Child values. Non state values set as state attributes. """ - self.port = port + self.gateway = gateway self.node_id = node_id self.child_id = child_id self._name = name @@ -135,30 +137,31 @@ class MySensorsSwitch(SwitchDevice): def turn_on(self): """Turn the switch on.""" - mysensors.GATEWAYS[self.port].set_child_value( + self.gateway.set_child_value( self.node_id, self.child_id, self.value_type, 1) self._values[self.value_type] = STATE_ON self.update_ha_state() def turn_off(self): """Turn the switch off.""" - mysensors.GATEWAYS[self.port].set_child_value( + self.gateway.set_child_value( self.node_id, self.child_id, self.value_type, 0) self._values[self.value_type] = STATE_OFF self.update_ha_state() - def update_sensor(self, values, battery_level): + def update(self): """Update the controller with the latest value from a sensor.""" - for value_type, value in values.items(): + node = self.gateway.sensors[self.node_id] + child = node.children[self.child_id] + for value_type, value in child.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: + if value_type == self.gateway.const.SetReq.V_ARMED or \ + value_type == self.gateway.const.SetReq.V_STATUS or \ + value_type == self.gateway.const.SetReq.V_LIGHT or \ + value_type == self.gateway.const.SetReq.V_LOCK_STATUS: self._values[value_type] = ( STATE_ON if int(value) == 1 else STATE_OFF) else: self._values[value_type] = value - self.battery_level = battery_level - self.update_ha_state() + self.battery_level = node.battery_level From 4c4e5d5f471ce6318a6d1c3a682dba745a1c9865 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Thu, 31 Dec 2015 06:19:47 +0100 Subject: [PATCH 11/12] Fix to remove old unused variables. --- homeassistant/components/switch/mysensors.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 2b886153d8f..33c214cda76 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -20,10 +20,6 @@ import homeassistant.components.mysensors as mysensors _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] -ADD_DEVICES = None -S_TYPES = None -V_TYPES = None - def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the mysensors platform for switches.""" From 2d8cf7de447cf4a3fc8b0df65c0ba34066e287f3 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Sun, 10 Jan 2016 04:10:38 +0100 Subject: [PATCH 12/12] Fix wrapper and S_BINARY and bump req. version * Wrap existing SerialGateway instance instead of subclassing SerialGatewat class. * Add S_BINARY in switch platform only in version 1.5 of mysenors api. * Use version 0.4 of pymysensors. * Show gateway port as state attribute. --- homeassistant/components/mysensors.py | 62 ++++++++++++-------- homeassistant/components/sensor/mysensors.py | 5 +- homeassistant/components/switch/mysensors.py | 7 ++- requirements_all.txt | 2 +- 4 files changed, 46 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index a0601850fa7..7fb1a7cb1d7 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -12,7 +12,8 @@ New features: New MySensors component. Updated MySensors Sensor platform. -New MySensors Switch platform. +New MySensors Switch platform. Currently only in optimistic mode (compare +with MQTT). Multiple gateways are now supported. Configuration.yaml: @@ -29,11 +30,6 @@ mysensors: """ import logging -try: - import mysensors.mysensors as mysensors -except ImportError: - mysensors = None - from homeassistant.helpers import validate_config import homeassistant.bootstrap as bootstrap @@ -55,10 +51,11 @@ DOMAIN = 'mysensors' DEPENDENCIES = [] REQUIREMENTS = [ 'https://github.com/theolind/pymysensors/archive/' - '2aa8f32908e8c5bb3e5c77c5851db778f8635792.zip#pymysensors==0.3'] + '005bff4c5ca7a56acd30e816bc3bcdb5cb2d46fd.zip#pymysensors==0.4'] _LOGGER = logging.getLogger(__name__) ATTR_NODE_ID = 'node_id' ATTR_CHILD_ID = 'child_id' +ATTR_PORT = 'port' GATEWAYS = None SCAN_INTERVAL = 30 @@ -82,21 +79,22 @@ def setup(hass, config): _LOGGER): return False - global mysensors # pylint: disable=invalid-name - if mysensors is None: - import mysensors.mysensors as _mysensors - mysensors = _mysensors + import mysensors.mysensors as mysensors version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION)) is_metric = (hass.config.temperature_unit == TEMP_CELCIUS) def setup_gateway(port, persistence, persistence_file, version): """Return gateway after setup of the gateway.""" - gateway = GatewayWrapper( - port, persistence, persistence_file, version) - # pylint: disable=attribute-defined-outside-init + gateway = mysensors.SerialGateway(port, event_callback=None, + persistence=persistence, + persistence_file=persistence_file, + protocol_version=version) gateway.metric = is_metric gateway.debug = config[DOMAIN].get(CONF_DEBUG, False) + gateway = GatewayWrapper(gateway, version) + # pylint: disable=attribute-defined-outside-init + gateway.event_callback = gateway.callback_factory() def gw_start(event): """Callback to trigger start of gateway and any persistence.""" @@ -172,30 +170,46 @@ def pf_callback_factory( return mysensors_callback -class GatewayWrapper(mysensors.SerialGateway): +class GatewayWrapper(object): """Gateway wrapper class, by subclassing serial gateway.""" - def __init__(self, port, persistence, persistence_file, version): + def __init__(self, gateway, version): """Setup class attributes on instantiation. Args: - port: Port of gateway to wrap. - persistence: Persistence, true or false. - persistence_file: File to store persistence info. - version: Version of mysensors API. + gateway (mysensors.SerialGateway): Gateway to wrap. + version (str): Version of mysensors API. Attributes: + _wrapped_gateway (mysensors.SerialGateway): Wrapped gateway. version (str): Version of mysensors API. platform_callbacks (list): Callback functions, one per platform. const (module): Mysensors API constants. + __initialised (bool): True if GatewayWrapper is initialised. """ - super().__init__(port, event_callback=self.callback_factory(), - persistence=persistence, - persistence_file=persistence_file, - protocol_version=version) + self._wrapped_gateway = gateway self.version = version self.platform_callbacks = [] self.const = self.get_const() + self.__initialised = True + + def __getattr__(self, name): + """See if this object has attribute name.""" + # Do not use hasattr, it goes into infinite recurrsion + if name in self.__dict__: + # this object has it + return getattr(self, name) + # proxy to the wrapped object + return getattr(self._wrapped_gateway, name) + + def __setattr__(self, name, value): + """See if this object has attribute name then set to value.""" + if '_GatewayWrapper__initialised' not in self.__dict__: + return object.__setattr__(self, name, value) + elif name in self.__dict__: + object.__setattr__(self, name, value) + else: + object.__setattr__(self._wrapped_gateway, name, value) def get_const(self): """Get mysensors API constants.""" diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index 3944cf4f982..3562af1949d 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -78,14 +78,14 @@ class MySensorsSensor(Entity): """Setup class attributes on instantiation. Args: - gateway (str): Gateway. + gateway (GatewayWrapper): Gateway object. node_id (str): Id of node. child_id (str): Id of child. name (str): Entity name. value_type (str): Value type of child. Value is entity state. Attributes: - gateway (str): Gateway. + gateway (GatewayWrapper): Gateway object. node_id (str): Id of node. child_id (str): Id of child. _name (str): Entity name. @@ -154,6 +154,7 @@ class MySensorsSensor(Entity): def state_attributes(self): """Return the state attributes.""" data = { + mysensors.ATTR_PORT: self.gateway.port, mysensors.ATTR_NODE_ID: self.node_id, mysensors.ATTR_CHILD_ID: self.child_id, ATTR_BATTERY_LEVEL: self.battery_level, diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 33c214cda76..d8d7d4d2473 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -36,7 +36,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): gateway.const.Presentation.S_MOTION, gateway.const.Presentation.S_SMOKE, gateway.const.Presentation.S_LIGHT, - gateway.const.Presentation.S_BINARY, gateway.const.Presentation.S_LOCK, ] v_types = [ @@ -46,6 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ] if float(gateway.version) >= 1.5: s_types.extend([ + gateway.const.Presentation.S_BINARY, gateway.const.Presentation.S_SPRINKLER, gateway.const.Presentation.S_WATER_LEAK, gateway.const.Presentation.S_SOUND, @@ -68,14 +68,14 @@ class MySensorsSwitch(SwitchDevice): """Setup class attributes on instantiation. Args: - port (str): Gateway port. + gateway (GatewayWrapper): Gateway object. node_id (str): Id of node. child_id (str): Id of child. name (str): Entity name. value_type (str): Value type of child. Value is entity state. Attributes: - port (str): Gateway port. + gateway (GatewayWrapper): Gateway object node_id (str): Id of node. child_id (str): Id of child. _name (str): Entity name. @@ -112,6 +112,7 @@ class MySensorsSwitch(SwitchDevice): def state_attributes(self): """Return the state attributes.""" data = { + mysensors.ATTR_PORT: self.gateway.port, mysensors.ATTR_NODE_ID: self.node_id, mysensors.ATTR_CHILD_ID: self.child_id, ATTR_BATTERY_LEVEL: self.battery_level, diff --git a/requirements_all.txt b/requirements_all.txt index e8709c789e3..9bfdd3b62f5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -90,7 +90,7 @@ https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b6 paho-mqtt==1.1 # homeassistant.components.mysensors -https://github.com/theolind/pymysensors/archive/2aa8f32908e8c5bb3e5c77c5851db778f8635792.zip#pymysensors==0.3 +https://github.com/theolind/pymysensors/archive/005bff4c5ca7a56acd30e816bc3bcdb5cb2d46fd.zip#pymysensors==0.4 # homeassistant.components.notify.pushbullet pushbullet.py==0.9.0