Refactory of envisalink (#6160)

* Refactory of envisalink

* remove event buss

* init dispatcher from hass.

* Move platform to new dispatcher

* fix lint

* add unittest & threadded functions

* fix copy & past error
This commit is contained in:
Pascal Vizeli 2017-02-23 22:02:56 +01:00 committed by Paulus Schoutsen
parent 4f990ce488
commit f2a2d6bfa1
7 changed files with 371 additions and 206 deletions

View file

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.envisalink/ https://home-assistant.io/components/alarm_control_panel.envisalink/
""" """
from os import path import asyncio
import logging import logging
import os
import voluptuous as vol 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.components.alarm_control_panel as alarm
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.components.envisalink import ( from homeassistant.components.envisalink import (
EVL_CONTROLLER, EnvisalinkDevice, PARTITION_SCHEMA, CONF_CODE, CONF_PANIC, DATA_EVL, EnvisalinkDevice, PARTITION_SCHEMA, CONF_CODE, CONF_PANIC,
CONF_PARTITIONNAME, SIGNAL_PARTITION_UPDATE, SIGNAL_KEYPAD_UPDATE) CONF_PARTITIONNAME, SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE)
from homeassistant.const import ( from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, ATTR_ENTITY_ID) STATE_UNKNOWN, STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, ATTR_ENTITY_ID)
@ -22,8 +26,6 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['envisalink'] DEPENDENCIES = ['envisalink']
DEVICES = []
SERVICE_ALARM_KEYPRESS = 'envisalink_alarm_keypress' SERVICE_ALARM_KEYPRESS = 'envisalink_alarm_keypress'
ATTR_KEYPRESS = 'keypress' ATTR_KEYPRESS = 'keypress'
ALARM_KEYPRESS_SCHEMA = vol.Schema({ ALARM_KEYPRESS_SCHEMA = vol.Schema({
@ -32,68 +34,72 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({
}) })
def alarm_keypress_handler(service): @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]
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.""" """Map services to methods on Alarm."""
entity_ids = service.data.get(ATTR_ENTITY_ID) entity_ids = service.data.get(ATTR_ENTITY_ID)
keypress = service.data.get(ATTR_KEYPRESS) keypress = service.data.get(ATTR_KEYPRESS)
_target_devices = [device for device in DEVICES target_devices = [device for device in devices
if device.entity_id in entity_ids] if device.entity_id in entity_ids]
for device in _target_devices: for device in target_devices:
EnvisalinkAlarm.alarm_keypress(device, keypress) device.async_alarm_keypress(keypress)
# pylint: disable=unused-argument
def setup_platform(hass, config, 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)
add_devices(DEVICES)
# Register Envisalink specific services # Register Envisalink specific services
descriptions = load_yaml_config_file( descriptions = yield from hass.loop.run_in_executor(
path.join(path.dirname(__file__), 'services.yaml')) 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 return True
class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel): class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Representation of an Envisalink-based alarm panel.""" """Representation of an Envisalink-based alarm panel."""
def __init__(self, partition_number, alarm_name, code, panic_type, info, def __init__(self, hass, partition_number, alarm_name, code, panic_type,
controller): info, controller):
"""Initialize the alarm panel.""" """Initialize the alarm panel."""
from pydispatch import dispatcher
self._partition_number = partition_number self._partition_number = partition_number
self._code = code self._code = code
self._panic_type = panic_type 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): def _update_callback(self, partition):
"""Update HA state, if needed.""" """Update HA state, if needed."""
if partition is None or int(partition) == self._partition_number: if partition is None or int(partition) == self._partition_number:
@ -126,39 +132,44 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
state = STATE_ALARM_DISARMED state = STATE_ALARM_DISARMED
return state return state
def alarm_disarm(self, code=None): @asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
if code: if code:
EVL_CONTROLLER.disarm_partition(str(code), self.hass.data[DATA_EVL].disarm_partition(
self._partition_number) str(code), self._partition_number)
else: else:
EVL_CONTROLLER.disarm_partition(str(self._code), self.hass.data[DATA_EVL].disarm_partition(
self._partition_number) 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.""" """Send arm home command."""
if code: if code:
EVL_CONTROLLER.arm_stay_partition(str(code), self.hass.data[DATA_EVL].arm_stay_partition(
self._partition_number) str(code), self._partition_number)
else: else:
EVL_CONTROLLER.arm_stay_partition(str(self._code), self.hass.data[DATA_EVL].arm_stay_partition(
self._partition_number) 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.""" """Send arm away command."""
if code: if code:
EVL_CONTROLLER.arm_away_partition(str(code), self.hass.data[DATA_EVL].arm_away_partition(
self._partition_number) str(code), self._partition_number)
else: else:
EVL_CONTROLLER.arm_away_partition(str(self._code), self.hass.data[DATA_EVL].arm_away_partition(
self._partition_number) 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.""" """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.""" """Send custom keypress."""
if keypress: if keypress:
EVL_CONTROLLER.keypresses_to_partition(self._partition_number, self.hass.data[DATA_EVL].keypresses_to_partition(
keypress) self._partition_number, keypress)

View file

@ -4,13 +4,14 @@ Support for Envisalink zone states- represented as binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.envisalink/ https://home-assistant.io/components/binary_sensor.envisalink/
""" """
import asyncio
import logging 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.binary_sensor import BinarySensorDevice
from homeassistant.components.envisalink import (EVL_CONTROLLER, from homeassistant.components.envisalink import (
ZONE_SCHEMA, DATA_EVL, ZONE_SCHEMA, CONF_ZONENAME, CONF_ZONETYPE, EnvisalinkDevice,
CONF_ZONENAME,
CONF_ZONETYPE,
EnvisalinkDevice,
SIGNAL_ZONE_UPDATE) SIGNAL_ZONE_UPDATE)
from homeassistant.const import ATTR_LAST_TRIP_TIME from homeassistant.const import ATTR_LAST_TRIP_TIME
@ -18,34 +19,41 @@ DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__) _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.""" """Setup Envisalink binary sensor devices."""
_configured_zones = discovery_info['zones'] configured_zones = discovery_info['zones']
for zone_num in _configured_zones:
_device_config_data = ZONE_SCHEMA(_configured_zones[zone_num]) devices = []
_device = EnvisalinkBinarySensor( for zone_num in configured_zones:
device_config_data = ZONE_SCHEMA(configured_zones[zone_num])
device = EnvisalinkBinarySensor(
hass,
zone_num, zone_num,
_device_config_data[CONF_ZONENAME], device_config_data[CONF_ZONENAME],
_device_config_data[CONF_ZONETYPE], device_config_data[CONF_ZONETYPE],
EVL_CONTROLLER.alarm_state['zone'][zone_num], hass.data[DATA_EVL].alarm_state['zone'][zone_num],
EVL_CONTROLLER) hass.data[DATA_EVL]
add_devices_callback([_device]) )
devices.append(device)
yield from async_add_devices(devices)
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
"""Representation of an Envisalink binary sensor.""" """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.""" """Initialize the binary_sensor."""
from pydispatch import dispatcher
self._zone_type = zone_type self._zone_type = zone_type
self._zone_number = zone_number self._zone_number = zone_number
_LOGGER.debug('Setting up zone: ' + zone_name) _LOGGER.debug('Setting up zone: ' + zone_name)
EnvisalinkDevice.__init__(self, zone_name, info, controller) super().__init__(zone_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_ZONE_UPDATE, async_dispatcher_connect(
sender=dispatcher.Any) hass, SIGNAL_ZONE_UPDATE, self._update_callback)
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@ -64,7 +72,8 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
"""Return the class of this sensor, from DEVICE_CLASSES.""" """Return the class of this sensor, from DEVICE_CLASSES."""
return self._zone_type return self._zone_type
@callback
def _update_callback(self, zone): def _update_callback(self, zone):
"""Update the zone's state, if needed.""" """Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number: 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())

View file

@ -4,20 +4,24 @@ Support for Envisalink devices.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/envisalink/ https://home-assistant.io/components/envisalink/
""" """
import asyncio
import logging import logging
import time
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity 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__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink' DOMAIN = 'envisalink'
EVL_CONTROLLER = None DATA_EVL = 'envisalink'
CONF_EVL_HOST = 'host' CONF_EVL_HOST = 'host'
CONF_EVL_PORT = 'port' CONF_EVL_PORT = 'port'
@ -43,9 +47,9 @@ DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening' DEFAULT_ZONETYPE = 'opening'
DEFAULT_PANIC = 'Police' DEFAULT_PANIC = 'Police'
SIGNAL_ZONE_UPDATE = 'zones_updated' SIGNAL_ZONE_UPDATE = 'envisalink.zones_updated'
SIGNAL_PARTITION_UPDATE = 'partition_updated' SIGNAL_PARTITION_UPDATE = 'envisalink.partition_updated'
SIGNAL_KEYPAD_UPDATE = 'keypad_updated' SIGNAL_KEYPAD_UPDATE = 'envisalink.keypad_updated'
ZONE_SCHEMA = vol.Schema({ ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONENAME): cv.string, vol.Required(CONF_ZONENAME): cv.string,
@ -77,119 +81,111 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument @asyncio.coroutine
def setup(hass, base_config): def async_setup(hass, config):
"""Common setup for Envisalink devices.""" """Common setup for Envisalink devices."""
from pyenvisalink import EnvisalinkAlarmPanel 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) controller = EnvisalinkAlarmPanel(
_port = config.get(CONF_EVL_PORT) host, port, panel_type, version, user, password, zone_dump,
_code = config.get(CONF_CODE) keep_alive, hass.loop)
_panel_type = config.get(CONF_PANEL_TYPE) hass.data[DATA_EVL] = controller
_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)
@callback
def login_fail_callback(data): def login_fail_callback(data):
"""Callback for when the evl rejects our login.""" """Callback for when the evl rejects our login."""
_LOGGER.error("The envisalink rejected your credentials.") _LOGGER.error("The envisalink rejected your credentials.")
_connect_status['fail'] = 1 sync_connect.set_result(False)
@callback
def connection_fail_callback(data): def connection_fail_callback(data):
"""Network failure callback.""" """Network failure callback."""
_LOGGER.error("Could not establish a connection with the envisalink.") _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): def connection_success_callback(data):
"""Callback for a successful connection.""" """Callback for a successful connection."""
_LOGGER.info("Established a connection with the envisalink.") _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): def zones_updated_callback(data):
"""Handle zone timer updates.""" """Handle zone timer updates."""
_LOGGER.info("Envisalink sent a zone update event. Updating zones...") _LOGGER.info("Envisalink sent a zone update event. Updating zones...")
dispatcher.send(signal=SIGNAL_ZONE_UPDATE, async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data)
sender=None,
zone=data)
@callback
def alarm_data_updated_callback(data): def alarm_data_updated_callback(data):
"""Handle non-alarm based info updates.""" """Handle non-alarm based info updates."""
_LOGGER.info("Envisalink sent new alarm info. Updating alarms...") _LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
dispatcher.send(signal=SIGNAL_KEYPAD_UPDATE, async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data)
sender=None,
partition=data)
@callback
def partition_updated_callback(data): def partition_updated_callback(data):
"""Handle partition changes thrown by evl (including alarms).""" """Handle partition changes thrown by evl (including alarms)."""
_LOGGER.info("The envisalink sent a partition update event.") _LOGGER.info("The envisalink sent a partition update event.")
dispatcher.send(signal=SIGNAL_PARTITION_UPDATE, async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data)
sender=None,
partition=data)
@callback
def stop_envisalink(event): def stop_envisalink(event):
"""Shutdown envisalink connection and thread on exit.""" """Shutdown envisalink connection and thread on exit."""
_LOGGER.info("Shutting down envisalink.") _LOGGER.info("Shutting down envisalink.")
EVL_CONTROLLER.stop() controller.stop()
def start_envisalink(event): controller.callback_zone_timer_dump = zones_updated_callback
"""Startup process for the Envisalink.""" controller.callback_zone_state_change = zones_updated_callback
hass.loop.call_soon_threadsafe(EVL_CONTROLLER.start) controller.callback_partition_state_change = partition_updated_callback
for _ in range(10): controller.callback_keypad_update = alarm_data_updated_callback
if 'success' in _connect_status: controller.callback_login_failure = login_fail_callback
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink) controller.callback_login_timeout = connection_fail_callback
return True controller.callback_login_success = connection_success_callback
elif 'fail' in _connect_status:
return False
else:
time.sleep(1)
_LOGGER.error("Timeout occurred while establishing evl connection.") _LOGGER.info("Start envisalink.")
return False controller.start()
EVL_CONTROLLER.callback_zone_timer_dump = zones_updated_callback result = yield from sync_connect
EVL_CONTROLLER.callback_zone_state_change = zones_updated_callback if not result:
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:
return False return False
# Load sub-components for Envisalink # Load sub-components for Envisalink
if _partitions: if partitions:
load_platform(hass, 'alarm_control_panel', 'envisalink', hass.async_add_job(async_load_platform(
{CONF_PARTITIONS: _partitions, hass, 'alarm_control_panel', 'envisalink', {
CONF_CODE: _code, CONF_PARTITIONS: partitions,
CONF_PANIC: _panic_type}, base_config) CONF_CODE: code,
load_platform(hass, 'sensor', 'envisalink', CONF_PANIC: panic_type
{CONF_PARTITIONS: _partitions, }, config
CONF_CODE: _code}, base_config) ))
if _zones: hass.async_add_job(async_load_platform(
load_platform(hass, 'binary_sensor', 'envisalink', hass, 'sensor', 'envisalink', {
{CONF_ZONES: _zones}, base_config) 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 return True

View file

@ -4,51 +4,55 @@ Support for Envisalink sensors (shows panel info).
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.envisalink/ https://home-assistant.io/components/sensor.envisalink/
""" """
import asyncio
import logging import logging
from homeassistant.components.envisalink import (EVL_CONTROLLER,
PARTITION_SCHEMA, from homeassistant.core import callback
CONF_PARTITIONNAME, from homeassistant.helpers.dispatcher import async_dispatcher_connect
EnvisalinkDevice, from homeassistant.components.envisalink import (
SIGNAL_PARTITION_UPDATE, DATA_EVL, PARTITION_SCHEMA, CONF_PARTITIONNAME, EnvisalinkDevice,
SIGNAL_KEYPAD_UPDATE) SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE)
from homeassistant.helpers.entity import Entity
DEPENDENCIES = ['envisalink'] DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__) _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.""" """Perform the setup for Envisalink sensor devices."""
_configured_partitions = discovery_info['partitions'] configured_partitions = discovery_info['partitions']
for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA( devices = []
_configured_partitions[part_num]) for part_num in configured_partitions:
_device = EnvisalinkSensor( device_config_data = PARTITION_SCHEMA(configured_partitions[part_num])
_device_config_data[CONF_PARTITIONNAME], device = EnvisalinkSensor(
hass,
device_config_data[CONF_PARTITIONNAME],
part_num, part_num,
EVL_CONTROLLER.alarm_state['partition'][part_num], hass.data[DATA_EVL].alarm_state['partition'][part_num],
EVL_CONTROLLER) hass.data[DATA_EVL])
add_devices_callback([_device]) devices.append(device)
yield from async_add_devices(devices)
class EnvisalinkSensor(EnvisalinkDevice): class EnvisalinkSensor(EnvisalinkDevice, Entity):
"""Representation of an Envisalink keypad.""" """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.""" """Initialize the sensor."""
from pydispatch import dispatcher
self._icon = 'mdi:alarm' self._icon = 'mdi:alarm'
self._partition_number = partition_number self._partition_number = partition_number
_LOGGER.debug('Setting up sensor for partition: ' + partition_name) _LOGGER.debug('Setting up sensor for partition: ' + partition_name)
EnvisalinkDevice.__init__(self, super().__init__(partition_name + ' Keypad', info, controller)
partition_name + ' Keypad',
info, async_dispatcher_connect(
controller) hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
dispatcher.connect(self._update_callback, async_dispatcher_connect(
signal=SIGNAL_PARTITION_UPDATE, hass, SIGNAL_PARTITION_UPDATE, self._update_callback)
sender=dispatcher.Any)
dispatcher.connect(self._update_callback,
signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
@property @property
def icon(self): def icon(self):
@ -65,7 +69,8 @@ class EnvisalinkSensor(EnvisalinkDevice):
"""Return the state attributes.""" """Return the state attributes."""
return self._info['status'] return self._info['status']
@callback
def _update_callback(self, partition): def _update_callback(self, partition):
"""Update the partition state in HA, if needed.""" """Update the partition state in HA, if needed."""
if partition is None or int(partition) == self._partition_number: 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())

View file

@ -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)

View file

@ -457,7 +457,6 @@ pycmus==0.1.0
# homeassistant.components.sensor.cups # homeassistant.components.sensor.cups
# pycups==1.9.73 # pycups==1.9.73
# homeassistant.components.envisalink
# homeassistant.components.zwave # homeassistant.components.zwave
# homeassistant.components.binary_sensor.hikvision # homeassistant.components.binary_sensor.hikvision
pydispatcher==2.0.5 pydispatcher==2.0.5

View file

@ -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']