This page is part of archived documentation for openHAB 3.3. Go to the current stable version

# 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

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. The following types are supported for configuration values: Boolean, boolean, String, BigDecimal, int, long, float and double.

# 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.openhab.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.openhab.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() {
    pollingJob = scheduler.scheduleWithFixedDelay(this::pollingCode, 0, 30, TimeUnit.SECONDS);
}

private void pollingCode() {
    // execute some binding specific polling code
}

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

@Override
public void dispose() {
    final job = pollingJob;
    if (job != null) {
        job.cancel(true);
        pollingJob = null;
    }
}

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.openhab.core.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.openhab.core.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.openhab.core.thing.binding.ConfigStatusThingHandler extends the BaseThingHandler and is to be used if the configuration status is to be provided for thing entities
  • org.openhab.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.openhab.core.config.core.status.ConfigStatusMessages.

# Internationalization of Config Status Messages

The framework will take care of internationalizing messages.

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 developing Module Types 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) { this.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).

Note

There must still be the ability to configure the binding via text files (.things and .items), also if you implement an automatic discovery of Things.

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 or configuration parameters, which best discriminates the result from other results of the same type. See chapter Representation Property below.

To simplify the implementation of custom discovery services, an abstract base class AbstractDiscoveryService implements the DiscoveryService and just needs to 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.

# Representation Property

The name of one of the properties or configuration parameters, which best discriminates the discovery result from other results of the same type. Typically this is a serial number, or an IP or MAC address. The representation property is used to auto-ignore discovery results of Things that already exist in the system. This can happen, a) if a Thing has been created manually, or b) if the Thing has been discovered separately by two mechanisms e.g. by mDNS, and by NetBios, or UPnP. If a new Thing goes online, the auto-ignore service of the inbox checks if the inbox already contains a discovery result of the same type where the existing representation property is identical to the representation property of the newly discovered Thing. If this is the case, the Thing in the inbox is automatically ignored. The representation property must be declared in the thing-types.xml

When comparing representation properties, the framework checks for matches between the representation property of the newly discovered Thing, and both the properties and the configuration parameters of existing Things.

If defining a representation property for a bridge, the representation property does not need to be globally unique, but only unique within the context of the bridge, so long as the discovery service calls .withBridge(bridgeUID) when building the DiscoveryResult. e.g. if bridge A. has child Things with representation properties of 1, 2, and 3, and bridge B. also has child Things with representation properties of 1, 2, and 3, they will not conflict.

DiscoveryResult result = DiscoveryResultBuilder.create(thingUID)
  .withProperty("uniqueId", nonUniquePropertyValue)
  .withBridge(bridgeUID) // bridgeUID plus nonUniquePropertyValue are unique
  .withRepresentationProperty("uniqueId")
  .build();

Furthermore, if a Thing has two configuration parameters where each individually is not globally unique, but the combination of the two is unique, one can define an extra property that combines the two:

String cfgParamValA = "value-of-non-unique-config-param-A";
String cfgParamValB = "value-of-non-unique-config-param-B";
String uniquePropVal = String.format("%s-%s", cfgParamValA, cfgParamValB);
...
DiscoveryResult hub = DiscoveryResultBuilder.create(thingUID)
  .withProperty("uniqueId", uniquePropVal)
  .withRepresentationProperty("uniqueId")
  .build();

If Things are created manually, the property or configuration parameter that will match the auto discovery representation property must be set. In the case that a property will be used to match the representation property its value must be set in the Thing handler's initialize() method:

updateProperty("uniqueId", uniquePropVal);

Alternatively in the case that a configuration parameter will be used to match the auto discovery representation property, the parameter must be declared in either, a) the thing-types.xml file, or b) the config-description XML file. And it must also be declared in the Thing handler's Configuration class:

public class MyThingConfiguration {
    public String uniqueId;
}

# 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.<binding-id>")

where <binding-id> 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.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.

# Ending an Active Scan

As described above an active scan is initiated via startScan. There is no explicit end to an active scan and discovery results can be provided even after startScan completes (e.g. from a separate thread). If you would like assistance with enforcing a scan end pass a timeout to the AbstractDiscoveryService constructor. stopScan will then be called on your discovery service upon timeout expiration allowing you to stop your scan however needed. If a timeout is specified the scan will be considered active until the timeout expires even if startScan completed beforehand. In particular UIs the scan will be shown as in progress throughout the timeout. If you override stopScan don't forget to call super.stopScan as AbstractDiscoveryService performs some cleanup in its version. If the timeout is set to 0 stopScan will not be called.

# 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());
    }

# Internationalization of Discovery result labels

The framework will take care of internationalizing labels of discovery results if you extend the AbstractDiscoveryService. See i18n for more information.

Hint!

To make it work you have to inject references to the LocaleProvider and the TranslationProvider services into your implementation. The AbstractDiscoveryService already provides protected properties, which are not yet linked to a service. The devoloper has to take care about that.

    protected @NonNullByDefault({}) TranslationProvider i18nProvider;
    protected @NonNullByDefault({}) LocaleProvider localeProvider;

