* Enable alarmdecoder to see open/close state of bypassed zones when armed The alarmdecoder component already reported RF state bits as attributes. If the user knows which loop is set up for the zone in the alarm panel, they can use that information to tell whether the zone is open or closed even when the system is armed by monitoring the appropriate attribute. That’s awkward, so this commit enables the user to simply configure which loop is used and the component will update the state itself. * Simplify, also it's more correct to treat it as a state change rather than a permanent state, since it's possible the decoder might miss some events. * Remove relative import
206 lines
6.8 KiB
Python
206 lines
6.8 KiB
Python
"""
|
|
Support for AlarmDecoder devices.
|
|
|
|
For more details about this component, please refer to the documentation at
|
|
https://home-assistant.io/components/alarmdecoder/
|
|
"""
|
|
import logging
|
|
|
|
from datetime import timedelta
|
|
import voluptuous as vol
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
|
from homeassistant.helpers.discovery import load_platform
|
|
from homeassistant.util import dt as dt_util
|
|
from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA
|
|
|
|
REQUIREMENTS = ['alarmdecoder==1.13.2']
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DOMAIN = 'alarmdecoder'
|
|
|
|
DATA_AD = 'alarmdecoder'
|
|
|
|
CONF_DEVICE = 'device'
|
|
CONF_DEVICE_BAUD = 'baudrate'
|
|
CONF_DEVICE_HOST = 'host'
|
|
CONF_DEVICE_PATH = 'path'
|
|
CONF_DEVICE_PORT = 'port'
|
|
CONF_DEVICE_TYPE = 'type'
|
|
CONF_PANEL_DISPLAY = 'panel_display'
|
|
CONF_ZONE_NAME = 'name'
|
|
CONF_ZONE_TYPE = 'type'
|
|
CONF_ZONE_LOOP = 'loop'
|
|
CONF_ZONE_RFID = 'rfid'
|
|
CONF_ZONES = 'zones'
|
|
CONF_RELAY_ADDR = 'relayaddr'
|
|
CONF_RELAY_CHAN = 'relaychan'
|
|
|
|
DEFAULT_DEVICE_TYPE = 'socket'
|
|
DEFAULT_DEVICE_HOST = 'localhost'
|
|
DEFAULT_DEVICE_PORT = 10000
|
|
DEFAULT_DEVICE_PATH = '/dev/ttyUSB0'
|
|
DEFAULT_DEVICE_BAUD = 115200
|
|
|
|
DEFAULT_PANEL_DISPLAY = False
|
|
|
|
DEFAULT_ZONE_TYPE = 'opening'
|
|
|
|
SIGNAL_PANEL_MESSAGE = 'alarmdecoder.panel_message'
|
|
SIGNAL_PANEL_ARM_AWAY = 'alarmdecoder.panel_arm_away'
|
|
SIGNAL_PANEL_ARM_HOME = 'alarmdecoder.panel_arm_home'
|
|
SIGNAL_PANEL_DISARM = 'alarmdecoder.panel_disarm'
|
|
|
|
SIGNAL_ZONE_FAULT = 'alarmdecoder.zone_fault'
|
|
SIGNAL_ZONE_RESTORE = 'alarmdecoder.zone_restore'
|
|
SIGNAL_RFX_MESSAGE = 'alarmdecoder.rfx_message'
|
|
SIGNAL_REL_MESSAGE = 'alarmdecoder.rel_message'
|
|
|
|
DEVICE_SOCKET_SCHEMA = vol.Schema({
|
|
vol.Required(CONF_DEVICE_TYPE): 'socket',
|
|
vol.Optional(CONF_DEVICE_HOST, default=DEFAULT_DEVICE_HOST): cv.string,
|
|
vol.Optional(CONF_DEVICE_PORT, default=DEFAULT_DEVICE_PORT): cv.port})
|
|
|
|
DEVICE_SERIAL_SCHEMA = vol.Schema({
|
|
vol.Required(CONF_DEVICE_TYPE): 'serial',
|
|
vol.Optional(CONF_DEVICE_PATH, default=DEFAULT_DEVICE_PATH): cv.string,
|
|
vol.Optional(CONF_DEVICE_BAUD, default=DEFAULT_DEVICE_BAUD): cv.string})
|
|
|
|
DEVICE_USB_SCHEMA = vol.Schema({
|
|
vol.Required(CONF_DEVICE_TYPE): 'usb'})
|
|
|
|
ZONE_SCHEMA = vol.Schema({
|
|
vol.Required(CONF_ZONE_NAME): cv.string,
|
|
vol.Optional(CONF_ZONE_TYPE,
|
|
default=DEFAULT_ZONE_TYPE): vol.Any(DEVICE_CLASSES_SCHEMA),
|
|
vol.Optional(CONF_ZONE_RFID): cv.string,
|
|
vol.Optional(CONF_ZONE_LOOP):
|
|
vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
|
|
vol.Inclusive(CONF_RELAY_ADDR, 'relaylocation',
|
|
'Relay address and channel must exist together'): cv.byte,
|
|
vol.Inclusive(CONF_RELAY_CHAN, 'relaylocation',
|
|
'Relay address and channel must exist together'): cv.byte})
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
DOMAIN: vol.Schema({
|
|
vol.Required(CONF_DEVICE): vol.Any(
|
|
DEVICE_SOCKET_SCHEMA, DEVICE_SERIAL_SCHEMA,
|
|
DEVICE_USB_SCHEMA),
|
|
vol.Optional(CONF_PANEL_DISPLAY,
|
|
default=DEFAULT_PANEL_DISPLAY): cv.boolean,
|
|
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
|
|
}),
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
def setup(hass, config):
|
|
"""Set up for the AlarmDecoder devices."""
|
|
from alarmdecoder import AlarmDecoder
|
|
from alarmdecoder.devices import (SocketDevice, SerialDevice, USBDevice)
|
|
|
|
conf = config.get(DOMAIN)
|
|
|
|
restart = False
|
|
device = conf.get(CONF_DEVICE)
|
|
display = conf.get(CONF_PANEL_DISPLAY)
|
|
zones = conf.get(CONF_ZONES)
|
|
|
|
device_type = device.get(CONF_DEVICE_TYPE)
|
|
host = DEFAULT_DEVICE_HOST
|
|
port = DEFAULT_DEVICE_PORT
|
|
path = DEFAULT_DEVICE_PATH
|
|
baud = DEFAULT_DEVICE_BAUD
|
|
|
|
def stop_alarmdecoder(event):
|
|
"""Handle the shutdown of AlarmDecoder."""
|
|
_LOGGER.debug("Shutting down alarmdecoder")
|
|
nonlocal restart
|
|
restart = False
|
|
controller.close()
|
|
|
|
def open_connection(now=None):
|
|
"""Open a connection to AlarmDecoder."""
|
|
from alarmdecoder.util import NoDeviceError
|
|
nonlocal restart
|
|
try:
|
|
controller.open(baud)
|
|
except NoDeviceError:
|
|
_LOGGER.debug("Failed to connect. Retrying in 5 seconds")
|
|
hass.helpers.event.track_point_in_time(
|
|
open_connection, dt_util.utcnow() + timedelta(seconds=5))
|
|
return
|
|
_LOGGER.debug("Established a connection with the alarmdecoder")
|
|
restart = True
|
|
|
|
def handle_closed_connection(event):
|
|
"""Restart after unexpected loss of connection."""
|
|
nonlocal restart
|
|
if not restart:
|
|
return
|
|
restart = False
|
|
_LOGGER.warning("AlarmDecoder unexpectedly lost connection.")
|
|
hass.add_job(open_connection)
|
|
|
|
def handle_message(sender, message):
|
|
"""Handle message from AlarmDecoder."""
|
|
hass.helpers.dispatcher.dispatcher_send(
|
|
SIGNAL_PANEL_MESSAGE, message)
|
|
|
|
def handle_rfx_message(sender, message):
|
|
"""Handle RFX message from AlarmDecoder."""
|
|
hass.helpers.dispatcher.dispatcher_send(
|
|
SIGNAL_RFX_MESSAGE, message)
|
|
|
|
def zone_fault_callback(sender, zone):
|
|
"""Handle zone fault from AlarmDecoder."""
|
|
hass.helpers.dispatcher.dispatcher_send(
|
|
SIGNAL_ZONE_FAULT, zone)
|
|
|
|
def zone_restore_callback(sender, zone):
|
|
"""Handle zone restore from AlarmDecoder."""
|
|
hass.helpers.dispatcher.dispatcher_send(
|
|
SIGNAL_ZONE_RESTORE, zone)
|
|
|
|
def handle_rel_message(sender, message):
|
|
"""Handle relay message from AlarmDecoder."""
|
|
hass.helpers.dispatcher.dispatcher_send(
|
|
SIGNAL_REL_MESSAGE, message)
|
|
|
|
controller = False
|
|
if device_type == 'socket':
|
|
host = device.get(CONF_DEVICE_HOST)
|
|
port = device.get(CONF_DEVICE_PORT)
|
|
controller = AlarmDecoder(SocketDevice(interface=(host, port)))
|
|
elif device_type == 'serial':
|
|
path = device.get(CONF_DEVICE_PATH)
|
|
baud = device.get(CONF_DEVICE_BAUD)
|
|
controller = AlarmDecoder(SerialDevice(interface=path))
|
|
elif device_type == 'usb':
|
|
AlarmDecoder(USBDevice.find())
|
|
return False
|
|
|
|
controller.on_message += handle_message
|
|
controller.on_rfx_message += handle_rfx_message
|
|
controller.on_zone_fault += zone_fault_callback
|
|
controller.on_zone_restore += zone_restore_callback
|
|
controller.on_close += handle_closed_connection
|
|
controller.on_relay_changed += handle_rel_message
|
|
|
|
hass.data[DATA_AD] = controller
|
|
|
|
open_connection()
|
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder)
|
|
|
|
load_platform(hass, 'alarm_control_panel', DOMAIN, conf, config)
|
|
|
|
if zones:
|
|
load_platform(
|
|
hass, 'binary_sensor', DOMAIN, {CONF_ZONES: zones}, config)
|
|
|
|
if display:
|
|
load_platform(hass, 'sensor', DOMAIN, conf, config)
|
|
|
|
return True
|