How To Implement A Binding - vpjuslin/openhab GitHub Wiki

Introduction

Bindings are the most interesting bit for people to get into coding for openHAB and therefore, many questions arise around this topic regularly.

This page tries to give you a starting point, if you intent to implement (and hopefully contribute) your own binding.

For information about how to setup a development environment, please see the according wiki page.

General Information about the Architecture

The openHAB runtime distribution comes without any binding. All bindings are considered to be "add-ons", which the user can optionally install by putting it in the "addons" folder of the runtime. As a consequence of this, a binding should usually be a single file and as a file corresponds to an OSGi bundle, a binding should be a single bundle.

The purpose of a binding is to translate between events on the openHAB event bus and an external system. This translation should happen "stateless", i.e. the binding must not access the ItemRegistry in order to get hold of the current state of an item. Likewise, it should not itself keep states of items in memory.

Creating a Bundle Skeleton

As explained above, a binding should correspond to one bundle. The naming convention for the binding bundle is "org.openhab.binding.<name>". To create a working binding skeleton one should use the Maven archetype which facilitates the creation process. The following steps have to be performed:

  1. run a full build (meaning run mvn clean install in the topmost directory)*

  2. cd ./bundles/archetype/org.openhab.archetype.binding

  3. mvn clean install

  4. cd ../../binding

  5. use maven archetype

     mvn archetype:generate -B -DarchetypeGroupId=org.openhab.archetype \
     -DarchetypeArtifactId=org.openhab.archetype.binding \
     -DarchetypeVersion=1.7.0-SNAPSHOT -Dauthor=<author> -Dversion=<target-version-of-binding> \
     -DartifactId=org.openhab.binding.<binding-name-in-small-caps> \
     -Dpackage=org.openhab.binding.<binding-name-in-small-caps> \
     -Dbinding-name=<binding-name-in-camel-case>
    
  6. import newly created project by selecting 'Import->Existing Java project'

  7. active the new plugin in !RunConfiguration 'Run Configurations->openHAB Runtime->Plugins->activate your plugin->Auto-start true'

If you have imported the project before, follow the same steps. If you select the same root directory, eclipse scans the path and detects your already imported components. You can only chose your new bundle to import.

Another possibility is to copy an existing binding and do a search&replace for the name. If the first step fails due to insufficient memory - Java's memory allowance may need to be increased (export MAVEN_OPTS="-Xmx1024m -XX:MaxPermSize=256m")

Global Binding Configuration

#####Integrated binding configuration approach with Declarative Services and Configuration Admin service

A binding may require general configuration settings, such as a host and port of the external system to connect to. This can be done through the OSGi Configuration Admin service. If the binding requires a configuration to be present in order to function, set the configuration policy in the OSGI-INF/binding.xml file to require.

configuration-policy="require"

This setting ensures that the binding method activate will only be called once there is a configuration for the corresponding PID available. The PID is generated by the archetype script and defaults to org.openhab.<name>, where <name> is the lower case name of the binding. This name is used in the openhab.cfg file as a prefix for the binding configuration.

Setting the configuration policy to optional means that the binding will be activated even if there is no configuration found for the PID. Should a configuration become available later, or in case the configuration is updated dynamically, the callback method modified is called from the binding.

Use the maven archetype to create a sample binding project and take a look at the generated stubs for comments.

