"""Support for AlarmDecoder devices.""" 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