diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index ccfd57aff8c..86889cd18df 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -17,6 +17,17 @@ DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' +SENSOR_CLASSES = [ + None, # Generic on/off + 'opening', # Door, window, etc + 'motion', # Motion sensor + 'gas', # CO, CO2, etc + 'smoke', # Smoke detector + 'moisture', # Specifically a wetness sensor + 'light', # Lightness threshold + 'power', # Power, over-current, etc + 'safety', # Generic on=unsafe, off=safe + ] def setup(hass, config): @@ -47,3 +58,14 @@ class BinarySensorDevice(Entity): def friendly_state(self): """ Returns the friendly state of the binary sensor. """ return None + + @property + def sensor_class(self): + """ Returns the class of this sensor, from SENSOR_CASSES. """ + return None + + @property + def device_state_attributes(self): + return { + 'sensor_class': self.sensor_class, + } diff --git a/homeassistant/components/binary_sensor/demo.py b/homeassistant/components/binary_sensor/demo.py index 087d7405d9b..90c7accf512 100644 --- a/homeassistant/components/binary_sensor/demo.py +++ b/homeassistant/components/binary_sensor/demo.py @@ -9,17 +9,23 @@ from homeassistant.components.binary_sensor import BinarySensorDevice def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the Demo binary sensors. """ add_devices([ - DemoBinarySensor('Basement Floor Wet', False), - DemoBinarySensor('Movement Backyard', True), + DemoBinarySensor('Basement Floor Wet', False, 'moisture'), + DemoBinarySensor('Movement Backyard', True, 'motion'), ]) class DemoBinarySensor(BinarySensorDevice): """ A Demo binary sensor. """ - def __init__(self, name, state): + def __init__(self, name, state, sensor_class): self._name = name self._state = state + self._sensor_type = sensor_class + + @property + def sensor_class(self): + """ Return our class. """ + return self._sensor_type @property def should_poll(self): diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py index 0565bb5d857..e7294c039c8 100644 --- a/homeassistant/components/binary_sensor/nx584.py +++ b/homeassistant/components/binary_sensor/nx584.py @@ -12,7 +12,8 @@ import time import requests -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import (BinarySensorDevice, + SENSOR_CLASSES) REQUIREMENTS = ['pynx584==0.2'] _LOGGER = logging.getLogger(__name__) @@ -24,11 +25,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): host = config.get('host', 'localhost:5007') exclude = config.get('exclude_zones', []) + zone_types = config.get('zone_types', {}) if not all(isinstance(zone, int) for zone in exclude): _LOGGER.error('Invalid excluded zone specified (use zone number)') return False + if not all(isinstance(zone, int) and ztype in SENSOR_CLASSES + for zone, ztype in zone_types.items()): + _LOGGER.error('Invalid zone_types entry') + return False + try: client = nx584_client.Client('http://%s' % host) zones = client.list_zones() @@ -42,7 +49,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False zone_sensors = { - zone['number']: NX584ZoneSensor(zone) + zone['number']: NX584ZoneSensor( + zone, + zone_types.get(zone['number'], 'opening')) for zone in zones if zone['number'] not in exclude} if zone_sensors: @@ -58,8 +67,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NX584ZoneSensor(BinarySensorDevice): """Represents a NX584 zone as a sensor.""" - def __init__(self, zone): + def __init__(self, zone, zone_type): self._zone = zone + self._zone_type = zone_type + + @property + def sensor_class(self): + return self._zone_type @property def should_poll(self): diff --git a/tests/components/binary_sensor/test_binary_sensor.py b/tests/components/binary_sensor/test_binary_sensor.py new file mode 100644 index 00000000000..9c3f7f99e0f --- /dev/null +++ b/tests/components/binary_sensor/test_binary_sensor.py @@ -0,0 +1,36 @@ +""" +tests.components.binary_sensor.test_binary_sensor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Test the binary_sensor base class +""" +import unittest +from unittest import mock + +from homeassistant.components import binary_sensor +from homeassistant.const import STATE_ON, STATE_OFF + + +class TestBinarySensor(unittest.TestCase): + def test_state(self): + sensor = binary_sensor.BinarySensorDevice() + self.assertEqual(STATE_OFF, sensor.state) + with mock.patch('homeassistant.components.binary_sensor.' + 'BinarySensorDevice.is_on', + new=False): + self.assertEqual(STATE_OFF, + binary_sensor.BinarySensorDevice().state) + with mock.patch('homeassistant.components.binary_sensor.' + 'BinarySensorDevice.is_on', + new=True): + self.assertEqual(STATE_ON, + binary_sensor.BinarySensorDevice().state) + + def test_attributes(self): + sensor = binary_sensor.BinarySensorDevice() + self.assertEqual({'sensor_class': None}, + sensor.device_state_attributes) + with mock.patch('homeassistant.components.binary_sensor.' + 'BinarySensorDevice.sensor_class', + new='motion'): + self.assertEqual({'sensor_class': 'motion'}, + sensor.device_state_attributes) diff --git a/tests/components/binary_sensor/test_nx584.py b/tests/components/binary_sensor/test_nx584.py index 67dbe18e866..dcf35850044 100644 --- a/tests/components/binary_sensor/test_nx584.py +++ b/tests/components/binary_sensor/test_nx584.py @@ -41,7 +41,7 @@ class TestNX584SensorSetup(unittest.TestCase): hass = mock.MagicMock() self.assertTrue(nx584.setup_platform(hass, {}, add_devices)) mock_nx.assert_has_calls([ - mock.call(zone) + mock.call(zone, 'opening') for zone in self.fake_zones]) self.assertTrue(add_devices.called) nx584_client.Client.assert_called_once_with('http://localhost:5007') @@ -58,8 +58,8 @@ class TestNX584SensorSetup(unittest.TestCase): hass = mock.MagicMock() self.assertTrue(nx584.setup_platform(hass, config, add_devices)) mock_nx.assert_has_calls([ - mock.call(self.fake_zones[0]), - mock.call(self.fake_zones[2]), + mock.call(self.fake_zones[0], 'opening'), + mock.call(self.fake_zones[2], 'motion'), ]) self.assertTrue(add_devices.called) nx584_client.Client.assert_called_once_with('http://foo:123') @@ -74,6 +74,9 @@ class TestNX584SensorSetup(unittest.TestCase): def test_setup_bad_config(self): bad_configs = [ {'exclude_zones': ['a']}, + {'zone_types': {'a': 'b'}}, + {'zone_types': {1: 'notatype'}}, + {'zone_types': {'notazone': 'motion'}}, ] for config in bad_configs: self._test_assert_graceful_fail(config) @@ -98,7 +101,7 @@ class TestNX584SensorSetup(unittest.TestCase): class TestNX584ZoneSensor(unittest.TestCase): def test_sensor_normal(self): zone = {'number': 1, 'name': 'foo', 'state': True} - sensor = nx584.NX584ZoneSensor(zone) + sensor = nx584.NX584ZoneSensor(zone, 'motion') self.assertEqual('foo', sensor.name) self.assertFalse(sensor.should_poll) self.assertTrue(sensor.is_on) @@ -113,8 +116,8 @@ class TestNX584Watcher(unittest.TestCase): zone1 = {'number': 1, 'name': 'foo', 'state': True} zone2 = {'number': 2, 'name': 'bar', 'state': True} zones = { - 1: nx584.NX584ZoneSensor(zone1), - 2: nx584.NX584ZoneSensor(zone2), + 1: nx584.NX584ZoneSensor(zone1, 'motion'), + 2: nx584.NX584ZoneSensor(zone2, 'motion'), } watcher = nx584.NX584Watcher(None, zones) watcher._process_zone_event({'zone': 1, 'zone_state': False})