Developing a Binding

A binding is an extension to openHAB that integrates an external system like a software service or a hardware device. The external system is represented as a set of Things and sometimes Bridges with Channels.

This chapter covers everything to know about binding development. It makes sense to briefly read over all sections to make you familiar with what the framework has to offer.

thing concept

During development you might come back with specific questions.

Structure of a Binding

The structure of a binding follows the structure of a typical OSGi bundle project.

|- src/main
|------- java             Your Java code
|---------- [...]
|------- tests            It's easy to write unit tests and fellow developers will thank you
|---------- [...]
|- src/main/resources/ESH-INF
|---- binding
|------- binding.xml      Binding name, description, author and other meta data
|---- thing
|------- thing-types.xml  One or more xml files with thing descriptions
|- pom.xml                Build system file: Describe your dependencies here

Every binding needs to define a binding.xml file. Find more information in the respective binding XML reference.

Describing Things

External systems are represented as Things in openHAB. When starting the implementation of a binding, you should think about the abstraction of your external system. Different services or devices should be represented as individual Things. Each functionality of the Thing should be modelled as a Channel.

Thing and Channel structures need to be explained to the openHAB runtime. This is done in a declarative way via XML files, so called ThingTypes and ChannelTypes.

Find more information in the respective Thing & Channel XML reference.

The ThingHandlerFactory

For each Thing the binding must provide a proper ThingHandler implementation that is able to handle the communication.

The ThingHandlerFactory is responsible for creating ThingHandler instances.

Every binding must implement a ThingHandlerFactory and register it as OSGi service so that the runtime knows which class needs to be called for creating and handling things.

When a new Thing is added, the openHAB runtime queries every ThingHandlerFactory for support of the ThingType by calling the supportsThingType method. When the method returns true, the runtime calls createHandler, which should then return a proper ThingHandler implementation.

A weather bindings WeatherHandlerFactory for example supports only one ThingType and instantiates a new WeatherHandler for a given thing:

@NonNullByDefault
@Component(configurationPid = "binding.myweatherbinding", service = ThingHandlerFactory.class)
public class WeatherHandlerFactory extends BaseThingHandlerFactory {
    
    private static final Collection<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(WeatherBindingConstants.THING_TYPE_WEATHER);
    
    @Override
    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
    }

    @Override
    protected @Nullable ThingHandler createHandler(Thing thing) {
        ThingTypeUID thingTypeUID = thing.getThingTypeUID();

        if (WeatherBindingConstants.THING_TYPE_WEATHER.equals(thingTypeUID)) {
            return new WeatherHandler(thing);
        }

        return null;
    }
}

Constants like the THING_TYPE_WEATHER UID and also Channel UIDs are typically defined inside a public BindingConstants class.

Depending on your implementation, each ThingType may use its own handler. It is also possible to use the same handler for different Things, or use different handlers for the same ThingType, depending on the configuration.

The ThingHandler

A ThingHandler handles the communication between openHAB and an entity from the real world, e.g. a physical device, a web service, represented by a Thing.

openHAB provides an abstract base class named BaseThingHandler. It is recommended to use this class, because it covers a lot of common logic. Most of the explanations are based on the assumption, that the binding inherits from the BaseThingHandler in all concrete ThingHandler implementations. Nevertheless if there are reasons why you can not use the base class, the binding can also directly implement the ThingHandler interface.

The communication between the framework and the ThingHandler is bidirectional.

If the framework wants the binding to do something or just notfiy it about changes, it calls methods like handleCommand, handleUpdate or thingUpdated.

If the ThingHandler wants to inform the framework about changes, it uses a callback The BaseThingHandler provides convience methods like updateState, updateStatus updateThing or triggerChannel, that can be used to inform the framework about changes.

The overall structure looks like this:

TODO

Lifecycle

The ThingHandler has two important lifecycle methods: initialize and dispose. The initialize method is called when the handler is started and dispose just before the handler is stopped. Therefore these methods can be used to allocate and deallocate resources. For an example, our exemplary Weather binding starts and stops a scheduled job to update weather information within these methods.

Startup

The startup of a handler is divided in two essential steps:

  1. Handler is registered: ThingHandler instance is created by a ThingHandlerFactory and tracked by the framework. In addition, the handler can be registered as a service if required, e.g. as FirmwareUpdateHandler or ConfigStatusProvider.

  2. Handler is initialized: ThingHandler.initialize() is called by the framework in order to initialize the handler. This method is only called if all 'required' configuration parameters of the Thing are present. The handler is ready to work (methods like handleCommand, handleUpdate or thingUpdated can be called).