Note, the activate and modified methods are called with a Map<String,Object> containing the configuration details. An exception thrown by either of the methods will ensure that the binding is not activated and an error is logged by the framework.

	/**
	 * Called by the SCR to activate the component with its configuration read from CAS
	 * 
	 * @param bundleContext BundleContext of the Bundle that defines this component
	 * @param configuration Configuration properties for this component obtained from the ConfigAdmin service
	 */
	public void activate(final BundleContext bundleContext, final Map<String, Object> configuration) {
	/**
	 * Called by the SCR when the configuration of a binding has been changed through the ConfigAdmin service.
	 * @param configuration Updated configuration properties
	 */
	public void modified(final Map<String, Object> configuration) {
		// update the internal configuration accordingly
	}

Furthermore, both methods may be declared to accept a BundleContext object. This object is only valid while the bundle containing the implementation is ACTIVE, i.e. started. While the object may be passed around to other objects, care must be taken with regard to its state. When the bundle is stopping, the BundleContext becomes invalid and throws exceptions on access.

A binding component is automatically deactivated when its bundle is stopped, a mandatory dependency is no longer available, or the required configuration is deleted. In any of these cases, the deactivate callback method is called with an Integer argument that gives a hint about the reason for the deactivation.

	/**
	 * Called by the SCR to deactivate the component when either the configuration is removed or
	 * mandatory references are no longer satisfied or the component has simply been stopped.
	 * @param reason Reason code for the deactivation:<br>
	 * <ul>
	 * <li> 0 – Unspecified
     * <li> 1 – The component was disabled
     * <li> 2 – A reference became unsatisfied
     * <li> 3 – A configuration was changed
     * <li> 4 – A configuration was deleted
     * <li> 5 – The component was disposed
     * <li> 6 – The bundle was stopped
     * </ul>
	 */
	public void deactivate(final int reason) {
		this.bundleContext = null;
		// deallocate resources here that are no longer needed and 
		// should be reset when activating this binding again
	}

In the openhab.cfg file configure the specific binding properties using the <name> prefix as described above.

################################ <name> Binding ##########################################
#
# refresh interval in milliseconds (optional, defaults to 900000 [15 minutes])
<name>:refresh=60000

#####Alternative manual binding configuration approach

This section describes how bindings can be configured using the OSGi Configuration Admin service.

A binding may require general configuration settings, such as a host and port of the external system to connect to. This can be done through the OSGi Configuration Admin service, i.e. by registering an OSGi service, which implements the interface org.osgi.service.cm.ManagedService.

Besides implementing the ManagedService interface, you have to add the service.pid property as well as the provide element for the ManagedService to the OSGi Declarative Services XML file (in the OSGI-INF folder):

<service>
   <provide interface="org.osgi.service.cm.ManagedService"/>
</service>
<property name="service.pid" type="String" value="org.openhab.<name>"/>

Replace <name> with the prefix to be used in openhab.cfg.

openHAB then allows to add configuration information in openhab.cfg, which is automatically dispatched to your ManagedService.

Please refer to the KNX binding for an example on how to implement a ManagedService and how to register it through OSGi Declarative Services.

Item-specific Binding Configuration

t.b.d.

Connecting to the Event Bus

There are two directions for the communication of a binding - either sending out commands and status updates from the openHAB even bus to some external system or to receive information from an external system and post this on the openHAB event bus for certain items.

A binding can implement the one or the other direction or both. We usually talk about "Out-", "In-" or "InOut-Bindings" respectively.

openHAB Event Bus -> External System

A good example for such an "Out-Binding" is the Exec-binding. When receiving a command for an item, a configured command is executed on the command line of the host system.

Such bindings can be implemented pretty easily: All you have to do is to extend the class AbstractBinding ([Abstract binding on GitHub] (https://github.com/openhab/openhab/blob/master/bundles/core/org.openhab.core/src/main/java/org/openhab/core/binding/AbstractBinding.java)) and override the methods

public void internalReceiveCommand(String itemName, Command command)

and / or

protected void internalReceiveUpdate(String itemName, State newState)

As the method signatures suggest, you are passed the name of the item and the command or state that was sent on the openHAB event bus. In these methods, you can then perform whatever code is appropriate for your use case. See the ExecBinding class as an example.

All there is left to do is to register your class with the OSGi event admin service. To do so, your component has to provide the EventHandler service and define the property event.topics. In your OSGI-INF/binding.xml file, this should look like this:

<service>
   <provide interface="org.osgi.service.event.EventHandler"/>
</service>
<property name="event.topics" type="String" value="openhab/*"/>

If you are only interested in commands, you can also choose openhab/command/* as a topic. See the component descriptor of the ExecBinding as an example.

External System -> openHAB Event Bus

If you receive information from an external system somewhere in your code and you want to post commands or status updates on the openHAB event bus, you can as well simply extend AbstractBinding for your implementation. Your binding class should then reference the EventPublisher service in the OSGI-INF/binding.xml file. This should look like this:

    	<reference bind="setEventPublisher" cardinality="1..1" interface="org.openhab.core.events.EventPublisher" name="EventPublisher" policy="dynamic" unbind="unsetEventPublisher"/>

In your code, you can then simply refer to the instance variable eventPublisher in order to very easily send events:

    	eventPublisher.postUpdate(itemName, state);
    	eventPublisher.postCommand(itemName, command);
    	eventPublisher.sendCommand(itemName, command);

Please note the difference between sendCommand and postCommand: Sending means a synchronous call, i.e. the method does not return until all event bus subscribers have processed the event. So if you just want to do a fire&forget, use the asynchronous postCommand method instead.

Handling background activities

In order to receive information from an external system, you usually either need some background thread continuously listening to the external system (e.g. on a socket or serial interface) or you need to regularly actively poll the external system. In both cases, choosing to extend the class AbstractActiveBinding (Abstract Active Binding on GitHub) will help you.

AbstractActiveBinding extends AbstractBinding (Abstract Binding on GitHub) so everything said above will still be the same. You will simply get an additional feature: The active binding provides a thread creation and handling and all you have to do is to specify a pause interval between calls of the execute() method. See the NtpBinding class for a simple example of such a binding. ntp:refresh in openhab.cfg is the ntp specific pause interval.

Lifecycle

A few notes on the lifecyle: Bindings are dynamic OSGi services and thus behave according to the OSGi specs. The dynamics make it easy to change openHAB during runtime (add new bindings later on, reconfigure items, etc.), but it also brings some complexity in the coding. In consequence the methods of the interfaces that the bindings implement (such as addBindingProvider(), allBindingsChanged(), updated(), processBindingConfiguration() etc.) can be called at any time and in any order. So you need to make sure that you write your code in a way that you can always react in a decent way when a method is called and reach a valid state of your binding.

This specifically means:

  • activate() is called as soon as all required dependencies (e.g. the first !BindingProvider) are resolved and set in your service instance
  • optional dependencies might be set any time (e.g. a second !BindingProvider is set through addBindingProvider())
  • updated() is called any time, but after activate(). So you should even consider the case that you receive invalid configuration at a later time and thus stop your services again (-> setProperlyConfigured(false)).
  • processBindingConfigurations() can be called anytime, but always after activate()

Building and Packaging

After IDE setup and creating/testing your binding, you may want others to use it. For this, you can use the Eclipse export function as follows:

  1. Right-click on your binding project
  2. Select Export
  3. Choose Plug-in Development->Deployable plug-ins and fragments
  4. Fill "Directory" with the Path where you want your jar-file to appear
  5. Check "Use class files compiled in the workspace" on the "Options" tab
  6. Click Finish and check yor Directory
⚠️ **GitHub.com Fallback** ⚠️