diff --git a/homeassistant/components/alarm_control_panel/envisalink.py b/homeassistant/components/alarm_control_panel/envisalink.py index 96b0fc83ea7..cd5bddbad49 100644 --- a/homeassistant/components/alarm_control_panel/envisalink.py +++ b/homeassistant/components/alarm_control_panel/envisalink.py @@ -4,16 +4,20 @@ Support for Envisalink-based alarm control panels (Honeywell/DSC). For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel.envisalink/ """ -from os import path +import asyncio import logging +import os + import voluptuous as vol +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.components.alarm_control_panel as alarm import homeassistant.helpers.config_validation as cv from homeassistant.config import load_yaml_config_file from homeassistant.components.envisalink import ( - EVL_CONTROLLER, EnvisalinkDevice, PARTITION_SCHEMA, CONF_CODE, CONF_PANIC, - CONF_PARTITIONNAME, SIGNAL_PARTITION_UPDATE, SIGNAL_KEYPAD_UPDATE) + DATA_EVL, EnvisalinkDevice, PARTITION_SCHEMA, CONF_CODE, CONF_PANIC, + CONF_PARTITIONNAME, SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, ATTR_ENTITY_ID) @@ -22,8 +26,6 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['envisalink'] -DEVICES = [] - SERVICE_ALARM_KEYPRESS = 'envisalink_alarm_keypress' ATTR_KEYPRESS = 'keypress' ALARM_KEYPRESS_SCHEMA = vol.Schema({ @@ -32,68 +34,72 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({ }) -def alarm_keypress_handler(service): - """Map services to methods on Alarm.""" - entity_ids = service.data.get(ATTR_ENTITY_ID) - keypress = service.data.get(ATTR_KEYPRESS) - - _target_devices = [device for device in DEVICES - if device.entity_id in entity_ids] - - for device in _target_devices: - EnvisalinkAlarm.alarm_keypress(device, keypress) - - -# pylint: disable=unused-argument -def setup_platform(hass, config, add_devices, discovery_info=None): +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Perform the setup for Envisalink alarm panels.""" - _configured_partitions = discovery_info['partitions'] - _code = discovery_info[CONF_CODE] - _panic_type = discovery_info[CONF_PANIC] - for part_num in _configured_partitions: - _device_config_data = PARTITION_SCHEMA( - _configured_partitions[part_num]) - _device = EnvisalinkAlarm( - part_num, - _device_config_data[CONF_PARTITIONNAME], - _code, - _panic_type, - EVL_CONTROLLER.alarm_state['partition'][part_num], - EVL_CONTROLLER) - DEVICES.append(_device) + configured_partitions = discovery_info['partitions'] + code = discovery_info[CONF_CODE] + panic_type = discovery_info[CONF_PANIC] - add_devices(DEVICES) + devices = [] + for part_num in configured_partitions: + device_config_data = PARTITION_SCHEMA(configured_partitions[part_num]) + device = EnvisalinkAlarm( + hass, + part_num, + device_config_data[CONF_PARTITIONNAME], + code, + panic_type, + hass.data[DATA_EVL].alarm_state['partition'][part_num], + hass.data[DATA_EVL] + ) + devices.append(device) + + yield from async_add_devices(devices) + + @callback + def alarm_keypress_handler(service): + """Map services to methods on Alarm.""" + entity_ids = service.data.get(ATTR_ENTITY_ID) + keypress = service.data.get(ATTR_KEYPRESS) + + target_devices = [device for device in devices + if device.entity_id in entity_ids] + + for device in target_devices: + device.async_alarm_keypress(keypress) # Register Envisalink specific services - descriptions = load_yaml_config_file( - path.join(path.dirname(__file__), 'services.yaml')) + descriptions = yield from hass.loop.run_in_executor( + None, load_yaml_config_file, os.path.join( + os.path.dirname(__file__), 'services.yaml')) + + hass.services.async_register( + alarm.DOMAIN, SERVICE_ALARM_KEYPRESS, alarm_keypress_handler, + descriptions.get(SERVICE_ALARM_KEYPRESS), schema=ALARM_KEYPRESS_SCHEMA) - hass.services.register(alarm.DOMAIN, SERVICE_ALARM_KEYPRESS, - alarm_keypress_handler, - descriptions.get(SERVICE_ALARM_KEYPRESS), - schema=ALARM_KEYPRESS_SCHEMA) return True class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel): """Representation of an Envisalink-based alarm panel.""" - def __init__(self, partition_number, alarm_name, code, panic_type, info, - controller): + def __init__(self, hass, partition_number, alarm_name, code, panic_type, + info, controller): """Initialize the alarm panel.""" - from pydispatch import dispatcher self._partition_number = partition_number self._code = code self._panic_type = panic_type - _LOGGER.debug("Setting up alarm: %s", alarm_name) - EnvisalinkDevice.__init__(self, alarm_name, info, controller) - dispatcher.connect( - self._update_callback, signal=SIGNAL_PARTITION_UPDATE, - sender=dispatcher.Any) - dispatcher.connect( - self._update_callback, signal=SIGNAL_KEYPAD_UPDATE, - sender=dispatcher.Any) + _LOGGER.debug("Setting up alarm: %s", alarm_name) + super().__init__(alarm_name, info, controller) + + async_dispatcher_connect( + hass, SIGNAL_KEYPAD_UPDATE, self._update_callback) + async_dispatcher_connect( + hass, SIGNAL_PARTITION_UPDATE, self._update_callback) + + @callback def _update_callback(self, partition): """Update HA state, if needed.""" if partition is None or int(partition) == self._partition_number: @@ -126,39 +132,44 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel): state = STATE_ALARM_DISARMED return state - def alarm_disarm(self, code=None): + @asyncio.coroutine + def async_alarm_disarm(self, code=None): """Send disarm command.""" if code: - EVL_CONTROLLER.disarm_partition(str(code), - self._partition_number) + self.hass.data[DATA_EVL].disarm_partition( + str(code), self._partition_number) else: - EVL_CONTROLLER.disarm_partition(str(self._code), - self._partition_number) + self.hass.data[DATA_EVL].disarm_partition( + str(self._code), self._partition_number) - def alarm_arm_home(self, code=None): + @asyncio.coroutine + def async_alarm_arm_home(self, code=None): """Send arm home command.""" if code: - EVL_CONTROLLER.arm_stay_partition(str(code), - self._partition_number) + self.hass.data[DATA_EVL].arm_stay_partition( + str(code), self._partition_number) else: - EVL_CONTROLLER.arm_stay_partition(str(self._code), - self._partition_number) + self.hass.data[DATA_EVL].arm_stay_partition( + str(self._code), self._partition_number) - def alarm_arm_away(self, code=None): + @asyncio.coroutine + def async_alarm_arm_away(self, code=None): """Send arm away command.""" if code: - EVL_CONTROLLER.arm_away_partition(str(code), - self._partition_number) + self.hass.data[DATA_EVL].arm_away_partition( + str(code), self._partition_number) else: - EVL_CONTROLLER.arm_away_partition(str(self._code), - self._partition_number) + self.hass.data[DATA_EVL].arm_away_partition( + str(self._code), self._partition_number) - def alarm_trigger(self, code=None): + @asyncio.coroutine + def async_alarm_trigger(self, code=None): """Alarm trigger command. Will be used to trigger a panic alarm.""" - EVL_CONTROLLER.panic_alarm(self._panic_type) + self.hass.data[DATA_EVL].panic_alarm(self._panic_type) - def alarm_keypress(self, keypress=None): + @callback + def async_alarm_keypress(self, keypress=None): """Send custom keypress.""" if keypress: - EVL_CONTROLLER.keypresses_to_partition(self._partition_number, - keypress) + self.hass.data[DATA_EVL].keypresses_to_partition( + self._partition_number, keypress) diff --git a/homeassistant/components/binary_sensor/envisalink.py b/homeassistant/components/binary_sensor/envisalink.py index 3d10736c9ee..279dadf120f 100644 --- a/homeassistant/components/binary_sensor/envisalink.py +++ b/homeassistant/components/binary_sensor/envisalink.py @@ -4,48 +4,56 @@ Support for Envisalink zone states- represented as binary sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.envisalink/ """ +import asyncio import logging + +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.envisalink import (EVL_CONTROLLER, - ZONE_SCHEMA, - CONF_ZONENAME, - CONF_ZONETYPE, - EnvisalinkDevice, - SIGNAL_ZONE_UPDATE) +from homeassistant.components.envisalink import ( + DATA_EVL, ZONE_SCHEMA, CONF_ZONENAME, CONF_ZONETYPE, EnvisalinkDevice, + SIGNAL_ZONE_UPDATE) from homeassistant.const import ATTR_LAST_TRIP_TIME DEPENDENCIES = ['envisalink'] _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup Envisalink binary sensor devices.""" - _configured_zones = discovery_info['zones'] - for zone_num in _configured_zones: - _device_config_data = ZONE_SCHEMA(_configured_zones[zone_num]) - _device = EnvisalinkBinarySensor( + configured_zones = discovery_info['zones'] + + devices = [] + for zone_num in configured_zones: + device_config_data = ZONE_SCHEMA(configured_zones[zone_num]) + device = EnvisalinkBinarySensor( + hass, zone_num, - _device_config_data[CONF_ZONENAME], - _device_config_data[CONF_ZONETYPE], - EVL_CONTROLLER.alarm_state['zone'][zone_num], - EVL_CONTROLLER) - add_devices_callback([_device]) + device_config_data[CONF_ZONENAME], + device_config_data[CONF_ZONETYPE], + hass.data[DATA_EVL].alarm_state['zone'][zone_num], + hass.data[DATA_EVL] + ) + devices.append(device) + + yield from async_add_devices(devices) class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): """Representation of an Envisalink binary sensor.""" - def __init__(self, zone_number, zone_name, zone_type, info, controller): + def __init__(self, hass, zone_number, zone_name, zone_type, info, + controller): """Initialize the binary_sensor.""" - from pydispatch import dispatcher self._zone_type = zone_type self._zone_number = zone_number _LOGGER.debug('Setting up zone: ' + zone_name) - EnvisalinkDevice.__init__(self, zone_name, info, controller) - dispatcher.connect(self._update_callback, - signal=SIGNAL_ZONE_UPDATE, - sender=dispatcher.Any) + super().__init__(zone_name, info, controller) + + async_dispatcher_connect( + hass, SIGNAL_ZONE_UPDATE, self._update_callback) @property def device_state_attributes(self): @@ -64,7 +72,8 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): """Return the class of this sensor, from DEVICE_CLASSES.""" return self._zone_type + @callback def _update_callback(self, zone): """Update the zone's state, if needed.""" if zone is None or int(zone) == self._zone_number: - self.hass.schedule_update_ha_state() + self.hass.async_add_job(self.async_update_ha_state()) diff --git a/homeassistant/components/envisalink.py b/homeassistant/components/envisalink.py index 2c101a227cf..05439213284 100644 --- a/homeassistant/components/envisalink.py +++ b/homeassistant/components/envisalink.py @@ -4,20 +4,24 @@ Support for Envisalink devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/envisalink/ """ +import asyncio import logging -import time + import voluptuous as vol + +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.entity import Entity -from homeassistant.components.discovery import load_platform +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.dispatcher import async_dispatcher_send -REQUIREMENTS = ['pyenvisalink==2.0', 'pydispatcher==2.0.5'] +REQUIREMENTS = ['pyenvisalink==2.0'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'envisalink' -EVL_CONTROLLER = None +DATA_EVL = 'envisalink' CONF_EVL_HOST = 'host' CONF_EVL_PORT = 'port' @@ -43,9 +47,9 @@ DEFAULT_ZONEDUMP_INTERVAL = 30 DEFAULT_ZONETYPE = 'opening' DEFAULT_PANIC = 'Police' -SIGNAL_ZONE_UPDATE = 'zones_updated' -SIGNAL_PARTITION_UPDATE = 'partition_updated' -SIGNAL_KEYPAD_UPDATE = 'keypad_updated' +SIGNAL_ZONE_UPDATE = 'envisalink.zones_updated' +SIGNAL_PARTITION_UPDATE = 'envisalink.partition_updated' +SIGNAL_KEYPAD_UPDATE = 'envisalink.keypad_updated' ZONE_SCHEMA = vol.Schema({ vol.Required(CONF_ZONENAME): cv.string, @@ -77,119 +81,111 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -# pylint: disable=unused-argument -def setup(hass, base_config): +@asyncio.coroutine +def async_setup(hass, config): """Common setup for Envisalink devices.""" from pyenvisalink import EnvisalinkAlarmPanel - from pydispatch import dispatcher - global EVL_CONTROLLER + conf = config.get(DOMAIN) - config = base_config.get(DOMAIN) + host = conf.get(CONF_EVL_HOST) + port = conf.get(CONF_EVL_PORT) + code = conf.get(CONF_CODE) + panel_type = conf.get(CONF_PANEL_TYPE) + panic_type = conf.get(CONF_PANIC) + version = conf.get(CONF_EVL_VERSION) + user = conf.get(CONF_USERNAME) + password = conf.get(CONF_PASS) + keep_alive = conf.get(CONF_EVL_KEEPALIVE) + zone_dump = conf.get(CONF_ZONEDUMP_INTERVAL) + zones = conf.get(CONF_ZONES) + partitions = conf.get(CONF_PARTITIONS) + sync_connect = asyncio.Future(loop=hass.loop) - _host = config.get(CONF_EVL_HOST) - _port = config.get(CONF_EVL_PORT) - _code = config.get(CONF_CODE) - _panel_type = config.get(CONF_PANEL_TYPE) - _panic_type = config.get(CONF_PANIC) - _version = config.get(CONF_EVL_VERSION) - _user = config.get(CONF_USERNAME) - _pass = config.get(CONF_PASS) - _keep_alive = config.get(CONF_EVL_KEEPALIVE) - _zone_dump = config.get(CONF_ZONEDUMP_INTERVAL) - _zones = config.get(CONF_ZONES) - _partitions = config.get(CONF_PARTITIONS) - _connect_status = {} - EVL_CONTROLLER = EnvisalinkAlarmPanel(_host, - _port, - _panel_type, - _version, - _user, - _pass, - _zone_dump, - _keep_alive, - hass.loop) + controller = EnvisalinkAlarmPanel( + host, port, panel_type, version, user, password, zone_dump, + keep_alive, hass.loop) + hass.data[DATA_EVL] = controller + @callback def login_fail_callback(data): """Callback for when the evl rejects our login.""" _LOGGER.error("The envisalink rejected your credentials.") - _connect_status['fail'] = 1 + sync_connect.set_result(False) + @callback def connection_fail_callback(data): """Network failure callback.""" _LOGGER.error("Could not establish a connection with the envisalink.") - _connect_status['fail'] = 1 + sync_connect.set_result(False) + @callback def connection_success_callback(data): """Callback for a successful connection.""" _LOGGER.info("Established a connection with the envisalink.") - _connect_status['success'] = 1 + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink) + sync_connect.set_result(True) + @callback def zones_updated_callback(data): """Handle zone timer updates.""" _LOGGER.info("Envisalink sent a zone update event. Updating zones...") - dispatcher.send(signal=SIGNAL_ZONE_UPDATE, - sender=None, - zone=data) + async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data) + @callback def alarm_data_updated_callback(data): """Handle non-alarm based info updates.""" _LOGGER.info("Envisalink sent new alarm info. Updating alarms...") - dispatcher.send(signal=SIGNAL_KEYPAD_UPDATE, - sender=None, - partition=data) + async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data) + @callback def partition_updated_callback(data): """Handle partition changes thrown by evl (including alarms).""" _LOGGER.info("The envisalink sent a partition update event.") - dispatcher.send(signal=SIGNAL_PARTITION_UPDATE, - sender=None, - partition=data) + async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data) + @callback def stop_envisalink(event): """Shutdown envisalink connection and thread on exit.""" _LOGGER.info("Shutting down envisalink.") - EVL_CONTROLLER.stop() + controller.stop() - def start_envisalink(event): - """Startup process for the Envisalink.""" - hass.loop.call_soon_threadsafe(EVL_CONTROLLER.start) - for _ in range(10): - if 'success' in _connect_status: - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink) - return True - elif 'fail' in _connect_status: - return False - else: - time.sleep(1) + controller.callback_zone_timer_dump = zones_updated_callback + controller.callback_zone_state_change = zones_updated_callback + controller.callback_partition_state_change = partition_updated_callback + controller.callback_keypad_update = alarm_data_updated_callback + controller.callback_login_failure = login_fail_callback + controller.callback_login_timeout = connection_fail_callback + controller.callback_login_success = connection_success_callback - _LOGGER.error("Timeout occurred while establishing evl connection.") - return False + _LOGGER.info("Start envisalink.") + controller.start() - EVL_CONTROLLER.callback_zone_timer_dump = zones_updated_callback - EVL_CONTROLLER.callback_zone_state_change = zones_updated_callback - EVL_CONTROLLER.callback_partition_state_change = partition_updated_callback - EVL_CONTROLLER.callback_keypad_update = alarm_data_updated_callback - EVL_CONTROLLER.callback_login_failure = login_fail_callback - EVL_CONTROLLER.callback_login_timeout = connection_fail_callback - EVL_CONTROLLER.callback_login_success = connection_success_callback - - _result = start_envisalink(None) - if not _result: + result = yield from sync_connect + if not result: return False # Load sub-components for Envisalink - if _partitions: - load_platform(hass, 'alarm_control_panel', 'envisalink', - {CONF_PARTITIONS: _partitions, - CONF_CODE: _code, - CONF_PANIC: _panic_type}, base_config) - load_platform(hass, 'sensor', 'envisalink', - {CONF_PARTITIONS: _partitions, - CONF_CODE: _code}, base_config) - if _zones: - load_platform(hass, 'binary_sensor', 'envisalink', - {CONF_ZONES: _zones}, base_config) + if partitions: + hass.async_add_job(async_load_platform( + hass, 'alarm_control_panel', 'envisalink', { + CONF_PARTITIONS: partitions, + CONF_CODE: code, + CONF_PANIC: panic_type + }, config + )) + hass.async_add_job(async_load_platform( + hass, 'sensor', 'envisalink', { + CONF_PARTITIONS: partitions, + CONF_CODE: code + }, config + )) + if zones: + hass.async_add_job(async_load_platform( + hass, 'binary_sensor', 'envisalink', { + CONF_ZONES: zones + }, config + )) return True diff --git a/homeassistant/components/sensor/envisalink.py b/homeassistant/components/sensor/envisalink.py index a29179598a8..20142c13c3b 100644 --- a/homeassistant/components/sensor/envisalink.py +++ b/homeassistant/components/sensor/envisalink.py @@ -4,51 +4,55 @@ Support for Envisalink sensors (shows panel info). For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.envisalink/ """ +import asyncio import logging -from homeassistant.components.envisalink import (EVL_CONTROLLER, - PARTITION_SCHEMA, - CONF_PARTITIONNAME, - EnvisalinkDevice, - SIGNAL_PARTITION_UPDATE, - SIGNAL_KEYPAD_UPDATE) + +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.components.envisalink import ( + DATA_EVL, PARTITION_SCHEMA, CONF_PARTITIONNAME, EnvisalinkDevice, + SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE) +from homeassistant.helpers.entity import Entity DEPENDENCIES = ['envisalink'] _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Perform the setup for Envisalink sensor devices.""" - _configured_partitions = discovery_info['partitions'] - for part_num in _configured_partitions: - _device_config_data = PARTITION_SCHEMA( - _configured_partitions[part_num]) - _device = EnvisalinkSensor( - _device_config_data[CONF_PARTITIONNAME], + configured_partitions = discovery_info['partitions'] + + devices = [] + for part_num in configured_partitions: + device_config_data = PARTITION_SCHEMA(configured_partitions[part_num]) + device = EnvisalinkSensor( + hass, + device_config_data[CONF_PARTITIONNAME], part_num, - EVL_CONTROLLER.alarm_state['partition'][part_num], - EVL_CONTROLLER) - add_devices_callback([_device]) + hass.data[DATA_EVL].alarm_state['partition'][part_num], + hass.data[DATA_EVL]) + devices.append(device) + + yield from async_add_devices(devices) -class EnvisalinkSensor(EnvisalinkDevice): +class EnvisalinkSensor(EnvisalinkDevice, Entity): """Representation of an Envisalink keypad.""" - def __init__(self, partition_name, partition_number, info, controller): + def __init__(self, hass, partition_name, partition_number, info, + controller): """Initialize the sensor.""" - from pydispatch import dispatcher self._icon = 'mdi:alarm' self._partition_number = partition_number + _LOGGER.debug('Setting up sensor for partition: ' + partition_name) - EnvisalinkDevice.__init__(self, - partition_name + ' Keypad', - info, - controller) - dispatcher.connect(self._update_callback, - signal=SIGNAL_PARTITION_UPDATE, - sender=dispatcher.Any) - dispatcher.connect(self._update_callback, - signal=SIGNAL_KEYPAD_UPDATE, - sender=dispatcher.Any) + super().__init__(partition_name + ' Keypad', info, controller) + + async_dispatcher_connect( + hass, SIGNAL_KEYPAD_UPDATE, self._update_callback) + async_dispatcher_connect( + hass, SIGNAL_PARTITION_UPDATE, self._update_callback) @property def icon(self): @@ -65,7 +69,8 @@ class EnvisalinkSensor(EnvisalinkDevice): """Return the state attributes.""" return self._info['status'] + @callback def _update_callback(self, partition): """Update the partition state in HA, if needed.""" if partition is None or int(partition) == self._partition_number: - self.hass.schedule_update_ha_state() + self.hass.async_add_job(self.async_update_ha_state()) diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py new file mode 100644 index 00000000000..324d4ccc621 --- /dev/null +++ b/homeassistant/helpers/dispatcher.py @@ -0,0 +1,42 @@ +"""Helpers for hass dispatcher & internal component / platform.""" + +from homeassistant.core import callback + +DATA_DISPATCHER = 'dispatcher' + + +def dispatcher_connect(hass, signal, target): + """Connect a callable function to a singal.""" + hass.add_job(async_dispatcher_connect, hass, signal, target) + + +@callback +def async_dispatcher_connect(hass, signal, target): + """Connect a callable function to a singal. + + This method must be run in the event loop. + """ + if DATA_DISPATCHER not in hass.data: + hass.data[DATA_DISPATCHER] = {} + + if signal not in hass.data[DATA_DISPATCHER]: + hass.data[DATA_DISPATCHER][signal] = [] + + hass.data[DATA_DISPATCHER][signal].append(target) + + +def dispatcher_send(hass, signal, *args): + """Send signal and data.""" + hass.add_job(async_dispatcher_send, hass, signal, *args) + + +@callback +def async_dispatcher_send(hass, signal, *args): + """Send signal and data. + + This method must be run in the event loop. + """ + target_list = hass.data.get(DATA_DISPATCHER, {}).get(signal, []) + + for target in target_list: + hass.async_add_job(target, *args) diff --git a/requirements_all.txt b/requirements_all.txt index 4577801a07d..35931779e87 100755 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -457,7 +457,6 @@ pycmus==0.1.0 # homeassistant.components.sensor.cups # pycups==1.9.73 -# homeassistant.components.envisalink # homeassistant.components.zwave # homeassistant.components.binary_sensor.hikvision pydispatcher==2.0.5 diff --git a/tests/helpers/test_dispatcher.py b/tests/helpers/test_dispatcher.py new file mode 100644 index 00000000000..fbac0689ff1 --- /dev/null +++ b/tests/helpers/test_dispatcher.py @@ -0,0 +1,103 @@ +"""Test dispatcher helpers.""" +import asyncio + +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import ( + dispatcher_send, dispatcher_connect) + +from tests.common import get_test_home_assistant + + +class TestHelpersDispatcher(object): + """Tests for discovery helper methods.""" + + def setup_method(self, method): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def teardown_method(self, method): + """Stop everything that was started.""" + self.hass.stop() + + def test_simple_function(self): + """Test simple function (executor).""" + calls = [] + + def test_funct(data): + """Test function.""" + calls.append(data) + + dispatcher_connect(self.hass, 'test', test_funct) + self.hass.block_till_done() + + dispatcher_send(self.hass, 'test', 3) + self.hass.block_till_done() + + assert calls == [3] + + dispatcher_send(self.hass, 'test', 'bla') + self.hass.block_till_done() + + assert calls == [3, 'bla'] + + def test_simple_callback(self): + """Test simple callback (async).""" + calls = [] + + @callback + def test_funct(data): + """Test function.""" + calls.append(data) + + dispatcher_connect(self.hass, 'test', test_funct) + self.hass.block_till_done() + + dispatcher_send(self.hass, 'test', 3) + self.hass.block_till_done() + + assert calls == [3] + + dispatcher_send(self.hass, 'test', 'bla') + self.hass.block_till_done() + + assert calls == [3, 'bla'] + + def test_simple_coro(self): + """Test simple coro (async).""" + calls = [] + + @asyncio.coroutine + def test_funct(data): + """Test function.""" + calls.append(data) + + dispatcher_connect(self.hass, 'test', test_funct) + self.hass.block_till_done() + + dispatcher_send(self.hass, 'test', 3) + self.hass.block_till_done() + + assert calls == [3] + + dispatcher_send(self.hass, 'test', 'bla') + self.hass.block_till_done() + + assert calls == [3, 'bla'] + + def test_simple_function_multiargs(self): + """Test simple function (executor).""" + calls = [] + + def test_funct(data1, data2, data3): + """Test function.""" + calls.append(data1) + calls.append(data2) + calls.append(data3) + + dispatcher_connect(self.hass, 'test', test_funct) + self.hass.block_till_done() + + dispatcher_send(self.hass, 'test', 3, 2, 'bla') + self.hass.block_till_done() + + assert calls == [3, 2, 'bla']