The diagram below illustrates the startup of a handler in more detail. The life cycle is controlled by the ThingManager.

thing_life_cycle_startup

The ThingManager mediates the communication between a Thing and a ThingHandler from the binding. The ThingManager creates for each Thing a ThingHandler instance using a ThingHandlerFactory. Therefore, it tracks all ThingHandlerFactorys from the binding.

The ThingManager determines if the Thing is initializable or not. A Thing is considered as initializable if all required configuration parameters (cf. property parameter.required in Configuration Description) are available. If so, the method ThingHandler.initialize() is called.

Only Things with status (cf. Thing Status) UNKNOWN, ONLINE or OFFLINE are considered as initialized by the framework and therefore it is the handler's duty to assign one of these states sooner or later. To achieve that, the status must be reported to the framework via the callback or BaseThingHandler.updateStatus(...) for convenience. Furthermore, the framework expects initialize() to be non-blocking and to return quickly. For longer running initializations, the implementation has to take care of scheduling a separate job which must guarantee to set the status eventually. Also, please note that the framework expects the initialize() method to handle anticipated error situations gracefully and set the thing to OFFLINE with the corresponding status detail (e.g. COMMUNICATION_ERROR or CONFIGURATION_ERROR including a meaningful description) instead of throwing exceptions.

If the Thing is not initializable the configuration can be updated via ThingHandler.handleConfigurationUpdate(Map). The binding has to notify the ThingManager about the updated configuration by a callback. The ThingManager tries to initialize the ThingHandler resp. Thing again.

After the handler is initialized, the handler must be ready to handle methods calls like handleCommand and handleUpdate, as well as thingUpdated.

Shutdown

The shutdown of a handler is also divided in two essential steps:

  1. Handler is unregistered: ThingHandler instance is no longer tracked by the framework. The ThingHandlerFactory can unregister handler services (e.g. FirmwareUpdateHandler or ConfigStatusProvider) if registered, or release resources.

  2. Handler is disposed: ThingHandler.disposed() method is called. The framework expects dispose() to be non-blocking and to return quickly. For longer running disposals, the implementation has to take care of scheduling a separate job.

thing_life_cycle_shutdown

After the handler is disposed, the framework will not call the handler anymore.

Bridge Status Changes

A ThingHandler is notified about Bridge status changes to ONLINE and OFFLINE after a BridgeHandler has been initialized. Therefore, the method ThingHandler.bridgeStatusChanged(ThingStatusInfo) must be implemented (this method is not called for a bridge status updated through the bridge initialization itself). If the Thing of this handler does not have a Bridge, this method is never called.

If the bridge status has changed to OFFLINE, the status of the handled thing must also be updated to OFFLINE with detail BRIDGE_OFFLINE. If the bridge returns to ONLINE, the thing status must be changed at least to OFFLINE with detail NONE or to another thing specific status.

Configuration

Things can be configured with parameters. To retrieve the configuration of a Thing one can call getThing().getConfiguration() inside the ThingHandler. The configuration class has the equivalent methods as the Map interface, thus the method get(String key) can be used to retrieve a value for a given key.

Moreover the configuration class has a utility method as(Class<T> configurationClass) that transforms the configuration into a Java object of the given type.

All configuration values will be mapped to properties of the class. The type of the property must match the type of the configuration. Only the following types are supported for configuration values: Boolean, String and BigDecimal.

For example, the Yahoo Weather binding allows configuration of the location and the refresh frequency.

Properties

Things can have properties. If you would like to add meta data to your thing, e.g. the vendor of the thing, then you can define your own thing properties by simply adding them to the thing type definition. The properties section here explains how to specify such properties.

To retrieve the properties one can call the operation getProperties of the corresponding org.eclipse.smarthome.core.thing.type.ThingType instance. If a thing will be created for this thing type then its properties will be automatically copied into the new thing instance. Therefore the org.eclipse.smarthome.core.thing.Thing interface also provides the getProperties operation to retrieve the defined properties. In contrast to the getProperties operation of the thing type instance the result of the thing´s getProperties operation will also contain the properties updated during runtime (cp. the thing handler documentation).

