martedì 26 aprile 2016

OpenHAB in action: a quick example

This is the third of a series of post explaining the experience of the JEMMA and Energy@home community with the OpenHAB framework. Find below links to all posts of the series:
  1. JEMMA and OpenHAB: a nice match (publication date: 18/4/2016)
  2. OpenHAB in theory: a short overview (publication date: 22/4/2016)
  3. OpenHAB in action: a quick example (publication date: 26/4/2016)
  4. The JEMMA - OpenHAB remote binding (publication date: 29/4/2016)
  5. The JEMMA - OpenHAB local binding (publication date: 3/5/2016)
  6. Conclusions and future work (publication date: 9/5/2016)

OpenHAB in action: a quick example

The easiest way to implement a binding consists in creating a preliminar skeleton using the OpenHAB script dedicated to the purpose and then redefining the placeholders inside the code just created.

Writing a binding requires definition of the following constitutive elements:
The Handler is an entity tightly bound to a specific Thing, indeed it provides the implementation of the thing capabilities. In order to receive/send commands and status update from/to the item it is required to implement the HandlerThing interface and overriding the handleCommand and handleUpdate methods.

The role of the handler factory is pretty straightforward. It instantiates the implemented handler when the framework service requests it. This happens anytime a new Thing is added to the framework. The factory implements the ThingHandlerFactory and must provide methods to specify whether it manages a particular ThingHandler.

This is made possible by attempting to match against the ThingTypeUID given as argument in the supportsThingType method. In case a match has been found the framework service calls the createHandler method which returns a new ThingHandler instance.

As pointed out in the previous post, a Thing is the immanent representation of an Item and as such is required to be able to perform actions that mean something for the specific item. These capabilities should be adequately designed and hence described inside the related xml file.

Events such as sending a command and posting an update, once triggered, flow into the OpenHAB's event bus and the framework is in charge of delivering such events to the proper recipient.

If we start to trace back the chain of operations it is possible to identify a  link between an item and its handler. This connection is partially defined inside the .item file with a reference to a thing definition. The last branch of the link is identified by the association between the xml file describing the Thing and its relative Handler. The framework will nicely do the rest.

The links defined inside the framework are not necessarily point to point, there might be a one to many configuration where an event is delivered to multiple handler and the opposite is also true.

However the bus operates as a messages exchange based system and thus there is no guarantee that messages are delivered in order and without overlaps among different events. Cool down mechanisms can be implemented to perform a sort of debouncing to avoid manifestation of undesired situations.

Some XML headers ...

To better understand how the whole system works we tried to implement our own test binding i.e. an event logger to log events relative to a binary switch, following various tutorials on the openHAB website.

Once the Eclipse Smart Home environment has been set up, it is possible to exploit the script
mentioned in the previous section and create a new binding skeleton.

The skeleton project can now be imported inside eclipse and edited to best suit your needs. In our case, we start by editing the ESH-INF/binding/binding.xml to look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="customlogger"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:binding="http://eclipse.org/smarthome/schemas/binding/v1.0.0"
        xsi:schemaLocation="http://eclipse.org/smarthome/schemas/binding/v1.0.0 http://eclipse.org/smarthome/schemas/binding-1.0.0.xsd">       

    <name>CustomLogger Binding</name>
    <description>This is the binding for CustomLogger.</description>
    <author>your name here</author>

</binding:binding>

The most important field here is represented by the <binding:binding id="customlogger"> element whose value will be used to identify the binding inside the whole framework.

Afterwards, it is possible to start describing the Thing element.

<thing:thing-descriptions bindingid="customlogger" xmlns:thing="http://eclipse.org/smarthome/schemas/thing-description/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://eclipse.org/smarthome/schemas/thing-description/v1.0.0 http://eclipse.org/smarthome/schemas/thing-description-1.0.0.xsd">

    <!-- Sample Thing Type -->
    <thing-type id="logger">
        <label>PertLogger Binding Thing</label>
        <description>Sample thing for CustomLogger Binding</description>        
        <channels>
            <channel id="log" typeid="logType">
        </channel></channels>
    </thing-type>

    <!-- Sample Channel Type -->
    <channel-type id="logType">
        <item-type>Switch</item-type>
        <label>CustomLogger Binding Channel</label>
        <description>Log channel for CustomLogger Binding</description>
    </channel-type>

</thing:thing-descriptions>

In this phase it is possible to give an ID to our new Thing. This information will be used in a while inside the ThingHandlerFactory to infer whether the framework has requested an instantiation of the handler associated to a particular Thing. Thing capabilities are described inside the channel definition. In our case we just need the logger to export the log capability for event associated to a binary switch.

... and finally, the code.

The CustomThingHandlerFactory definition is simple as this:

public final static ThingTypeUID THING_TYPE_LOGGER = new ThingTypeUID(BINDING_ID, "logger");
    private final static Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_LOGGER);

    @Override
    public boolean supportsThingType(ThingTypeUID thingTypeUID) {

        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);

    }

    @Override
    protected ThingHandler createHandler(Thing thing) {

        ThingTypeUID thingTypeUID = thing.getThingTypeUID();

        if (thingTypeUID.equals(THING_TYPE_LOGGER)) {
            return new CustomLoggerHandler(thing);
        }

        return null;
    }


