""" Support for exposing nx584 elements as sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.nx584/ """ import logging import threading import time import requests from homeassistant.components.binary_sensor import ( SENSOR_CLASSES, BinarySensorDevice) REQUIREMENTS = ['pynx584==0.2'] _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup nx584 binary sensor platform.""" from nx584 import client as nx584_client 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() except requests.exceptions.ConnectionError as ex: _LOGGER.error('Unable to connect to NX584: %s', str(ex)) return False version = [int(v) for v in client.get_version().split('.')] if version < [1, 1]: _LOGGER.error('NX584 is too old to use for sensors (>=0.2 required)') return False zone_sensors = { zone['number']: NX584ZoneSensor( zone, zone_types.get(zone['number'], 'opening')) for zone in zones if zone['number'] not in exclude} if zone_sensors: add_devices(zone_sensors.values()) watcher = NX584Watcher(client, zone_sensors) watcher.start() else: _LOGGER.warning('No zones found on NX584') return True class NX584ZoneSensor(BinarySensorDevice): """Represents a NX584 zone as a sensor.""" def __init__(self, zone, zone_type): """Initialize the nx594 binary sensor.""" self._zone = zone self._zone_type = zone_type @property def sensor_class(self): """Return the class of this sensor, from SENSOR_CLASSES.""" return self._zone_type @property def should_poll(self): """No polling needed.""" return False @property def name(self): """Return the name of the binary sensor.""" return self._zone['name'] @property def is_on(self): """Return true if the binary sensor is on.""" # True means "faulted" or "open" or "abnormal state" return self._zone['state'] class NX584Watcher(threading.Thread): """Event listener thread to process NX584 events.""" def __init__(self, client, zone_sensors): """Initialize nx584 watcher thread.""" super(NX584Watcher, self).__init__() self.daemon = True self._client = client self._zone_sensors = zone_sensors def _process_zone_event(self, event): zone = event['zone'] zone_sensor = self._zone_sensors.get(zone) # pylint: disable=protected-access if not zone_sensor: return zone_sensor._zone['state'] = event['zone_state'] zone_sensor.update_ha_state() def _process_events(self, events): for event in events: if event.get('type') == 'zone_status': self._process_zone_event(event) def _run(self): """Throw away any existing events so we don't replay history.""" self._client.get_events() while True: events = self._client.get_events() if events: self._process_events(events) def run(self): """Run the watcher.""" while True: try: self._run() except requests.exceptions.ConnectionError: _LOGGER.error('Failed to reach NX584 server') time.sleep(10)