Handling Commands

For handling commands the ThingHandler interface defines the handleCommand method. This method is called when a command is sent to an item, which is linked to a channel of the Thing. A Command represents the intention that an action should be executed on the external system, or that the state should be changed. Inside the handleCommand method binding specific logic can be executed.

The ThingHandler implementation must be prepared to

  • handle different command types depending on the item types, that are defined by the channels,
  • be called at the same time from different threads.

If an exception is thrown in the method, it will be caught by the framework and logged as an error. So it is better to handle communication errors within the binding and to update the thing status accordingly.

The following code block shows a typical implementation of the handleCommand method:

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
    try {
    	switch (channelUID.getId()) {
	    	case CHANNEL_TEMPERATURE:
	        	if(command instanceof OnOffType.class) {
	        		// binding specific logic goes here
	        		SwitchState deviceSwitchState = convert((OnOffType) command);
	        		updateDeviceState(deviceSwitchState);
	        	}
	        	break;
	    	// ...
    	}
    	statusUpdated(ThingStatus.ONLINE);
	} catch(DeviceCommunicationException ex) {
		// catch exceptions and handle it in your binding
        statusUpdated(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
    }
}

Handling RefreshType Command

If the framework requires the value of a channel, for example after bootup or because a user-interface requested a refreshed value, if will send a RefreshType command.

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
    if (command instanceof RefreshType) {
        
        updateWeatherData();
        
        switch (channelUID.getId()) {
            case CHANNEL_TEMPERATURE:
                updateState(channelUID, getTemperature());
                break;
            case CHANNEL_HUMIDITY:
                [...]
        }
    }
}

In this example, when a RefreshType command is sent to the ThingHandler it updates the weather data by executing an HTTP call in the updateWeatherData method and sends a state update via the updateState method. This will update the state of the Item, which is linked to the channel for the given channel UID.

Updating the Channel State

State updates are sent from the binding to inform the framework, that the state of a channel has been updated. For this the binding developer can call a method from the BaseThingHandler class like this:

updateState("channelId", OnOffType.ON)

The call will be delegated to the framework, which changes the state of all bound items. It is binding specific when the channel should be updated. If the device or service supports an event mechanism the ThingHandler should make use of it and update the state every time when the device changes its state.

Polling for a State

If no event mechanism is available, the binding can poll for the state. The BaseThingHandlerFactory has an accessible ScheduledExecutorService, which can be used to schedule a job. The following code block shows how to start a polling job in the initialize method of a ThingHandler, which runs with an interval of 30 seconds:

@Override
public void initialize() {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // execute some binding specific polling code
        }
    };
    pollingJob = scheduler.scheduleAtFixedDelay(runnable, 0, 30, TimeUnit.SECONDS);
}

Of course, the polling job must be cancelled in the dispose method:

@Override
public void dispose() {
    pollingJob.cancel(true);
}

Even if the state has not changed since the last update, the binding should inform the framework, because it indicates that the value is still present.

Trigger a channel

The binding can inform the framework, that a channel has been triggered. For this the binding developer can call a method from the BaseThingHandler class like this:

triggerChannel("channelId")

If an event payload is needed, use the overloaded version:

triggerChannel("channelId", "PRESSED")

The call will be delegated to the framework. It is binding specific when the channel should be triggered.

Updating the Thing Status

The ThingHandler must also manage the thing status (see also: Thing Status Concept). If the device or service is not working correctly, the binding should change the status to OFFLINE and back to ONLINE, if it is working again. The status can be updated via an inherited method from the BaseThingHandler class by calling:

updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR);

The second argument of the method takes a ThingStatusDetail enumeration value, which further specifies the current status situation. A complete list of all thing statuses and thing status details is listed in the Thing Status chapter.

The binding should also provide additional status description, if available. This description might contain technical information (e.g. an HTTP status code, or any other protocol specific information, which helps to identify the current problem):

updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "HTTP 403 - Access denied");

After the thing is created, the framework calls the initialize method of the handler. At this time the state of the thing is INTIALIZING as long as the binding sets it to something else. Because of this the default implementation of the initialize() method in the BaseThingHandler just changes the status to ONLINE.

Note: A binding should not set any other state than ONLINE, OFFLINE and UNKNOWN. Additionally, REMOVED must be set after handleRemoval() has completed the removal process. All other states are managed by the framework.

