* Add keypress & output control services to Envisalink component Add services to allow sending custom keypresses and activating programmable outputs on an alarm control panel. Implemented for the Envisalink alarm, and moving to new version of pyenvisalink to support this. Replicated the service handler mapping code from Cover component into Alarm Control Panel to allow handling alternative schemas if required by new services. * Update requirements_all.txt * Updated services.yaml * Removed requirement to enter code in HA UI Incorporated changes suggested by @sriram https://github.com/srirams/home-assistant/commit/2f8deb70cb5f3621a69b6b9 acb72f8e29123650c Including pending state for exit/entry delay Clarified services to use the code passed to them as a first priority, otherwise use the code from configuration Swapped back to using NotImplementedError for the service definitions * - Add support for alarm_keypress to manual alarm (functions like a standard alarm keypad where entering the code disarms or arms the alarm) - Add tests for alarm_keypress to manual alarm - Style corrections (too many returns, comment & whitespace issues) * Removed alarm_output_control service as unable to incorporate in the demo/test in a meaningful way * Add keypress & output control services to Envisalink component Add services to allow sending custom keypresses and activating programmable outputs on an alarm control panel. Implemented for the Envisalink alarm, and moving to new version of pyenvisalink to support this. Replicated the service handler mapping code from Cover component into Alarm Control Panel to allow handling alternative schemas if required by new services. * Update requirements_all.txt * Updated services.yaml * Removed requirement to enter code in HA UI Incorporated changes suggested by @sriram https://github.com/srirams/home-assistant/commit/2f8deb70cb5f3621a69b6b9 acb72f8e29123650c Including pending state for exit/entry delay Clarified services to use the code passed to them as a first priority, otherwise use the code from configuration Swapped back to using NotImplementedError for the service definitions * - Add support for alarm_keypress to manual alarm (functions like a standard alarm keypad where entering the code disarms or arms the alarm) - Add tests for alarm_keypress to manual alarm - Style corrections (too many returns, comment & whitespace issues) * Removed alarm_output_control service as unable to incorporate in the demo/test in a meaningful way * Moved the Alarm_Keypress service into Envisalink component out of the generic * Update envisalink.py * Update services.yaml
214 lines
7.5 KiB
Python
214 lines
7.5 KiB
Python
"""
|
|
Support for Envisalink devices.
|
|
|
|
For more details about this component, please refer to the documentation at
|
|
https://home-assistant.io/components/envisalink/
|
|
"""
|
|
import logging
|
|
import time
|
|
import voluptuous as vol
|
|
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
|
|
|
|
REQUIREMENTS = ['pyenvisalink==1.9', 'pydispatcher==2.0.5']
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
DOMAIN = 'envisalink'
|
|
|
|
EVL_CONTROLLER = None
|
|
|
|
CONF_EVL_HOST = 'host'
|
|
CONF_EVL_PORT = 'port'
|
|
CONF_PANEL_TYPE = 'panel_type'
|
|
CONF_EVL_VERSION = 'evl_version'
|
|
CONF_CODE = 'code'
|
|
CONF_USERNAME = 'user_name'
|
|
CONF_PASS = 'password'
|
|
CONF_EVL_KEEPALIVE = 'keepalive_interval'
|
|
CONF_ZONEDUMP_INTERVAL = 'zonedump_interval'
|
|
CONF_ZONES = 'zones'
|
|
CONF_PARTITIONS = 'partitions'
|
|
|
|
CONF_ZONENAME = 'name'
|
|
CONF_ZONETYPE = 'type'
|
|
CONF_PARTITIONNAME = 'name'
|
|
CONF_PANIC = 'panic_type'
|
|
|
|
DEFAULT_PORT = 4025
|
|
DEFAULT_EVL_VERSION = 3
|
|
DEFAULT_KEEPALIVE = 60
|
|
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'
|
|
|
|
ZONE_SCHEMA = vol.Schema({
|
|
vol.Required(CONF_ZONENAME): cv.string,
|
|
vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string})
|
|
|
|
PARTITION_SCHEMA = vol.Schema({
|
|
vol.Required(CONF_PARTITIONNAME): cv.string})
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
DOMAIN: vol.Schema({
|
|
vol.Required(CONF_EVL_HOST): cv.string,
|
|
vol.Required(CONF_PANEL_TYPE):
|
|
vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])),
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
vol.Required(CONF_PASS): cv.string,
|
|
vol.Required(CONF_CODE): cv.string,
|
|
vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string,
|
|
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
|
|
vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
|
|
vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port,
|
|
vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION):
|
|
vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
|
|
vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
|
|
vol.All(vol.Coerce(int), vol.Range(min=15)),
|
|
vol.Optional(CONF_ZONEDUMP_INTERVAL,
|
|
default=DEFAULT_ZONEDUMP_INTERVAL):
|
|
vol.All(vol.Coerce(int), vol.Range(min=15)),
|
|
}),
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
# pylint: disable=unused-argument
|
|
def setup(hass, base_config):
|
|
"""Common setup for Envisalink devices."""
|
|
from pyenvisalink import EnvisalinkAlarmPanel
|
|
from pydispatch import dispatcher
|
|
|
|
global EVL_CONTROLLER
|
|
|
|
config = base_config.get(DOMAIN)
|
|
|
|
_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)
|
|
|
|
def login_fail_callback(data):
|
|
"""Callback for when the evl rejects our login."""
|
|
_LOGGER.error("The envisalink rejected your credentials.")
|
|
_connect_status['fail'] = 1
|
|
|
|
def connection_fail_callback(data):
|
|
"""Network failure callback."""
|
|
_LOGGER.error("Could not establish a connection with the envisalink.")
|
|
_connect_status['fail'] = 1
|
|
|
|
def connection_success_callback(data):
|
|
"""Callback for a successful connection."""
|
|
_LOGGER.info("Established a connection with the envisalink.")
|
|
_connect_status['success'] = 1
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
def stop_envisalink(event):
|
|
"""Shutdown envisalink connection and thread on exit."""
|
|
_LOGGER.info("Shutting down envisalink.")
|
|
EVL_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)
|
|
|
|
_LOGGER.error("Timeout occurred while establishing evl connection.")
|
|
return False
|
|
|
|
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:
|
|
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)
|
|
|
|
return True
|
|
|
|
|
|
class EnvisalinkDevice(Entity):
|
|
"""Representation of an Envisalink device."""
|
|
|
|
def __init__(self, name, info, controller):
|
|
"""Initialize the device."""
|
|
self._controller = controller
|
|
self._info = info
|
|
self._name = name
|
|
|
|
@property
|
|
def name(self):
|
|
"""Return the name of the device."""
|
|
return self._name
|
|
|
|
@property
|
|
def should_poll(self):
|
|
"""No polling needed."""
|
|
return False
|