OpenHAB2 binding architecture
by kacang bawang
In this article I will walk the reader through the inner workings of an OpenHAB2 binding. It is meant to answer the question of “who calls who and when?”, which invariably arises when one starts working with a new framework. It is a summary of what I had learned while writing the binding for my smart switch. If you are thinking about writing a custom binding from scratch, then this article is for you!
What is a binding?
An OpenHAB binding is the layer between OpenHAB and your device, which manages internal OpenHAB commands and decides when and how to send them to your actual device (and vice versa). Think of it as a driver for your device in the OpenHAB “OS”. OpenHAB only knows its own language and its own commands, while the binding translates those OpenHAB commands into different commands that the device expects and understands.
How is a binding implemented?
A binding is implemented as an OSGI bundle, which somewhat resembles a Java DLL. It contains a bunch of code and one or more classes exposed for outside users for something to grab on to. So, to create a binding we will create an OSGI bundle which will expose a class that OpenHAB will know what to do with.
How will OpenHAB detect my binding?
Openhab will look in a certain directory, and will load all bundles (jar files, called addons) within it. It will then look at the “exposed handles” of each addon and will attempt to interact with it if it finds what it was looking for. Each such handle is declared via an xml file, found in:
1 2 3 4 5 |
.../myBundle ... /OSGI-INF exportedClass.xml ... |
Typically, such exported classes will be:
– DiscoveryService
– ThingHandlerFactory
How will OpenHAB interface to my binding?
There are two major parts to a binding:
1. Thing discovery
2. Thing management
Discovery service is a class that is instantiated as OpenHAB is starting regardless of whether or not a device is actually connected. Its job is to look for devices as they are connected. It is typically implemented as a thread, which either polls for new devices or listens for new devices to report in on their own. Discovery is not mandatory – if you do not have a discovery service for your device, each new device will need to be manually added via the OpenHAB configs.
OpenHAB will look for a DiscoveryService within our bundle and launch it if it finds one.
Thing handler is a class that gets instantiated when a device is known to the system from either the config files or through the discovery service. ThingHandler’s correspond 1:1 with “Things” – which, in turn, are the virtual counterparts to our device/sensor/etc within OpenHAB. ThingHandler class will act upon received OpenHAB commands and will also provide a way for the device to relay information to OpenHAB.
OpenHAB will look for a ThingHandlerFactory in our binding bundle. This is what it will use to instantiate ThingHandler’s. Only the ThingHandlerFactory needs to be exposed to OpenHAB.
How will the binding communicate with the physical device
Generally we need to be able to send messages to our device and either: poll for device status or listen for asynchronous communications from it. These operations rely on whatever physical protocol the device utilizes: RF/WiFi/RS232/etc.
This is the point where there will be a lot of variation in terms of binding architecture design. For example, one can imagine a system that keeps one “server” that is central to all messages in and out, and which has a link to every ThingHandler in order to call its methods. Another, less efficient, approach would be for every ThingHandler to maintain its own thread for sending and receiving messages.
Example
Let’s dissect the yahooweather binding as an example. The first thing that happens is a DiscoveryService is created. Why? Because we declared it as an OSGI export. So upon the loading of our addon bundle, OSGI will create and launch it. Let’s take a look at the key functions in this class.
These functions must be overridden in order to inherit from AbstractDiscoveryService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class YahooWeatherDiscoveryService extends AbstractDiscoveryService { // Supplies a list of supported Things (by name) to the parent class. public YahooWeatherDiscoveryService() // Same as consturctor - supplies a list of supported things, but this time // to the caller. This is used for checks of Thing types, when Things are being // discovered. public Set getSupportedThingTypes() // This is the manual scan, as can be triggered from PaperUI/other UI. // Here it will do an HTTP request to freegeoip.com and act upon results // by calling the thingDiscovered() method of the parent. protected void startScan() ... } |
Above is all that is required. However, the really useful methods are the following, despite them being “optional”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class YahooWeatherDiscoveryService extends AbstractDiscoveryService { ... // This method gets run upon DiscoveryService creation, if autodiscovery is // enabled. Which it is, by default. Typically a thread will be started here // that will spin on a discovery procedure, or will otherwise listen for // devices to check in. However, this can be a one-shot task (still on its own // thread) as it is in the yahooweather binding. Here it does the same thing // as in startScan() but asynchronously, that is - contact a geoip service // over HTTP, and act upon the result by calling thingDiscovered() of the // parent class. protected void startBackgroundDiscovery() // If the thread created above is persistent, this is our chance to wind it // down. In this binding, the thread is not persistent, so this "optional" // method is not implemented. protected void stopBackgroundDiscovery() ... } |
The last several methods worth mentioning are the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public class YahooWeatherDiscoveryService extends AbstractDiscoveryService { ... // Parent's method, which communicates to the framework that a new Thing has // been discovered, and provides a unique identifier to reference this thing // by. The unique id is, at its foundation, simply a string. Once the // discovery service establishes communication with a new device it will need // to call this method. // // The Thing in question is not directly created in this function. It only // sends a message to the framework. ThingHandler creation will then be // triggered via the proper ThingHandlerFactory protected void thingDiscovered() // Parent's method, the opposite of thingDiscovered(). When the discovery // service deems that a device has gone offline it should call this method. // Again, unique identifier is given to identify the Thing in question. // // But note that, calling thingRemoved() does not really remove the Thing. // It simply marks it as OFFLINE. The Thing will still appears in the control // panel, and has functional elements (eg ON/OFF toggle) that result in // messages being sent from OpenHAB to this Thing (despite it being offline). // // Yahooweather binding does not use this function. protected void thingRemoved() // Wrapper for calling thingDiscovered(). Useful if need to reach // thingDiscovered() from other classes. public/private void submitDiscoveryResults() // Wrapper for calling thingRemoved(). Useful if need to reach // thingDiscovered() from other classes. public/private void submitRemovalResults() } |
So, our discovery thread kicked off, went, and fetched a location based on our IP address, and submitted a locationName/locationId pair to thingDiscovered. Out of these two, a unique id for the Thing is constructed. After some internal digestion OpenHAB will realize that to handle this type of Thing it needs a YahooWeatherHandler, which can only be made by a YahooWeatherHandlerFactory. Helpfully, the Factory class has also already been created by OSGI for us, as we declared it in the OSGI exports.
We are required to implement the following two methods. This class is pretty straightforward.
1 2 3 4 5 6 7 8 9 |
public class YahooWeatherHandlerFactory extends BaseThingHandlerFactory { // Returns a list of supported types. Used for checking whether this factory // is able to instantiate a particular Thing type. public boolean supportsThingType() // This is the main factory method which returns new instances of ThingHandler protected ThingHandler createHandler() } |
Ok, now that the factory has been identified, the system, acting on the thingDiscovered() message will grab a ThingHandler from the factory. Then it will proceed to call some of the handler’s methods. Let’s take a look at key functions of the ThingHandler class.
First the functions that must be defined:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class YahooWeatherHandler extends BaseThingHandler { // This constructor will be used by the factory and will receive a Thing as an // argument. We just need to pass this Thing to the parent class. public YahooWeatherHandler() // This is arguably the most important function of the ThingHandler class. It // receives commands from the framework. Commands such as 'ON', 'OFF', etc. // Our binding is then responsible to pass these on to the actual device. // // In yahooweather binding's case, handleCommand() looks for a Refresh command. // It arrives, for example, when you click the "reload arrow" in the PaperUI // interface. In response, the binding updates its state (more on this a // little bit later) - and sends an event that is propagated to the rest of // the system (including UI). public void handleCommand() ... } |
Next are some lifecycle functions that can be overridden from the parent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class YahooWeatherHandler extends BaseThingHandler { ... // Will get called shortly after this class is created. It means that the Thing // has been created in the system and now gets a chance to do whatever // initialization tasks it needs to do. Note that it doesn't mean that the // Thing is online! Things that are defined in config files and Things that // have previously been discovered will go through here. It simply means that // the Thing is now part of the system and is ready to receive commands // and discovery callbacks (despite it being offline). // // For yahooweather, there are no online/offline states as it is not really // a device. Therefore, the binding starts a polling thread that periodically // contacts the weather server over HTTP and updates Thing state based on the // results. public void initialize() // The opposite of initialize(). Will be called if, for example, you click // on the trashcan/delete button in PaperUI public void dispose() ... } |
The following paragraph does not apply to YahooWeather binding, as it does not use what is discussed within.
The problem is this – known Things will be created whether they are currently available (online) or not. This is somewhat logical – just because an item went offline (could be a flaky connection) doesn’t mean it’s uninstalled from the home. Therefore we must make a distinction between an Thing structure being allocated in the system, and Thing coming online. The former is realized in initialize(), the latter is best realized though the discovery service. Thus, the discovery service accepts new devices as well as prodigal ones.
To receive these discovery events, the ThingHandler class can realize the DiscoveryListener interface. The framework will then call the relevant
methods for all discovery events that go through the system – such as when DiscoveryService calls thingDiscovered().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class NotYahooWeatherHandler extends BaseThingHandler implements DiscoveryListener { ... // Will get called with *all* discovery results. We have to filter for results // that are destined for us. Typically this function means that the device has // come online and is ready to communicate. Good place to mark Thing as ONLINE. // Also a good place to start polling for device status. public void thingDiscovered() // Will get called with *all* removal results. Requires DiscoveryService // keeping track of disconnects somehow and notifying system of such - via // DiscoveryService.thingRemoved(). public void thingRemoved() ... } |
So far, the discussion of ThingHandler has only concerned with data coming /in/ to the Thing. But how can the Thing send messages to the framework? For example – temperature reading changed, as indicated by polling, now we need to update the UI. What to do?
The following functions are available to a ThingHandler (via parent class) to send data /out/ of the Thing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class YahooWeatherHandler extends BaseThingHandler { ... // The most used function of the set. It sends an updated state of our Thing // into the framework. Other Things and components will receive this update // and act upon it. For example, update the UI. // // In the YahooWeather binding it is called from the polling thread after // receiving results. void updateState() // Similarly to above, sends out a status announcement to the framework. // // In the yahooweather binding is called on every HTTP request. // If successful - update to ONLINE, if failed update to OFFLINE. void updateStatus() // This function is a way to issue commands to the underlying Thing // (self or other Things). Note that you shouldn't reach into the depths of // the ThingHandler implementation and send, say, an OFF command directly to // the device. By going through postCommand the rest of the framework will // know what we're up to, and the sending code will get triggered in // handleCommand(). void postCommand() // The following functions also communicate data /out/ from the Thing. However, // I will only mention them in passing, as we have not talked about // Configurations and Properties of Things in this article. void updateThing() void updateConfiguration() void updateProperties() void updateProperty() } |
What is a Status and what is a State?
Status describes things like whether the Thing is ONLINE or not. It does not describe the “contents” of the Thing, such as a temperature reading, or a relay position in device. Here is a list of possible statuses:
1 2 3 4 5 6 7 8 |
//an enum of framework-defined states: ThingStatus status UNINITIALIZED(0) INITIALIZING(1) ONLINE(2) OFFLINE(3) REMOVING(4) REMOVED(5) |
State describes the value of one of the readings from the device that is stored in the Thing. These readings are represented by Channels. Thus, a Thing includes one or more Channels, where Channels can be of different types. For example, a Switch channel with two states – ON and OFF. Or a Decimal channel, whose state is the value of that Decimal. Therefore, an update of State involves telling the framework that the value of one of the channels changed, and here’s the new value.
The full list of States can be found in this directory. All classes that implement State are “States”.
Incidentally, that same folder also contains all the Commands. Any class that implements Command are “Command”s. As such, the same class frequently represents both a State and a Command.
In Conclusion
With this our discussion of OpenHAB binding architecture has come to an end. Hopefully, the above will save you some time designing your custom binding when you first dig your teeth into OpenHAB2.