Furthermore bindings can specify a localized description of the thing status by providing the reference of the localization string, e.g @text/rate_limit. The corresponding handler is able to provide placeholder values as a JSON-serialized array of strings:

&#64;text/rate_limit ["60", "10", "@text/hour"]
rate_limit=Device is blocked by remote service for {0} minutes.
Maximum limit of {1} configuration changes per {2} has been exceeded.
For further info please refer to device vendor.

Some bindings might want to start specific functionality for a channel only if an item is linked to the channel. The ThingHandler has two callback methods channelLinked(ChannelUID channelUID) and channelUnlinked(ChannelUID channelUID), which are called for every link that is added or removed to/from a channel. So please be aware of the fact that both methods can be called multiple times.

The channelLinked method is only called, if the thing handler has been initialized (status ONLINE/OFFLINE/UNKNOWN). To actively check if a channel is linked, you can use the isChannelLinked(ChannelUID channelUID) method of the ThingHandlerCallback.

Updating the Thing from a Binding

It can happen that the binding wants to update the configuration or even the whole structure of a thing. If the BaseThingHandler class is used, it provides some helper methods for modifying the thing.

Updating the Configuration

Usually the configuration is maintained by the user and the binding is informed about the updated configuration. But if the configuration can also be changed in the external system, the binding should reflect this change and notify the framework about it.

If the configuration should be updated, then the binding developer can retrieve a copy of the current configuration by calling editConfiguration(). The updated configuration can be stored as a whole by calling updateConfiguration(Configuration).

Suppose that an external system causes an update of the configuration, which is read in as a DeviceConfig instance. The following code shows how to update configuration:

protected void deviceConfigurationChanged(DeviceConfig deviceConfig) {
    Configuration configuration = editConfiguration();
    configuration.put("parameter1", deviceConfig.getValue1());
    configuration.put("parameter2", deviceConfig.getValue2());
    updateConfiguration(configuration);
}

Updating Thing Properties

Thing properties can be updated in the same way as the configuration. The following example shows how to modify two properties of a thing:

protected void devicePropertiesChanged(DeviceInfo deviceInfo) {
	Map<String, String> properties = editProperties();
    properties.put(Thing.PROPERTY_SERIAL_NUMBER, deviceInfo.getSerialNumber());
    properties.put(Thing.PROPERTY_FIRMWARE_VERSION, deviceInfo.getFirmwareVersion());
    updateProperties(properties);
}

If only one property must be changed, there is also a convenient method updateProperty(String name, String value). Both methods will only inform the framework that the thing was modified, if at least one property was added, removed or updated.

Thing handler implementations must not rely on properties to be persisted as not all providers support that.

Updating the Thing Structure

The binding also has the possibility to change the thing structure by adding or removing channels. The following code shows how to use the ThingBuilder to add one channel to the thing:

protected void thingStructureChanged() {
    ThingBuilder thingBuilder = editThing();
    Channel channel = ChannelBuilder.create(new ChannelUID("bindingId:type:thingId:1"), "String").build();
    thingBuilder.withChannel(channel);
    updateThing(thingBuilder.build());
}

Handling Thing Updates

If the structure of a thing has been changed during runtime (after the thing was created), the binding is informed about this change in the ThingHandler within the thingUpdated method. The BaseThingHandler has a default implementation for this method:

@Override
public void thingUpdated(Thing thing) {
    dispose();
    this.thing = thing;
    initialize();
}

If your binding contains resource-intensive logic in your initialize method, you should think of implementing the method by yourself and figuring out what is the best way to handle the change.

For configuration updates, which are triggered from the binding, like in the previous three section, the framework does not call the thingUpdated method to avoid infinite loops.

Bridges

In the domain of an IoT system there are often hierarchical structures of devices and services. For example, one device acts as a gateway that enables communication with other devices that use the same protocol. In openHAB this kind of device or service is called Bridge. Philips Hue is one example of a system that requires a bridge. The Hue gateway is an IP device with an HTTP API, which communicates over the ZigBee protocol with the Hue bulbs. In the openHAB model the Hue gateway is represented as a Bridge with connected Things, that represent the Hue bulbs. Bridge inherits from Thing, so that it also has Channels and all other features of a thing, with the addition that it also holds a list of things.

We have a FAQ, dicussing Thing, Bridge and Channel modelling.