Finally, we just need to hook the handleCommand implementation. Note: as log messages tends to grow exponentially when developing for OSGI framwork in the example we don't use simple calls to System.out but we rely on a standard sl4j or log4j logger.

private Logger logger = LoggerFactory.getLogger(CustomLoggerHandler.class);
    private static final String STRING_FORMAT = "CUSTOMLOGGER: ";
    public final static String CHANNEL_LOG = "log";

    @Override
    public void handleCommand(ChannelUID channelUID, Command command)
    {
        if (channelUID.getId().equals(CHANNEL_LOG))
        {
            if (command instanceof OnOffType)
            {
                switch ((OnOffType) command)
                {
                    case ON:
                        logger.info(STRING_FORMAT + "Light has been switched ON");
                        break;
                    case OFF:
                        logger.info(STRING_FORMAT + "Light has been switched OFF");
                        break;
                }

            } 
            else            
            {
                logger.info(STRING_FORMAT + "RECEIVED UNKNOWN COMMAND");
            }
        }
    }
Having defined such classes makes the binding ready to receive events relative to the logger Thing and for the associated Channel. In our specific case we are interested in OnOffType commands associated to the "log" channel. The extremely clever operations carried out by the logger consist in simply logging down the state of our switch. We are now ready to export our plugin and make it available to the OpenHAB runtime. To avoid trouble during the exporting process i suggest to close all test projects inside the Smart Home workspace in order to avoid circular dependencies.

OpenHAB configuration files

Once the export operation completes successfully we are now ready to switch to the OpenHAB context. Set up of the OpenHAB environment can be accomplished by following the instructions reported here. The first operation required consists in adding our brand new plug-in to the target platform dependencies of the runtime and then update the openhab2 run configuration to include our plug-in.


It finally came the time to edit the OpenHAB configuration files. Inside the demo-resources/src/main/resources/things folder it is possible to find a demo.things file where inside are defined the Thing objects that will be instantiated once the runtime started.

 Our thing instance should be declared in this file using the following representation:

bindingId:thingId:thingInstanceName     ["parameters"]
customlogger:logger:myLogger            []

It's now the turn of the "demo.item" file to be edited.

At the bottom of the file a list of them items is defined. Our goal is to update the line relative to the DemoSwitch and link it to the log channel provided by the CustomLogger Thing instance. To do so we just append the bindingconfig {channel="customlogger:logger:myLogger:log"} to the DemoSwitch line.

Switch DemoSwitch               "Switch" {channel="customlogger:logger:myLogger:log"}

The previous line will inform the OpenHAB framework that any command/update event associated to the DemoSwitch item must be delivered to our logger and to the channel indentified in the .item configuration file. The information related to the channel is attached to the event and will be used by the CustomLoggerHandler to establish what functionality has been requested and needs to be activated. What just done completes the chain of communication set up procedure.

Running the demo

If everything went fine it is possible to type the ss command inside the Eclipse console and see the list of active bundles and information associated to their current status. Scrolling the list you shuld be able to see something like this:

119 ACTIVE      org.eclipse.jetty.client_9.2.12.c20150709
120 ACTIVE      org.eclipse.smarthome.binding.customlogger_0.8.0.0.201602011529
121 RESOLVED    org.eclipse.jetty.osgi.alpn.fragment_1.1.2

It is now possible to see demo items by reaching http://yourhost:8080/basicui/app location in a browser or remotely inside a smartphone. By getting inside the Widget Overview category it is possible to examine the demo items defined in the associated configuration file.

The subsection containing Binary Widgets it is possible to notice the presence of our toggle switch.


I know it's hard to resist, thus you should definitely go for it and click the button.

What happens next is more or less what explained in the previous sections, hence we expect some events to ride all over around and hopefully to reach our binding.

By examining the eclipse console we should be able to se something very similar to the following:
[DEBUG] [.c.thing.internal.ThingManager:298 ] - Delegating command 'ON' for item 'DemoSwitch' to handler for channel 'customlogger:logger:mylogger:log'
[INFO ] [.b.p.handler.CustomLoggerHandler:73    ] - CUSTOMLOGGER: Light has been switched ON

This is exactly the output we prefigured out to see. The framework informs us that a command relative to our DemoSwitch item has been delegated to our Thing at a specific channel. The line that follows is the result generated by our logger class that behave as expected and log down the command reception.

Authors

This series of posts has been prepared by Sandro Tassone, supported by Riccardo Tomasi(ISMB). Sandro holds a MSc in Computer Engineering from Politecnico di Torino. He is currently collaborating with ISMB and Energy@home on JEMMA and other OSGi-based projects. He is a Software and C++ enthusiast.&; This activity has been partially co-funded by Energy@home. Thanks to IOOOTA for providing initial ideas and support for the jemma remote binding component.

Nessun commento:

Posta un commento