Home Assistant component code description

This is my document about the development of a component for the Home Assistant project.

Through out the examples I will add Github acceptance notes:
These are things that were in the old code or rules that must be followed for acceptance into the official HA git repository.



W800rf32 component code

This is the configuration for our component, it is from the configuration.yaml file. "w800rf32" is the DOMAIN as set below and device is the serial port our w800 hardware is connected to.

Github acceptance note:
You must not include an example of this in your source code.

# Example configuration.yaml entry
w800rf32:
  device: PATH_TO_DEVICE

Start of source code

"""
Support for w800rf32 components.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/w800rf32/

Imports

The next section shows the imports we need, remember this is a fairly simple component, more complex components will have more imports. The logging and voluptuous imports are normal python libraries.

"""
import logging
import voluptuous as vol
from homeassistant.const import (CONF_DEVICE,
                                 EVENT_HOMEASSISTANT_START,
                                 EVENT_HOMEASSISTANT_STOP)

These are pretty obvious ~/homeassistant/const.py holds the constants for the Home Assistant project. Almost all of the constants are strings so you could just use the string, however, if the HA project changes a constant it is global, so best to use them.

Here is a description.
    1. CONF_DEVICE = 'device'<- the component device from config, in this case the serial port
    2. EVENT_HOMEASSISTANT_START = 'homeassistant_start'<- event that fires upon HA startup
    3. EVENT_HOMEASSISTANT_STOP = 'homeassistant_stop'<- event that fires upon HA shutdown

Import the config_validation, explained below.

import homeassistant.helpers.config_validation as cv

Import HA dispatcher_send() which is used to communicate to our platform.

from homeassistant.helpers.dispatcher import (dispatcher_send)

Requirements

HA covers the REQUIREMENTS section well, here is the link.
In a nutshell REQUIREMENTS is a project on PYPI that gets pulled into HA (pip install) and is responsible for encoding and decoding data from the hardware.


REQUIREMENTS = ['pyW800rf32']

Domain

DOMAIN is how your component will be known under HA. Since my hardware is a W800rf32A, I opted to call my DOMAIN 'w800rf32'.
The DOMAIN is a value stored in a dictionary called config and holds such items as config[DOMAIN][ATTR_DEVICE], which in this case, is the serial port the w800rf32 is connected to.

The code in ~/homeassistant/config.py reads the configuration.yaml file and stores the information in config[DOMAIN].


DOMAIN = 'w800rf32'

Constants

These are constants that we use, that are not defined in the ~/homeassistant/const.py file. They are specific to this component.

DATA_W800RF32 = 'data_w800rf32'
W800RF32_DEVICE = 'w800rf32_{}'

Logger

This is the python logger and just returns the name of the module for logging. HA covers the LOGGING section well, except it seems it's not well linked to. here is the link.


_LOGGER = logging.getLogger(__name__)

Configuration

The voluptuous library is used to validate the schema of the configuration data, set in the configuration.yaml file (or possibly an included file).
In this component we have one variable allowed in the configuration.yaml file:
    1. "device": which is CONF_DEVICE

Just to refresh your memory, the configuration.yaml file looks like so:
    
w800rf32:
  device: /dev/ttyUSB0
  
device is a required parameter, in other words it must be present. It's value, in this case '/dev/ttyUSB0', is also checked to be a string value, (cv.string).

The extra=vol.ALLOW_EXTRA is there to allow additional keys in the data that are not listed in the schema, from throwing an exception.

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Required(CONF_DEVICE): cv.string
    }),
}, extra=vol.ALLOW_EXTRA)

setup()

Called from: homeassistant.setup
Returns: True

Setup is where we actually setup our component for HA.


First we should come up with a check list of things to do:
    1. Define a setup function and pass in the hass object and the config dictionary
    2. Import the W800rf32 module
    3. Setup an event handler to deal with in coming data
    4. Get the device from config, call connect and return an w800_object
    5. Setup two event handlers to deal with HA start and stop events
    6. Hand our w800_object over to hass
    7. Return if all is well

Setup is pretty simple, just define a function passing in the hass object and the config dictionary.
The hass object is described here. It is the HA instamce that has access to all the parts of the running HA system.

def setup(hass, config):
    """Set up the w800rf32 component."""

Load the W800rf32 module (REQUIREMENTS) which was installed from the PYPI repository. The HA documentation requests that modules are loaded in functions and not globally at the top.

    # Load the W800rf32 module.
    import W800rf32 as w800

Define an event handler to handle data coming from the hardware. This is fairly simple, it just logs the event and then runs a handler for the specific entity. This could be simplified, however, it's a good way to write it, if you want expanded functionality in a more complex component.
handle_receive gets added to the HA event loop when HA starts running, see code below.

    # Declare the Handle event
    def handle_receive(event):
        """Handle received messages from w800rf32 gateway."""
        # Log event
        if not event.device:
            return
        _LOGGER.debug("Receive W800rf32 event in handle_receive")

Github acceptance note:
All method calls between your component and platform must be done with dispatch_connect/send() calls.

Here we grab the device_id (our hardware sensor) of an incoming event, then construct a signal consisting of our platform and the hardware sensor I/O point.

Next we call dispatcher_send() to connect the event to a matching dispatcher_connect() call that is setup in our platform code.

When data is received from hardware, it triggers the handle_receive function which logs it and then runs dispatcher_send(), in this case binary_sensor_update() from the platform code.

        # Get device_type from device_id in hass.data
        device_id = event.device.lower()
        signal = W800RF32_DEVICE.format(device_id)
        dispatcher_send(hass, signal, event)

Right, next thing we need to do is grab the serial port device form the configuration.yaml file which was parsed into the config dictionary.
If we break this down it would look like so: device = config['w800rf32']['device']

Now call the Connect function in the PYPI module code that opens the serial port and setups the event handlers, returning an object. Remeber HA doesn't want us to do any lowlevel handling of the hardware in HA code, it must be a third party PYPI module.

    # device --> /dev/ttyUSB0
    device = config[DOMAIN][CONF_DEVICE]
    w800_object = w800.Connect(device, None)

Define two small functions and add them to the HA event loop, for events that get triggered on startup and shutdown of HA.
_start_w800rf32() is where the handle_receive function listed above gets added to the HA event loop.

    def _start_w800rf32(event):
        w800_object.event_callback = handle_receive
    hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_w800rf32)

_shutdown_w800rf32() should be fairly obvious.

    def _shutdown_w800rf32(event):
        """Close connection with w800rf32."""
        w800_object.close_connection()
    hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown_w800rf32)

The last task in setup is to add our object to the global hass.data dictionary and then return True

    hass.data['w800object'] = w800_object
    return True