When implementing a binding with Bridges, the logic to communicate with the external system is often shared between the different ThingHandler implementations. In that case it makes sense to implement a handler for the Bridge and delegate the actual command execution from the ThingHandler to the BridgeHandler. To access the BridgeHandler from the ThingHandler, call getBridge().getHandler()

The following excerpt shows how the HueLightHandler delegates the command for changing the light state to the HueBridgeHandler:

@Override
public void handleCommand(ChannelUID channelUID, Command command) {

    HueBridgeHandler hueBridgeHandler = (HueBridgeHandler) getBridge().getHandler();

    switch (channelUID.getId()) {
        case CHANNEL_ID_COLOR_TEMPERATURE:
            StateUpdate lightState = lightStateConverter.toColorLightState(command);    
            hueBridgeHandler.updateLightState(getLight(), lightState);
            break;    
        case CHANNEL_ID_COLOR: 
            // ...
    }
}

Inside the BridgeHandler the list of Things can be retrieved via the getThings() call.

Bridge Handler Implementation

A BridgeHandler handles the communication between the openHAB framework and a bridge (a device that acts as a gateway to enable the communication with other devices) represented by a Bridge instance.

A bridge handler has the same properties as thing handler. Therefore, the BridgeHandler interface extends the ThingHandler interface.

The BaseBridgeHandler

openHAB provides an abstract implementation of the BridgeHandler interface named BaseBridgeHandler. It is recommended to use this class, because it covers a lot of common logic.

Life cycle

A BridgeHandler has the same life cycle than a ThingHandler (created by a ThingHandlerFactory, well defined life cycle by handler methods initialize() and dispose(), see chapter Life Cycle). A bridge acts as a gateway in order to provide access to other devices, the child things. Hence, the life cycle of a child handler depends on the life cycle of a bridge handler. Bridge and child handlers are subject to the following restrictions:

  • A BridgeHandler of a bridge is initialized before ThingHandlers of its child things are initialized.
  • A BridgeHandler is disposed after all ThingHandlers of its child things are disposed.

Handler initialization notification

A BridgeHandler is notified about the initialization and disposal of child things. Therefore, the BridgeHandler interface provides the two methods childHandlerInitialized(ThingHandler, Thing) and childHandlerDisposed(ThingHandler, Thing).

These methods can be used to allocate and deallocate resources for child things.

Config Status Provider

Each entity that has a configuration can provide its current configuration status to provide further information, especially in an error case.

This information is available to user-interfaces to present configuration errors to the user.

For this purpose the handler of the entity implements the interface org.eclipse.smarthome.config.core.status.ConfigStatusProvider.

Providing the Configuration Status

A ThingHandler as handler for the thing entity can provide the configuration status of the thing by implementing the org.eclipse.smarthome.config.core.status.ConfigStatusProvider interface.

For things that are created by sub-classes of the BaseThingHandlerFactory the provider is already automatically registered as an OSGi service if the concrete thing handler implements the configuration status provider interface. Currently the framework provides two base thing handler implementations for the configuration status provider interface:

  • org.eclipse.smarthome.core.thing.binding.ConfigStatusThingHandler extends the BaseThingHandler and is to be used if the configuration status is to be provided for thing entities
  • org.eclipse.smarthome.core.thing.binding.ConfigStatusBridgeHandler extends the BaseBridgeHandler and is to be used if the configuration status is to be provided for bridge entities

Sub-classes of these handlers must only override the operation getConfigStatus to provide the configuration status in form of a collection of org.eclipse.smarthome.config.core.status.ConfigStatusMessages.

Internationalizing

The framework will take care of internationalizing messagess.

For this purpose there must be an i18n properties file inside the bundle of the configuration status provider that has a message declared for the message key of the ConfigStatusMessage. The actual message key is built by the operation withMessageKeySuffix(String) of the message´s builder in the manner that the given message key suffix is appended to config-status."config-status-message-type.".

As a result depending on the type of the message the final constructed message keys are:

  • config-status.information.any-suffix
  • config-status.warning.any-suffix
  • config-status.error.any-suffix
  • config-status.pending.any-suffix

Handling Thing / Bridge Removal

If a thing should be removed, the framework informs the binding about the removal request by calling handleRemoval at the thing/bridge handler.