# 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, and may optionally implement a fourth:

  • 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.
  • getRemovalGracePeriodSeconds (OPTIONAL) - Returns an additional grace period delay in seconds before the device will be removed from the Inbox. This method is called when the discovery service is about to remove a Thing from the Inbox. Some bindings handle devices that can sometimes be a bit late in sending their 'ssdp:alive' notifications even though they have not really gone offline. This means that the device is repeatedly removed from, and (re)added to, the Inbox. To prevent this, a binding may OPTIONALLY implement this method to specify an additional delay period (grace period) to wait before the device is removed from the Inbox.

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;
    }
}

The following is an example of how to implement the OPTIONAL getRemovalGracePeriodSeconds method.

@Component(configurationPid = "discovery.hue")
public class HueBridgeDiscoveryParticipant implements UpnpDiscoveryParticipant {

    private long removalGracePeriodSeconds = 15;

    @Activate
    public void activate(@Nullable Map<String, Object> configProperties) {
        updateRemovalGracePeriod(configProperties);
    }

    @Modified
    public void modified(@Nullable Map<String, Object> configProperties) {
        updateRemovalGracePeriod(configProperties);
    }

    private void updateRemovalGracePeriod(Map<String, Object> configProperties) {
        if (configProperties != null) {
            Object value = configProperties.get(HueBindingConstants.REMOVAL_GRACE_PERIOD);
            if (value != null) {
                try {
                    removalGracePeriodSeconds = Integer.parseInt(value.toString());
                } catch (NumberFormatException e) {
                    logger.warn("Configuration property '{}' has invalid value: {}",
                            HueBindingConstants.REMOVAL_GRACE_PERIOD, value);
                }
            }
        }
    }

    @Override
    public long getRemovalGracePeriodSeconds(ServiceInfo serviceInfo) {
        return removalGracePeriodSeconds;
    }
}

# 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 (opens new window).
  • 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 mDNS 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.
  • getRemovalGracePeriodSeconds (OPTIONAL) - Returns an additional grace period delay in seconds before the device will be removed from the Inbox. This method is called when the discovery service is about to remove a Thing from the Inbox. Some bindings handle devices that can sometimes be a bit late in sending their mDNS notifications even though they have not really gone offline. This means that the device is repeatedly removed from, and (re)added to, the Inbox. To prevent this, a binding may OPTIONALLY implement this method to specify an additional delay period (grace period) to wait before the device is removed from the Inbox. See the example code for the getRemovalGracePeriodSeconds() method under the "UPnP Discovery" chapter above.

# Discovery that is bound to a Bridge

When the discovery process is dependent on a configured bridge the discovery service must be bound to the bridge handler. Binding additional services to a handler can be achieved by implementing the service as a ThingHandlerService.

Instead of using the Component annotation your discovery service implements the ThingHandlerService. It should extend the AbstractDiscoveryService (which implements DiscoveryService) just like a normal service:

public class <your binding bridge DiscoveryService> extends AbstractDiscoveryService
        implements ThingHandlerService {

The interface ThingHandlerService has 2 methods to pass the handler of the bridge. A typical implementation is:

    @Override
    public void setThingHandler(@Nullable ThingHandler handler) {
        if (handler instanceof <your binding handler>) {
            bridgeHandler = (<your binding handler>) handler;
        }
    }

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

The setThingHandler is called by the openHAB framework and give you access to the binding bridge handler. The handler can be used to get the bridge UID or to get access to the configured device connected to the bridge handler.

In the bridge handler you need to activate the thing handler service. This is done by implementing the getServices method in your bridge handler:

    @Override
    public Collection<Class<? extends ThingHandlerService>> getServices() {
        return Collections.singleton(<your binding bridge DiscoveryService>.class);
    }

# 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 bundle pom.xml (opens new window).
  • Add a new line in the binding pom.xml (opens new window).
  • 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 in the /src/main/feature/feature.xml file in your binding folder. See the other bindings as an example.
  • Add your binding to the CODEOWNERS (opens new window) file so that you get notified by Github when someone adds a pull request towards your binding.

Please make sure you add the above entries at their alphabetically correct position!

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 (opens new window) 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). to

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

mvn spotless:apply

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

# Add your binding's logo to the openHAB website

After your pull request has been merged and the next openHAB version is released, your binding will be available in the addons search on the openHAB website with a default logo.

You can upload a logo to display it on the openhab.org (opens new window) start page, the addon search and in the readme.

These are the requirements for logos:

  • PNG (transparancy is preferred)
  • 512x512 pixels or smaller in one dimension, if it's not a square logo
  • Less than 30kB

File size is key as the website displays hundreds of small logos on the same page. To shrink the file size, save your logo with Palette-Based Colors (sometimes called "Indexed-RGBA"). Also, JPEG compression artifacts from prior conversions or halo around the logo increases file size dramatically. There are online converters to convert your True Color PNG logo to Palette-Based Colors. E.g. https://compresspng.com/ (opens new window). Or use zopflipng: zopflipng -m --filters=0me --lossy_8bit --lossy_transparent -y logo.png logo.png

After your binding's pull request has been merged, you can upload your logo by filing another pull request to the openhab-docs/images/addons/ (opens new window) repository. Your logo will be available after the next website build.