From 08aaea5444d49303bfefd8d3fdbf27b10ea6c4fb Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Sat, 20 Feb 2016 03:59:06 +0100 Subject: [PATCH] Add mysensors binary sensor * Add mysensors binary sensor. * Add discovery platforms to binary_sensor base component. * Replace device_state_attributes with state_attributes in binary_sensor base class. * Fix docstrings. * Add discovery of binary sensor to mysensors component. * Add child.type as argument to mysensors device_class. * Move binary sensor types from sensor to binary_sensor module. * Fix binary_sensor attribute tests. Use state_attributes instead of device_state_attributes. --- .../components/binary_sensor/__init__.py | 26 ++- .../components/binary_sensor/mysensors.py | 163 ++++++++++++++++++ homeassistant/components/light/mysensors.py | 3 +- homeassistant/components/mysensors.py | 4 +- homeassistant/components/sensor/mysensors.py | 13 +- homeassistant/components/switch/mysensors.py | 4 +- .../binary_sensor/test_binary_sensor.py | 8 +- 7 files changed, 200 insertions(+), 21 deletions(-) create mode 100644 homeassistant/components/binary_sensor/mysensors.py diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 86889cd18df..3934feae179 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -12,6 +12,7 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) +from homeassistant.components import (mysensors, ) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 @@ -27,13 +28,19 @@ SENSOR_CLASSES = [ 'light', # Lightness threshold 'power', # Power, over-current, etc 'safety', # Generic on=unsafe, off=safe - ] +] + +# Maps discovered services to their platforms +DISCOVERY_PLATFORMS = { + mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', +} def setup(hass, config): - """ Track states and offer events for binary sensors. """ + """Track states and offer events for binary sensors.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, + DISCOVERY_PLATFORMS) component.setup(config) @@ -42,30 +49,31 @@ def setup(hass, config): # pylint: disable=no-self-use class BinarySensorDevice(Entity): - """ Represents a binary sensor. """ + """Represent a binary sensor.""" @property def is_on(self): - """ True if the binary sensor is on. """ + """Return True if the binary sensor is on.""" return None @property def state(self): - """ Returns the state of the binary sensor. """ + """Return the state of the binary sensor.""" return STATE_ON if self.is_on else STATE_OFF @property def friendly_state(self): - """ Returns the friendly state of the binary sensor. """ + """Return the friendly state of the binary sensor.""" return None @property def sensor_class(self): - """ Returns the class of this sensor, from SENSOR_CASSES. """ + """Return the class of this sensor, from SENSOR_CASSES.""" return None @property - def device_state_attributes(self): + def state_attributes(self): + """Return device specific state attributes.""" return { 'sensor_class': self.sensor_class, } diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py new file mode 100644 index 00000000000..ffa6742697a --- /dev/null +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -0,0 +1,163 @@ +""" +Support for MySensors binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.mysensors/ +""" +import logging + +import homeassistant.components.mysensors as mysensors +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON) +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, SENSOR_CLASSES) + +_LOGGER = logging.getLogger(__name__) +DEPENDENCIES = [] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the mysensors platform for sensors.""" + # 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. Map them in a dict of lists. + pres = gateway.const.Presentation + set_req = gateway.const.SetReq + map_sv_types = { + pres.S_DOOR: [set_req.V_TRIPPED], + pres.S_MOTION: [set_req.V_TRIPPED], + pres.S_SMOKE: [set_req.V_TRIPPED], + } + if float(gateway.version) >= 1.5: + map_sv_types.update({ + pres.S_SPRINKLER: [set_req.V_TRIPPED], + pres.S_WATER_LEAK: [set_req.V_TRIPPED], + pres.S_SOUND: [set_req.V_TRIPPED], + pres.S_VIBRATION: [set_req.V_TRIPPED], + pres.S_MOISTURE: [set_req.V_TRIPPED], + }) + + devices = {} + gateway.platform_callbacks.append(mysensors.pf_callback_factory( + map_sv_types, devices, add_devices, MySensorsBinarySensor)) + + +class MySensorsBinarySensor(BinarySensorDevice): + """Represent 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, child_type): + """Setup class attributes on instantiation. + + Args: + 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. + child_type (str): Child type of child. + + Attributes: + 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. + child_type (str): Child type of child. + battery_level (int): Node battery level. + _values (dict): Child values. Non state values set as state attributes. + """ + self.gateway = gateway + self.node_id = node_id + self.child_id = child_id + self._name = name + self.value_type = value_type + self.child_type = child_type + self.battery_level = 0 + self._values = {} + + @property + def should_poll(self): + """MySensor gateway pushes its state to HA.""" + return False + + @property + def name(self): + """The name of this entity.""" + return self._name + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + attr = { + 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, + } + + set_req = self.gateway.const.SetReq + + for value_type, value in self._values.items(): + if value_type != self.value_type: + try: + attr[set_req(value_type).name] = value + except ValueError: + _LOGGER.error('value_type %s is not valid for mysensors ' + 'version %s', value_type, + self.gateway.version) + return attr + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + if self.value_type in self._values: + return self._values[self.value_type] == STATE_ON + return False + + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CASSES.""" + pres = self.gateway.const.Presentation + class_map = { + pres.S_DOOR: 'opening', + pres.S_MOTION: 'motion', + pres.S_SMOKE: 'smoke', + } + if float(self.gateway.version) >= 1.5: + class_map.update({ + pres.S_SPRINKLER: 'sprinkler', + pres.S_WATER_LEAK: 'leak', + pres.S_SOUND: 'sound', + pres.S_VIBRATION: 'vibration', + pres.S_MOISTURE: 'moisture', + }) + if class_map.get(self.child_type) in SENSOR_CLASSES: + return class_map.get(self.child_type) + + @property + def available(self): + """Return True if entity is available.""" + return self.value_type in self._values + + def update(self): + """Update the controller with the latest values from a sensor.""" + node = self.gateway.sensors[self.node_id] + child = node.children[self.child_id] + for value_type, value in child.values.items(): + _LOGGER.debug( + "%s: value_type %s, value = %s", self._name, value_type, value) + 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 = node.battery_level diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index 3d9d2c14b13..c0daca374cb 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -58,7 +58,8 @@ class MySensorsLight(Light): # pylint: disable=too-many-arguments,too-many-instance-attributes - def __init__(self, gateway, node_id, child_id, name, value_type): + def __init__( + self, gateway, node_id, child_id, name, value_type, child_type): """Setup instance attributes.""" self.gateway = gateway self.node_id = node_id diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index a05435b2e8c..0510dc6cece 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -42,12 +42,14 @@ GATEWAYS = None DISCOVER_SENSORS = 'mysensors.sensors' DISCOVER_SWITCHES = 'mysensors.switches' DISCOVER_LIGHTS = 'mysensors.lights' +DISCOVER_BINARY_SENSORS = 'mysensors.binary_sensor' # Maps discovered services to their platforms DISCOVERY_COMPONENTS = [ ('sensor', DISCOVER_SENSORS), ('switch', DISCOVER_SWITCHES), ('light', DISCOVER_LIGHTS), + ('binary_sensor', DISCOVER_BINARY_SENSORS), ] @@ -148,7 +150,7 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class): else: device_class = entity_class devices[key] = device_class( - gateway, node_id, child.id, name, value_type) + gateway, node_id, child.id, name, value_type, child.type) _LOGGER.info('Adding new devices: %s', devices[key]) add_devices([devices[key]]) diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index 7d70a532164..a55c7fd3335 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -28,9 +28,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres = gateway.const.Presentation set_req = gateway.const.SetReq map_sv_types = { - pres.S_DOOR: [set_req.V_TRIPPED], - pres.S_MOTION: [set_req.V_TRIPPED], - pres.S_SMOKE: [set_req.V_TRIPPED], pres.S_TEMP: [set_req.V_TEMP], pres.S_HUM: [set_req.V_HUM], pres.S_BARO: [set_req.V_PRESSURE, set_req.V_FORECAST], @@ -64,9 +61,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): set_req.V_IMPEDANCE], pres.S_SPRINKLER: [set_req.V_TRIPPED], pres.S_WATER_LEAK: [set_req.V_TRIPPED], - pres.S_SOUND: [set_req.V_TRIPPED, set_req.V_LEVEL], - pres.S_VIBRATION: [set_req.V_TRIPPED, set_req.V_LEVEL], - pres.S_MOISTURE: [set_req.V_TRIPPED, set_req.V_LEVEL], + pres.S_SOUND: [set_req.V_LEVEL], + pres.S_VIBRATION: [set_req.V_LEVEL], + pres.S_MOISTURE: [set_req.V_LEVEL], pres.S_AIR_QUALITY: [set_req.V_LEVEL], pres.S_DUST: [set_req.V_LEVEL], }) @@ -82,7 +79,8 @@ class MySensorsSensor(Entity): # pylint: disable=too-many-arguments - def __init__(self, gateway, node_id, child_id, name, value_type): + def __init__( + self, gateway, node_id, child_id, name, value_type, child_type): """Setup class attributes on instantiation. Args: @@ -91,6 +89,7 @@ class MySensorsSensor(Entity): child_id (str): Id of child. name (str): Entity name. value_type (str): Value type of child. Value is entity state. + child_type (str): Child type of child. Attributes: gateway (GatewayWrapper): Gateway object. diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 4254158d745..e0797e42c54 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -52,7 +52,8 @@ class MySensorsSwitch(SwitchDevice): # pylint: disable=too-many-arguments - def __init__(self, gateway, node_id, child_id, name, value_type): + def __init__( + self, gateway, node_id, child_id, name, value_type, child_type): """Setup class attributes on instantiation. Args: @@ -61,6 +62,7 @@ class MySensorsSwitch(SwitchDevice): child_id (str): Id of child. name (str): Entity name. value_type (str): Value type of child. Value is entity state. + child_type (str): Child type of child. Attributes: gateway (GatewayWrapper): Gateway object diff --git a/tests/components/binary_sensor/test_binary_sensor.py b/tests/components/binary_sensor/test_binary_sensor.py index 9c3f7f99e0f..9079b208204 100644 --- a/tests/components/binary_sensor/test_binary_sensor.py +++ b/tests/components/binary_sensor/test_binary_sensor.py @@ -11,7 +11,10 @@ from homeassistant.const import STATE_ON, STATE_OFF class TestBinarySensor(unittest.TestCase): + """Test the binary_sensor base class.""" + def test_state(self): + """Test binary sensor state.""" sensor = binary_sensor.BinarySensorDevice() self.assertEqual(STATE_OFF, sensor.state) with mock.patch('homeassistant.components.binary_sensor.' @@ -26,11 +29,12 @@ class TestBinarySensor(unittest.TestCase): binary_sensor.BinarySensorDevice().state) def test_attributes(self): + """Test binary sensor attributes.""" sensor = binary_sensor.BinarySensorDevice() self.assertEqual({'sensor_class': None}, - sensor.device_state_attributes) + sensor.state_attributes) with mock.patch('homeassistant.components.binary_sensor.' 'BinarySensorDevice.sensor_class', new='motion'): self.assertEqual({'sensor_class': 'motion'}, - sensor.device_state_attributes) + sensor.state_attributes)