The thing will not be removed from the runtime until the binding confirms the deletion by setting the thing status to REMOVED. If no special removal handling is required by the binding, you do not have to care about removal because the default implementation of this method in the BaseThingHandler class just calls updateStatus(ThingStatus.REMOVED).

However, for some radio-based devices it is needed to communicate with the device in order to unpair it safely. After the device was successfully unpaired, the binding must inform the framework that the thing was removed by setting the thing status to REMOVED.

After the removal was requested (i.e. the thing is in REMOVING state), it cannot be changed back anymore to ONLINE/OFFLINE/UNKNOWN by the binding. The binding may only initiate the status transition to REMOVED.

Actions bound to a Thing

Quite often the device or service you expose via openHAB Things allows certain actions to be performed.

Examples are:

  • Reboot / Restart device
  • Start searching for new lights for a Hue lights bridge
  • Send message (via E-Mail / SMS Gateway service / Instant Messanger)

If you implement the ThingActions interface, you can tell the framework about your Thing related actions.

Please note that for actions not related to Things you will instead implement an ActionHandler as described in the Module Development chapter.

You start things off by implementing ThingActions and annotate your class with @ThingActionsScope:

@ThingActionsScope(name = "mqtt") // Your bindings id is usually the scope
@NonNullByDefault
public class MQTTActions implements ThingActions {
    private @Nullable AbstractBrokerHandler handler;

    @Override
    public void setThingHandler(@Nullable ThingHandler handler) { handler = (AbstractBrokerHandler) handler; }

    @Override
    public @Nullable ThingHandler getThingHandler() { return handler; }
}

The second step is to return this class in your Thing handlers getServices() method:

public class MyThingHandler extends BaseThingHandler {
    ...
    @Override
    public Collection<Class<? extends ThingHandlerService>> getServices() {
        return Collections.singleton(MQTTActions.class);
    }
}

As you can see in the above MqttActions implementation, the framework will call you back with the ThingHandler.

You are now free to specify as many actions as you want in MqttActions.

In the following example we provide a "publishMQTT" action. An action must be annotated with @RuleAction, a label and a description must be provided. In this case we refer to translation, see i18n support, instead of directly providing a string.

@RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
    public void publishMQTT(
            @ActionInput(name = "topic", label = "@text/actionInputTopicLabel", description = "@text/actionInputTopicDesc") @Nullable String topic,
            @ActionInput(name = "value", label = "@text/actionInputValueLabel", description = "@text/actionInputValueDesc") @Nullable String value) {
        ...
    }

    public static void publishMQTT(@Nullable ThingActions actions, @Nullable String topic, @Nullable String value) {
        if (actions instanceof MQTTActions) {
            ((MQTTActions) actions).publishMQTT(topic, value);
        } else {
            throw new IllegalArgumentException("Instance is not an MQTTActions class.");
        }
    }

Each member method also requires a static method with the same name. This is to support the old DSL rules engine and make the action available there.

Each parameter of an action member method must be annotated with @ActionInput.

If you return values, you do so by returning a Map<String,Object> and annotate the method itself with as many @ActionOutputs as you will return map entries.

Firmware information / Firmware update

TODO

Implementing a Discovery Service

Bindings can implement the DiscoveryService interface and register it as an OSGi service to inform the framework about devices and services, that can be added as things to the system (see also Inbox & Discovery Concept).

A discovery service provides discovery results. The following table gives an overview about the main parts of a DiscoveryResult:

