diff --git a/.coveragerc b/.coveragerc
index 578276cac4316e9923985e8675fe79ce521f33b0..b2f6a21c84b5b4b4c25a32d7d5e2a150004ca2ad 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -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
 
diff --git a/homeassistant/components/alarm_control_panel/alarmdecoder.py b/homeassistant/components/alarm_control_panel/alarmdecoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..f176a87827b2d92489370871eef3b61658691ac0
--- /dev/null
+++ b/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))
diff --git a/homeassistant/components/alarmdecoder.py b/homeassistant/components/alarmdecoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec99f2381e5fb3d95af1314b7043358670845aa5
--- /dev/null
+++ b/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
diff --git a/homeassistant/components/binary_sensor/alarmdecoder.py b/homeassistant/components/binary_sensor/alarmdecoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..e62921287109291b561e976c73f3c72370280d2b
--- /dev/null
+++ b/homeassistant/components/binary_sensor/alarmdecoder.py
@@ -0,0 +1,123 @@
+"""
+Support for AlarmDecoder zone states- represented as binary sensors.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/binary_sensor.alarmdecoder/
+"""
+import asyncio
+import logging
+
+from homeassistant.core import callback
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.components.binary_sensor import BinarySensorDevice
+
+from homeassistant.const import (STATE_ON, STATE_OFF, STATE_OPEN, STATE_CLOSED)
+from homeassistant.components.alarmdecoder import (ZONE_SCHEMA,
+                                                   CONF_ZONES,
+                                                   CONF_ZONE_NAME,
+                                                   CONF_ZONE_TYPE,
+                                                   SIGNAL_ZONE_FAULT,
+                                                   SIGNAL_ZONE_RESTORE)
+
+
+DEPENDENCIES = ['alarmdecoder']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+@asyncio.coroutine
+def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
+    """Setup AlarmDecoder binary sensor devices."""
+    configured_zones = discovery_info[CONF_ZONES]
+
+    devices = []
+
+    for zone_num in configured_zones:
+        device_config_data = ZONE_SCHEMA(configured_zones[zone_num])
+        zone_type = device_config_data[CONF_ZONE_TYPE]
+        zone_name = device_config_data[CONF_ZONE_NAME]
+        device = AlarmDecoderBinarySensor(hass,
+                                          zone_num,
+                                          zone_name,
+                                          zone_type)
+        devices.append(device)
+
+    async_add_devices(devices)
+
+    return True
+
+
+class AlarmDecoderBinarySensor(BinarySensorDevice):
+    """Representation of an AlarmDecoder binary sensor."""
+
+    def __init__(self, hass, zone_number, zone_name, zone_type):
+        """Initialize the binary_sensor."""
+        self._zone_number = zone_number
+        self._zone_type = zone_type
+        self._state = 0
+        self._name = zone_name
+        self._type = zone_type
+
+        _LOGGER.debug('AlarmDecoderBinarySensor: Setup up zone: ' + zone_name)
+
+    @asyncio.coroutine
+    def async_added_to_hass(self):
+        """Register callbacks."""
+        async_dispatcher_connect(
+            self.hass, SIGNAL_ZONE_FAULT, self._fault_callback)
+
+        async_dispatcher_connect(
+            self.hass, SIGNAL_ZONE_RESTORE, self._restore_callback)
+
+    @property
+    def state(self):
+        """Return the state of the binary sensor."""
+        if self._type == 'opening':
+            return STATE_OPEN if self.is_on else STATE_CLOSED
+
+        return STATE_ON if self.is_on else STATE_OFF
+
+    @property
+    def name(self):
+        """Return the name of the entity."""
+        return self._name
+
+    @property
+    def icon(self):
+        """Icon for device by its type."""
+        if "window" in self._name.lower():
+            return "mdi:window-open" if self.is_on else "mdi:window-closed"
+
+        if self._type == 'smoke':
+            return "mdi:fire"
+
+        return None
+
+    @property
+    def should_poll(self):
+        """No polling needed."""
+        return False
+
+    @property
+    def is_on(self):
+        """Return true if sensor is on."""
+        return self._state == 1
+
+    @property
+    def device_class(self):
+        """Return the class of this sensor, from DEVICE_CLASSES."""
+        return self._zone_type
+
+    @callback
+    def _fault_callback(self, zone):
+        """Update the zone's state, if needed."""
+        if zone is None or int(zone) == self._zone_number:
+            self._state = 1
+            self.hass.async_add_job(self.async_update_ha_state())
+
+    @callback
+    def _restore_callback(self, zone):
+        """Update the zone's state, if needed."""
+        if zone is None or int(zone) == self._zone_number:
+            self._state = 0
+            self.hass.async_add_job(self.async_update_ha_state())
diff --git a/homeassistant/components/sensor/alarmdecoder.py b/homeassistant/components/sensor/alarmdecoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..88246cc0bc2218cf6451ce6f6be76864b3100876
--- /dev/null
+++ b/homeassistant/components/sensor/alarmdecoder.py
@@ -0,0 +1,75 @@
+"""
+Support for AlarmDecoder Sensors (Shows Panel Display).
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/sensor.alarmdecoder/
+"""
+import asyncio
+import logging
+
+from homeassistant.core import callback
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.entity import Entity
+
+from homeassistant.components.alarmdecoder import (SIGNAL_PANEL_MESSAGE)
+
+from homeassistant.const import (STATE_UNKNOWN)
+
+DEPENDENCIES = ['alarmdecoder']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+@asyncio.coroutine
+def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
+    """Perform the setup for AlarmDecoder sensor devices."""
+    _LOGGER.debug("AlarmDecoderSensor: async_setup_platform")
+
+    device = AlarmDecoderSensor(hass)
+
+    async_add_devices([device])
+
+
+class AlarmDecoderSensor(Entity):
+    """Representation of an AlarmDecoder keypad."""
+
+    def __init__(self, hass):
+        """Initialize the alarm panel."""
+        self._display = ""
+        self._state = STATE_UNKNOWN
+        self._icon = 'mdi:alarm-check'
+        self._name = 'Alarm Panel Display'
+
+        _LOGGER.debug("AlarmDecoderSensor: 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 self._display != message.text:
+            self._display = message.text
+            self.hass.async_add_job(self.async_update_ha_state())
+
+    @property
+    def icon(self):
+        """Return the icon if any."""
+        return self._icon
+
+    @property
+    def state(self):
+        """Return the overall state."""
+        return self._display
+
+    @property
+    def name(self):
+        """Return the name of the device."""
+        return self._name
+
+    @property
+    def should_poll(self):
+        """No polling needed."""
+        return False
diff --git a/requirements_all.txt b/requirements_all.txt
index a37ecb1f775848265f1b1f5b68f342b6b319c691..cbecf913943eca8a2e8c77513b73003965bf8606 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -47,6 +47,9 @@ aiohttp_cors==0.5.2
 # homeassistant.components.light.lifx
 aiolifx==0.4.4
 
+# homeassistant.components.alarmdecoder
+alarmdecoder==0.12.1.0
+
 # homeassistant.components.camera.amcrest
 # homeassistant.components.sensor.amcrest
 amcrest==1.1.8