Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding AlarmDecoder platform #6900

Merged
merged 4 commits into from Apr 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Expand Up @@ -8,6 +8,9 @@ omit =
homeassistant/helpers/signal.py

# omit pieces of code that rely on external devices being present
homeassistant/components/alarmdecoder.py
homeassistant/components/*/alarmdecoder.py

homeassistant/components/apcupsd.py
homeassistant/components/*/apcupsd.py

Expand Down
119 changes: 119 additions & 0 deletions homeassistant/components/alarm_control_panel/alarmdecoder.py
@@ -0,0 +1,119 @@
"""
Support for AlarmDecoder-based alarm control panels (Honeywell/DSC).

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdecoder/
"""
import asyncio
import logging

from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.components.alarm_control_panel as alarm

from homeassistant.components.alarmdecoder import (DATA_AD,
SIGNAL_PANEL_MESSAGE)

from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, STATE_ALARM_TRIGGERED)

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = ['alarmdecoder']


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Perform the setup for AlarmDecoder alarm panels."""
_LOGGER.debug("AlarmDecoderAlarmPanel: setup")

device = AlarmDecoderAlarmPanel("Alarm Panel", hass)

async_add_devices([device])

return True


class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
"""Representation of an AlarmDecoder-based alarm panel."""

def __init__(self, name, hass):
"""Initialize the alarm panel."""
self._display = ""
self._name = name
self._state = STATE_UNKNOWN

_LOGGER.debug("AlarmDecoderAlarm: Setting up panel")

@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_PANEL_MESSAGE, self._message_callback)

@callback
def _message_callback(self, message):
if message.alarm_sounding or message.fire_alarm:
if self._state != STATE_ALARM_TRIGGERED:
self._state = STATE_ALARM_TRIGGERED
self.hass.async_add_job(self.async_update_ha_state())
elif message.armed_away:
if self._state != STATE_ALARM_ARMED_AWAY:
self._state = STATE_ALARM_ARMED_AWAY
self.hass.async_add_job(self.async_update_ha_state())
elif message.armed_home:
if self._state != STATE_ALARM_ARMED_HOME:
self._state = STATE_ALARM_ARMED_HOME
self.hass.async_add_job(self.async_update_ha_state())
else:
if self._state != STATE_ALARM_DISARMED:
self._state = STATE_ALARM_DISARMED
self.hass.async_add_job(self.async_update_ha_state())

@property
def name(self):
"""Return the name of the device."""
return self._name

@property
def should_poll(self):
"""No polling needed."""
return False

@property
def code_format(self):
"""Regex for code format or None if no code is required."""
return '^\\d{4,6}$'

@property
def state(self):
"""Return the state of the device."""
return self._state

@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""
_LOGGER.debug("AlarmDecoderAlarm::alarm_disarm: %s", code)
if code:
_LOGGER.debug("AlarmDecoderAlarm::alarm_disarm: sending %s1",
str(code))
self.hass.data[DATA_AD].send("{!s}1".format(code))

@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
_LOGGER.debug("AlarmDecoderAlarm::alarm_arm_away: %s", code)
if code:
_LOGGER.debug("AlarmDecoderAlarm::alarm_arm_away: sending %s2",
str(code))
self.hass.data[DATA_AD].send("{!s}2".format(code))

@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
_LOGGER.debug("AlarmDecoderAlarm::alarm_arm_home: %s", code)
if code:
_LOGGER.debug("AlarmDecoderAlarm::alarm_arm_home: sending %s3",
str(code))
self.hass.data[DATA_AD].send("{!s}3".format(code))
171 changes: 171 additions & 0 deletions homeassistant/components/alarmdecoder.py
@@ -0,0 +1,171 @@
"""
Support for AlarmDecoder devices.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alarmdecoder/
"""
import asyncio
import logging

import voluptuous as vol
import homeassistant.helpers.config_validation as cv

from homeassistant.core import callback
from homeassistant.const import EVENT_HOMEASSISTANT_STOP

from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send

REQUIREMENTS = ['alarmdecoder==0.12.1.0']

_LOGGER = logging.getLogger(__name__)

DOMAIN = 'alarmdecoder'

DATA_AD = 'alarmdecoder'


CONF_DEVICE = 'device'
CONF_DEVICE_TYPE = 'type'
CONF_DEVICE_HOST = 'host'
CONF_DEVICE_PORT = 'port'
CONF_DEVICE_PATH = 'path'
CONF_DEVICE_BAUD = 'baudrate'

CONF_ZONES = 'zones'
CONF_ZONE_NAME = 'name'
CONF_ZONE_TYPE = 'type'

CONF_PANEL_DISPLAY = 'panel_display'

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'

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): cv.string})

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)


@asyncio.coroutine
def async_setup(hass, config):
"""Common setup for AlarmDecoder devices."""
from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import (SocketDevice, SerialDevice, USBDevice)

conf = config.get(DOMAIN)

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

sync_connect = asyncio.Future(loop=hass.loop)

def handle_open(device):
"""Callback for a successful connection."""
_LOGGER.info("Established a connection with the alarmdecoder.")
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder)
sync_connect.set_result(True)

@callback
def stop_alarmdecoder(event):
"""Callback to handle shutdown alarmdecoder."""
_LOGGER.debug("Shutting down alarmdecoder.")
controller.close()

@callback
def handle_message(sender, message):
"""Callback to handle message from alarmdecoder."""
async_dispatcher_send(hass, SIGNAL_PANEL_MESSAGE, message)

def zone_fault_callback(sender, zone):
"""Callback to handle zone fault from alarmdecoder."""
async_dispatcher_send(hass, SIGNAL_ZONE_FAULT, zone)

def zone_restore_callback(sender, zone):
"""Callback to handle zone restore from alarmdecoder."""
async_dispatcher_send(hass, SIGNAL_ZONE_RESTORE, zone)

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_open += handle_open
controller.on_message += handle_message
controller.on_zone_fault += zone_fault_callback
controller.on_zone_restore += zone_restore_callback

hass.data[DATA_AD] = controller

controller.open(baud)

result = yield from sync_connect

if not result:
return False

hass.async_add_job(async_load_platform(hass, 'alarm_control_panel', DOMAIN,
conf, config))

if zones:
hass.async_add_job(async_load_platform(
hass, 'binary_sensor', DOMAIN, {CONF_ZONES: zones}, config))

if display:
hass.async_add_job(async_load_platform(hass, 'sensor', DOMAIN,
conf, config))

return True