Field Description
thingUID The thingUID is the unique identifier of the specific discovered thing (e.g. a device's serial number). It must not be constructed out of properties, that can change (e.g. IP addresses). A typical thingUID could look like this: hue:bridge:001788141f1a
thingTypeUID Contrary to the thingUID is the thingTypeUID that specifies the type the discovered thing belongs to. It could be constructed from e.g. a product number. A typical thingTypeUID could be the following: hue:bridge.
bridgeUID If the discovered thing belongs to a bridge, the bridgeUID contains the UID of that bridge.
properties The properties of a DiscoveryResult contain the configuration for the newly created thing.
label The human readable representation of the discovery result. Do not put IP/MAC addresses or similar into the label but use the special representationProperty instead.
representationProperty The name of one of the properties which discriminates the discovery result best against other results of the same type. Typically this is a serial number, IP or MAC address. The representationProperty often matches a configuration parameter and is also explicitly given in the thing-type definition.

To simplify the implementation of own discovery services, an abstract base class AbstractDiscoveryService implements the DiscoveryService, that must only be extended. Subclasses of AbstractDiscoveryService do not need to handle the DiscoveryListeners themselves, they can use the methods thingDiscovered and thingRemoved to notify the registered listeners. Most of the descriptions in this chapter refer to the AbstractDiscoveryService.

For UPnP and mDNS there already are generic discovery services available. Bindings only need to implement a UpnpDiscoveryParticipant resp. mDNSDiscoveryParticipant. For details refer to the chapters UPnP Discovery and mDNS Discovery.

The following example is taken from the HueLightDiscoveryService, it calls thingDiscovered for each found light. It uses the DiscoveryResultBuilder to create the discovery result.

    private void onLightAddedInternal(FullLight light) {
        ThingUID thingUID = getThingUID(light);
        if (thingUID != null) {
            ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
            Map<String, Object> properties = new HashMap<>(1);
            properties.put(LIGHT_ID, light.getId());
            DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
                    .withBridge(bridgeUID).withLabel(light.getName()).build();
            thingDiscovered(discoveryResult);
        } else {
            logger.debug("discovered unsupported light of type '{}' with id {}", light.getModelID(), light.getId());
        }
    }

The discovery service needs to provide the list of supported thing types, that can be found by the discovery service. This list will be given to the constructor of AbstractDiscoveryService and can be requested by using DiscoveryService#getSupportedThingTypes method.

Registering as an OSGi service

The Discovery class of a binding which implements AbstractDiscoveryService should be annotated with

@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.<bindingID>")

where <bindingID> is the id of the binding, i.e. astro for the Astro binding. Such a registered service will be picked up automatically by the framework.

Background Discovery

If the implemented discovery service enables background discovery, the AbstractDiscoveryService class automatically starts it. If background discovery is enabled, the framework calls AbstractDiscoveryService#startBackgroundDiscovery when the binding is activated and AbstractDiscoveryService#stopBackgroundDiscovery when the component is deactivated. The default implementations of both methods are empty and could be overridden by the binding developer. Depending on the concrete implementation the discovery service might start and stop a scheduler in these method or register a listener for an external protocol. The thingDiscovered method can be used to notify about a newly discovered thing.

The following example shows the implementation of the above mentioned methods in the Wemo binding.

    @Override
    protected void startBackgroundDiscovery() {
        logger.debug("Start WeMo device background discovery");
        if (wemoDiscoveryJob == null || wemoDiscoveryJob.isCancelled()) {
            wemoDiscoveryJob = scheduler.scheduleWithFixedDelay(wemoDiscoveryRunnable, 0, refreshInterval, TimeUnit.SECONDS);
        }
    }

    @Override
    protected void stopBackgroundDiscovery() {
        logger.debug("Stop WeMo device background discovery");
        if (wemoDiscoveryJob != null && !wemoDiscoveryJob.isCancelled()) {
            wemoDiscoveryJob.cancel(true);
            wemoDiscoveryJob = null;
        }
    }

Active Scan

If the user triggers an active scan for a binding or specific set of thing types, the method startScan of each discovery service which supports these thing type is called. Within these methods the things can be discovered. The abstract base class automatically starts a thread, so the implementation of this method can be long-running.

The following example implementation for startScan is taken from the HueLightDiscoveryService, that triggers a scan for known and also for new lights of the hue bridge. Already discovered things are identified by the ThingUID the DiscoveryResult was created with, and won't appear in the inbox again.

    @Override
    public void startScan() {
        List<FullLight> lights = hueBridgeHandler.getFullLights();
        if (lights != null) {
            for (FullLight l : lights) {
                onLightAddedInternal(l);
            }
        }
        // search for unpaired lights
        hueBridgeHandler.startSearch();
    }

Re-Discovered Results and Things

The getThingUID method of the discovery service should create a consistent UID every time the same thing gets discovered. This way existing discovery results and existing things with this UID will be updated with the properties from the current scan. With this, dynamic discoveries (like UPnP or mDNS) can re-discover existing things and update communication properties like host names or TCP ports.

Remove older results

Normally, older discovery results already in the inbox are left untouched by a newly triggered scan. If this behavior is not appropriate for the implemented discovery service, one can override the method stopScan to call removeOlderResults as shown in the following example from the Hue binding:

    @Override
    protected synchronized void stopScan() {
        super.stopScan();
        removeOlderResults(getTimestampOfLastScan());
    }

UPnP Discovery

UPnP discovery is implemented in the framework as UpnpDiscoveryService. It is widely used in bindings. To facilitate the development, binding developers only need to implement a UpnpDiscoveryParticipant. Here the developer only needs to implement three simple methods:

  • getSupportedThingTypeUIDs - Returns the list of thing type UIDs that this participant supports. The discovery service uses this method of all registered discovery participants to return the list of currently supported thing type UIDs.
  • getThingUID - Creates a thing UID out of the UPnP result or returns null if this is not possible. This method is called from the discovery service during result creation to provide a unique thing UID for the result.
  • createResult - Creates the DiscoveryResult out of the UPnP result. This method is called from the discovery service to create the actual discovery result. It uses the getThingUID method to create the thing UID of the result.

The following example shows the implementation of the UPnP discovery participant for the Hue binding, the HueBridgeDiscoveryParticipant.

public class HueBridgeDiscoveryParticipant implements UpnpDiscoveryParticipant {

    @Override
    public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
        return Collections.singleton(THING_TYPE_BRIDGE);
    }

    @Override
    public DiscoveryResult createResult(RemoteDevice device) {
        ThingUID uid = getThingUID(device);
        if (uid != null) {
            Map<String, Object> properties = new HashMap<>(2);
            properties.put(HOST, device.getDetails().getBaseURL().getHost());
            properties.put(SERIAL_NUMBER, device.getDetails().getSerialNumber());

            DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
                    .withLabel(device.getDetails().getFriendlyName()).withRepresentationProperty(SERIAL_NUMBER).build();
            return result;
        } else {
            return null;
        }
    }

    @Override
    public ThingUID getThingUID(RemoteDevice device) {
        DeviceDetails details = device.getDetails();
        if (details != null) {
            ModelDetails modelDetails = details.getModelDetails();
            if (modelDetails != null) {
                String modelName = modelDetails.getModelName();
                if (modelName != null) {
                    if (modelName.startsWith("Philips hue bridge")) {
                        return new ThingUID(THING_TYPE_BRIDGE, details.getSerialNumber());
                    }
                }
            }
        }
        return null;
    }
}

