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:
parent
4f990ce488
commit
f2a2d6bfa1
7 changed files with 371 additions and 206 deletions
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
42
homeassistant/helpers/dispatcher.py
Normal file
42
homeassistant/helpers/dispatcher.py
Normal 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)
|
|
@ -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
|
||||||
|
|
103
tests/helpers/test_dispatcher.py
Normal file
103
tests/helpers/test_dispatcher.py
Normal 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']
|
Loading…
Add table
Add a link
Reference in a new issue