mDNS Discovery

mDNS discovery is implemented in the framework as MDNSDiscoveryService. To facilitate the development, binding developers only need to implement a MDNSDiscoveryParticipant. Here the developer only needs to implement four simple methods:

  • getServiceType - Defines the mDNS service type.
  • getSupportedThingTypeUIDs - Returns the list of thing type UIDs that this participant supports. The discovery service uses this method of all registered discovery participants to return the list of currently supported thing type UIDs.
  • getThingUID - Creates a thing UID out of the mDNS service info or returns null if this is not possible. This method is called from the discovery service during result creation to provide a unique thing UID for the result.
  • createResult - Creates the DiscoveryResult out of the UPnP result. This method is called from the discovery service to create the actual discovery result. It uses the getThingUID method to create the thing UID of the result.

Discovery that is bound to a Thing

TODO

Frequently asked questions / FAQ

Various binding related questions are answered in our Binding development FAQ.

Include the Binding in the Build

Once you are happy with your implementation, you need to integrate it in the Maven build and add it to the official distro.

  • Add a new line in the binding pom.xml at the alphabetically correct position.
  • Furthermore add it to the feature.xml, again at the alphabetically correct position.
  • If you have a dependency on a transport bundle (e.g. upnp, mdns or serial) or an external library, make sure to add a line for this dependency as well (see the other bindings as an example)
  • Add your binding to the CODEOWNERS at the alphabetically correct position. This is so that you get notified by Github when someone adds a pull request towards your binding and hopefully can assist in reviewing that.

Before you create a pull request on GitHub, you should now run

mvn clean install

from the repository root to ensure that the build works smoothly (that step takes about 30 minutes).

The build includes Tooling for static code analysis that will validate your code against the openHAB Coding Guidelines and some additional best practices. Please fix all the priority 1 issues and all issues with priority 2 and 3 that are relevant (if you have any doubt don't hesitate to ask).

You can always run the above command from within your bindings directory to speed the build up and fix and check reported errors.

Re-run the build to confirm that the checks are passing. If it does, it is time to contribute your work!