Rewrite developer section (#942)

* Rewrite developer section
* More work on module types
* More module-type stuff
* Adapt vuepress files
* Write binding doc
* Add TODOs for missing topics
* Add all missing topics from ESH.
* Tests
* Audio extensions
* Utilities
* i18n
* Event-bus
* Small fixes
* Command Description
* Add some tocs
* Add concept drawing to binding dev.
* Update developers/osgi/configadmin.md
Co-Authored-By: davidgraeff <david.graeff@web.de>
* Update administration/logging.md
Co-Authored-By: davidgraeff <david.graeff@web.de>
* Update developers/bindings/thing-xml.md
Co-Authored-By: davidgraeff <david.graeff@web.de>
* Convert HTML tables to Markdown
Signed-off-by: Hakan Tandogan <hakan@tandogan.com>
* Resolved one line per sentence review parts.
Signed-off-by: Jerome Luckenbach <github@luckenba.ch>
* Update developers/index.md
* Update developers/buildsystem.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Add persistence placeholder. Address review comments
* fix persistence subdirectory
Signed-off-by: David Graeff <david.graeff@web.de>
* Update developers/audio/index.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/guidelines.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/contributing.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/buildsystem.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/guidelines.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/index.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/index.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/osgi/osgids.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/transformations/index.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Update developers/ioservices/index.md
Co-Authored-By: davidgraeff <davgraeff@gmail.com>
* Remove extensionservices (for now ;)
Signed-off-by: David Graeff <david.graeff@web.de>
* Fix typos
Signed-off-by: David Graeff <david.graeff@web.de>
* Added example code for 'DynamicCommandDescriptionProvider' and 'DynamicCommandDescriptionProvider'
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
* Added example code for 'ThingHandlerFactory'
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
* Applied review comments
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
pull/958/head
David Gräff 2019-05-02 20:52:37 +02:00 committed by Jerome Luckenbach
parent e0b4723789
commit 64e3d99edd
55 changed files with 5181 additions and 1378 deletions

View File

@ -108,17 +108,32 @@ module.exports = [
]
},
{
title: 'Developer Guide',
title: 'Addon Development',
collapsable: false,
children: [
['developer/', 'Development Overview'],
'developer/contributing/contributing',
'developer/contributing/governance',
'developer/development/ide',
'developer/development/guidelines',
'developer/development/bindings',
'developer/development/logging',
'developer/development/compatibilitylayer',
['developer/', 'Overview & Introduction'],
'developer/guidelines',
'developer/bindings/',
'developer/module-types/',
'developer/transformations/',
'developer/ioservices/',
'developer/persistence/',
'developer/audio/',
]
},
{
title: 'Additional Developer Information',
collapsable: false,
children: [
'developer/buildsystem',
'developer/osgi/osgi',
'developer/utils/tools',
'developer/utils/i18n',
'developer/utils/events',
'developer/tests',
'developer/contributing',
'developer/governance',
'developer/legacy/compatibilitylayer',
]
},
]

View File

@ -128,17 +128,22 @@ def process_main_docs(docs_source_dir)
puts ">>> Migrating the Developer section"
process_file("#{docs_source_dir}/developers", "index.md", "docs/developer", "#{$docs_repo_root}/developer/index.md")
["prerequisites", "development", "contributing"].each { |subsection|
Dir.glob("#{docs_source_dir}/developers/*.md") { |path|
file = File.basename(path)
puts " -> #{file}"
process_file("#{docs_source_dir}/developers", file, "docs/developer", "#{$docs_repo_root}/developer/#{file}")
}
["audio", "bindings", "ioservices", "legacy", "module-types", "osgi", "persistence", "transformations", "utils"].each { |subsection|
Dir.glob("#{docs_source_dir}/developers/#{subsection}/*.md") { |path|
file = File.basename(path)
puts " -> #{subsection}/#{file}"
process_file("#{docs_source_dir}/developers/#{subsection}", file, "docs/developer/#{subsection}", "#{$docs_repo_root}/developer/#{subsection}/#{file}")
}
if subsection != "contributing" then
puts " -> #{subsection}/images"
FileUtils.cp_r("#{docs_source_dir}/developers/#{subsection}/images", "docs/developer/#{subsection}")
end
}
puts " -> images"
FileUtils.cp_r("#{docs_source_dir}/developers/bindings/images", "docs/developer/bindings/images")
FileUtils.cp_r("#{docs_source_dir}/developers/legacy/images", "docs/developer/legacy/images")
FileUtils.cp_r("#{docs_source_dir}/developers/osgi/images", "docs/developer/osgi/images")
end

View File

@ -172,3 +172,129 @@ log4j2.appender.ZWave.policies.size.size = 10MB
log4j2.appender.ZWave.strategy.type = DefaultRolloverStrategy
log4j2.appender.ZWave.strategy.max = 10
```
## Logback Configuration File
In order to define custom log patterns, log to network sockets and so on we can prepare a logging configuration file.
There are several things, that you might want to change in the configuration:
- the log level for a logger;
- the pattern of an appender;
- redirect the log to a text file.
The configuration file for openHAB is placed in the [openhab-distro/lauch/home/logback_debug.xml](https://github.com/openhab/openhab-distro/blob/master/launch/home/logback_debug.xml) file. We have added a few comments on this file in order to attract your attention on some significant points:
```xml
<configuration scan="true">
<!-- This is the appender that displays the logging on the console -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- This pattern will display the time, the log level, the name of the logger and the log message -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%-30.30logger{36}:%-5line] - %msg%ex{10}%n</pattern>
</encoder>
</appender>
<!-- Another appender that logs into a file -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${openhab.logdir:-userdata/logs}/openhab.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%-30.30logger{36}:%-5line] - %msg%ex{10}%n</pattern>
</encoder>
</appender>
<!-- We have removed part of the original file in this example -->
...
<logger name="smarthome.event" level="INFO" additivity="false">
<!-- The element <logger> may contain zero or more <appender-ref> elements -->
<appender-ref ref="EVENTFILE" />
<appender-ref ref="STDOUT" />
</logger>
<!-- Logback uses inheritance to determine the log level.
If a given logger is NOT assigned a level it inherits one from the its closest ancestor -->
<logger name="org.openhab" level="DEBUG" />
<logger name="org.eclipse.smarthome" level="DEBUG" />
<logger name="org.eclipse.jetty" level="INFO" />
<logger name="org.jupnp" level="ERROR"/>
<logger name="javax.jmdns" level="OFF"/>
<logger name="javax.jmdns.impl" level="OFF"/>
<logger name="javax.jmdns.impl.constants" level="OFF"/>
<logger name="tuwien.auto.calimero" level="WARN" />
<!-- The root logger is at the top of the logger hierarchy.
All loggers inherit its level, if there are no ancestors
between the root and the logger -->
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
<logger name="OSGi" level="OFF" />
</configuration>
```
If you are not aware of [appenders](http://logback.qos.ch/manual/appenders.html) and [logger elements](http://logback.qos.ch/manual/configuration.html#loggerElement), you might want to take a look at the links to the the logback documentation.
You can pass the start argument `-Dlogback.configurationFile` to use your own logging configuration file.
### Setting up the Log Level
As you can see from the example configuration file above, the level for jUPnP is set to ERROR. If you develop a binding that is using jUPnP, you might want to see more logs on your console. You can simply change the log level to TRACE or DEBUG:
```xml
<logger name="org.jupnp" level="TRACE"/>
```
### Changing the Pattern of an Appender
Another useful option is to change the pattern of an appender.
This pattern defines what information will be logged.
The logback logger provides many [predefined conversion words](http://logback.qos.ch/manual/layouts.html#conversionWord) for the most common use cases.
Use the most appropriate ones for your specific case.
For the purpose of exploring and debugging multithreaded applications you might need to see which thread has generated certain log event.
In order to see this in the logs, you have to add the conversion word `t` to the pattern above:
```xml
<pattern>%t %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%-30.30logger{36}:%-5line] - %msg%ex{10}%n</pattern>
```
### Redirect the Log to a Text File
You might want to redirect your log to a text file.
This gives you the flexibility to search easier for a specific log or to save your logs.
If you have looked at the [default configuration file](#logback-configuration-file) you might have noticed that several appenders are listed there.
The process of redirecting the log to a text file is as simple as :
- adding a new `FileAppender` and specifying the path of the output file:
```xml
<appender name="YOUR_APPENDER_NAME" class="ch.qos.logback.core.FileAppender">
<file>relative/path/to/file.log</file>
<encoder>
<!--Pick up a pattern that will log the information taht you will need -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%-30.30logger{36}:%-5line] - %msg%ex{10}%n</pattern>
</encoder>
</appender>
```
- adding your logger, setting up the log level and adding `appender-ref` element to the logger element.
For this example we will assume that you want to add the following logger with the name `com.logger.example`:
```xml
<logger name="com.logger.example" level="INFO">
<appender-ref ref="YOUR_APPENDER_NAME" />
</logger>
```
### Further Reading
- <http://www.slf4j.org/>
- <http://logback.qos.ch/>
- <http://www.slf4j.org/manual.html>
- <http://stackoverflow.com/questions/6699537/how-to-use-multiple-configurations-with-logback-in-a-single-project>

37
developers/audio/index.md Normal file
View File

@ -0,0 +1,37 @@
---
layout: developersguide
title: Audio & Voice
---
{% include base.html %}
# Audio & Voice
openHAB provides a modular architecture that enables all kinds of different use cases.
At its core, there is the notion of an _audio stream_.
Audio streams are provided by _audio sources_ and consumed by _audio sinks_.
Each binding for handling and controlling audio services can implement an audio sink to provide their supported devices to the framework to be used as sound output.
An audio sink is identified by an unique id which in general is similar to the thing type id.
The framework itself can handle multiple audio sinks at the same time.
## Build-in Audio Sinks
The distribution comes with these built-in audio sinks options:
| Output device | Audio sink | Description |
|-------------------|-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| javasound | System Speaker | This uses the JRE sound drivers to play audio to the local sound interface. |
| enhancedjavasound | System Speaker (with mp3 support) | This uses the JRE sound drivers plus an additional 3rd party library, which adds support for mp3 files. |
| webaudio | Web Audio | If sounds should not be played on the server but on the client: This sink sends the audio stream through HTTP to web clients, which then cause it to be played back by the browser. The browser needs to be opened and have a compatible UI running. Currently this feature is supported by Paper UI and HABPanel. |
The framework is able to play sound either from the file system, from URLs (e.g. Internet radio streams) or generated by text-to-speech engines (which are available as optional Voice add-ons).
## Audio sink implementation
TODO
## Audio source implementation
TODO

View File

@ -0,0 +1,82 @@
---
layout: developersguide
title: Binding Descriptions
---
## Binding Definitions
Every binding has to provide meta information such as binding id or name.
Background information: The meta information of all bindings is accessible through the `org.eclipse.smarthome.core.binding.BindingInfoRegistry` service.
Although binding definitions are usually specified in a declarative way (as described in this section),
they can also be provided as `org.eclipse.smarthome.core.binding.BindingInfo`.
Any `BindingInfo` must be registered as service at the *OSGi* service registry.
The full Java API for binding definitions can be found in the Java package `org.eclipse.smarthome.core.binding`.
For the declarative way, you add your binding information in form of a `binding.xml` file to the bundle's folder `/src/main/resources/ESH-INF/binding/binding.xml`.
### XML Structure for Binding Definitions
```xml
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="bindingID"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0
https://openhab.org/schemas/binding-1.0.0.xsd">
<name>String</name>
<description>String</description>
<author>String</author>
<config-description>
...
</config-description>
OR
<config-description-ref uri="{binding|thing-type|channel-type|any_other}:bindingID:..." />
</binding:binding>
```
| Property | Description | |
|----------------------------|----------------------------------------------|-----|
| binding.id | An identifier for the binding | mandatory |
| name | A human-readable name for the binding | mandatory |
| description | A human-readable description for the binding | optional |
| author | The author of the binding | optional |
| service-id | The ID (service.pid or component.name) of the main binding service, which can be configured through OSGi configuration admin service. Should only be used in combination with a config description definition | optional |
| config-description | The configuration description for the binding within the ConfigDescriptionRegistry | optional |
| config-description-ref | The reference to a configuration description for the binding within the ConfigDescriptionRegistry | optional |
| config-description-ref.uri | The URI of the configuration description for the binding within the ConfigDescriptionRegistry | mandatory |
The full XML schema for binding definitions is specified in the [ESH binding XSD](https://openhab.org/schemas/binding-1.0.0.xsd) file.
**Hints:**
- The attribute `uri` in the section `config-description` is optional, it *should not* be specified in binding definition files because it's an embedded configuration. If the `uri` is *not* specified, the configuration description is registered as `binding:bindingID`, otherwise the given `uri` is used.
- If a configuration description is already specified somewhere else and the binding wants to (re-)use it, a `config-description-ref` should be used instead.
- Normally the service id must not be defined, because it is implicitly set to "binding.&lt;binding.id&gt;".
A binding can register an OSGi service which implements the ManagedService interface and define the service.pid as e.g."binding.hue" to receive the configuration.
### Example
The following code gives an example for a binding definition.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="bindingID"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0
https://openhab.org/schemas/binding-1.0.0.xsd">
<name>hue Binding</name>
<description>The hue Binding integrates the Philips hue system. It allows to control hue bulbs.</description>
<author>ACME</author>
</binding:binding>
```

View File

@ -0,0 +1,210 @@
---
layout: developersguide
title: Configuration Descriptions
---
{% include base.html %}
# Configuration Descriptions
Specific services or bindings usually require a configuration to be operational in a meaningful way.
To visualize or validate concrete configuration properties, configuration descriptions should be provided.
All available configuration descriptions are accessible through the `org.eclipse.smarthome.config.core.ConfigDescriptionRegistry` service.
Although configuration descriptions are usually specified in a declarative way (as described in this section), they can also be provided as `org.eclipse.smarthome.config.core.ConfigDescriptionProvider`.
Any `ConfigDescriptionProvider`s must be registered as service at the *OSGi* service registry.
The full Java API for configuration descriptions can be found in the Java package `org.eclipse.smarthome.config.core`.
In addition to this there is a `org.eclipse.smarthome.config.core.validation.ConfigDescriptionValidator` that can be used to validate a set of configuration parameters against their declarations in the configuration description before the actual configuration is updated with the new configuration parameters.
Configuration descriptions must be placed as XML file(s) (with the ending `.xml`) in the bundle's folder `/ESH-INF/config/`.
## Formatting Labels
The label and descriptions for things, channels and config descriptions should follow the following format.
The label should be short so that for most UIs it does not spread across multiple lines.
The description can contain longer text to describe the thing in more detail.
Limited use of HTML tags is permitted to enhance the description - if a long description is provided, the first line should be kept short and a line break (```<br>```) should be placed at the end of the line to allow UIs to display a short description in limited space.
Configuration options should be kept short so that they are displayable on a single line in most UIs.
If you want to provide a longer description of the options provided by a particular parameter, then this should be placed into the ```<description>``` of the parameter to keep the option label short.
The description can include limited HTML to enhance the display of this information.
The following HTML tags are allowed -: ```<b>, <br>, <em>, <h1>, <h2>, <h3>, <h4>, <h5>, <h6>, <i>, <p>, <small>, <strong>, <sub>, <sup>, <ul>, <ol>, <li>```.
These must be inside the XML escape sequence - eg.
```<description><![CDATA[ HTML marked up text here ]]></description>```.
## XML Structure for Configuration Descriptions
```xml
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="{binding|thing-type|channel-type|any_other}:bindingID:...">
<parameter-group name="String">
<label>String</label>
<description>String</description>
<context>String</context>
<advanced>{true|false}</advanced>
</parameter-group>
<parameter name="String" type="{text|integer|decimal|boolean}" min="Decimal" max="Decimal" step="Decimal" pattern="String" required="{true|false}" readOnly="{true|false}" multiple="{true|false}" groupName="String" unit="A|cd|K|kg|m|mol|s|g|rad|sr|Hz|N|Pa|J|W|C|V|F|Ω|S|Wb|T|H|Cel|lm|lx|Bq|Gy|Sv|kat|m/s2|m2v|m3|kph|%|l|ms|min|h|d|week|y">
<context>{network-address|serial-port|password|password-create|color|date|datetime|email|month|week|dayOfWeek|time|tel|url|item|thing|group|tag|service|channel|rule|location}</context>
<required>{true|false}</required>
<default>String</default>
<label>String</label>
<description>String</description>
<unitLabel>String</unitLabel>
<options>
<option value="String">String</option>
</options>
<filter>
<criteria name="String">String</criteria>
</filter>
</parameter>
</config-description>
<config-description uri="{binding|thing-type|channel-type|any_other}:bindingID:...">
...
</config-description>
...
</config-description:config-descriptions>
```
<table>
<tr><td><b>Property</b></td><td><b>Description</b></td></tr>
<tr><td>config-description.uri</td><td>The URI of this description within the ConfigDescriptionRegistry (mandatory).</td></tr>
<tr><td>parameter</td><td>The description of a concrete configuration parameter (optional).</td></tr>
<tr><td>parameter.name</td><td>The name of the configuration parameter (mandatory).</td></tr>
<tr><td>parameter.type</td><td>The data type of the configuration parameter (mandatory).</td></tr>
<tr><td>parameter.min</td><td>The minimal value for numeric types, or the minimal length of strings. Note that the value of any options may be outside of this value. (optional).</td></tr>
<tr><td>parameter.max</td><td>The maximum value for numeric types, or the maximum length of strings. Note that the value of any options may be outside of this value. (optional).</td></tr>
<tr><td>parameter.step</td><td>The value granularity for a numeric value (optional).</td></tr>
<tr><td>parameter.pattern</td><td>The regular expression for a text type (optional).</td></tr>
<tr><td>parameter.required</td><td>Specifies whether the value is required (optional).</td></tr>
<tr><td>parameter.readOnly</td><td>Specifies whether the value is read-only (optional).</td></tr>
<tr><td>parameter.multiple</td><td>Specifies whether multiple selections of options are allowed (optional).</td></tr>
<tr><td>parameter.groupName</td><td>Sets a group name for this parameter (optional).</td></tr>
<tr><td>parameter.unit</td><td>Specifies the unit of measurements. The unit declaration in the parameter definition shown above contains the set of valid units. The unit must only be set if the type of the parameter is either integer or decimal (optional).</td></tr>
<tr><td>advanced</td><td>Specifies that this is an advanced parameter. Advanced parameters may be hidden by a UI (optional).</td></tr>
<tr><td>verify</td><td>Specifies that this is parameter requires a verification stage with the user before sending. Parameters flagged with *verify=true* could be considered dangerous and should be protected from accidental use by a UI - eg by adding an "Are you sure" prompt (optional).</td></tr>
<tr><td>context</td><td>The context of the configuration parameter (optional).</td></tr>
<tr><td>required</td><td>The flag indicating if the configuration parameter has to be set or not (deprecated, optional, default: false).</td></tr>
<tr><td>default</td><td>The default value of the configuration parameter (optional).</td></tr>
<tr><td>label</td><td>A human-readable label for the configuration parameter (optional).</td></tr>
<tr><td>description</td><td>A human-readable description for the configuration parameter (optional).</td></tr>
<tr><td>unitLabel</td><td>The unit label represents a human-readable label for the unit. It can also be used to provide unit labels for natural language units as iterations, runs, etc. The unit label must only be set if the type of the parameter is either integer or decimal (optional).</td></tr>
<tr><td>option</td><td>The element definition of a static selection list (optional).</td></tr>
<tr><td>option.value</td><td>The value of the selection list element. Note that the value may be outside of the range specified in the min/max if this is specified.</td></tr>
<tr><td>multipleLimit</td><td>If multiple is true, sets the maximum number of options that can be selected (optional).</td></tr>
<tr><td>limitToOptions</td><td>If true (default) will only allow the user to select items in the options list. If false, will allow the user to enter other text (optional).</td></tr>
<tr><td>criteria</td><td>The filter criteria for values of a dynamic selection list (optional).</td></tr>
<tr><td>criteria.name</td><td>The name of the context related filter.</td></tr>
</table>
### Supported Contexts
Context is used to provide some semantic details about the parameter. The UIs use it to render different kind of input widgets. The following contexts require a specific format of the content:
<table><tr><th>Name</th><th>Type</th><th>Format</th><th>Sample implementation</th></tr>
<tr><td>network-address</td><td>text</td><td>IPv4,IPv6, domain name</td><td><code>&lt;input type="text"/></code></td></tr>
<tr><td>serial-port</td><td>text</td><td>Serial port name, e.g. COM1</td><td>custom input field</td></tr>
<tr><td>password</td><td>text</td><td>alphanumeric characters</td><td><code>&lt;input type="password"/></code></td></tr>
<tr><td>password-create</td><td>text</td><td>alphanumeric characters</td><td>custom password input</td></tr>
<tr><td>color</td><td>text</td><td>#000000 - #ffffff (hex color)</td><td><code>&lt;input type="color"/></code></td></tr>
<tr><td>date</td><td>text</td><td>YYYY-MM-DD</td><td><code>&lt;input type="date"/></code></td></tr>
<tr><td>datetime</td><td>text</td><td>YYYY-MM-DD hh:mm</td><td>custom input field</td></tr>
<tr><td>email</td><td>text</td><td>username@domain.com</td><td><code>&lt;input type="email"/></code></td></tr>
<tr><td>month</td><td>text</td><td>month of year</td><td>custom input field</td></tr>
<tr><td>week</td><td>integer</td><td>week of year</td><td>custom input field</td></tr>
<tr><td>dayOfWeek</td><td>text</td><td>MON, TUE, WED, THU, FRI, SAT, SUN <br></td><td>custom input field</td></tr>
<tr><td>time</td><td>text/integer</td><td>hh:mm:ss/milliseconds since epoch</td><td><code>&lt;input type="time"/></code></td></tr>
<tr><td>telephone</td><td>text</td><td>telephone number</td><td>custom input field</td></tr>
<tr><td>url</td><td>text</td><td>web url</td><td><code>&lt;input type="url"/></code></td></tr>
<tr><td>item</td><td>text</td><td>Item name</td><td>custom input field</td></tr>
<tr><td>thing</td><td>text</td><td>UID of a thing</td><td>custom input field</td></tr>
<tr><td>group</td><td>text</td><td>group name to which this parameter belongs</td><td></td></tr>
<tr><td>tag</td><td>text</td><td>tag name</td><td>custom input field</td></tr>
<tr><td>service</td><td>text</td><td>service name</td><td>custom input field</td></tr>
<tr><td>channel</td><td>text</td><td>UID of a channel<br></td><td>custom input field</td></tr>
<tr><td>rule</td><td>text</td><td>UID of a rule<br></td><td>custom input field</td></tr>
<tr><td>location</td><td>text</td><td>latitude,longitude[,altitude]<br></td><td>custom input field</td></tr>
</table>
Further, the <strong>item</strong> context can contain criteria to filter the list of items. For example:
```xml
<filter>
<criteria name="type">Switch,Dimmer</criteria>
<criteria name="tag">Light,Heating</criteria>
</filter>
```
In the case of above filter only those items will be shown that satisfy the filter's conditions.
The above filter is evaluated as follows:
```
(type=Switch OR type=Dimmer) AND (tag=Light OR tag=Heating)
```
Similarly, the <strong>Channel</strong> context can contain criteria to filter channels based on <strong>kind</strong> field.
The value of <strong>kind</strong> can either be STATE or TRIGGER.
For example:
```xml
<filter>
<criteria name="kind">STATE|TRIGGER</criteria>
</filter>
```
Groups allow parameters to be grouped together into logical blocks so that the user can find the parameters they are looking for.
A parameter can be placed into a group so that the UI knows how to display the information.
<table>
<tr><td><b>Property</b></td><td><b>Description</b></td></tr>
<tr><td>group.name</td><td>The group name - this is used to link the parameters into the group, along with the groupName option in the parameter (mandatory).</td></tr>
<tr><td>label</td><td>The human-readable label of the group. (mandatory).</td></tr>
<tr><td>description</td><td>The description of the group. (optional).</td></tr>
<tr><td>context</td><td>Sets a context tag for the group. The context may be used in the UI to provide some feedback on the type of parameters in this group (optional).</td></tr>
<tr><td>advanced</td><td>Specifies that this is an advanced group. The UI may hide this group from the user (optional).</td></tr>
</table>
The full XML schema for configuration descriptions is specified in the [openHAB config description XSD](https://openhab.org/schemas/config-description-1.0.0.xsd) file.
**Hints:**
- Although the attribute `uri` is optional, it *must* be specified in configuration description files.
Only for embedded configuration descriptions in documents for binding definitions and `Thing` type descriptions, the attribute is optional.
## Example
The following code gives an example for one configuration description.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-description uri="thing-type:my-great-binding:my-bridge-name"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<parameter name="ipAddress" type="text" required="true">
<context>network-address</context>
<label>Network Address</label>
<description>Network address of the device.</description>
</parameter>
<parameter name="userName" type="text" required="true">
<label>User Name</label>
</parameter>
<parameter name="password" type="text" required="false">
<context>password</context>
</parameter>
</config-description:config-description>
```

View File

@ -0,0 +1,66 @@
---
layout: developersguide
title: FAQ
---
{% include base.html %}
# Frequently Asked Questions (FAQs)
Here is a list of frequently asked questions around the development of bindings.
If you do not find an answer to your question, do not hesitate to ask it on the support forum.
### Structuring Things and Thing Types
1. _I am implementing a binding for system X.
Shall I design this as one Thing or as a Bridge with multiple Things for the different functionalities?_
In general, both options are valid:
1. You have one Thing which has channels for all your actors, sensors and other functions
2. You have one Bridge and an additional Thing for every actor and sensor and they would hold the channels
The preferred architecture is the latter, if this is feasible.
This means that the physical devices should be represented by a Thing each.
This only makes sense if your system allows you to identify the different physical devices at all.
Especially, such an architecture is useful if you can do a discovery of new devices that could then be presented to the user/admin.
If your system does not provide you any possibility to get hold of such information, but rather only shows you a "logical" view of it, then 1) is also a valid option to pursue.
2. _Do I have to create XML files in `ESH-INF/thing` for all devices or is there any other option?_
No, the XML files are only one way to describe your devices.
Alternatively, you can implement your own [ThingTypeProvider](https://github.com/eclipse/smarthome/blob/master/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/ThingTypeProvider.java), through which you can provide thing descriptions in a programmatic way.
Nonetheless, the static XML descriptions of thing types can be picked up for documentation generation and other purposes.
So whenever possible, static XML descriptions should be provided.
3. _For my system XY, there are so many different variants of devices.
Do I really need to define a thing type for every single one of them?_
Thing types are important if you have no chance to request any structural information about the devices from your system and if you need users to manually chose a thing to add or configure (i.e. there is also no automatic discovery).
The thing types that you provide will be the list the user can choose from.
If your system supports auto-discovery and you can also dynamically construct things (and their channels) from structural information that you can access during runtime, there is in theory no need to provide any thing type description at all.
Nonetheless, static descriptions of thing types have the advantage that the user knows which kind of devices are supported, no matter if he has a device at home or not - so you should at least have static XML descriptions for the devices that are known to you at implementation time.
4. _I have a device that can have different firmware versions with slightly different functionality.
Should I create one or two thing types for it?_
If the firmware version makes a huge difference for the device (and can be seen as a different model of it), then it is ok to have different things defined.
If the list of channels can be determined by knowing the firmware revision, this is good.
If you can only determine the existing channels by querying the device itself, it might be the better option to have only one thing type and construct its channels upon first access.
5. _When creating a Thing through my ThingHanderFactory, does it exactly have to have the channels that are defined in the thing type description?_
It must at least have the channels that are defined in its thing type (and they are already automatically added by the super() implementation).
Nonetheless, you are free to add any number of additional channels to the thing.
### State Updates of Channels
1. I have an image in my binding and want to pass it to the framework. What is the best way to achieve this?
The Thing that wants to provide the image should have a Channel defined with `<item-type>Image</item-type>`.
The `ThingHandler` can update this Channel with a state of type `RawType` that represents the raw bytes of the image.
If the image should be downloaded from a URL, the helper method `HttpUtil.downloadImage(URL url)` can be used.
A user may link this Channel to an Item of type `Image` which can then be displayed.
Please note that data put as a `RawType` in a Channel will stay in **memory only**, i.e., this data will **not** be persisted anywhere.
Also keep in mind that the memory needed for these images will be consumed on the server running the framework, so creating a lot of `RawType` channels is not recommended.

View File

@ -0,0 +1 @@
<mxfile modified="2019-04-20T09:30:44.777Z" host="www.draw.io" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" etag="TVdvkOH_J1kM9pO_D5tW" version="10.6.3" type="device"><diagram id="Wn3mFzr1MTYCMt5Axupw" name="Page-1">7Zrbkps4EIafxrWTi0yBMNi+9GGcqdpkd2q9qU2utmRoQBuMiJBPefqVQJiTT5mYwXHGU+VBTQuL/tTNj6BjjBebdwxH/gfqQNBBmrPpGJMOQkgzuuKftGxTi671lcVjxFG23DAj3yBzVNYlcSAuOXJKA06istGmYQg2L9kwY3RddnNpUP7VCHtQM8xsHNSt/xCH+6m1j3q5/RGI52e/rFuDdM8CZ87qTGIfO3RdMBkPHWPMKOXp1mIzhkBGL4tL2m96YO9uYAxCfk6HxdsPfxn839/fTxda8LVPn/7UV2+RlR5mhYOlOmM1Wr7NQsDoMnRAHkXrGKO1TzjMImzLvWtBXdh8vghESxebLgmCMQ0oS/oarin/pJ2GXNFF0g8HxAtFIwBXjH4Uc0a/QKGjlXxUx4I9/Qh7/fRVRFbAOGwKJhWOd0AXwNlWuKi9ZoZmNzlVe52TRn1l8wuU0UAZsZpd3u7YOQCxoRh8B4/uaRzgiAmqmpRxn3o0xMFDbh2VgeU+7ymNFKb/gPOt4oGXnJYhHoxtTJfMhiPjz1IUMw/4ET8j9ZPncpQUgwBzsion48WjfkYSvEDUYUP4J9n93lStz4U9k406ctLYqkbzpMxLk0q6DhnD24JDREnI48KRn6QhT9VdGqpUrRW7in+3e9RfbKQjyOfM7lSeP410vc1poxcmTT6F9k8bMVvY9lOxUeglm3m3pPVy0y277l9JZdDNVivBc5DqV4e0e11Ia8V+JJSnGBqyAilF5kxseXLr7mGDF5FEOhQdHoU70lLfN3Xni8omB0PftffqIrsPc/cy+gdV9Y9Z1z862qN/rKbkj268VtATF+LT6YauKt3MWrr97ZPQO5Vto2UwbzrLpmhqTcdNZ5lRlS5W61nWu315a5ybLdZVZYs++FnQ5AUwr3mfSyXv0gXwbKT9q0JqPK8AziCMKbuNElgVGu2XwEENyoTEVJzR9ntkYCwPQbD4/vj0x1PTrFzXRfZeUehYc8u0LsNKL6MyBm1frLRD+aM5EIGIZnLf75DYVvy0MrA4SSSJCoeO+J4LZRG/eWkyHsMOgdJSppt8LrSU2b06KY8OcsM2JzQ8lWgfwwgTpoA1nVugOyb09hEcWD0Dt0Cw/Rqp1xejxzR0ibdkOAWouQDOHNtfmr4dNqHvdPfR6aO5YTVT+a4gherr0mMfhyEEcTIsMSoZYsxPrl2kEUNa8iWuXWLsIcRx81cssA7Uxd5grh2Vg89XF0avdXD9eu0TQfdAht9OEZ5CJqxfl/LZ4GgCK2JDilrERF7DXMIWaxEzsbmMZGGC+9z/NqhWb5vbp4rqSiSLdBzhMIt1WiNnIi2X8ROjK+JI7Kmf+N2iaw2LiA0vx74c45CGUAGiTJXnqTLOxMbBUJkXxHGSe8V9qMuTofqk9kdYZqU0W0YvsOvuQYcaQ7dn0RfHkKr4R6ELg5zRkay5NTyH1oFbx1XXjhJXoh9faVVvplHbtOorHHcJqmGi889SGTfKqirqjdZZ1TX9cC5CLW7JJtkd9AxYKjh+OWpZRt23TenAQxNV+xSg3+JfEFGWWEY5sfaIw5cl9tM8Ujkbxsn1eHTuE0l08XeIfoxV/e7s7lARfL125VWxOVUomvl7sOnbWPnrxMbD/w==</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -0,0 +1,899 @@
---
layout: developersguide
title: Bindings
---
{% include base.html %}
# Developing a Binding
{:.no_toc}
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](images/concept.png)
During development you might come back with specific questions.
{::options toc_levels="2,3"/}
* TOC
{:toc}
## 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](binding-xml.html).
## 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](thing-xml.html).
## 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:
```java
@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:
```java
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](images/thing_life_cycle_startup.png)
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 `ThingHandlerFactory`s 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](xml-reference.html)) are available.
If so, the method `ThingHandler.initialize()` is called.
Only Things with status (cf. [Thing Status](../../concepts/things.html#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](images/thing_life_cycle_shutdown.png)
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](thing-definition.html#Properties) 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](thing-handler.html)).
### 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:
```java
@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.
```java
@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:
```java
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:
```java
@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:
```java
@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:
```java
triggerChannel("channelId")
```
If an event payload is needed, use the overloaded version:
```java
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](../../concepts/things.html#thing-status)).
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:
```java
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](../../concepts/things.html#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):
```java
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 &#64;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.
```
### Channel Links
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:
```java
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:
```java
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:
```java
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:
```java
@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](faq.html#structuring-things-and-thing-types).
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`:
```java
@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](thing-handler.html#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 `ThingHandler`s of its child things are initialized.
- A `BridgeHandler` is disposed after all `ThingHandler`s 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.ConfigStatusMessage`s.
#### 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](../module-types.html) chapter.
You start things off by implementing `ThingActions` and annotate your class with `@ThingActionsScope`:
```java
@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:
```java
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](utils/i18n.html) support, instead of directly providing a string.
```java
@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 `@ActionOutput`s 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](../../concepts/discovery.html)).
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](#upnp-discovery) and [mDNS Discovery](#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.
```java
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
```java
@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.
```java
@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.
```java
@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:
```java
@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`.
```java
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](http://www.dns-sd.org/ServiceTypes.html).
- `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](faq.html).
## 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](https://github.com/openhab/openhab2-addons/blob/master/bundles/pom.xml) at the alphabetically correct position.
* Furthermore add it to the [feature.xml](https://github.com/openhab/openhab2-addons/blob/master/features/karaf/openhab-addons/src/main/feature/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](https://github.com/openhab/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](../contributing/contributing.html)!

View File

@ -0,0 +1,827 @@
---
layout: developersguide
title: Thing Descriptions
---
{% include base.html %}
# Binding Definitions
{:.no_toc}
In order to work with *Things* and *Channels*, some meta information about them is needed.
These are provided through 'ThingType' and 'ChannelType' definitions,
which describe details about their functionality and configuration options.
{::options toc_levels="2,3"/}
* TOC
{:toc}
## ThingTypeProvider / ChannelTypeProvider
Technically, the thing types are provided by `ThingTypeProvider`s (`org.eclipse.smarthome.core.thing.binding.ThingTypeProvider`).
openHAB comes with an implementation of such a provider that reads XML files from the folder `ESH-INF/thing` of bundles.
Although we refer to this XML syntax in the following, you also have the option to provide directly object model instances through your own provider implementation.
The same applies for the channel types.
The `ChannelTypeProvider` interface can be registered as OSGi service to provide channel types programmatically.
When implementing a dynamic `ThingTypeProvider` you can also refer to the channel types that are defined inside XML files.
## Things
Things represent devices or services that can be individually added to, configured or removed from the system.
They either contain a set of channels or a set of channel groups.
A bridge is a specific type of thing as it can additionally provide access to other Things as well.
Which Things can be associated through which bridge type is defined within the description of a thing:
```xml
<thing-type id="thingTypeID">
<supported-bridge-type-refs>
<bridge-type-ref id="bridgeTypeID" />
</supported-bridge-type-refs>
<label>Sample Thing</label>
<description>Some sample description</description>
<category>Lightbulb</category>
...
</thing-type>
```
Bindings may optionally set the listing of a thing type.
By doing do, they indicate to user interfaces whether it should be shown to the users or not, e.g. when pairing things manually:
```xml
<thing-type id="thingTypeID" listed="false">
...
</thing-type>
```
Thing types are listed by default, unless specified otherwise.
Hiding thing types potentially makes sense if they are deprecated and should not be used anymore.
Also, this can be useful if users should not be bothered with distinguishing similar devices which for technical reasons have to have separate thing types.
In that way, a generic thing type could be listed for users and a corresponding thing handler would change the thing type immediately to a more concrete one, handing over control to the correct specialized handler.
### Thing Categories
A description about thing categories as well as an overview about which categories exist can be found in our [categories overview](../../concepts/categories.html).
## Channels
A channel describes a specific functionality of a thing and can be linked to an item.
So the basic information is, which command types the channel can handle and which state it sends to the linked item.
This can be specified by the accepted item type.
Inside the thing type description XML file a list of channels can be referenced.
The channel type definition is specified on the same level as the thing type definition.
That way channels can be reused in different things.
The granularity of channel types should be on its semantic level, i.e. very fine-grained:
If a Thing measures two temperature values, one for indoor and one for outdoor, this should be modelled as two different channel types.
Overriding labels of a channel type must only be done if the very same functionality is offered multiple times, e.g. having an actuator with 5 relays, which each is a simple "switch", but you want to individually name the channels (1-5).
The following XML snippet shows a thing type definition with 2 channels and one referenced channel type:
```xml
<thing-type id="thingTypeID">
<label>Sample Thing</label>
<description>Some sample description</description>
<channels>
<channel id="switch" typeId="powerSwitch" />
<channel id="temperature" typeId="setpointTemperature" />
</channels>
</thing-type>
<channel-type id="setpointTemperature" advanced="true">
<item-type>Number</item-type>
<label>Setpoint Temperature</label>
<category>Temperature</category>
<state min="12" max="30" step="0.5" pattern="%.1f °C" readOnly="false" />
</channel-type>
```
In order to reuse identical channels in different bindings a channel type can be system-wide.
A channel type can be declared as system-wide by setting its `system` property to true and can then be referenced using a `system.` prefix in a `channel` `typeId` attribute in any binding - note that this should only be done in the core framework, but not by individual bindings!
The following XML snippet shows a system channel type definition and thing type definition that references it:
```xml
<thing-type id="thingTypeID">
<label>Sample Thing</label>
<description>Some sample description</description>
<channels>
<channel id="s" typeId="system.system-channel" />
</channels>
</thing-type>
<channel-type id="system-channel" system="true">
<item-type>Number</item-type>
<label>System Channel</label>
<category>QualityOfService</category>
</channel-type>
```
### System State Channel Types
There exist system-wide channel types that are available by default:
| Channel Type ID | Reference typeId | Item Type | Category | Description |
|----------------------|-----------------------------|----------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| signal-strength | system.signal-strength | Number | QualityOfService | Represents signal strength of a device as a Number with values 0, 1, 2, 3 or 4; 0 being worst strength and 4 being best strength. |
| low-battery | system.low-battery | Switch | Battery | Represents a low battery warning with possible values on (low battery) and off (battery ok). |
| battery-level | system.battery-level | Number | Battery | Represents the battery level as a percentage (0-100%). Bindings for things supporting battery level in a different format (e.g. 4 levels) should convert to a percentage to provide a consistent battery level reading. |
| power | system.power | Switch | - | Turn a device on/off. |
| brightness | system.brightness | Dimmer | Light | Brightness of a bulb (0-100%). |
| color | system.color | Color | ColorLight | Color of a bulb. |
| color-temperature | system.color-temperature | Dimmer | ColorLight | Color temperature of a bulb (0-100%). 0% should be the coldest setting (highest Kelvin value). |
| location | system.location | Location | - | Location in lat.,lon.,height coordinates. |
| motion | system.motion | Switch | Motion | Motion detected by the device (ON if motion is detected). |
| mute | system.mute | Switch | SoundVolume | Turn on/off the volume of a device. |
| volume | system.volume | Dimmer | SoundVolume | Change the sound volume of a device (0-100%). |
| media-control | system.media-control | Player | MediaControl | Control for a media player. |
| media-title | system.media-title | String | - | Title of a (played) media file. |
| media-artist | system.media-artist | String | - | Artist of a (played) media file. |
| outdoor-temperature | system.outdoor-temperature | Number:Temperature | Temperature | Current outdoor temperature. |
| wind-direction | system.wind-direction | Number:Angle | Wind | Wind direction in degrees (0-360°). |
| wind-speed | system.wind-speed | Number:Speed | Wind | Wind speed |
| atmospheric-humidity | system.atmospheric-humidity | Number:Dimensionless | Humidity | Atmospheric humidity in percent. |
| barometric-pressure | system.barometric-pressure | Number:Pressure | Pressure | Barometric pressure |
For further information about categories see the [categories page](../../concepts/categories.html).
The `advanced` property indicates whether this channel is a basic or a more specific functionality of the thing.
If `advanced` is set to `true` a user interface may hide this channel by default.
The default value is `false` and thus will be taken if the `advanced` attribute is not specified.
Especially for complex devices with a lot of channels, only a small set of channels - the most important ones - should be shown to the user to reduce complexity.
Whether a channel should be declared as `advanced` depends on the device and can be decided by the binding developer.
If a functionality is rarely used it should be better marked as `advanced`.
The following XML snippet shows a trigger channel:
```xml
<thing-type id="thingTypeID">
<label>Sample Thing</label>
<description>Some sample description</description>
<channels>
<channel id="s" typeId="trigger-channel" />
</channels>
</thing-type>
<channel-type id="trigger-channel">
<kind>trigger</kind>
<label>Trigger Channel</label>
<event>
<options>
<option value="PRESSED">pressed</option>
<option value="RELEASED">released</option>
<option value="DOUBLE_PRESSED">double pressed</option>
</options>
</event>
</channel-type>
```
This channel can emit the event payloads `PRESSED`, `RELEASED` and `DOUBLE_PRESSED`.
If no `<event>` tag is specified, the channel can be triggered, but has no event payload.
If an empty `<event>` tag is specified, the channel can trigger any event payload.
### System Trigger Channel Types
There exist system-wide trigger channel types that are available by default:
| Channel Type ID | Reference typeId | Description |
|-----------------|------------------------|------------- |
| trigger | system.trigger | Can only trigger, no event payload |
| rawbutton | system.rawbutton | Can trigger `PRESSED` and `RELEASED` |
| button | system.button | Can trigger `SHORT_PRESSED`, `DOUBLE_PRESSED` and `LONG_PRESSED` |
| rawrocker | system.rawrocker | Can trigger `DIR1_PRESSED`, `DIR1_RELEASED`, `DIR2_PRESSED` and `DIR2_RELEASED` |
In the following sections the declaration and semantics of tags, state descriptions and channel categories will be explained in more detail.
For a complete sample of the thing types XML file and a full list of possible configuration options please see the [XML Configuration Guide](xml-reference.html).
### Default Tags
The XML definition of a ThingType allows to assign default tags to channels.
All items bound to this channel will automatically be tagged with these default tags.
The following snippet shows a 'Lighting' tag definition:
```xml
<tags>
<tag>Lighting</tag>
</tags>
```
Please note that only tags from a pre-defined tag library should be used.
This library is still in development., and only a very small set of tags are defined so far:
| Tag | Item Types | Description |
|--------------------|----------------------------|---------------------------------------------------------------------------------------|
| Lighting | Switch, Dimmer, Color | A light source, either switchable, dimmable or color |
| Switchable | Switch, Dimmer, Color | An accessory that can be turned off and on. |
| CurrentTemperature | Number, Number:Temperature | An accessory that provides a single read-only temperature value. |
| TargetTemperature | Number, Number:Temperature | A target temperature that should engage a thermostats heating and cooling actions. |
| CurrentHumidity | Number | An accessory that provides a single read-only value indicating the relative humidity. |
### State Description
The state description allows to specify restrictions and additional information for the state of an item, that is linked to the channel.
Some configuration options are only valid for specific item types.
The following XML snippet shows the definition for a temperature actuator channel:
```xml
<state min="12" max="30" step="0.5" pattern="%.1f %unit%" readOnly="false"></state>
```
* The attributes `min` and `max` can only be declared for channel with the item type `Number`.
It defines the range of the numeric value.
The Java data type is a BigDecimal.
For example user interfaces can create sliders with an appropriate scale based on this information.
* The `step` attribute can be declared for `Number` and `Dimmer` items and defines what is the minimal step size that can be used.
* The `readonly` attribute can be used for all item types and defines if the state of an item can be changed.
For all sensors the `readonly` attribute should be set to `true`.
* The `pattern` attribute can be used for `Number` and `String` items.
It gives user interface a hint how to render the item.
The format of the pattern must be compliant to the [Java Number Format](http://docs.oracle.com/javase/tutorial/java/data/numberformat.html).
The pattern can be localized (see also [Internationalization](../../features/internationalization.html)).
The special pattern placeholder `%unit%` is used for channels which bind to items of type `Number:<dimension>` which define a dimension for unit support.
These channels will send state updates of type [QuantityType](../../concepts/units-of-measurement.html#quantitytype) and the unit is then rendered for the placeholder.
Some channels might have only a limited and countable set of states.
These states can be specified as options.
A `String` item must be used as item type.
The following XML snippet defines a list of predefined state options:
```xml
<state readOnly="true">
<options>
<option value="HIGH">High Pressure</option>
<option value="MEDIUM">Medium Pressure</option>
<option value="LOW">Low Pressure</option>
</options>
</state>
```
The user interface can use these values to render labels for values or to provide a selection of states, when the channel is writable.
The option labels can also be localized.
### Command Description
If the primary purpose of a channel is to send commands towards a device (i.e. the opposite direction of trigger channels),
it can use command options.
A `String` item must be used as item type.
The following XML snippet defines a list of commands:
```xml
<command>
<options>
<option value="ALARM">Alarm</option>
<option value="LSELECT">Long Alarm</option>
</options>
</command>
```
The user interface can use these values to render
* a drop down and also represent the current state or
* as push buttons to simply send a command to the ThingHandler.
The option labels can also be localized.
#### Dynamic State / Command Description
In situations where the static definition of a state description is not sufficient a binding would implement a `DynamicStateDescriptionProvider` or a `DynamicCommandDescriptionProvider`.
These providers allow to provide a `StateDescription` (or `CommandDescription` respectively) based on the specific `Channel`.
Also implement this interface if you want to provide dynamic state / command options.
The original `StateDescription`/`CommandDescription` is available for modification and enhancement.
The framework provides two abstract implementations for bindings to support translation and other basic features: `BaseDynamicStateDescriptionProvider` and `BaseDynamicCommandDescriptionProvider`.
The `StateDescriptionFragmentBuilder` (and `CommandDescriptionBuilder`) can be used to only provide the information which is available at the time of construction.
##### Example code for a `DynamicStateDescriptionProvider` implementation
```java
@Component(service = { DynamicStateDescriptionProvider.class, ExampleDynamicStateDescriptionProvider.class })
public class ExampleDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider {
private final Map<ChannelUID, @Nullable List<StateOption>> channelOptionsMap = new ConcurrentHashMap<>();
/**
* For a given channel UID, set a {@link List} of {@link StateOption}s that should be used for the channel, instead
* of the one defined statically in the {@link ChannelType}.
*
* @param channelUID the channel UID of the channel
* @param options a {@link List} of {@link StateOption}s
*/
public void setStateOptions(ChannelUID channelUID, List<StateOption> options) {
channelOptionsMap.put(channelUID, options);
}
@Override
public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
@Nullable Locale locale) {
List<StateOption> options = channelOptionsMap.get(channel.getUID());
if (options == null) {
return null;
}
StateDescriptionFragmentBuilder builder = (original == null) ? StateDescriptionFragmentBuilder.create()
: StateDescriptionFragmentBuilder.create(original);
return builder.withOptions(options).build().toStateDescription();
}
@Deactivate
public void deactivate() {
channelOptionsMap.clear();
}
}
```
##### Exampla code for a `DynamicStateDescriptionProvider` implementation which extends the `BaseDynamicStateDescriptionProvider`
```java
@Component(service = { DynamicStateDescriptionProvider.class, ExampleDynamicStateDescriptionProvider.class })
public class ExampleDynamicStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
@Reference
protected void setChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
protected void unsetChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = null;
}
}
```
##### Example code for a `DynamicCommandDescriptionProvider` implementation
```java
@Component(service = { DynamicCommandDescriptionProvider.class, ExampleDynamicCommandDescriptionProvider.class })
public class ExampleDynamicCommandDescriptionProvider implements DynamicCommandDescriptionProvider {
private final Map<ChannelUID, @Nullable List<CommandOption>> channelOptionsMap = new ConcurrentHashMap<>();
/**
* For a given channel UID, set a {@link List} of {@link CommandOption}s that should be used for the channel,
* instead of the one defined statically in the {@link ChannelType}.
*
* @param channelUID the channel UID of the channel
* @param options a {@link List} of {@link CommandOption}s
*/
public void setCommandOptions(ChannelUID channelUID, List<CommandOption> options) {
channelOptionsMap.put(channelUID, options);
}
@Override
public @Nullable CommandDescription getCommandDescription(Channel channel,
@Nullable CommandDescription originalCommandDescription, @Nullable Locale locale) {
List<CommandOption> options = channelOptionsMap.get(channel.getUID());
if (options == null) {
return null;
}
CommandDescriptionBuilder builder = CommandDescriptionBuilder.create();
options.forEach(co -> builder.withCommandOption(co));
return builder.build();
}
}
```
Most of the times handlers need to modify those dynamic information.
Therefore the `ThingHandlerFactory` has to reference the bundle instance and pass it to the handler.
```java
public class ExampleHandlerFactory extends BaseThingHandlerFactory {
@Reference
private ExampleDynamicStateDescriptionProvider stateDescriptionProvider;
@Override
protected ThingHandler createHandler(Thing thing) {
if (EXAMPLE_THING_TYPE.equals(thing.getThingTypeUID())) {
return new ExampleHandler(thing, stateDescriptionProvider);
}
return null;
}
}
```
### Channel Categories
A description about channel categories as well as an overview about which categories exist can be found in out [categories overview](../../concepts/categories.html).
### Channel Groups
Some devices might have a lot of channels.
There are also complex devices like a multi-channel actuator, which is installed inside the switchboard, but controls switches in other rooms.
Therefore channel groups can be used to group a set of channels together into one logical block.
A thing can only have direct channels or channel groups, but not both.
Inside the thing types XML file channel groups can be defined like this:
```xml
<thing-type id="multiChannelSwitchActor">
<!-- ... -->
<channel-groups>
<channel-group id="switchActor1" typeId="switchActor" />
<channel-group id="switchActor2" typeId="switchActor" />
</channel-groups>
<!-- ... -->
</thing-type>
```
The channel group type is defined on the same level as the thing types and channel types.
The group type must have a label, an optional description, and an optional [category](../../concepts/categories.html).
Moreover the list of contained channels must be specified:
```xml
<channel-group-type id="switchActor">
<label>Switch Actor</label>
<description>This is a single switch actor with a switch channel</description>
<category>Light</category>
<channels>
<channel id="switch" typeId="switch" />
</channels>
</channel-group-type>
```
When a thing will be created for a thing type with channel groups, the channel UID will contain the group ID in the last segment divided by a hash (#).
If an Item should be linked to a channel within a group, the channel UID would be `binding:multiChannelSwitchActor:myDevice:switchActor1#switch` for the XML example before.
Details about the category can be found in our [categories overview](../../concepts/categories.html).
## Properties
Solutions based on openHAB might require meta data from a device.
These meta data could include:
- general device information, e.g. the device vendor, the device series or the model ID, ...
- device characteristics, e.g. if it is battery based, which home automation protocol is used, what is the current firmware version or the serial number, ...
- physical descriptions, e.g. what is the size, the weight or the color of the device, ...
- any other meta data that should be made available for the solution by the binding
Depending on the solution the provided meta data can be used for different purposes.
Among others the one solution could use the data during a device pairing process whereas another solution might use the data to group the devices/things by the vendors or by the home automation protocols on a user interface.
To define such thing meta data the thing type definition provides the possibility to specify so-called `properties`:
```xml
<thing-type id="thingTypeId">
...
<properties>
<property name="vendor">MyThingVendor</property>
<property name="modelId">thingTypeId</property>
<property name="protocol">ZigBee</property>
...
</properties>
...
</thing-type>
```
In general each `property` must have a name attribute which should be written in camel case syntax.
The actual property value is defined as plain text and is placed as child node of the property element.
It is recommended that at least the vendor and the model id properties are specified here since they should be definable for the most of the devices.
In contrast to the properties defined in the 'ThingType' definitions the thing handler [documentation](thing-handler.html) explains how properties can be set during runtime.
### Representation Property
A thing type can contain a so-called `representation property`.
This optional property contains the _name_ of a property whose value can be used to uniquely identify a device.
The `thingUID` cannot be used for this purpose because there can be more than one thing for the same device.
Each physical device normally has some kind of a technical identifier which is unique.
This could be a MAC address (e.g. Hue bridge, camera, all IP-based devices), a unique device id (e.g. a Hue lamp) or some other property that is unique per device type.
This property is normally part of a discovery result for that specific thing type.
Having this property identified per binding it could be used as the `representation property` for this thing.
The `representation property` will be defined in the thing type XML:
```xml
<thing-type id="thingTypeId">
...
<properties>
<property name="vendor">Philips</property>
</properties>
<representation-property>serialNumber</representation-property>
...
</thing-type>
```
Note that the `representation property` is normally not listed in the `properties` part of the thing type, as this part contains static properties, that are the same for each thing of this thing type.
The name of the `representation property` identifies a property that is added to the thing in the thing handler upon successful initialization.
### Representation Property and Discovery
The representation property is being used to auto-ignore discovery results of devices that already have a corresponding thing.
This happens if a device is being added manually.
If the new thing is going online, the auto-ignore service of the inbox checks if the inbox already contains a discovery result of the same type where the value of its `representation property` is identical to the value of the `representation property` of the newly added thing.
If this is the case, the result in the inbox is automatically set to ignored.
Note that this result is automatically removed when the manual added thing is eventually removed.
A new discovery would then automatically find this device again and add it to the inbox properly.
## Formatting Labels and Descriptions
The label and descriptions for things, channels and config descriptions should follow the following format.
The label should be short so that for most UIs it does not spread across multiple lines.
The description can contain longer text to describe the thing in more detail.
Limited use of HTML tags is permitted to enhance the description - if a long description is provided, the first line should be kept short, and a line break (```<br />```) placed at the end of the line to allow UIs to display a short description in limited space.
Configuration options should be kept short so that they are displayable in a single line in most UIs.
If you want to provide a longer description of the options provided by a particular parameter, then this should be placed into the ```<description>``` of the parameter to keep the option label short.
The description can include limited HTML to enhance the display of this information.
The following HTML tags are allowed : ```<b>, <br />, <em>, <h1>, <h2>, <h3>, <h4>, <h5>, <h6>, <i>, <p>, <small>, <strong>, <sub>, <sup>, <ul>, <ol>, <li>```.
These must be inside the XML escape sequence - e.g. ```<description><![CDATA[ HTML marked up text here ]]></description>```.
## Auto Update Policies
Channel types can optionally define a policy with respect to the auto update handling.
This influences the decision within the framework if an auto-update of the item's state should be sent in case a command is received for it.
The auto update policy typically is inherited by the channel from its channel type.
Nevertheless, this value can be overridden in the channel definition.
In this example, an auto update policy is defined for the channel type, but is overridden in the channel definition:
```xml
<channel-type id="channel">
<label>Channel with an auto update policy</label>
<autoUpdatePolicy>recommend</autoUpdatePolicy>
</channel-type>
<thing-type id="thingtype">
<label>Sample Thing</label>
<description>Thing type which overrides the auto update policy of a channel</description>
<channels>
<channel id="instance" typeId="channel">
<autoUpdatePolicy>default</autoUpdatePolicy>
</channel>
</channels>
</thing-type>
```
The following policies are supported:
* **veto**: No automatic state update should be sent by the framework.
The thing handler will make sure it sends a state update and it can do it better than just converting the command to a state.
* **default**: The binding does not care and the framework may do what it deems to be right.
The state update which the framework will send out normally will correspond the command state anyway.
This is the default if no other policy is set explicitly.
* **recommend**: An automatic state update should be sent by the framework because no updates are sent by the binding.
This usually is the case when devices don't expose their current state to the handler.
## Bridges and Thing Descriptions
Every binding has to provide meta information about which bridges and/or *Thing*s it provides and how their relations to each other are structured.
In that way a binding could describe that it requires specific bridges to be operational or define which channels (e.g. temperature, color, etc.) it provides.
Every bridge or *Thing* has to provide meta information such as label or description.
The meta information of all bridges and *Thing*s is accessible through the `org.eclipse.smarthome.core.thing.binding.ThingTypeProvider` service.
Bridge and *Thing* descriptions must be placed as XML file(s) (with the ending `.xml`) in the bundle's folder `/ESH-INF/thing/`.
The full Java API for bridge and *Thing* descriptions can be found in the Java package `org.eclipse.smarthome.core.thing.type`.
### XML Structure for Thing Descriptions
```xml
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bindingID"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0
https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="bridgeTypeID" listed="{true|false}" extensible="channelTypeId1,channelTypeId2,...">
<supported-bridge-type-refs>
<bridge-type-ref id="bridgeTypeID" />
...
</supported-bridge-type-refs>
<label>String</label>
<description>String</description>
<category>String</category>
<channels>
<channel id="channelID" typeId="channelTypeID" />
OR
<channel id="channelID" typeId="channelTypeID">
<label>String</label>
<description>String</description>
</channel>
...
</channels>
OR
<channel-groups>
<channel-group id="channelGroupID" typeId="channelGroupTypeID" />
OR
<channel-group id="channelGroupID" typeId="channelGroupTypeID">
<label>String</label>
<description>String</description>
</channel-group>
...
</channel-groups>
<properties>
<property name="propertyName">propertyValue</property>
...
</properties>
<representation-property>propertyName</representation-property>
<config-description>
...
</config-description>
OR
<config-description-ref uri="{binding|thing-type|channel-type|any_other}:bindingID:..." />
</bridge-type>
<thing-type id="thingTypeID" listed="{true|false}" extensible="channelTypeId1,channelTypeId2,...">
<supported-bridge-type-refs>
<bridge-type-ref id="bridgeTypeID" />
...
</supported-bridge-type-refs>
<label>String</label>
<description>String</description>
<category>String</category>
<channels>
<channel id="channelID" typeId="channelTypeID" />
OR
<channel id="channelID" typeId="channelTypeID">
<label>String</label>
<description>String</description>
</channel>
...
</channels>
OR
<channel-groups>
<channel-group id="channelGroupID" typeId="channelGroupTypeID" />
OR
<channel-group id="channelGroupID" typeId="channelGroupTypeID">
<label>String</label>
<description>String</description>
</channel-group>
...
</channel-groups>
<properties>
<property name="propertyName">propertyValue</property>
...
</properties>
<representation-property>propertyName</representation-property>
<config-description>
...
</config-description>
OR
<config-description-ref uri="{binding|thing-type|channel-type|any_other}:bindingID:..." />
</thing-type>
<channel-type id="channelTypeID" advanced="{true|false}">
<item-type>Dimmer</item-type>
OR
<kind>trigger</kind>
<label>String</label>
<description>String</description>
<category>String</category>
<tags>
<tag>String</tag>
...
</tags>
<state min="decimal" max="decimal" step="decimal" pattern="String" readOnly="{true|false}">
<options>
<option value="String" />
OR
<option value="String">String</option>
...
</options>
</state>
OR
<event>
<options>
<option value="String" />
OR
<option value="String">String</option>
...
</options>
</event>
<command>
<options>
<option value="String" />
OR
<option value="String">String</option>
...
</options>
</command>
<config-description>
...
</config-description>
OR
<config-description-ref uri="{binding|thing-type|channel-type|any_other}:bindingID:..." />
</channel-type>
<channel-group-type id="channelGroupTypeID" advanced="{true|false}">
<label>String</label>
<description>String</description>
<category>String</category>
<channels>
<channel id="channelID" typeId="channelTypeID" />
...
</channels>
</channel-group-type>
...
</thing:thing-descriptions>
```
| Property | Description | |
|------------------------------|----------------------------------------------|--------|
| thing-descriptions.bindingId | The identifier of the binding this types belong to | mandatory |
**Bridges and Things:**
| Property | Description | |
|--------------------------------|----------------------------------------------|--------|
| bridge-type.id / thing-type.id | An identifier for the bridge/Thing type | mandatory |
| bridge-type.listed / thing-type.listed | Denotes if user interfaces should list the bridge/Thing, e.g. for pairing | optional, defaults to true |
| bridge-type.extensible / thing-type.extensible | If the bridge/Thing supports a generic number of channels the allowed channelTypeIds can be listed here. This provides a hint for UIs to support adding/removing channels. Channel groups are not supported. | optional |
| supported-bridge-type-refs | The identifiers of the bridges this bridge/Thing can connect to | optional |
| bridge-type-ref.id | The identifier of a bridge this bridge/Thing can connect to | mandatory |
| label | A human-readable label for the bridge/Thing | mandatory |
| description | A human-readable description for the bridge/Thing | optional |
| category | Category this bridge/Thing belongs to, see categories) | optional |
| channels | The channels the bridge/Thing provides | optional |
| channel.id | An identifier of the channel the bridge/Thing provides | mandatory |
| channel.typeId | An identifier of the channel type definition the bridge/Thing provides | mandatory |
| label | A human-readable label for the channel | optional |
| description | A human-readable description for the channel | optional |
| channel-groups | The channel groups defining the channels the bridge/Thing provides | optional |
| channel-group.id | An identifier of the channel group the bridge/Thing provides | mandatory ||
| channel-group.typeId | An identifier of the channel group type definition the bridge/Thing provides | mandatory |
| properties | Name/value pairs for properties to be set to the thing | optional |
| representation-property | The name of the property that contains a unique identifier of the thing | optional |
| config-description | The configuration description for the bridge/Thing within the ConfigDescriptionRegistry | optional |
| config-description-ref | The reference to a configuration description for the bridge/Thing within the ConfigDescriptionRegistry | optional |
| config-description-ref.uri | The URI of the configuration description for the bridge/Thing within the ConfigDescriptionRegistry | mandatory |
**Channels:**
| Property | Description | |
|-------------------------------|----------------------------------------------|--------|
| channel-type.id | An identifier for the channel type | mandatory |
| channel-type.advanced | The flag indicating if this channel contains advanced functionalities which should be typically not shown in the basic view of user interfaces | optional, default: false |
| kind | The kind of channel. state for channels which have a state, trigger for trigger channels. state is the default. | |
| item-type | An item type of the channel. All item types are specified in ItemFactory instances. The following items belong to the core: Switch, Rollershutter, Contact, String, Number, Dimmer, DateTime, Color, Image. | mandatory if kind state, which is the default |
| label | A human-readable label for the channel | mandatory |
| description | A human-readable description for the channel | optional |
| category | The category for the channel, e.g. TEMPERATURE | optional |
| tags | A list of default tags to be assigned to bound items | optional |
| tag | A tag semantically describes the feature (typical usage) of the channel e.g. AlarmSystem. There are no pre-default tags, they are custom-specific | mandatory |
| state | The restrictions of an item state which gives information how to interpret it | optional |
| state.min | The minimum decimal value of the range for the state | optional |
| state.max | The maximum decimal value of the range for the state | optional |
| state.step | The increasing/decreasing decimal step size within the defined range, specified by the minimum/maximum values | optional |
| state.pattern | The pattern following the printf syntax to render the state | optional |
| state.readOnly | The flag indicating if the state is read-only or can be modified | optional, default: false |
| options | A list restricting all possible values | optional |
| option | The description for the option | optional |
| option.value | The value for the option. Note that the value may be outside of the range specified in the min/max if this is specified. | mandatory |
| command | Commands this channel will send to the binding. This is used to model "write-only" channels and gives UIs a hint to display push-buttons without state | optional |
| options | A list defining the possible commands | optional |
| option | The description for the option | optional |
| option.value | The value for the option. This is the actual command send to the channel. | mandatory |
| event | The restrictions of an trigger event which gives information how to interpret it | optional |
| autoUpdatePolicy | The auto update policy to use | optional |
| config-description | The configuration description for the channel within the ConfigDescriptionRegistry | optional |
| config-description-ref | The reference to a configuration description for the channel within the ConfigDescriptionRegistry | optional |
| config-description-ref.uri | The URI of the configuration description for the channel within the ConfigDescriptionRegistry | mandatory |
**Channel Groups:**
| Property | Description | |
|-----------------------------|----------------------------------------------|--------|
| channel-group-type.id | An identifier for the channel group type | mandatory |
| channel-group-type.advanced | The flag indicating if this channel group contains advanced functionalities which should be typically not shown in the basic view of user interfaces | optional, default: false |
| label | A human-readable label for the channel group | mandatory |
| description | A human-readable description for the channel group | optional |
| category | The category for the channel group, e.g. TEMPERATURE | optional |
| channels | The channels the bridge/Thing provides | mandatory |
| channel.id | An identifier of the channel the bridge/Thing provides | mandatory |
| channel.typeId | An identifier of the channel type definition the bridge/Thing provides | mandatory |
The full XML schema for Thing type descriptions is specified in the <a href="https://openhab.org/schemas/thing-description-1.0.0.xsd">openHAB thing description XSD</a> file.
**Hints:**
- Any identifiers of the types are automatically mapped to unique identifiers: `bindingID:id`.
- The attribute `uri` in the section `config-description` is optional, it *should not* be specified in bridge/*Thing*/channel type definition files because it's an embedded configuration.
If the `uri` is *not* specified, the configuration description is registered as `thing-type:bindingID:id` or `channel-type:bindingID:id` otherwise the given `uri` is used.s
- If a configuration description is already specified somewhere else and the bridge/*Thing*/channel type wants to (re-)use it, a `config-description-ref` should be used instead.

84
developers/buildsystem.md Normal file
View File

@ -0,0 +1,84 @@
---
layout: developersguide
title: Build System
---
{% include base.html %}
# Build System
The buildsystem is based on maven.
A very common tool for Java development.
Maven is a convention centric, declarative system that is extensible via addional plugins.
That means if you stick 100% to mavens idea of a java project, your buildsystem instruction file is not longer than 10 lines.
openHAB has a few extra requirements and we use about 10 additional plugins,
ranging from OSGi specific ones (bnd) to publish and testing plugins.
This section talks about some common buildsystem related topics and also some quirks that you will encounter.
## Adding Dependencies
Generally all dependencies should be OSGi-bundles and available on JCenter.
** External dependency **
In most cases they should be referenced in the project POM with scope `provided`:
```
<dependencies>
<dependency>
<groupId>foo.bar</groupId>
<artifactId>baz</artifactId>
<version>1.0.0</version>
<scope>provided</provided>
</dependency>
</dependencies>
```
To ensure that they are available at runtime they also need to be added to the `feature.xml`:
```
<bundle dependency="true">mvn:foo.bar/baz/1.0.0</bundle>
```
If the bundle is not an OSGi-bundle, you need to wrap it and provide proper names
```
<feature prerequisite="true">wrap</feature>
<bundle dependency="true">wrap:mvn:foo.bar/baz/1.0.0$Bundle-Name=Foobar%20Baz%20Library&amp;Bundle-SymbolicName=foo.bar.baz&amp;Bundle-Version=1.0.0</bundle>
```
** Internal dependency **
In two cases libraries can be added to the `/lib` directory:
1. The bundle is not available for download
2. The bundle is not an OSGi bundle but needs to be used for integration tests.
Unlike Karaf, BND is not able to wrap bundles on its own.
In this case the binding works as wrapper.
You need add the library and export all needed packages manually.
The build system automatically picks up all JAR files in `/lib` and embeds them in the new bundle.
In this case they must not be added to the feature.
If the bundles manifest is not properly exporting all needed packages, you can import them manually by adding
```
<properties>
<bnd.importpackage>foo.bar.*,foo.baz.*</bnd.importpackage>
</properties>
```
to the POM.
If the imported packages need to be exposed to other bundles (e.g. case 2 above), this has to be done manually by adding
```
<properties>
<bnd.exportpackage>foo.bar.*;version="1.0.0"</bnd.exportpackage>
</properties>
```
to the POM.
If `version="1.0.0"` is not set, the packages are exported with the same version as the main bundle.
Optional parameters available for importing/exporting packages (e.g. `foo.bar.*;resolution:="optional"`) are available, too.
Packages can be excluded from import/export by prepending `!` in front of the name (e.g. `<bnd.importpackage>!foo.bar.*</bnd.importpackage>' would prevent all packages starting with foo.bar from being imported).

View File

@ -34,7 +34,7 @@ We don't want it to do everything for everybody.
This means that we might decide against incorporating a new feature.
However, there might be a way to implement that feature *on top of* openHAB.
### Discuss your Design on the Mailing List
### Discuss Your Design in the Community
We recommend discussing your plans [in the discussion forum](https://community.openhab.org)
before starting to code - especially for more ambitious contributions.

View File

@ -1,107 +0,0 @@
---
layout: developersguide
title: Developing bindings
---
{% include base.html %}
# Developing a Binding for openHAB
This page describes the necessary steps in order to implement a new binding for openHAB.
For information about code style and naming conventions, please see the [coding guidelines](guidelines.html).
Choose the option "openHAB 2 Add-ons" in [your IDE setup](ide.html), and create a skeleton for your binding.
To do this, use a command line terminal and go to your Git repository folder under `git/openhab2-addons/addons/binding` and call the script `create_openhab_binding_skeleton.sh` with two arguments.
It is important that your binding name is in camel case (e.g. 'ACMEProduct' or 'SomeSystem').
After the binding name, provide your name as the author (surrounded by quotes if you want to use whitespaces to separate your first and last name).
Example: `~/git/openhab2-addons/addons/binding/create_openhab_binding_skeleton.sh BindingName "Firstname Lastname"`
Now go back to the IDE and choose `File->Import->General->Existing Projects into Workspace`, enter the folder of the newly created skeleton as the root directory, and press "Finish".
_Note:_ Here you can find a [screencast of the binding skeleton creation](http://youtu.be/30nhm0yIcvA).
## Before You Start
If you plan to contribute your binding to openHAB through a PR, here is what we suggest to help keeping the potential effort for changes due to reviews low:
- Before you start with the implementation, create an issue on GitHub and talk about your plan to come up with a PR.
Maybe there are others that have privately a similar binding before and would be ready to contribute it.
Also, it might help finding collaborators for the implementation.
- It is a good idea to start with modelling the Things for your binding, i.e. what kind of Bridge-Types, Thing-Types and Channel-Types it should have.
Also, you should create the classes for the handlers that you need as well as potential discovery services (but no need to implement any working code at that stage).
With this in place, you are invited to already create a PR and mark it as [WIP] (for: work in progress) and ask for a first review.
You might get valuable input from maintainers and other experts and you avoid having to refactor all details of an implementation.
- Once the general architecture/modelling of your binding is approved, go for the implementation details and ask for a final review in the end.
## Implement the Binding
The skeleton should give you an easy starting point for your developments.
To learn about the internal structure and the concepts of a binding, please see the [Eclipse tutorial on binding development](https://www.eclipse.org/smarthome/documentation/development/bindings/how-to.html).
Please especially note our [coding guidelines](guidelines), which must be respected for having pull requests approved.
If you have any special dependencies in your code, please check the [library recommendations](https://www.eclipse.org/smarthome/documentation/development/bindings/dependencies.html) at Eclipse SmartHome.
This will ensure that everyone uses the same libraries for e.g. JSON and XML processing or for HTTP and websocket communication.
## Setup and Run the Binding
To setup the binding you need to configure at least one *Thing* and link an *Item* to it.
In your workspace in `distro-resources/src/main/resources/things`, you can define and configure *Things* in files with a `*.things` extension.
The following file defines a thing for the Yahoo Weather binding:
```
yahooweather:weather:berlin [ location="638242" ]
```
In this example a *Thing* of the *ThingType* `yahooweather:weather` is defined with a configuration for the location.
Next you need to create *Items* and link them to the *Channel* of your binding.
Here is the example of the Yahoo Weather binding:
```
Number Berlin_Temperature "Temperature in Berlin [%.1f °C]" { channel="yahooweather:weather:berlin:temperature" }
Number Berlin_Humidity "Humidity in Berlin [%d %%]" { channel="yahooweather:weather:berlin:humidity" }
```
The syntax for a channel link is `{ channel = "<binding-id>:<thing-type-id>:<thing-id>:<channel-id>" }`.
If you start the openHAB runtime including the binding now (make sure that your binding is checked in the launch configuration dialog!), the code inside your `ThingHandler` implementation is executed.
## Include the Binding in the Build and the Distro
Once you are happy with your implementation, you need to integrate it in the Maven build and add it to the official distro.
For the Maven build, please add a new line in the [binding pom.xml](https://github.com/openhab/openhab2-addons/blob/master/addons/binding/pom.xml) at the alphabetically correct position.
In order to have the binding being included by the distro, you furthermore need to add it to the [feature.xml](https://github.com/openhab/openhab2-addons/blob/master/features/karaf/openhab-addons/src/main/feature/feature.xml), again at the alphabetically correct position.
If you have a dependency on a transport bundle (e.g. upnp, mdns or serial), make sure to add a line for this dependency as well (see the other bindings as an example).
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.
If it does, it is time to [contribute your work](../contributing/contributing.html)!
### Static Code Analysis
The build includes [Tooling for static code analysis](https://github.com/openhab/static-code-analysis) that will validate your code against the openHAB Coding Guidelines and some additional best practices.
Information about the checks can be found [here](https://github.com/openhab/static-code-analysis#esh-guidelines-covered).
The tool will generate an individual report for each binding, which you can find in `.../your_binding_directory/target/code-analysis/report.html` file and a report for the whole build that contains links to the individual reports in the `../openhab2-addons/target/summary_report.html`.
The tool categorizes the found issues by priority: 1(error), 2(warning) or 3(info).
If any error is found within your code the Maven build will end with failure.
You will receive detailed information (path to the file, line and message) listing all problems with priority 1 on the console:
```
...
[ERROR] Failed to execute goal org.openhab.tools:static-code-analysis:0.0.4:report (default) on project org.openhab.binding.example: Code Analysis Tool has found 1 error(s)!
[ERROR] Please fix the errors and rerun the build.
[ERROR] Errors list:
[ERROR] ERROR found by checkstyle: .binding.example.test/build.properties:0 Missing build.properties file.
[ERROR] Detailed report can be found at: file////path_to_openhab/openhab2-addons/addons/binding/org.openhab.binding.example/target/code-analysis/report.html
...
```
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).
Re-run the build to confirm that the checks are passing.

View File

@ -1,161 +0,0 @@
---
layout: developersguide
title: Logging
---
{% include base.html %}
Logging
=======
## Introduction
During development you might want to change the logging configuration in order to debug your application and the underlying framework or services.
openHAB is using a combination of the Simple Logging Facade for Java ([slf4j](http://www.slf4j.org/)) and a [logback](http://logback.qos.ch/) implementation. In this page we will give a few tips on how you can use and configure the logger to suit your needs.
## Set up Logging
In order to enable logging in your run configuration, you have to add the following bundles to the runtime configuration:
- org.slf4j.api;
- ch.qos.logback.classic;
- ch.qos.logback.core;
- ch.qos.logback.slf4j.
In your bundle, you will depend only on the `org.slf4j` package, so add it as a dependency in the `Import-Package` header of the `MANIFEST.MF` file.
You can use a logger the following way:
```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
static Logger logger = LoggerFactory.getLogger(this.getClass());
public static void main(String[] args) {
logger.info("Hello World");
}
}
```
## Logback Configuration File
If we have setup the logging by just adding the bundles above we will end up in a logging with default configuration. In order to define custom logging configuration we can prepare a logging configuration file.
The configuration file for openHAB is placed in the [openhab-distro/lauch/home/logback_debug.xml](https://github.com/openhab/openhab-distro/blob/fa20ddb3555873ea98fe9fc5a5c0c28bf33003d5/launch/home/logback_debug.xml) file. We have added a few comments on this file in order to attract your attention on some significant points:
```xml
<configuration scan="true">
<!-- This is the appender that displays the logging on the console -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- This pattern will display the time, the log level, the name of the logger and the log message -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%-30.30logger{36}:%-5line] - %msg%ex{10}%n</pattern>
</encoder>
</appender>
<!-- Another appender that logs into a file -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${openhab.logdir:-userdata/logs}/openhab.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%-30.30logger{36}:%-5line] - %msg%ex{10}%n</pattern>
</encoder>
</appender>
<!-- We have removed part of the original file in this example -->
...
<logger name="smarthome.event" level="INFO" additivity="false">
<!-- The element <logger> may contain zero or more <appender-ref> elements -->
<appender-ref ref="EVENTFILE" />
<appender-ref ref="STDOUT" />
</logger>
<!-- Logback uses inheritance to determine the log level.
If a given logger is NOT assigned a level it inherits one from the its closest ancestor -->
<logger name="org.openhab" level="DEBUG" />
<logger name="org.eclipse.smarthome" level="DEBUG" />
<logger name="org.eclipse.jetty" level="INFO" />
<logger name="org.jupnp" level="ERROR"/>
<logger name="javax.jmdns" level="OFF"/>
<logger name="javax.jmdns.impl" level="OFF"/>
<logger name="javax.jmdns.impl.constants" level="OFF"/>
<logger name="tuwien.auto.calimero" level="WARN" />
<!-- The root logger is at the top of the logger hierarchy.
All loggers inherit its level, if there are no ancestors
between the root and the logger -->
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
<logger name="OSGi" level="OFF" />
</configuration>
```
If you are not aware of the different [log levels](http://logback.qos.ch/manual/architecture.html#effectiveLevel), [appenders](http://logback.qos.ch/manual/appenders.html) and [logger elements](http://logback.qos.ch/manual/configuration.html#loggerElement), you might want to take a look at the links to the the logback documentation.
If you want to change the logger's configuration, you might change the content of this file, but a better approach will be to make a copy of this file and apply the changes in the new file.
The path to the logging file is passed as an argument to the JVM, so if you want to use different configuration file, you have to change the property `-Dlogback.configurationFile` with the path to the new file in the run configuration that you are using.
## Configuration
There are several things, that you might want to change in the configuration:
- the log level for a logger;
- the pattern of an appender;
- redirect the log to a text file.
### Setting up the Log Level
As you can see from the example configuration file above, the level for jUPnP is set to ERROR. If you develop a binding that is using jUPnP, you might want to see more logs on your console. You can simply change the log level to TRACE or DEBUG:
```xml
<logger name="org.jupnp" level="TRACE"/>
```
### Changing the Pattern of an Appender
Another useful option is to change the pattern of an appender. This pattern defines what information will be logged. The logback logger provides many [predefined conversation words](http://logback.qos.ch/manual/layouts.html#conversionWord) for the most common use cases. Use the most appropriate ones for your specific case. For the purpose of exploring and debugging multi threaded applications you might need to see which thread has generated certain log event. In order to see this in the logs, you have to add the conversion word `t` to the pattern above:
```xml
<pattern>%t %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%-30.30logger{36}:%-5line] - %msg%ex{10}%n</pattern>
```
### Redirect the Log to a Text File
You might want to redirect your log to a text file. This gives you the flexibility to search easier for a specific log or to save your logs. If you have looked at the [default configuration file](#logback-configuration-file) you might have noticed that several appenders are listed there. The process of redirecting the log to a text file is as simple as :
- adding a new `FileAppender` and specifying the path of the output file:
```xml
<appender name="YOUR_APPENDER_NAME" class="ch.qos.logback.core.FileAppender">
<file>relative/path/to/file.log</file>
<encoder>
<!--Pick up a pattern that will log the information taht you will need -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%-30.30logger{36}:%-5line] - %msg%ex{10}%n</pattern>
</encoder>
</appender>
```
- adding your logger, setting up the log level and adding `appender-ref` element to the logger element. For this example we will assume that you want to add the following logger with the name `com.logger.example`:
```xml
<logger name="com.logger.example" level="INFO">
<appender-ref ref="YOUR_APPENDER_NAME" />
</logger>
```
## Further Reading
- <http://www.slf4j.org/>
- <http://logback.qos.ch/>
- <http://www.slf4j.org/manual.html>
- <http://stackoverflow.com/questions/6699537/how-to-use-multiple-configurations-with-logback-in-a-single-project>

View File

@ -0,0 +1,21 @@
---
layout: developersguide
title: Extension service
---
{% include base.html %}
# Extension service
openHAB comes with an API that allows implementing a service that manages the installed extensions within a solution.
All that needs to be done is to register an OSGi service that implements `org.eclipse.smarthome.core.extension.ExtensionService`.
Such a service has to provide a list of available extensions and then can be called in order to install and uninstall them.
What kind of implementation is chosen is completely up to the solution.
Suitable mechanisms might be Apache Felix FileInstall, Apache Karaf FeatureInstaller, etc.
For testing purposes, openHAB comes with a sample implementation in the bundle `org.eclipse.smarthome.core.extension.sample`.
Installation and uninstallation requests are executed by a thread pool named "extensionService".
If an implementation does not support concurrent execution of such operations, the thread pool size should be set to 1.
TODO

View File

@ -6,6 +6,7 @@ title: Coding Guidelines
{% include base.html %}
# Coding Guidelines
{:.no_toc}
The following guidelines apply to all (Java) code of the openHAB project.
They must be followed to ensure a consistent code base for easy readability and maintainability.
@ -14,7 +15,13 @@ Exceptions can certainly be made, but they should be discussed and approved by a
Note that this list also serves as a checklist for code reviews on pull requests.
To speed up the contribution process, we therefore advice to go through this checklist yourself before creating a pull request.
## A. Directory and file layout
If you are just keen on binding development, you may skip this document first and come back later.
{::options toc_levels="2,3"/}
* TOC
{:toc}
## A. Directory and File Layout
The following directory and file layout must be respected:
@ -93,9 +100,9 @@ Data-transfer-objects (DTOs map from Json/XML to Java classes) do not require Ja
* To allow optimized runtimes, the set of Java packages to be used is further restricted to [Compact Profile 2](http://www.oracle.com/technetwork/java/embedded/resources/tech/compact-profiles-overview-2157132.html)
2. The [OSGi R5](http://www.osgi.org/Download/Release5) release is targeted, and newer features should not be used.
3. slf4j is used for logging.
4. Some utility libraries are available which can be used throughout the code:
- Apache Commons IO
- Apache Commons Lang
You might also have the need to use other libraries for specific use cases like XML processing, networking etc.
See [Default libraries](#default-libraries) for more details.
## E. Runtime Behavior
@ -236,3 +243,40 @@ You must therefore disable null-checks for such references:
@Reference
private @NonNullByDefault({}) MyService injectedService;
```
### Default Libraries
In order to not have every binding use a different library, the following packages are available by default:
For XML Processing
* com.thoughtworks.xstream
* com.thoughtworks.xstream.annotations
* com.thoughtworks.xstream.converters
* com.thoughtworks.xstream.io
* com.thoughtworks.xstream.io.xml
For JSON Processing
* com.google.gson.*
For HTTP Operations
* org.eclipse.jetty.client.*
* org.eclipse.jetty.client.api.*
* org.eclipse.jetty.http.*
* org.eclipse.jetty.util.*
Note: HttpClient instances should be obtained by the handler factory through the HttpClientFactory service and unless there are specific configuration requirements, the shared instance should be used.
For Web Socket Operations
* org.eclipse.jetty.websocket.client
* org.eclipse.jetty.websocket.api
Note: WebSocketClient instances should be obtained by the handler factory through the WebSocketClientFactory service and unless there are specific configuration requirements, the shared instance should be used.
Additionally these libraries are allowed
* Apache Commons IO
* Apache Commons Lang

View File

@ -1,26 +1,90 @@
---
layout: developersguide
title: Developer Guide
title: Addon Development
---
# Overview
The _Developer Guide_ contains practical and technical information for people that want to get into the development of openHAB.
In this chapter of the documentation you will learn how to develop an addon for openHAB.
A variety of different parts of openHAB can be extended, we go through all of them.
- Get an overview of how the openHAB project work is [organised](contributing/governance.html).
- Learn [how to contribute](contributing/contributing.html) to the project.
- Review the [coding guidelines](development/guidelines.html) and [conventions](development/conventions.html).
- See the details of how to [setup an IDE](development/ide.html) for development.
- Get into the details of [implementing bindings](development/bindings.html).
openHAB uses [git](https://git-scm.com/) as its version control system and [GitHub](https://github.com/openhab) for hosting the different repositories and source code.
You will get in contact with git in many places and it makes sense to get familiar with its basic commands and concepts.
There are many pages to learn about Git.
Try [git - the simple guide](http://rogerdudler.github.io/git-guide/) as a start.
This guide also covers the technologies that are fundamental to the openHAB Core runtime. If you want to learn some details on those, here are useful introductions to:
Addons and the core itself are written in Java.
Java is not hard to learn, but it helps if you have a general technical understanding of programming languages.
- [OSGi](prerequisites/osgi.html)
- [OSGi Declarative Services](prerequisites/osgids.html)
- [OSGi Configuration Admin](prerequisites/configadmin.html)
- [OSGi Event Admin](prerequisites/eventadmin.html)
- [Eclipse Equinox](prerequisites/equinox.html)
- [Eclipse Tycho](prerequisites/tycho.html)
- [Eclipse Targetplatform](prerequisites/targetplatform.html)
- [OSGi Hands-on](prerequisites/osgitasks.html)
The different guides of this chapter assume that you are somewhat familiar with Java 8 and that you have a rough idea of Git's workflow (eg "checkout", "branches", "push").
## Choose the Right Concept
openHAB allows you to build up on the following concepts:
* **Bindings**: A binding connects to external services or devices,
* **Automation engine module**: A trigger, condition or action that can be used in automation rules (or scripts),
* **Transformation / Profiles**: Can be used to transform a *Thing Channel* value before it is assigned to an *Item*,
* **an IO service**: Exposes openHAB internals via a defined interface (for example the REST interface, HomeKit or Hue Emulation Service)
* **a Persistence service**: Persist item state updates and/or changes and allow them to be retrieved for specific points in time.
* **Natural language processing skill**:
Executes something depending on the understood Intents and returns something back to the user,
* **Audio sinks/sources**:
Extend where audio can be played on or implement audio sources.
* and many more (not covered yet).
Sometimes though its just not worth writing a binding and you are better off
just using an http action in a rule or script to retrieve some values.
Therefore: First think what you want to archive! Check our [community forum](https://community.openhab.org)
and discuss your concept.
Find the right abstraction and the corresponding link on the left navigation panel.
General [coding guidelines](development/guidelines.html) apply to all types of addon development.
## Setup the Development Environment
You can use any IDE that is suitable for OSGi/Java development.
We recomment the freely available [Eclipse IDE](https://wiki.eclipse.org/Eclipse_Installer).
Development can happen on any of the supported operating systems (Windows, Mac OS, Linux).
Please ensure that you have the following prerequisites installed as well:
1. [Git](https://git-scm.com/downloads) For retrieving our source code and push changes back
1. [Maven 3.x](https://maven.apache.org/download.cgi) Our buildsystem tool
1. Java JDK 8, for example from Oracle [Oracle JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
**TODO**: We are currently reworking how to setup a perfect development enviroment.
A new step by step guide will appear here after the migration is done.
Watch [Issue 5005](https://github.com/openhab/openhab2-addons/issues/5005) for further information and progress.
For now follow these steps:
1. Install Bndtools in your Eclipse IDE (Help -> Install new software. Use "-- All available sites--" for "Work with".)
2. Checkout the openHAB demo application: `git clone --depth=1 https://github.com/maggu2810/openhab-demo`.
3. Open the directory in Eclipse (File -> Import -> Existing maven projects).
Wait for the download and build to finish (about 3-5 minutes).
4. Checkout the openhab2-addons repository: `git clone --depth=1 https://github.com/openhab/openhab2-addons`
5. Add the binding that you want to develop on to the workspace (File -> Import -> Existing maven projects).
Do not add all projects.
6. In Eclipse Package Explorer: Search for `pom.xml` in the demo-app project.
![pom xml](https://user-images.githubusercontent.com/66436/54607049-a9031700-4a4d-11e9-9b9d-64a620270d28.png)
Add your addon as maven dependency like so (replace `astro`!):
```xml
<project ...>
...
<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.astro</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
```
7. In Eclipse Package Explorer: Search for `app.bndrun` in the "demo-app" project.
Double click (takes a few seconds).
8. Add your project to "Run requirements" via drag&drop from the Package Explorer.
![Bndtools](https://user-images.githubusercontent.com/66436/54527103-2c066d80-4979-11e9-8852-c06a41f4d50b.png)
9. Execute with "Run OSGi" or "Debug OSGi"

View File

@ -0,0 +1,10 @@
---
layout: developersguide
title: IO Services
---
{% include base.html %}
# Developing an IO Service
TODO

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -69,9 +69,6 @@ Eclipse has a graphical interface for working with the target platform. It is lo
Depending on the repositories that you have selected in the installation process, you might have many options in this window. For openHAB development choose `openHAB Target Platform`. Make sure that it is checked and that it is "Active".
<img src="images/targetplatform.png" width="600"/>
Fig. 1. Target Platform dialog window
Hint! If for some reason the target platform that you want to use is missing from this list, the easiest way to add it is to close this window and simply drag and drop the target definition file into Eclipse. In the top right corner you will see a button `Set as current target platform`.
### Reloading
@ -96,8 +93,6 @@ You can edit the target definition file with any text editor that you want, but
- in the `Locations` tab select `Add..`;
- Select `Folder` and follow the wizard.
<img src="images/edittarget.png" width="600"/>
Fig. 2. Edit Target Definition dialog window
## Further Reading

View File

@ -0,0 +1,638 @@
---
layout: developersguide
title: Automation Modules
---
{% include base.html %}
# Developing Automation Modules
{:.no_toc}
In this section you will be guided through developing *Module Types* and corresponding *Module Handlers* for the automation engine of openHAB.
{::options toc_levels="2,3"/}
* TOC
{:toc}
## Module Types and Module Handlers
Module Types describe Conditions, Triggers and Actions for the automation engine in terms of user visible strings like a label, a description, tags.
But also what configuration values are available and what inputs and outputs a Module Type provides.
For each *Module Type* a corresponding *Module Handler* is in place to actually execute code.
To better get into the topic, let's develop a rule for the automation engine that is compromised completely out of custom modules (in contrast to core provided ones).
In our hands-on application we will switch on a virtual air conditioner (`Action`) as soon as the outdoor temperature is over a certain value (`Trigger`), but only if a person is at home (`Condition`).
We want the air conditioner to operate at different levels depending on the temperature.
First you want to create a new bundle for example via the skeleton.
## Module Type Provider
To inform the rule engine of your module types, you implement an OSGi service that extends `ModuleTypeProvider`.
As soon as this provider is active, you have access to all the exposed triggers, conditions and actions from
within old xtend rules, scripts and automation engine rules.
Just keep in mind, that this only describes your triggers, conditions and actions,
but the automation engine will not yet know what to do when encountering such a module.
We implement the handlers in a follow up section.
```java
@NonNullByDefault
@Component(immediately=true, service={ModuleTypeProvider.class})
public class MyModuleTypeProvider implements ModuleTypeProvider {
private Map<String, ModuleType> providedModuleTypes;
public MyModuleTypeProvider() {
providedModuleTypes = new HashMap<String, ModuleType>();
providedModuleTypes.put(TemperatureTriggerType.UID, TemperatureTriggerType.initialize());
providedModuleTypes.put(PresenceConditionType.UID, PresenceConditionType.initialize());
providedModuleTypes.put(AirConditionerActionType.UID, AirConditionerActionType.initialize());
}
@SuppressWarnings("unchecked")
@Override
public <T extends ModuleType> T getModuleType(String UID, Locale locale) {
return (T) providedModuleTypes.get(UID);
}
@SuppressWarnings("unchecked")
@Override
public <T extends ModuleType> Collection<T> getModuleTypes(Locale locale) {
return (Collection<T>) providedModuleTypes.values();
}
@Override
public void addProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
// does nothing because this provider does not change
}
@Override
public Collection<ModuleType> getAll() {
return Collections.unmodifiableCollection(providedModuleTypes.values());
}
@Override
public void removeProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
// does nothing because this provider does not change
}
}
```
The above factory is exposing all three *Module Types* that we need for our scenario.
We do not need to care about the `ProviderChangeListener` methods here, because our types are rather static.
If module types change over time in your factory, you need to notify the automation engine.
It is common practise to define the unique ID (UID) within the type class itself.
Let's have a look at all of our type classes:
```java
@NonNullByDefault
public class TemperatureTriggerType extends TriggerType {
public static final String UID = "TemperatureTriggerType";
public static final String DATA_CURRENT_TEMPERATURE = "temperature";
public static final String CONFIG_OPERATOR = "operator";
public static final String CONFIG_TEMPERATURE = "temperature";
public static TriggerType initialize() { // Factory method Pattern
// Define outputs
List<Output> output = new ArrayList<>();
Output temperature = new Output(DATA_CURRENT_TEMPERATURE, Integer.class.getName(),
"Temperature", "Indicates the current room temperature", null, null, null);
output.add(temperature);
// Define configurations
final ConfigDescriptionParameter temperature = ConfigDescriptionParameterBuilder
.create(CONFIG_TEMPERATURE, Type.INTEGER).withRequired(true).withReadOnly(true).withMultiple(false)
.withLabel("Temperature").withDescription("Trigger temperature").build();
final ConfigDescriptionParameter operator = ConfigDescriptionParameterBuilder.create(CONFIG_OPERATOR, Type.TEXT)
.withRequired(true).withReadOnly(true).withMultiple(false).withLabel("Operator")
.withDescription("Below/Above temperature").withDefault("above").build();
final List<ConfigDescriptionParameter> config = new ArrayList<>();
config.add(temperature);
config.add(operator);
return new AirConditionerTriggerType(output,config);
}
public AirConditionerTriggerType(List<Output> output, List<ConfigDescriptionParameter> config) {
super(UID, config, "Temperature Trigger", "This triggers when the temperature has reached a certain value", null, Visibility.VISIBLE, output);
}
}
```
As you can see the constructor of the extended `TriggerType` expects us to fill in a label and description text and lets us define if the trigger is for public use (`VISIBLE`).
Configuration parameters are described to the user and to user-interfaces via `ConfigDescriptionParameter`s.
A configuration parameter needs a unique ID, a label, optionally a description and a type (TEXT, INTEGER, BOOLEAN and so on).
Triggers and Conditions can also output data.
And our temperature trigger will not only trigger on a configured temperature, but also output it.
To define an *Output* you need to pass at least an ID, a type, a label and a description to the constructor.
The type is the fully qualified name of a class, in this case from the `Integer` class.
Conditions and Actions can take inputs on the other hand.
Speaking of which, let's have a look at the code.
```java
public class PresenceConditionType extends ConditionType {
public static final String UID = "PresenceConditionType";
public static final String DATA_PRESENCE = "presence";
public static final String CONFIG_PRESENCE_ITEM = "presence";
public static ConditionType initialize() {
List<ConfigDescriptionParameter> config = new ArrayList<>();
ConfigDescriptionParameter presenceItemConfig;
presenceItemConfig = ConfigDescriptionParameterBuilder.create(CONFIG_PRESENCE_ITEM, Type.TEXT)
.withRequired(true).withReadOnly(true).withMultiple(false).withLabel("Presence item")
.withDescription("The item that decides if this condition is satisfied").build();
config.add(presenceItemConfig);
List<Input> input = new ArrayList<>();
List<Output> output = new ArrayList<>();
Output state = new Output(DATA_PRESENCE, "State", "Presence",
"Indicates the state of the presence detector via an ON or OFF", null, null, null);
output.add(state);
return new PresenceConditionType(config, input, output);
}
public PresenceConditionType(List<ConfigDescriptionParameter> config, List<Input> input, List<Input> output) {
super(UID, config, "Presence Condition", "This condition is satisfied when the configure presence item is in ON state", output, Visibility.VISIBLE, input);
}
}
```
The Condition that we are going to implement in the Condition Handler latter on will use the state of an item to decide if this presence condition is satisfied.
Notice that our output is of type "State" instead of a fully qualified class name like `java.lang.String`.
The following openHAB classes have short forms:
* "State" (an *Item* state)
* "Event" (an openHAB event from the event bus)
* "Command" (a command targeting an *Item*)
And now let's have a look at the Action type.
```java
public class AirConditionerActionType extends ActionType {
public static final String UID = "AirConditionerActionType";
public static final String CONFIG_LEVEL1_MIN_TEMP = "level1_min_temp";
public static final String CONFIG_LEVEL2_MIN_TEMP = "level2_min_temp";
public static ActionType initialize() {
final ConfigDescriptionParameter temp1 = ConfigDescriptionParameterBuilder.create(CONFIG_LEVEL1_MIN_TEMP, Type.INTEGER)
.withRequired(true).withReadOnly(true).withMultiple(false).withLabel("Temperature for level 1")
.withDescription("Level 1 on the given temperature in celsius").build();
final ConfigDescriptionParameter temp2 = ConfigDescriptionParameterBuilder.create(CONFIG_LEVEL2_MIN_TEMP, Type.INTEGER)
.withRequired(true).withReadOnly(true).withMultiple(false).withLabel("Temperature for level 2")
.withDescription("Level 2 on the given temperature in celsius").build();
List<ConfigDescriptionParameter> config = new ArrayList<ConfigDescriptionParameter>();
config.add(temp1);
config.add(temp2);
Input currentTemperature = new Input(TemperatureTriggerType.DATA_CURRENT_TEMPERATURE, Integer.class.getName(), "Current Temperature", "Depending on this temperature input the AC will turn on", null, true, null, null);
List<Input> input = new ArrayList<>();
input.add(currentTemperature);
return new AirConditionerActionType(config, input);
}
public AirConditionerActionType(List<ConfigDescriptionParameter> config, List<Input> input) {
super(UID, config, "Switch an air conditioner", "Control an air conditioner. Depending on the configuration and inputs it is switched into different power levels.", null,
Visibility.VISIBLE, input, null);
}
}
```
Our Action is quite simple.
Our air conditioner is turned off by default, is going into power level 1 when a certain temperature is reached and into level 2 on a second configured temperature.
It is the task of a rule to wire outputs to inputs.
In our module types we just have to make sure that output and input types are matching.
In this action for example we have an input of type `TemperatureTriggerType.DATA_CURRENT_TEMPERATURE` which is exactly the output of our trigger module type.
The programmatic way is useful for dynamically appearing module types.
You will learn an easier way in a moment for statically defined module types
### Module types via json
In the last section we learned about the programmatic way of exposing *Module Types*.
It is actually way easier to just descripe your module types in a declarative way via json and bundle them with your addon.
To describe your modules (triggers, conditions, actions), add json files to `src/main/resources/ESH-INF/automation/moduletypes/`.
A module type file can contain one or multiple type descriptions.
For our scenario we go with one file:
```json
{
"triggers":[
{
"uid":"TemperatureTriggerType",
"label":"Temperature Trigger",
"description":"This triggers when the temperature has reached a certain value",
"configDescriptions":[
{
"name":"temperature",
"type":"INTEGER",
"label":"Temperature",
"description":"Trigger temperature",
"required":true
},
{
"name":"operator",
"type":"TEXT",
"label":"Operator",
"description":"Below/Above temperature",
"required":true,
"default": "above"
}
],
"outputs":[
{
"name":"temperature",
"type":"java.lang.Integer",
"label":"Current Temperature",
"description":"Indicates the current room temperature"
}
]
}
],
"conditions":[
{
"uid":"PresenceConditionType",
"label":"Presence Condition",
"description":"This condition is satisfied when the configure presence item is in ON state",
"configDescriptions":[
{
"name":"presence",
"type":"TEXT",
"label":"Presence item",
"description":"The item that decides if this condition is satisfied",
"required":true
}
],
"outputs":[
{
"name":"presence",
"type":"State",
"label":"Output value",
"description":"Indicates the state of the presence detector via an ON or OFF"
}
]
}
],
"actions":[
{
"uid":"AirConditionerActionType",
"label":"Switch an air conditioner",
"description":"Control an air conditioner. Depending on the configuration and inputs it is switched into different power levels",
"configDescriptions":[
{
"name":"level1_min_temp",
"type":"INTEGER",
"label":"Temperature for level 1",
"description":"Level 1 on the given temperature in celsius",
"required":true
},
{
"name":"level2_min_temp",
"type":"INTEGER",
"label":"Temperature for level 2",
"description":"Level 2 on the given temperature in celsius",
"required":true
}
],
"inputs":[
{
"name":"temperature",
"type":"java.lang.Integer",
"label":"Current Temperature",
"description":"Depending on this temperature input the AC will turn on"
}
]
}
]
}
```
## Module Handlers
You now have semantically described your modules.
The pieces of code that actually
* trigger, in case of Trigger types,
* decide on condition satisfaction for Condition types or
* execute something in case of Action types
are called *Module handlers*.
We now go over the implementation for all of our custom modules.
As usual, we need a factory that creates module handlers on demand for the automation engine:
```java
@NonNullByDefault
@Component(service={ModuleHandlerFactory.class})
public class MyHandlerFactory extends BaseModuleHandlerFactory {
public static final String MODULE_HANDLER_FACTORY_NAME = "[MyHandlerFactory]";
private static final Collection<String> TYPES;
private final Logger logger = LoggerFactory.getLogger(MyHandlerFactory.class);
@Reference
private @NonNullByDefault({}) ItemRegistry itemRegistry;
static {
List<String> temp = new ArrayList<String>();
temp.add(TemperatureTriggerType.UID);
temp.add(PresenceConditionType.UID);
temp.add(AirConditionerActionType.UID);
TYPES = Collections.unmodifiableCollection(temp);
}
// Tell the automation engine about our handlers
@Override public Collection<String> getTypes() { return TYPES; }
@Override
protected ModuleHandler internalCreate(Module module, String ruleUID) {
ModuleHandler moduleHandler = null;
if (TemperatureTriggerType.UID.equals(module.getTypeUID())) {
moduleHandler = new TemperatureTriggerHandler((Action) module);
} else if (PresenceConditionType.UID.equals(module.getTypeUID())) {
moduleHandler = new PresenceConditionHandler((Condition) module);
} else if (AirConditionerActionType.UID.equals(module.getTypeUID())) {
moduleHandler = new AirConditionerActionHandler((Condition) module);
} else {
logger.warn(MODULE_HANDLER_FACTORY_NAME + "Not supported moduleHandler: {}", module.getTypeUID());
}
return moduleHandler;
}
}
```
In the next three sections we'll implement those three handlers.
### Trigger Handler
A *Trigger Handler* is created by the automation engine for each trigger module type in actual rules,
via the factory that we have implemented above.
The handler tells the rule engine that something happened.
In our example scenario that is when the temperature of an imaginary external device has reached a specific value.
```java
public class TemperatureTriggerHandler extends BaseTriggerModuleHandler
implements ExternalDeviceTempChangeListener // We are listening to imaginary temp change events
{
final double temperature;
final boolean onAbove;
// Evaluate your configuration in the constructor
public TemperatureTriggerHandler(Trigger module) {
super(module);
Number tempNumber = (Number) context.get(TemperatureTriggerType.CONFIG_TEMPERATURE);
temperature = (tempNumber!=null) ? tempNumber.doubleValue() : 20.0;
String tempOp = (String) context.get(TemperatureTriggerType.CONFIG_OPERATOR);
onAbove = (tempOp != null && "below".equals(tempOp)) ? false : true;
}
// Setup your triggering stuff in here
@Override
public void setRuleEngineCallback(RuleEngineCallback ruleCallback) {
super.setRuleEngineCallback(ruleCallback);
// Register to temp change events on our imaginary external device
ExternalTemperatureDevice.registerTemperatureChangeListener(this);
}
@Override
public void dispose() { // Do your clean up here
ExternalTemperatureDevice.unregisterTemperatureChangeListener(this);
}
// Event from our imaginary temperature device. Triggers connected rules if temp is
// over the configured threshold.
@Override
public void tempChangedOnImaginaryDevice(int tempInCelsius) {
if (
(tempInCelsius>temperature && onAbove) ||
(tempInCelsius<temperature && !onAbove)
) {
((TriggerHandlerCallback) callback).triggered(module, context);
}
}
}
```
### Condition Handler
Condition Handler serves to help the Automation Engine to decide if it continues with the execution of the rule or to terminate it.
```java
public class PresenceConditionHandler extends BaseModuleHandler<Condition> implements ConditionHandler {
final ItemRegistry itemRegistry;
public PresenceConditionHandler(Condition module, ItemRegistry itemRegistry) {
super(module);
this.itemRegistry = itemRegistry;
}
@Override
public boolean isSatisfied(Map<String, Object> context) {
Number left = (Number) context.get(TemperatureConditionType.INPUT_CURRENT_TEMPERATURE);
Number right = (Number) module.getConfiguration().get(TemperatureConditionType.CONFIG_TEMPERATURE);
String operator = (String) module.getConfiguration().get(TemperatureConditionType.CONFIG_OPERATOR);
if (TemperatureConditionType.OPERATOR_HEATING.equals(operator)) {
if (left != null && right != null && left.intValue() < right.intValue()) {
return true;
}
} else if (TemperatureConditionType.OPERATOR_COOLING.equals(operator)) {
if (left != null && right != null && left.intValue() > right.intValue()) {
return true;
}
}
return false;
}
}
```
### Action Handler
Action Handler is used to help the Automation Engine to execute the specific Actions.
A simple implementation of it can be seen into `WelcomeHomeActionHandler` class.
## Tie everything together: Define rules
Until now we have taught the automation engine a way to trigger on specific temperatures,
to tell us if someone is at home,
and to switch on an air conditioner at different levels.
We now need to write an actual rule to use our custom modules.
### Via json files
The automation engine reads rule json files from the `{openhab-dir}/automation/*.json` directory.
The rule that implements our application is declaratively described in the json format like this:
```json
[
{
"uid":"JsonDemoRule",
"name":"DemoRule",
"triggers":[
{
"id":"RuleTrigger",
"label":"Item State Change Trigger",
"description":"This triggers a rule if an items state changed",
"type":"ItemStateChangeTrigger",
"configuration":{
"itemName":"DemoSwitch"
}
}
],
"conditions":[
],
"actions":[
{
"id":"RuleAction",
"label":"Post command to an item",
"description":"Posts commands on items",
"type":"ItemPostCommandAction",
"configuration":{
"itemName":"DemoDimmer",
"command":"ON"
}
}
]
}
]
```
### Programmatically define rules
You can also define rules programmatically and add them to the `RuleRegistry`.
Rules defined and added to the registry like this can be changed via user-interfaces.
```java
@NonNullByDefault
@Component(immediately=true)
public class MyRuleRegistrationComponent {
@Reference
protected @NonNullByDefault({}) RuleRegistry ruleRegistry;
@Activated
public void activated() {
ruleRegistry.add(createACRule());
}
private Rule createACRule() {
// initialize the trigger
String triggerId = "LightsSwitchOnRuleTrigger";
List<Trigger> triggers = new ArrayList<Trigger>();
triggers.add(new Trigger(triggerId, LightsTriggerType.UID, null));
// initialize the condition - here the tricky part is the referring into the condition input - trigger output.
// The syntax is a similar to the JUEL syntax.
Configuration config = new Configuration();
config.put(StateConditionType.CONFIG_STATE, "on");
List<Condition> conditions = new ArrayList<Condition>();
Map<String, String> inputs = new HashMap<String, String>();
inputs.put(StateConditionType.INPUT_CURRENT_STATE, triggerId + "." + StateConditionType.INPUT_CURRENT_STATE);
conditions.add(new Condition("LightsStateCondition", StateConditionType.UID, config, inputs));
// initialize the action - here the tricky part is the referring into the action configuration parameter - the
// template configuration parameter. The syntax is a similar to the JUEL syntax.
config = new Configuration();
config.put(WelcomeHomeActionType.CONFIG_DEVICE, "Lights");
config.put(WelcomeHomeActionType.CONFIG_RESULT, "Lights are switched on");
List<Action> actions = new ArrayList<Action>();
actions.add(new Action("LightsSwitchOnAction", WelcomeHomeActionType.UID, config, null));
// initialize the configDescriptions
List<ConfigDescriptionParameter> configDescriptions = new ArrayList<ConfigDescriptionParameter>();
final ConfigDescriptionParameter device = ConfigDescriptionParameterBuilder
.create(WelcomeHomeRulesProvider.CONFIG_UNIT, Type.TEXT).withRequired(true).withReadOnly(true)
.withMultiple(false).withLabel("Device").withDescription("Device description").build();
final ConfigDescriptionParameter result = ConfigDescriptionParameterBuilder
.create(WelcomeHomeRulesProvider.CONFIG_EXPECTED_RESULT, Type.TEXT).withRequired(true)
.withReadOnly(true).withMultiple(false).withLabel("Result").withDescription("Result description")
.build();
configDescriptions.add(device);
configDescriptions.add(result);
// initialize the configuration
config = new Configuration();
config.put(CONFIG_UNIT, "Lights");
config.put(CONFIG_EXPECTED_RESULT, "The lights are switched on.");
// create the rule
Rule lightsSwitchOn = new Rule(L_UID);
lightsSwitchOn.setTriggers(triggers);
lightsSwitchOn.setConfigurationDescriptions(configDescriptions);
lightsSwitchOn.setConditions(conditions);
lightsSwitchOn.setActions(actions);
// initialize the tags
Set<String> tags = new HashSet<String>();
tags.add("lights");
// set the tags
lightsSwitchOn.setTags(tags);
return lightsSwitchOn;
}
}
```
### Define constant / non-changable rules
We have seen how to define rules via json files as well as programatically.
A third way is to inject rules via an own `RuleProvider`.
Those rules are compiled into the bundle and cannot be changed later on, which might be desired.
We will use the `createACRule` method from above.
```java
@NonNullByDefault
@Component(immediately=true, service={RuleProvider.class})
public class MyStaticRulesProvider implements RuleProvider {
private Collection<ProviderChangeListener<Rule>> listeners = new ArrayList<>();
private Map<String, Rule> rules = Collections.singletonMap("AirConditionerSwitchOnRule", createACRule());
@Override
public void addProviderChangeListener(ProviderChangeListener<Rule> listener) { listeners.add(listener); }
@Override
public Collection<Rule> getAll() { return rules.values(); }
@Override
public void removeProviderChangeListener(ProviderChangeListener<Rule> listener) { listeners.remove(listener); }
}
```
## Further reading
This document does not yet cover all features of the automation engine.
We skipped rule templates and did not cover existing module types for common tasks,
like triggering on an item state change or post a command as an action.
Please discover existing module types on your own from within the user interfaces
and by consulting our user documentation.
For rule templates you may just ask in our friendly community or extend this document.

View File

@ -0,0 +1,143 @@
---
layout: developersguide
title: Configuration Admin
---
{% include base.html %}
# Configuration Admin Service
As defined in the [OSGi Compendium specification][OSGi-cmpn] *configuration is the process of defining the configuration data of bundles and assuring that those bundles receive that data when they are active in the OSGi Service Platform.*
### Configuration Admin Service
In OSGi, configurations are stored in a central database that is being managed by a special service - the *Configuration Admin Service*(`org.osgi.service.cm.ConfigurationAdmin`).
This service monitors the service registry and **provides a configuration to the services** that are registered with a *service.pid* property.
Configuration changes are first made persistent, and then are passed to the target service.
It is important to understand that **the target bundle receives updates from the Configuration Admin service**.
Implementations should be aware that the update reception could be delayed if the Configuration Admin service is missing.
### Configuration properties
Each configuration is uniquely identified by a PID (Persistent IDentifier) and stores properties.
The properties can be edited, or new properties could be added during runtime by other bundle that uses the Configuration Admin service.
In OSGi, configurations properties are key-value pairs that are accessible via `Map<String, Object>`.
Please note that the following types of objects are supported:
- String;
- Boolean;
- Byte;
- Short;
- Integer;
- Long;
- Float;
- Double;
- Character.
If the Configuration Admin service detects an unsupported property type, it will throw `IllegalArgumentException`.
## Configuring Declarative Services
[Declarative Services](osgids.html) (DS) is highly integrated with the Configuration Admin service.
Each DS component has a configuration object that is related with it.
The configuration is injected in the bundle `activate`, `deactivate` and `modified` methods.
The code below is an example, how to register a service and how to use the injected configuration.
```java
package com.example;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
@Component(service={TimeEventHandler.class, EventHandler.class}, property={"event.topics=some/topic"})
public class TimeEventHandler implements EventHandler {
// Use the default formatter
private SimpleDateFormat dateFormatter = new SimpleDateFormat();
@Activated
protected void activate(ComponentContext context, Map<String, Object> properties) {
modified(properties);
}
/**
* This method could be called by multiple threads
*/
@Modified
public synchronized void modified(Map<String, Object> properties) {
// note, that Config Admin may send an empty configuration
if (properties != null) {
String pattern = (String) properties.get("formatting");
dateFormatter = new SimpleDateFormat(pattern);
}
}
@Deactivated
protected void deactivate(ComponentContext context, Map<String, Object> properties) {
modified(properties);
}
@Override
public void handleEvent(Event event) {
Long timeInMillis = (Long) event.getProperty("time");
Date date = new Date(timeInMillis);
System.out.println("The current time is: " + dateFormatter.format(date));
}
}
```
## Using the Configuration Admin to update the configuration
As we have seen in the previous examples Configuration Admin service tracks the Service Registry and the internal configuration database for changes.
It can be used to update the configuration (or create if it is missing) as well.
The flow is the following:
```java
@Component(service={TimeEventHandler.class})
public class TimeEventHandler{
@Reference
ConfigurationAdmin configurationAdmin;
// Here we perform a configuration update as soon as this service gets activated
@Activated
protected void activate() {
Configuration config = configurationAdmin.getConfiguration("com.example.handler", null);
Dictionary<String, Object> props = config.getProperties();
if (props == null) { // if null, the configuration is new
props = new Hashtable();
}
// set some properties
props.put("formatting", "EEE, d MMM yyyy HH:mm:ss Z");
// update the configuration, the target bundle will be notified for the change
config.update(props);
}
}
```
After the call to `update` the Configuration Admin service persists the new configuration data and sends an update to the ManagedService registered with the service PID asynchronously.
Hint!
Configuration objects have a security feature called `Location` that prevents other
bundles from modifying their configuration. In the example above we have created a
configuration using the `Configuration config = configurationAdmin.getConfiguration
("com.example.handler", null);`. The second parameter ('null') guarantees that the
location for the configuration will be set when the service with this PID is
registered for the first time. If the location is not set correctly the Config
Admin may not send the update to the bundle.
## Further Reading
- <http://enroute.osgi.org/services/org.osgi.service.cm.html>
- <http://blog.vogella.com/2016/09/26/configuring-osgi-declarative-services/>
- [OSGi Service Platform Service Compendium, Release 5][OSGi-cmpn]
[OSGi-cmpn]: https://osgi.org/download/r5/osgi.cmpn-5.0.0.pdf

View File

@ -5,29 +5,23 @@ title: Equinox
{% include base.html %}
Equinox
=======
## Table of contents
# Equinox
{:.no_toc}
[Equinox][Equinox] is considered to be a reference implementation of the [OSGi Core specification][OSGi-Core].
It is an [open source project][Equinox-repo], part of the [Eclipse project][Eclipse].
It provides a set of bundles, that implement various optional OSGi services.
The openHAB bundles are deployed on an Equinox runtime.
Knowledge about how to start the runtime and execute basic commands will help you to speedup the development process.
{::options toc_levels="2,3"/}
* TOC
{:toc}
## I. Introduction
## Start Equinox Runtime from Eclipse
[Equinox][Equinox] is considered to be a reference implementation of the [OSGi Core 4.x specification][OSGi-4.x] and one of the most widely used. It is an [open source project][Equinox-repo], part of the [Eclipse project][Eclipse]. It provides a set of bundles, that implement various optional OSGi services.
The openHAB bundles are deployed on Equinox runtime. Knowledge about how to start the runtime and execute basic commands will help you to speedup the development process. Some of the bundles that you are going to use from Eclipse SmartHome**<sup>TM</sup>** and openHAB depend on Equinox bundles and this article will list some of the Equinox core bundles and the services that they provide.
## II. Start Equinox Runtime from Eclipse
First make sure that you have installed [openHAB Eclipse IDE](../development/ide.html).
Then follow these steps:
1. Start Eclipse and go to "Run" -> "Run Configurations".
1. Go to "Run" -> "Run Configurations".
2. From the list in the left panel choose "OSGi Framework". Right click on it and choose "New".
3. After you've been created a new configuration, select the bundles that you need from the workspace.
4. Then make sure that the following bundles from the target platform are selected, otherwise the OSGi console will not be available:
@ -43,37 +37,18 @@ Then follow these steps:
![Run Configurations dialog window](images/runconfiguration.png)
If you use Eclipse for an IDE, this will be the easiest way to run your bundles in an Equinox runtime. If you do not have experience with writing OSGi bundles, go to our [coding tasks page](osgitasks.html).
## Run Equinox from the command line
## III. Run Equinox standalone
The **org.eclipse.osgi** bundle is the framework implementation of the Core Framework specification in a standalone package.
The **org.eclipse.osgi** bundle is the framework implementation of the Core Framework R4 specification in a standalone package.
If you have compiled openHAB once either via command line (`mvn install`) or the Eclipse IDE,
maven will have downloaded Equinox for you already.
Find it in your maven cache directory (linux `.m2/repository/org/eclipse/platform/org.eclipse.osgi/3.13.300/org.eclipse.osgi-3.13.300.jar`
and windows `C:\Users\your.name\.m2\...`).
You can get it from your [Eclipse IDE installation for openHAB](../development/ide.html) (If you're using Windows, it should be located at `C:\Users\your.name\.p2\pool\plugins\org.eclipse.osgi_3.x.x_xxxx.jar`).
1. Equinox versions before 3.8.0.M4.
In some older version of Equinox, using the following command line:
```shell
java -jar org.eclipse.osgi_3.x.x_xxxx.jar -console
```
was enough to run it standalone.
2. Equinox versions after 3.8.0.M4.
Starting from Equinox 3.8.0.M4, it has a new console. So the command line above will probably not work. You need some additional bundles in order to run Equinox properly. There are different ways to add those bundles and one of them is given below:
1. After you have downloaded and installed Eclipse IDE for openHAB, find your .p2 repository and go to the `plugins` folder. (if you're using Windows, it should be located at `C:\Users\your.name\.p2\pool\plugins`).
2. Make sure that there is a `org.eclipse.osgi_3.x.x_xxxx.jar` in that folder.
3. Create `configuration` folder in the `plugins` folder.
4. Inside the `configuration` folder create a file `config.ini`.
5. Save the following content in the `config.ini` file:
1. Create `configuration` folder in that directory.
2. Inside the `configuration` folder create a file `config.ini`.
3. Save the following content in the `config.ini` file:
```ini
osgi.bundles=\
@ -96,10 +71,10 @@ Starting from Equinox 3.8.0.M4, it has a new console. So the command line above
6. Use the following command line to run Equinox:
```shell
java -jar org.eclipse.osgi_3.x.x_xxxx.jar -console -configuration configuration
java -jar org.eclipse.osgi-3.x.x_xxxx.jar -console -configuration configuration
```
## IV. Commands
## Commands
Once you have Equinox running, you will see a prompt. Some of the basic
osgi commands are:
@ -116,74 +91,23 @@ osgi commands are:
Table 1. Equinox commands (Source: <http://www.eclipse.org/equinox/documents/quickstart-framework.php>)
## V. Equinox Bundles
## Equinox Bundles
Another part of the Equinox project is the [Equinox Bundles][Equinox-Bundles] component. It consists of bundles that implement all add-on services from the OSGi specification and additional services defined in various OSGi expert groups.
Another part of the Equinox project is [Equinox Bundles][Equinox-Bundles].
It consists of bundles that implement all add-on services from the OSGi specification and additional services defined in various OSGi expert groups.
Some of the core bundles are listed in the table below. Some or all of these bundles must be included in your runtime configuration, if you want to use the services that they provide.
Name | Bundle Symbolic Name | Description
-------- | -------- | --------
Declarative Services | org.eclipse.equinox.ds | An implementation of the OSGi R4 [Declarative Services](osgids.html) specification
Declarative Services | org.eclipse.equinox.ds | An implementation of the OSGi [Declarative Services](osgids.html) specification
Event Admin Service | org.eclipse.equinox.event | OSGi R4 [Event Admin](https://osgi.org/javadoc/r4v42/org/osgi/service/event/EventAdmin.html) Service provides an inter-bundle communication mechanism based on an event publish and subscribe model
Log Service | org.eclipse.equinox.log | This [LogService](https://osgi.org/javadoc/r4v42/org/osgi/service/log/LogService.html) provides a general-purpose message logger for the OSGi environment
Equinox Utilities | org.eclipse.equinox.util | A library of utility classes that are frequently used from the Equinox OSGi Service implementation bundles
OSGi Services API | org.eclipse.osgi.service | This bundle contains the set of OSGi specified service APIs.
Table 2. OSGi Bundles (Full list can be found at: <http://www.eclipse.org/equinox/bundles/>)
## VI. p2
The [p2 project][p2] is a sub-project of [Equinox][Equinox] that focuses on provisioning technology for OSGi-based applications. Provisioning is the act of finding and installing new functionality and updating or removing existing functionality; it is distinct from building.
Although p2 has a specific support for Equinox and [Eclipse][Eclipse], can be used as a general purpose provisioning infrastructure.
### 1. Core Concepts
p2 manages artifacts, such as [plug-ins(bundles)][plugins], [features][features] and [products][products]. You can think of these as bags of bytes. p2 not only stores these artifacts, it also **stores metadata about these artifacts**, such as version information, cryptographic signatures, dependencies, platform specifics and special installation requirements.
### 2. Installable Unit
Every p2 artifact *Installable Unit or IU* is uniquely identified by an **identifier** and **version number**. For example, in the Equinox OSGi container from the Indigo release there is a bundle whose identifier is *org.eclipse.osgi* and version *3.7.0.v20110110*. p2 assumes that two artifacts with the same identifier and same version number are the same artifact.
An IU representing the SWT bundle:
```
id=org.eclipse.swt, version=3.5.0, singleton=true
Capabilities:
{namespace=org.eclipse.equinox.p2.iu, name=org.eclipse.swt, version=3.5.0}
{namespace=org.eclipse.equinox.p2.eclipse.type name=bundle version=1.0.0}
{namespace=java.package, name=org.eclipse.swt.graphics, version=1.0.0}
{namespace=java.package, name=org.eclipse.swt.layout, version=1.2.0}
Requirements:
{namespace=java.package, name=org.eclipse.swt.accessibility2, range=[1.0.0,2.0.0), optional=true, filter=(&(os=linux))}
{namespace=java.package, name=org.mozilla.xpcom, range=[1.0.0, 1.1.0), optional=true, greed=false}
Updates:
{namespace=org.eclipse.equinox.p2.iu, name=org.eclipse.swt, range=[0.0.0, 3.5.0)}
```
As you can see the installable unit defines *capabilities* - what the IU expose to the rest of the world, *requirements* - what the IU needs (the requirements are satisfied by capabilities). This metadata is used in the resolvement process.
### 3. Update sites
Installable units can be grouped into a **p2 repository** (also called **update site**). A repository is defined via its URI and can point to a local file system or to a web server. A p2 repository is also frequently called update site.
The most important characteristic of p2 repositories (and difference compared to the Maven repositories) is that **IU do not depend directly on each other**, they depend on packages identified by namespace + name + version(unless *Require-Bundle* is specified).
A sample p2 repository(update site) has the following layout
```
features
plugins
artifacts.jar
content.jar
site.xml
```
Some of the most popular p2 repositories are [orbit p2 Repo](http://download.eclipse.org/tools/orbit/downloads/drops/R20160221192158/) and [Eclipse p2 Repo](http://download.eclipse.org/eclipse/updates).
You can find information about the Eclipse SmartHome**<sup>TM</sup>** update sites at [this link](http://www.eclipse.org/smarthome/documentation/community/downloads.html).
## VII. Further Reading
## Further Reading
- <http://www.eclipse.org/equinox/>
- <http://www.eclipse.org/equinox/bundles/>
@ -202,7 +126,7 @@ You can find information about the Eclipse SmartHome**<sup>TM</sup>** update sit
- [RT meets p2](https://bkapukaranov.wordpress.com/category/tech/virgo/)
[Equinox]: http://www.eclipse.org/equinox/
[OSGi-4.x]: https://www.osgi.org/osgi-service-platform-release-4-downloads/
[OSGi-Core]: https://osgi.org/download/r5/osgi.core-5.0.0.pdf
[Equinox-repo]: http://git.eclipse.org/c/equinox/
[Eclipse]: https://eclipse.org/eclipse/
[Equinox-Bundles]: http://www.eclipse.org/equinox/bundles/

View File

@ -0,0 +1,88 @@
---
layout: developersguide
title: Event Admin
---
{% include base.html %}
# Introduction
In a dynamic environment like OSGi, communication with events has a wide variety of use cases.
A lot of core services share information using events, so understanding how to use events in OSGi is fundamental.
OSGi events are based on the publish-subscribe messaging pattern.
Let's use the definition for the pattern that can be found in the [OSGi Compendium Specification][OSGi-cmpn]:
*This pattern decouples sources from their handlers by interposing an event channel between them.
The publisher posts events to the channel, which identifies which handlers need to be notified and then takes care of the notification process.*
Both publishers and subscribers can disappear at any time.
A central module to track the handlers availability is needed - the *Event Admin Service*.
## Event Admin Service
The *Event Admin Service* (`org.osgi.service.event.EventAdmin`) takes a central place in the communication between *Event Publishers* and subscribers (*Event Listeners*).
It is responsible for keeping track of the listeners, and sending events to them.
It supports both synchronous and asynchronous sending that will be reviewed in more details in the [section about sending events](#send-events).
But let's illustrate that with the following picture:
![Bundle lifecycle][fig1]
Fig.1 Event Admin Service (Source: <http://enroute.osgi.org/img/services/org.osgi.service.event.overview.png>)
Before going into more details, let's take a look at the events.
## Event
The *Event* interface(`org.osgi.service.event.Event`) encapsulates a single message. It contains:
- topic - used from the *Event Admin Service* as a filter to dispatch the events only to the listeners that are interested;
- payload - the information that we would like to send. It is represented by a key-value pair.
## Receive Events
In order to receive an event through the *Event Admin Service* we have to register a service that implements the `org.osgi.service.event.EventHandler` interface.
A property *event.topics* must be defined that contains all topics that we are interested in.
```java
package com.example.handler;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(service = {LogEventHandler.class,EventHandler.class}, property = "event.topics=*")
public class LogEventHandler implements EventHandler {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void handleEvent(Event event) {
logger.info(" Recevied event with topic: {}", event.getTopic());
}
}
```
* You can register a handler for multiple topics by separating them by comma: `event.topics=some/topic,other/topic`
* You can register for all events with `*`.
## Send Events
As we have already mentioned, you will need an *Event Admin Service* implementation to send events.
In Equinox the service is implemented in the `org.eclipse.equinox.event` bundle.
The service contains two methods for sending events:
- `void postEvent(Event event)` - sends an Event asynchronously;
- `void sendEvent(Event event)` - sends an Event synchronously;
## Further Reading
- [*OSGi Service Platform Service Compendium, Release 5][OSGi-cmpn]
- <http://enroute.osgi.org/services/org.osgi.service.event.html>
- <http://blog.vogella.com/2017/05/16/osgi-event-admin-publish-subscribe/>
[fig1]:images/event-admin.png
[OSGi-cmpn]: https://osgi.org/download/r5/osgi.cmpn-5.0.0.pdf

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -5,16 +5,16 @@ title: OSGi
{% include base.html %}
OSGi Overview
====
Introduction
------------
# OSGi Overview
{:.no_toc}
openHAB is being based on [OSGi][OSGi] and understanding of OSGi modular architecture is very important. This page is aimed to help developers, that are going to use OSGi for the first time and contains a basic overview of the OSGi technology.
Concepts
--------
{::options toc_levels="2,3"/}
* TOC
{:toc}
## Concepts
As described in the [OSGi architecture page][OSGi-architecture], *OSGi is a set of [specifications](https://www.osgi.org/developer/specifications/) that define a dynamic component system for Java. These specifications enable a development model, where applications are dynamically composed of many different reusable components. The OSGi specifications enable components to hide their implementations from other components while communicating through services, which are objects that are specifically shared between components*. This architecture significantly reduces the overall complexity of building, maintaining and deploying applications.
@ -24,8 +24,7 @@ Key features of OSGi are:
- **Runtime Dynamics** - software components can be managed at runtime;
- **Service Orientation** - components communicate between each other through [*services*](#important-definitions).
Layering
--------
## Layering
The OSGi framework consist of several layers build on top of each other (See Fig. 1):
@ -40,12 +39,12 @@ Fig.1 OSGi Layering (Source:<https://www.osgi.org/wp-content/uploads/layering-os
More details about the OSGi architecture can be found at <https://www.osgi.org/developer/architecture/>
Bundles
-------
## Bundles
Modules (called **bundles**) are the smallest unit of modularization. Technically a bundle is a JAR file with additional meta information. This information is stored in file called [**manifest**](#important-definitions) file. The manifest file is part of the standard [Java specification](http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#), but OSGi adds additional metadata to it in form of specific headers. The *Bundle-SymbolicName* and the *Bundle-Version* headers uniquely identify a bundle. In OSGi is allowed to have **bundles with same name, but different version running at the same time.**
Some of the most important information that the manifest contains are the bundle dependencies. **A bundle can depend on another bundle or on a package**. Preferred way to define dependencies in a bundle is with *Import-Package* and *Export-Package* headers and not with *Require-Bundle* header. This gives you an access only to the packages that you need and allows you to exchange the packages at a later point in time. You can find more information in [Why using Require-Bundle is a bad practice and should be avoided](http://web.ist.utl.pt/ist162500/?p=104).
Some of the most important information that the manifest contains are the bundle dependencies.
**A bundle can depend on another bundle or on a package**.
The **OSGi runtime uses the information about the dependencies to *wire* the bundles and hides everything in this JAR unless it is explicitly exported**. The dependencies to the Java standard libraries are managed by the *Bundle-RequiredExecutionEnvironment* header, so it is not needed to import the Java core packages.
@ -65,8 +64,7 @@ Export-Package: org.example.provided
Bundles are used often to register and consume services. You will find more information about that in the [Services](#services) section.
Lifecycle
---------
## Lifecycle
OSGi is a dynamic platform. That means that bundles may be *installed, uninstalled, started, stopped or updated* at runtime (See Table 1). The OSGi specification defines a mechanism how to manage the dependencies between the bundles and the functionality that they provide. This is achieved with the help of the *lifecycle* concept.
@ -88,34 +86,62 @@ The possible status transitions are shown in the state diagram below:
![Bundle lifecycle][fig2]
Fig.2 Bundle State diagram
Services
-------
## Services
Another main concept, that allows the bundles to communicate between each other, is the *service* model.
**In OSGi, a bundle can register a *service* in a central [service registry](#important-definitions) under one ore more *service interface***. Published services also have service properties associated with them in the registry. It is an important feature of OSGi, because it provides a central place to register and get services. A bundle is permitted to register service objects at any time during the STARTING, ACTIVE or STOPPING states. Other bundles can go the registry and list all objects, that are registered under a specific interface or class.
**In OSGi, a bundle can register a *service* in a central [service registry](#important-definitions) under one ore more *service interface***.
It is an important feature of OSGi, because it provides a central place to register and get services.
A bundle is permitted to register service objects at any time during the STARTING, ACTIVE or STOPPING states.
Other bundles can go the registry and list all objects, that are registered under a specific interface or class.
A bundle can therefore register a service, it can get a service and it can track for appearing and disappearing of service. **Any number of bundles can register the same service type and any number of bundles can get the same service.** A simple diagram of the service usage and tracking is shown on Fig. 3.
A bundle can therefore register a service, it can get a service and it can track for appearing and disappearing of service.
**Any number of bundles can register the same service type and any number of bundles can get the same service.**
A simple diagram of the service usage and tracking is shown on Fig. 3.
![OSGi Services][fig3]
Fig.3 OSGi Services (Source: <https://www.osgi.org/wp-content/uploads/services.png>)
Popular OSGi Containers
-----------------------
If you are keen to know how that works in code, read on in [OSGi Declarative Services](osgids.html).
We have talked so far about the OSGi specification. Here is the place to emphasize that the different containers might implement different parts of the OSGi specifications and might provide slightly different API (which could make the bundle not fully portable between different containers).
Some important core services are presented below.
### Configuration Admin Service
In OSGi, configurations are stored in a central database that is being managed by a special service - the *Configuration Admin Service*(`org.osgi.service.cm.ConfigurationAdmin`).
This service monitors the service registry and **provides a configuration to the services** that are registered with a *service.pid* property.
Configuration changes are first made persistent, and then are passed to the target service.
It is important to understand that **the target bundle receives updates from the Configuration Admin service**. Implementations should be aware that the update reception could be delayed if the Configuration Admin service is missing.
- [OSGi Configuration Admin](configadmin.html)
### Event Admin Service
In a dynamic environment like OSGi, communication with events has a wide variety of use cases.
OSGi events are based on the publish-subscribe messaging pattern.
The *Event Admin Service* (`org.osgi.service.event.EventAdmin`) takes a central place in the communication between *Event Publishers* and subscribers (*Event Listeners*).
It is responsible for keeping track of the listeners, and sending events to them.
- [OSGi Event Admin](eventadmin.html)
## Popular OSGi Containers
We have talked so far about the OSGi specification. Here is the place to emphasize that the different containers might implement different parts of the OSGi specifications.
We will list the most popular OSGi containers with a short description of their goals. We can divide them into:
- Open source:
- [Equinox](https://www.eclipse.org/equinox/) - this is the reference implementation of the OSGi R4.x Core Specification and one of the mostly used. As it is used in the openHAB project, we have prepared a [wiki page](equinox.html);
- [Apache Felix](http://felix.apache.org/) - implements OSGi R5 Core Specification, developed by the Apache Software Foundation. [Apache Karaf](http://karaf.apache.org/) is distribution based on Apache Felix that provides some additional features on top of it (e.g. folder based hot deployment, improved default console with remote SSH, maven plugins and others);
- [Concierge](https://www.eclipse.org/concierge/) - implements OSGi R5 Core Specification and is aimed at mobile and embedded devices. With a size of around 250 kb it has the smallest footprint of the presented containers;
- [Equinox](https://www.eclipse.org/equinox/) - this is the reference implementation of OSGi and developed by the Eclipse Foundation.
As it is used in the openHAB project, we have [more detailed information](equinox.html).
- [Apache Felix](http://felix.apache.org/) - developed by the Apache Software Foundation.
[Apache Karaf](http://karaf.apache.org/) is a distribution based on Apache Felix that provides some additional features on top of it (e.g. folder based hot deployment, improved default console with remote SSH, maven plugins and others). The openHAB project is using karaf for runtime dependency installation.
- [Concierge](https://www.eclipse.org/concierge/) - is aimed at mobile and embedded devices. With a size of around 250 kb it has the smallest footprint of the presented containers;
- Commercial:
- [Bosch IoT Gateway Software](https://www.bosch-si.com/iot-platform/iot-platform/gateway/software.html) - implements OSGi R6 Core and Compendium Specification. This implementation is low-footprint, optimized for embedded products, provides custom remote management agent and many more additional components. The implementation was previously known as ProSyst OSGi Framework.
- [Bosch IoT Gateway Software](https://www.bosch-si.com/iot-platform/iot-platform/gateway/software.html) - implements OSGi R6 Core and Compendium Specification. This implementation is low-footprint, optimized for embedded products, provides custom remote management agent and many more additional components.
Important Definitions
---------------------
## Important Definitions
**bundle** - a unit of modularization, defined by the OSGi framework. A bundle is comprised of Java classes and other resources, which together can provide functions to end users. For more detailed definition - [OSGi Service Platform Core Specification, Chapter 3.2][OSGi-Core]
@ -125,22 +151,18 @@ Important Definitions
**Service Registry** - enables a bundle to publish objects to a shared registry, advertised via a given set of Java interfaces.
Further Reading
----------
- [OSGi Service Platform Core Specification, Version 4.2][OSGi-Core]
- [OSGi For Eclipse Developers - only the presentation materials](http://www.slideshare.net/caniszczyk/osgi-for-eclipse-developers-1331901)
- [OSGi For Eclipse Developers - video](https://www.youtube.com/watch?v=4YfAo9ZoEGQ)
- [OSGi API](https://osgi.org/javadoc/r4v42/index.html)
## Further Reading
- [OSGi Service Platform Core Specification, Version 5][OSGi-Core]
- [OSGi API](https://osgi.org/javadoc/r5/core/index.html)
- [OSGi Vogella guide](http://www.vogella.com/tutorials/OSGi/article.html)
- [OSGi Framework Architecture Three Conceptual Layers ](http://www.programcreek.com/2011/07/osgi-framework-architecture-three-conceptual-layers/)
- [OSGi in action, Creating Modular Applications in Java](http://www.opus-college.net/devcorner/OSGi_in_Action.pdf)
- [Lifecycle of a bundle](https://developer.atlassian.com/docs/atlassian-platform-common-components/plugin-framework/behind-the-scenes-in-the-plugin-framework/lifecycle-of-a-bundle)
- [OSGi enRoute](http://enroute.osgi.org/)
- <https://www.osgi.org/developer/where-to-start/>
[OSGi]: https://www.osgi.org/
[OSGi-Core]: https://osgi.org/download/r4v42/r4.core.pdf
[OSGi-Core]: https://osgi.org/download/r5/osgi.core-5.0.0.pdf
[fig1]:images/layeringosgi.png
[fig2]:images/states.png
[fig3]:images/services.png

264
developers/osgi/osgids.md Normal file
View File

@ -0,0 +1,264 @@
---
layout: developersguide
title: OSGi Declarative Services
---
{% include base.html %}
# Declarative Services
{:.no_toc}
In the [OSGi Overview article](osgi.html) we have mentioned that a bundle can register, unregister, get and unget services from a central point - the Service Registry.
In order to simplify the usage of services the [OSGi Alliance](https://www.osgi.org/about-us/) has developed a model of managing services dynamically called *Declarative Services*.
{::options toc_levels="2"/}
* TOC
{:toc}
## Model & Terms
In order to understand this model, we will have to first explain a few terms, used below:
- **Declarative Services Container** (we will use the shorthand **DS**) - a module that is managing the [lifecycle](#vii-component-lifecycle) of a *service component* dynamically.
It activates and deactivates different components, basing its decisions on the information contained in the *component description*;
- **Service Component** (or also **component**) - an object whose lifecycle is managed,
usually by a component framework such as Declarative Services (DS).
A component may publish itself as an OSGi service or consume OSGi services.
A component can refer to a number of services;
- **Component Description** - The declaration of a component.
It is contained within an XML document in a bundle.
## DS Container
In order to use the Declarative Services functionality you have to start a bundle with an implementation of the DS container.
In [Equinox](http://www.eclipse.org/equinox/) (the reference implementation of OSGi that is used in openHAB) this bundle is called `org.eclipse.equinox.ds`.
When a bundle that contains a component is added to the framework, DS reads its component description (the XML file that is generated by the Java annotations).
If the conditions, described in this file are fulfilled (you will understand more about this in the next chapters), the DS activates the component (more on the that in the [component lifecycle](#vii-component-lifecycle) chapter).
More importantly, after some of the requirements are not met anymore, the DS container deactivates the component.
This ensures that service components are managed dynamically.
## Components
It is important to understand the difference between a *component* and a *service*.
A component is a normal Java class, that can reference services and provide services.
What makes a component specific is that it is declared in a XML file and is managed completely by DS.
That means that DS instantiates the component, calls method on this component and manages the lifecycle of a component.
A component requires the following artifacts in a bundle:
- **XML description** of the component;
- **Service-Component manifest header**, which contains the location of the XML description;
- An **implementation class** that is specified in the component description.
Because we do not write the xml files or the manifest ourselves,
we will concentrate on the Java annotations in the examples below.
## Example - Reference Service via Fields
In this example our component needs the openHAB `ItemRegistry` (`org.openhab.core.items.ItemRegistry`).
We will use DS to inject an implementation of this service in our class.
```java
@Component(service={MyService.class}, immediate=true)
public class MyService {
@Reference // you can add some configuration parameters to this annotation
protected @NonNullByDefault({}) ItemRegistry itemRegistry;
@Activate
protected void activate(BundleContext context) {
System.out.println("Bundle is activated and itemRegistry is available!");
}
@Deactivate
protected void deactivate(BundleContext context) {
System.out.println("Bundle is deactivated!");
}
}
```
Let's take a look at some configuration parameters, that we can apply:
- **immediate**:
- *true* - the component is activated as soon as all dependencies are satisfied. Adding this option will ensure that the component will be activated right after the bundle is activated and the component is satisfied;
- *false* - the component has lazy activation. The activation of the component is delayed(lazy activation) until the service is requested. This is the default value;
- **cardinality**:
- *1..1* - single service reference, that is mandatory. If your referenced service gets inactive, DS deactivates your service component as well (default value);
- *0..1* - single service reference(not mandatory). You have to be aware that you might not have your reference resolved, your component can live with the absence;
- *0..n* - multiple service references (not mandatory). The referenced service can be implemented multiple times, so they are added to a list in your component implementation;
- *1..n* = like the above, but mandatory;
- **policy**:
- *static* - the default policy. Component configuration are deactivated every time, when a reference with static policy becomes unavailable. This causes the activating and deactivating of the component. It can be very expensive, when we have multiple bound services, or when a service is often unregistered and re-registered;
- *dynamic* - with this policy the component is not deactivated, when a referenced service is changed. It is slightly more complex, as the component implementation has to properly handle changes in the set of bound services.
### Reference service via methods
If you want to react when a service got resolved, you may use annotated methods instead of fields like in the example below.
The annotated `activate()` and `deactivate()` methods are called from DS,
when the component configuration is activated and deactivated (more about [activation](osgids.html#activation)):
```java
package com.example.consumer;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.LogService;
@Component(service = MyServiceImpl.class, immediate = true)
public class MyServiceImpl {
private LogService log;
public MyServiceImpl() {
}
@Activate
protected void activate(BundleContext context) {
System.out.println("Bundle is activated!");
// No specific action is needed here in this case
}
@Deactivate
protected void deactivate(BundleContext context) {
System.out.println("Bundle is deactivated!");
// No specific action is needed here in this case
}
@Reference
public void setLog(LogService l) {
log = l;
System.out.println("Log service is available!");
// We store a reference to the LogService !
}
public void unsetLog(LogService l) {
log = null;
System.out.println("Log service isn`t available anymore!");
// We have to clean up after ourselves, when the reference is not needed anymore !
}
}
```
## Example - Provide Service
Very often you will have to register a service, that implements an interface defined in the framework (e.g [*EventHandler*](https://osgi.org/javadoc/r4v42/org/osgi/service/event/EventHandler.html)) or interface, that you have defined.
An interface allows you to change the implementation easily or register multiple implementations in the Service Registry.
We will use DS to register an implementation of the EventHandler service in the OSGi Service Registry.
```java
@Component(service={MyService.class,EventHandler.class}, immediate=true, property = { "event.topics=some/topic" })
public class MyService implements EventHandler {
@Activate
protected void activate(BundleContext context) {
System.out.println("Bundle is activated!");
}
@Deactivate
protected void deactivate(BundleContext context) {
System.out.println("Bundle is deactivated!");
}
@Override
public void handleEvent(Event event) {
String topic = event.getTopic();
System.out.println("Received event with topic: " + topic);
}
}
```
We are providing some service specific properties as well (the property with name `event.topics`).
In this case we tell tell Event Admin Service in which topics we are interested.
## Component types
A component lifecycle depends of the type of the component.
Three type of components are defined:
- **immediate** - with ```immediate``` attribute set to ```true``` - see the [example component description](#service-component-description);
- **delayed** - with ```immediate``` attribute set to ```false```;
- **factory** - we will not discuss the lifecycle of this type in this article.
You can find information in the [OSGi Compendium Specification, Chapter Factory Component][OSGi-cmpn].
## Component Lifecycle
A component goes through several states in his lifecycle:
- **UNSATISFIED** - initial state of the Service Component, after the bundle is started;
- **REGISTERED** - temporary state. Only *delayed* components go through this state;
- **ACTIVE** - the component is active and component instance is created.
<img src="images/immediatecomponent.png" width="320" height="200" />
Fig.1 Immediate component lifecycle
<img src="images/delayedcomponent.png" width="400" height="200" />
Fig.2 Delayed component lifecycle
### States
Like described above, the component lifecycle depends on the lifecycle of the bundle, that includes the component.
Component must be enabled before it can be used.
A component is **enabled**, when the component's bundle is **started** and **disabled**, when the bundle is **stopped**.
After the Component is enabled, it is moved to the UNSATISFIED state.
The next step is to satisfy the component configuration.
The component **configuration is satisfied** when:
- component is **enabled**;
- all the **component references are satisfied**.
A reference is satisfied when the reference specifies optional cardinality or there is at least one target service for the reference.
If the component has lazy initialization (the component is *delayed*), it is moved to the REGISTERED state and it is waiting to be activated, when the service is requested (see Fig.2).
Otherwise (the component is *immediate*) as soon as its dependencies are satisfied, the component is activated (see Fig.1).
### Activation
Activation consists of following steps:
- load the implementation class;
- create component instance (constructor is run);
- bind `@Reference`d services
- The method that is annotated with `@Activate` is called, if present
After the activation, the component is in ACTIVE state.
From this state the component can go back to the *REGISTERED* state (or to the *UNSATISFIED* state), if the component configuration becomes unsatisfied.
### Deactivation
Deactivation consists of the following actions:
- The method that is annotated with `@Deactivate` is called, if present
- All `@Reference`d services are unbound
- Release all references to the component instance.
## Troubleshooting
Find a few tips down below if something has gone wrong.
Mind that most of the tips below are specific to Equinox as the OSGi Runtime.
You might review again the [Equinox commands](equinox.html#iv-commands) before you continue.
- Start the runtime and check the log.
Do you see any Errors like "MESSAGE [SCR] Error while trying to bind reference"? Read the error message and proceed accordingly.
- Make sure that the bundle that is providing the service is in "ACTIVE" state.
If that is not the case, start it manually with the "start id" command, where id is the id of the bundle;
- [Equinox only] While the runtime is running type the "services" command and search if your service is registered.
In case it is not registered, most probably you have missed some of the steps above :);
- [Equinox only] While the runtime is running type "ls -c id", where id is the id of the bundle that is providing the service.
This will give detailed information about the services that are registered by this bundle, read the information if the component is satisfied carefully;
- Is your service in use?
In case it is not and you are not seeing it registered, the component might have lazy activation policy
(this is the default policy).
Add "immediate=true" in your Component Definition if you like to change the activation policy.
## Further Reading
- [*OSGi Service Platform Service Compendium, Release 5][OSGi-cmpn]
- [Lars Vogel - Declarative services](http://www.vogella.com/tutorials/OSGiServices/article.html#declarativeservices)
- [Getting Started with OSGi: Declarative Services](http://www.eclipsezone.com/eclipse/forums/t97690.rhtml)
- <http://stackoverflow.com/questions/8886430/what-is-the-difference-between-osgi-components-and-services>
[OSGi-cmpn]: https://osgi.org/download/r5/osgi.cmpn-5.0.0.pdf

View File

@ -0,0 +1,10 @@
---
layout: developersguide
title: Persistence Service
---
{% include base.html %}
# Developing a Persistence Service
TODO

View File

@ -1,181 +0,0 @@
---
layout: developersguide
title: Configuration Admin
---
{% include base.html %}
# Configuration Admin Service
## Introduction
In the previous article about [OSGi Declarative Services](osgids.html) we have learned how to register and track services. Here we will configure them in runtime.
## Basics
As defined in the [OSGi Compendium specification](https://osgi.org/download/r4v42/r4.cmpn.pdf) *configuration is the process of defining the configuration data of bundles and assuring that those bundles receive that data when they are active in the OSGi Service Platform.*
### Configuration Admin Service
In OSGi, configurations are stored in a central database that is being managed by a special service - the *Configuration Admin Service*(`org.osgi.service.cm.ConfigurationAdmin`). This service monitors the service registry and **provides a configuration to the services** that are registered with a *service.pid* property. Configuration changes are first made persistent, and then are passed to the target service. It is important to understand that **the target bundle receives updates from the Configuration Admin service**. Implementations should be aware that the update reception could be delayed if the Configuration Admin service is missing.
### Configuration properties
Each configuration is uniquely identified by a PID (Persistent IDentifier) and stores properties. The properties can be edited, or new properties could be added during runtime by other bundle that uses the Configuration Admin service.
In OSGi, configurations properties are key-value pairs that are accessible via `Map<String, Object>`. Please note that the following types of objects are supported:
- String;
- Boolean;
- Byte;
- Short;
- Integer;
- Long;
- Float;
- Double;
- Character.
If the Configuration Admin service detects an unsupported property type, it will throw `IllegalArgumentException`.
## Configuring Declarative Services
[Declarative Services](osgids.html) (DS) is highly integrated with the Configuration Admin service. Each DS component has a configuration object that is related with it, when the *service.pid* property is missing, the name of the component is used as a PID. The configuration is injected in the bundle `activate`, `deactivate` and `modified` methods. The code below is an example, how to register a service with a *service.pid* property and how to use the injected configuration.
```xml
?xml version="1.0" encoding="UTF-8"?>
<scr:component name="com.example.handler" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
<implementation class="com.example.TimeEventHandler"/>
<property name="event.topics">some/topic</property>
<service>
<provide interface="org.osgi.service.event.EventHandler"/>
</service>
<property name="service.pid" type="String" value="com.example.handler"/>
</scr:component>
```
```java
package com.example;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
public class TimeEventHandler implements EventHandler {
// Use the default formatter
privateSimpleDateFormat dateFormatter = new SimpleDateFormat();
protected void activate(ComponentContext context, Map<String, Object> properties) {
modified(properties);
}
/**
* This method could be called by multiple threads
*/
public synchronized void modified(Map<String, Object> properties) {
// note, that Config Admin may send an empty configuration
if (properties != null) {
String pattern = (String) properties.get("formatting");
dateFormatter = new SimpleDateFormat(pattern);
}
}
protected void deactivate(ComponentContext context, Map<String, Object> properties) {
modified(properties);
}
@Override
public void handleEvent(Event event) {
Long timeInMillis = (Long) event.getProperty("time");
Date date = new Date(timeInMillis);
System.out.println("The current time is: " + dateFormatter.format(date));
}
}
```
## Managed Services
`org.osgi.service.cm.ManagedService` interface is another way to register a service that needs configuration. You have to register your service as a MangedService with a *service.pid* property. Configuration Admin tracks the Service Registry and will update the MangedService, when the configuration is changed. The examples below illustrate how to register a service as ManagedService:
```xml
?xml version="1.0" encoding="UTF-8"?>
<scr:component name="com.example.handler" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
<implementation class="com.example.TimeEventHandler"/>
<property name="event.topics">some/topic</property>
<service>
<provide interface="org.osgi.service.cm.MangedService"/>
<provide interface="org.osgi.service.event.EventHandler"/>
</service>
<property name="service.pid" type="String" value="com.example.handler"/>
</scr:component>
```
The MangedService interface contains a single method `updated()` that will be called from the Configuration Admin service when the configuration is changed.
```java
package com.example.provider;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
public class TimeEventHandler implements EventHandler, ManagedService {
@Override
public void updated(Dictionary<String, ?> properties) {
// process configuration properties here
}
@Override
public void handleEvent(Event event) {
// handle the event
}
}
```
## Using the Configuration Admin to update the configuration
As we have seen in the previous examples Configuration Admin service tracks the Service Registry and the internal configuration database for changes. It can be used to update the configuration(or create if it is missing) as well. The flow is the following:
```java
// gets the configuration or creates new if it is missing
Configuration config = configurationAdmin.getConfiguration(
"com.example.handler", null);
Dictionary<String, Object> props = config.getProperties();
// if null, the configuration is new
if (props == null) {
props = new Hashtable();
}
// set some properties
props.put("formatting", "EEE, d MMM yyyy HH:mm:ss Z");
// update the configuration, the target bundle will be notified for the change
config.update(props);
```
Hint!
The implementaiton of the Configuration Admin Service in Equinox is provided by the
`org.eclipse.equinox.cm` bundle.
After the call to `update` the Configuration Admin service persists the new configuration data and sends an update to the ManagedService registered with the service PID asynchronously.
Hint!
Configuration objects have a security feature called `Location` that prevents other
bundles from modifying their configuration. In the example above we have created a
configuration using the `Configuration config = configurationAdmin.getConfiguration
("com.example.handler", null);`. The second paramter ('null') guarantees that the
location for the configuration will be set when the service with this PID is
registered for the first time. If the location is not set correctly the Config
Admin may not send the update to the bundle.
## Further Reading
- <http://enroute.osgi.org/services/org.osgi.service.cm.html>
- <http://blog.vogella.com/2016/09/26/configuring-osgi-declarative-services/>
- <https://osgilook.wordpress.com/2009/03/22/configuration-admin-service-explained-the-managedservice-interface/>
- [OSGi Service Platform Service Compendium, Release 4, Version 4.2,August 2009](https://osgi.org/download/r4v42/r4.cmpn.pdf)

View File

@ -1,96 +0,0 @@
---
layout: developersguide
title: Event Admin
---
{% include base.html %}
# Event Admin Service
## Introduction
In a dynamic environment like OSGi, communication with events has a wide variety of use cases. A lot of core services share information using events, so understanding how to use events in OSGi is fundamental.
## Basics
### Publish-Subscribe Pattern
OSGi events are based on the publish-subscribe messaging pattern. Let's use the definition for the pattern that can be found in the [OSGi Compendium Specification](https://osgi.org/download/r4v42/r4.cmpn.pdf) : *This pattern decouples sources from their handlers by interposing an event channel between them. The publisher posts events to the channel, which identifies which handlers need to be notified and then takes care of the notification process.*
What is interesting about the OSGi model is that both publishers and subscribers can disappear at any time. A central module to track the handlers availability is needed - the *Event Admin Service*.
### Event Admin Service
The *Event Admin Service* (`org.osgi.service.event.EventAdmin`) takes a central place in the communication between *Event Publishers* and subscribers (*Event Listeners*). It is responsible for keeping track of the listeners, and sending events to them. It supports both synchronous and asynchronous sending that will be reviewed in more details in the [section about sending events](#send-events). But let's illustrate that with the following picture:
![Bundle lifecycle][fig1]
Fig.1 Event Admin Service (Source: <http://enroute.osgi.org/img/services/org.osgi.service.event.overview.png>)
Before going into more details, let's take a look at the events.
### Event
The *Event* interface(`org.osgi.service.event.Event`) encapsulates a single message. It contains:
- topic - used from the *Event Admin Service* as a filter to dispatch the events only to the listeners that are interested;
- payload - the information that we would like to send. It is represented by a key-value pair.
## Receive Events
In order to receive an event through the *Event Admin Service* we have to register a service that implements the `org.osgi.service.event.EventHandler` interface with a property *event.topics* that contains all topics that we are interested in. An example with Declarative Service:
```xml
<scr:component name="com.example.handler" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
<implementation class="com.example.LogEventHandler"/>
<property name="event.topics" type="String" value="some/topic"/>
<service>
<provide interface="org.osgi.service.event.EventHandler"/>
</service>
<reference cardinality="1..1" interface="org.osgi.service.log.LogService" name="LogService" policy="static"/>
</scr:component>
```
```java
package com.example.handler;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogService;
public class LogEventHandler implements EventHandler {
private LogService logService;
protected void bind(LogService logService) {
this.logService = logService;
}
@Override
public void handleEvent(Event event) {
logService.log(LogService.LOG_DEBUG, " Recevied event with topic: " + event.getTopic());
}
protected void undbind(LogService logService) {
this.logService = null;
}
}
```
You can register a handler for multiple topics by adding the topics to the *event.topics* property (`<property name="event.topics" type="String" value="some/topic,other/topic"/>`) or by using wildcard symbol (`<property name="event.topics" type="String" value="some/topic/*"/>`).
## Send Events
As we have already mentioned, you will need an *Event Admin Service* implementation to send events. In Equinox the service is implemented in the `org.eclipse.equinox.event` bundle. The service contains two methods for sending events:
- `void postEvent(Event event)` - sends an Event asynchronously;
- `void sendEvent(Event event)` - sends an Event synchronously;
## Further Reading
- [*OSGi Service Platform Service Compendium, Release 4, Version 4.2,August 2009*](https://osgi.org/download/r4v42/r4.cmpn.pdf)
- <http://enroute.osgi.org/services/org.osgi.service.event.html>
- <http://blog.vogella.com/2017/05/16/osgi-event-admin-publish-subscribe/>
[fig1]:images/event-admin.png

View File

@ -1,327 +0,0 @@
---
layout: developersguide
title: OSGi Declarative Services
---
{% include base.html %}
Declarative Services
====================
## Table of contents
{:.no_toc}
{::options toc_levels="2"/}
* TOC
{:toc}
## I. Introduction
The OSGi Declarative Services functionality is part of the [OSGi 4.2 Compendium Specification][OSGi-Compendium] and allows you to define and consume services via metadata (XML) without any dependency in your source code to the OSGi framework.
## II. Declarative Services
In the [OSGi Overview article](osgi.html) we have mentioned that a bundle can register, unregister, get and unget services from a central point - the Service Registry. In order to simplify the usage of services the [OSGi Alliance](https://www.osgi.org/about-us/) has developed a model of managing services dynamically called *Declarative Services*. In order to understand this model, we will have to first explain a few terms, used below:
- **Declarative Services Container** (we will use the shorthand **DS**) - a module that is managing the [lifecycle](#vii-component-lifecycle) of a *service component* dynamically. It activates and deactivates different components, basing its decisions on the information contained in the *component description*;
- **Service Component** (or also **component**) - an object whose lifecycle is managed, usually by a component framework such as Declarative Services (DS). A component may publish itself as an OSGi service or consume OSGi services. A component can refer to a number of services;
- **Component Description** - The declaration of a component. It is contained
within an XML document in a bundle. (Source: [OSGi Service Platform Service Compendium Chapter 112.1][OSGi-Compendium]).
### 1. DS container
In order to use the Declarative Services functionality you have to start a bundle with an implementation of the DS container. In [Equinox](http://www.eclipse.org/equinox/) (the reference implementation of OSGi that is used in openHAB) this bundle is called `org.eclipse.equinox.ds`.
When a bundle that contains a component is added to the framework, DS reads its component description (the XML file). If the conditions, described in this file are fulfilled (you will understand more about this in the next chapters), the DS activates the component (more on the that in the [component lifecycle](#vii-component-lifecycle) chapter). More importantly, after some of the requirements are not met anymore, the DS container deactivates the component. This ensures that service components are managed dynamically.
### 2. Components
It is important to understand the difference between a *component* and a *service*. A component is a normal Java class, that can reference services and provide services. What makes a component specific is that it is declared in a XML file and is managed completely by DS. That means that DS instantiates the component, calls method on this component and manages the lifecycle of a component.
A component requires the following artifacts in a bundle:
- **XML description** of the component;
- **Service-Component manifest header**, which contains the location of the XML description;
- An **implementation class** that is specified in the component description.
In the examples below we will take a look over more details.
For more detailed definition - [OSGi Service Platform Service Compendium Chapter 112.1][OSGi-Compendium].
### 3. Component Description
The component description is a XML document that contains the *service component description* . The syntax of this document is described in [Component Description Schema, OSGi 4.2 Compendium 112.10][OSGi-Compendium]. The document contains basic information like the component name, component dependencies and services that are implemented by the component. It also describes, how the DS will handle changes in some of the referenced services and how the service will be activated ([lazy](https://www.osgi.org/developer/design/lazy-start/) or not). We will provide some examples below, that cover the most common use cases.
## III. Advantages
For the user of the service it is from no importance, if the service was registered with the [low level API](https://osgi.org/javadoc/r4v43/core/org/osgi/framework/BundleContext.html#registerService(java.lang.Class,%20S,%20java.util.Dictionary)) or with Declarative Services. However the using of Declarative Services has the following advantages:
- **they are declarative** - it is not needed to write explicit code to register and get services. This means that a lot of boilerplate code can be removed and the developer can concentrate on the business logic;
- **DS container manages the creation of instances** - whenever the conditions, described in the *component description*, are satisfied *DS* will instantiate the service for us. When the instances are not needed anymore, *DS* will remove the references to them;
- **components are not initialized until a service is requested** - this is called [lazy initialization](https://www.osgi.org/developer/design/lazy-start/) and it is the default behavior;
- **a component has a lifecycle, that is bound to the defining bundle's lifecycle** - more information can be found in the chapter [component lifecycle](#vii-component-lifecycle).
Components have two most common use cases (which are combined very often):
- a component can [use services](#iv-example---reference-service) from other components;
- a component can [publish itself as an OSGi service](#v-example---provide-service).
## IV. Example - Reference Service
In this example our component needs a [LogService](https://osgi.org/javadoc/r4v42/org/osgi/service/log/LogService.html). We will use DS to inject an implementation of this service in our class.
### 1. Service Component Description
This is the XML component description. This file is located in the *OSGI-INF* folder and is named *consumer.xml*.
Note! Please keep in mind that the Service Component Description can be
automatically generated using SCR Annotations like @Component, @Reference,
@Activate, @Deacticate and etc that are supported e.g. in Apache Felix. In the openHAB project
it is preferred to use these annotations and not create the XML component description by hand.
This should be accompanied with a .gitignore file in the OSGI-INF with /*.xml as content.
This is needed to avoid adding the generated XML component files to git.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="com.example.consumer" immediate="true">
<!-- name = the component name must be unique
immediate = optional parameter that specifies if the component has immediate activation. Options are true or false
-->
<implementation class="com.example.consumer.MyServiceImpl"/>
<!--The fully qualified name of the implemenation class-->
<!--Here we declare references to other services, which we want to use in our implementation class.
bind = method in the implementation class which is called when the referenced services gets active
unbind = method in the implementation class which is called when the referenced services gets inactive
cardinality = regarding the cardinality of your reference DS handles the lifecycle of your servive as well
policy = this reference can be handled as static or dynamic
-->
<reference
interface="org.osgi.service.log.LogService"
bind="setLog"
unbind="unsetLog"
cardinality="1..1"
policy="static"
/>
</scr:component>
```
Let's take a look at some settings, that we can apply:
- **immediate**:
- *true* - the component is activated as soon as all dependencies are satisfied. Adding this option will ensure that the component will be activated right after the bundle is activated and the component is satisfied;
- *false* - the component has lazy activation. The activation of the component is delayed(lazy activation) until the service is requested. This is the default value;
- **cardinality**:
- *1..1* - single service reference, that is mandatory. If your referenced service gets inactive, DS deactivates your service component as well (default value);
- *0..1* - single service reference(not mandatory). You have to be aware that you might not have your reference resolved, your component can live with the absence;
- *0..n* - multiple service references (not mandatory). The referenced service can be implemented multiple times, so they are added to a list in your component implementation;
- *1..n* = like the above, but mandatory;
- **policy**:
- *static* - the default policy. Component configuration are deactivated every time, when a reference with static policy becomes unavailable. This causes the activating and deactivating of the component. It can be very expensive, when we have multiple bound services, or when a service is often unregistered and re-registered;
- *dynamic* - with this policy the component is not deactivated, when a referenced service is changed. It is slightly more complex, as the component implementation has to properly handle changes in the set of bound services.
### 2. Implementation Class
Most of the bundles will require access to other services. A component may be used to inject the service in the bundle through the `bind()` and `unbind()` methods called on the component instance (the implementation class). In the component description above, we have set that our `bind()` method is named `setLog()`, so now we can inspect an instance of the *LogService* provided by the DS. The `activate()` and `deactivate()` methods are called from DS, when the component configuration is activated and deactivated (more about [activation](osgids.html#activaiton)):
```java
package com.example.consumer;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.LogService;
@Component(service = MyServiceImpl.class, immediate = true)
public class MyServiceImpl {
private LogService log;
public MyServiceImpl() {
}
@Activate
protected void activate(BundleContext context) {
System.out.println("Bundle is activated!");
// No specific action is needed here in this case
}
@Deactivate
protected void deactivate(BundleContext context) {
System.out.println("Bundle is deactivated!");
// No specific action is needed here in this case
}
@Reference
public void setLog(LogService l) {
log = l;
System.out.println("Log service is available!");
// We store a reference to the LogService !
}
public void unsetLog(LogService l) {
log = null;
System.out.println("Log service isn`t available anymore!");
// We have to clean up after ourselves, when the reference is not needed anymore !
}
}
```
### 3. Service-Component Header
- The purpose of the *Service-Component* header in the MANIFEST.MF file is to show the DS container, where the component description is located. According to the OSGi specification, such resources must be placed in the *OSGI-INF* folder:
```
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Consumer Example
Bundle-SymbolicName: com.example.consumer
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: org.osgi.framework,
org.osgi.service.log
Service-Component: OSGI-INF/consumer.xml
```
Notice that we have also included this header as well:
```
...
Service-Component: OSGI-INF/*.xml
...
```
## V. Example - Provide Service
Very often you will have to register a service, that implements an interface defined in the framework (e.g [*EventHandler*](https://osgi.org/javadoc/r4v42/org/osgi/service/event/EventHandler.html)) or interface, that you have defined. An interface allows you to change the implementation easily or register multiple implementations in the Service Registry.
We will use DS to register an implementation of the EventHandler service in the OSGi Service Registry.
### 1. Component Description
This is how the component description may look like. This file is located in the *OSGI-INF* folder and is named *provider.xml*. We are providing some service specific properties as well (the property with name `event.topics`). The usage of this property is desrcribed in the [API documentation](https://osgi.org/javadoc/r4v42/org/osgi/service/event/EventConstants.html#EVENT_TOPIC):
```
<?xml version="1.0" encoding="UTF-8"?>
<scr:component name="com.example.handler" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
<implementation class="com.example.HandlerImpl"/>
<property name="event.topics">some/topic</property>
<service>
<provide interface="org.osgi.service.event.EventHandler"/>
</service>
</scr:component>
```
### 2. Implementation Class
```java
package com.example.provider;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
@Component(service = EventHandler.class, property = { "event.topics=some/topic" })
public class HandlerImpl implements EventHandler {
@Override
public void handleEvent(Event event) {
String topic = event.getTopic();
System.out.println("Received event with topic: " + topic);
}
}
```
### 3. Service-Component Header
```
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Provider Example
Bundle-SymbolicName: com.example.provider
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: org.osgi.service.event
Service-Component: OSGI-INF/provider.xml
```
## VI. Component types
A component lifecycle depends of the type of the component. Three type of components are defined:
- **immediate** - with ```immediate``` attribute set to ```true``` - see the [example component description](#service-component-description);
- **delayed** - with ```immediate``` attribute set to ```false```;
- **factory** - we will not discuss the lifecycle of this type in this article. You can find information in the [OSGi 4.2 Compendium Specifiaction, Chapter 112.2.4 Factory Component][OSGi-Compendium].
## VII. Component Lifecycle
A component goes through several states in his lifecycle:
- **UNSATISFIED** - initial state of the Service Component, after the bundle is started;
- **REGISTERED** - temporary state. Only *delayed* components go through this state;
- **ACTIVE** - the component is active and component instance is created.
<img src="images/immediatecomponent.png" width="320" height="200" />
Fig.1 Immediate component lifecycle
<img src="images/delayedcomponent.png" width="400" height="200" />
Fig.2 Delayed component lifecycle
### 1. States
Like described above, the component lifecycle depends on the lifecycle of the bundle, that includes the component. Component must be enabled before it can be used.
A component is **enabled**, when the component's bundle is **started** and **disabled**, when the bundle is **stopped**.
After the Component is enabled, it is moved to the UNSATISFIED state. The next step is to satisfy the component configuration.
The component **configuration is satisfied** when:
- component is **enabled**;
- all the **component references are satisfied**. A reference is satisfied when the reference specifies optional cardinality or there is at least one target service for the reference.
If the component has lazy initialization (the component is *delayed*), it is moved to the REGISTERED state and it is waiting to be activated, when the service is requested (see Fig.2).
Otherwise (the component is *immediate*) as soon as its dependencies are satisfied, the component is activated (see Fig.1).
### 2. Activation
Activation consists of following steps:
- load the implementation class;
- create component instance;
- bind target services (`bind()` method is called);
- call the `activate()` method, if present.
After the activation, the component is in ACTIVE state. From this state the component can go back to the *REGISTERED* state (or to the *UNSATISFIED* state), if the component configuration becomes unsatisfied.
### 3. Deactivation
Deactivation consists of the following actions:
- the `deactivate` method is called, if present;
- unbind target services (`unbind()` method is called);
- release all references to the component instance.
For more information - [OSGi 4.2 Compendium Specifiaction, Chapter 112.5 Component Life Cycle][OSGi-Compendium].
If you want to practice, what you have learned so far, you can try out our [OSGi Coding tasks](osgitasks.html). In chapter IV. there are tasks, that are using OSGi DS. It is recommended to start from the beginning (chapter I.), as the tasks are related between each other.
## VIII. Troubleshooting
If you are new to Declarative Services and you don't know how to troubleshoot, maybe we can help you. We will give you a few tips what might have gone wrong. Some of the steps below depend on your development IDE and the OSGi Container that you use. Mind that most of the tips below are specific to Eclipse IDE and Equinox as the OSGi Runtime. You might review again the [Equinox commands](equinox.html#iv-commands) before you continue:
- make sure that your Component Description (the XML file) contains the necessary information to provide or reference service (see the examples above);
- make sure that your Component Description is placed in the OSGI-INF folder;
- [Eclipse IDE only] make sure that no warnings are displayed in the Component Description file (a warning might indicate that some of the class names that you have used are not correct !);
- open the bundle manifest file (located in META-INF/MANIFEST.MF) and check if your Component Description is added in the "Service-Component" header (it can be added as "OSGI-INF/*.xml or "OSGI-INF/component.xml");
- [Eclipse IDE only] open the build.properties file and make sure that the Component Description is added in the bin.includes entry (it can be added as "OSGI-INF/" or "OSGI-INF/component.xml"). If it is not included in the build.properties file, it will not end in the .jar file that will be deployed in the Equinox runtime and SCR will be not able to find it;
- Start the runtime and check the log. Do you see any Errors like "MESSAGE [SCR] Error while trying to bind reference"? Read the error message and proceed accordingly.
- Make sure that the bundle that is providing the service is in "ACTIVE" state, if it is not, start it manually with the "start id" command, where id is the id of the bundle;
- [Equinox only] While the runtime is running type the "services" command and search if your service is registered. In case it is not registered, most probably you have missed some of the steps above :);
- [Equinox only] While the runtime is running type "ls -c id", where id is the id of the bundle that is providing the service. This will give detailed information about the services that are registered by this bundle, read the information if the component is satisfied carefully;
- Is your service in use? In case it is not and you are not seeing it registered, the component might have lazy activation policy(this is the default policy). Add "immediate=true" in your Component Definition if you like to change the activation policy.
## XIX. Further Reading
- [*OSGi Service Platform Service Compendium, Release 4, Version 4.2,
August 2009*](https://osgi.org/download/r4v42/r4.cmpn.pdf)
- [Lars Vogel - Declarative services](http://www.vogella.com/tutorials/OSGiServices/article.html#declarativeservices)
- [Getting Started with OSGi: Declarative Services](http://www.eclipsezone.com/eclipse/forums/t97690.rhtml)
- <http://stackoverflow.com/questions/8886430/what-is-the-difference-between-osgi-components-and-services>
[OSGi-Compendium]: https://osgi.org/download/r4v42/r4.cmpn.pdf

View File

@ -1,209 +0,0 @@
---
layout: developersguide
title: Coding tasks for OSGi
---
{% include base.html %}
Coding tasks for OSGi
=====================
## Introduction
OSGi coding tasks can help Java developers with no experience in OSGi and Eclipse Plug-in development to get familiar with writing bundles. This page contains tasks, related to several fundamental topics, and references to tutorials, where similar tasks are implemented.
## Prerequisites
Before you start, you should first install the [Eclipse IDE](../development/ide.html).
In order to run your samples in an OSGi Runtime you might want to start Equinox in a command line.
You might take a look at the [Coding Guidelines](/developers/development/guidelines.html) as well.
## Tasks
The tasks are divided in several sections:
{::options toc_levels="3"/}
* TOC
{:toc}
Sample implementations are present in the [openHAB docs repo](https://github.com/openhab/openhab-docs/tree/gh-pages/_sample_code/osgi_codings_tasks/bundles).
### I. Writing basic OSGi bundle
1. Create an `org.openhab.training.helloosgi` bundle, that prints a message on the console, when it is started and stopped. Test it in the OSGi Container.
Hint! You can install a bundle in the OSGi Container with the `install {url} ` command.
Hint! If you still have difficulties look at the
[reference section](#writing-basic-osgi-bundle).
2. Create an `org.openhab.training.utils` bundle, which contains class `TimeUtilities`, that has a single method `String getCurrentTimeStamp()` that returns the current time stamp in format "hh:mm". Mind the `TimeUtilities` class might be used in some other bundles.
Hint! Find out how can you export packages in OSGi.
3. Create an `org.openhab.training.helloosgi.modified` bundle to use the `TimeUtilities` class to display the current time stamp on the console when it is started.
### II. Services
1. Create an `org.openhab.training.electricity.provider` bundle, which defines a service interface `ElectricityProvider` with one method:
- `boolean provide (int value)` - discharges the provider with the 'value' parameter. It should return true, if the requested charge is available in the provider and false, if it is not;
2. Create an `org.openhab.training.electricity.homenetwork` bundle:
- with `HomeElectricityNetwork` implementation of the `ElectricityProvider` interface with infinite charge (the `provide()` method should always return true);
- which registers the `HomeElectricityNetwork` as `ElectricityProvider` in the OSGi *Service Registry* with [*BundleContext*][BundleContext].
Hint! You can find similar tasks in the
[examples below](#services).
3. Create an `org.openhab.training.electricity.consumer` bundle, which contains an
interface `ElectricityConsumer` with methods:
- `void startConsuming()` - starts the device and displays a message on the console. If the device requires a provider (it is not energy independent), this method can set the current provider. After the device is started it should try to consume electricity equal to the device consumption from the current provider at fixed interval from 5 seconds and display a message on the console, which provider is in use at the moment;
- `void stopConsuming()` - stops the device and displays message on the console. The device should not consume electricity anymore. The device should stop, if the current provider is discharged;
- `void setCurrentProvider(ElectricityProvider)` - sets a current provider from a list with available providers;
- `List<ElectricityProvider> getAllAvailableProviders()` - returns a list with available providers (a provider is available, when it is registered in the *Service Registry* and has more charge, than the device consumption).
4. Create an `org.openhab.training.electricity.radio` bundle, which:
- implements the `ElectricityConsumer` interface in a `BasicRadio` consumer, which has specific consumption (e.g `int consumption = 5`) and can be connected to only one provider;
- the device should be able to stop automatically, when there is no provider available and display a message on the console. After that it must be started manually;
- use the `ElectricityProvider` service by getting it directly from the *BundleContext*.
### III. Service Tracker
1. Create an `org.openhab.training.electricity.battery` bundle, which implements and registers `Battery` implementation of the `ElectricityProvider` service with finite charge (e.g `int capacity = 20`).
Hint! An ElectricityProvider can be used by different consumers simultaneously. Consider whether it is necessary to make an implementation of this class thread-safe.
2. Create an interface `DynamicConsumer` in an `org.openhab.training.electricity.dynamicconsumer` bundle that should manage the availability of the different `ElectricityProvider`. It defines two methods:
- `void providerAdded(ElectricityProvider)` - called when a new provider is registered in the *ServiceRegistry*. It should add the provider in a list with available electricity sources for the current device;
- `void providerRemoved(ElectricityProvider)` - called when a provider is unregistered from the *ServiceRegistry*. It should remove the provider from a list with available electricity sources for the current device.
3. Create a `TV` consumer with `int consumption = 10`, that implements the `ElectricityConsumer` interface and the `DynamicConsumer`interface in an `org.openhab.training.electricity.tv` bundle by using [*ServiceTracker*][ServiceTracker]. The `TV` should be able to dynamically switch between different `ElectricityProvider`:
- when both providers are available, it should work with the `HomeElectricityNetwork`;
- otherwise it should use the provider that is available at the moment;
- when no provider is available or it can not provide enough charge, the `TV` should stop;
- it should be able to start automatically, if a new provider is available and is able to provide enough electricity.
Hint! Install `org.eclipse.osgi.util` bundle in order to use *ServiceTracker*.
Tutorials with examples can be found
[below](#service-trackers).
### IV. Declarative Services and Components
1. Provider and consumer:
- rewrite all tasks in section [II.](#ii-services) and [III.](#iii-service-tracker) to use the `ElectricityProvider` service by injecting it by the means of [*Declarative Services*][ds] (do not use Service Tracker and BundleContext).The name of the new bundles will be the same, but ending with a .ds (e.g. `org.openhab.training.electricity.tv.ds`);
Hint! Install `org.eclipse.equinox.ds` bundle in order to use *Declarative Services*.
2. Create a `SolarRadio` consumer that extends `BasicRadio` from IV.1 in an `org.openhab.training.electricity.solarradio` bundle. This radio does not need any `ElectricityProvider` to run, but will use one, if it is available (the device should not stop when there are no providers available).
Hint! Read about cardinality in OSGi Declarative Services!
3. Create a `CombinedSolarRadio` that extends `SolarRadio` and implements `DynamicConsumer` in `org.openhab.training.electricity.combinedradio` it the following way:
- the consumer does not need any provider to run;
- if one is available, it must set it as current and use it;
- if more than one are available, add all to the list with available providers.
Hint! What happens when you stop the provider that is not in use? Why does the `CombinedSolarRadio` stops and starts? Modify the consumer to handle the removal of the service dynamically - without calling the stop and start methods every time! Read about service policy in OSGi!
Hint! Test all the bundles in the OSGi container by starting and stopping the different `ElectricityProvider` implementations and track, if the consumers are used as expected.
### V. Events
1. Create a bundle `org.openhab.training.util.sender` that registers as a service `TimeEventSender`, that sends events with topic "org/openhab/training/time" every minute with the current time stamp (hh:mm) (you can use the `TimeUtilities` class from chapter [I.](#i-writing-basic-osgi-bundle)).
Hint! Install `org.eclipse.equinox.event` bundle in order to use *EventAdmin* service for sending events.
Hint! [ScheduledExecutorService](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html) can be used in this task.
2. Extend the `org.openhab.training.electricity.tv.ds.TV` class from IV.1 in an `org.openhab.training.electricity.tv.events` bundle to listen for events and print them on the console.
### VI. Managed Services
1. Implement the [*ManagedService*][ManagedService] interface in the `org.openhab.training.electricity.tv.events` bundle and:
- add a configuration to the `TV` class with property "autoSleep" and value a timestamp (hh:mm);
- modify the method that listens for events with topic "time" to stop the bundle, when the "autoSleep" property is equal to the "time" topic.
2. Create another implementation of the `ElectricityProvider` interface (`RechargableBattery`) in an `org.openhab.training.electricity.rechargeablebattery` bundle that:
- has a finite charge (e.g `int capacity = 30`);
- implements the *ManagedService* interface. After this modification, it should be possible to change the charge of a battery with a `setCharge(int capacity)` method;
Hint! Read about the [ConfigurationAdmin](https://osgi.org/javadoc/r4v42/org/osgi/service/cm/ConfigurationAdmin.html) service and how you can use it to change the configuration of a ManagedService. You might want to implement additional bundle that is using the ConfigurationAdmin service to change the configuration of the battery to perform a quick test.
Hint! Install `org.eclipse.equinox.cm` bundle in order to use *ConfigurationAdmin* service.
3. Create a `org.openhab.training.electricity.recharger` bundle that will wait 10 seconds after it is activated and will recharge a rechargeable battery if it is registered as a service.
### VII. Console Commands
1. Implement a service in the `org.openhab.training.util.console` bundle that adds console command, that:
- displays all `ElectricityProvider` implementations that are registered with their charge;
- displays all consumers, to which providers are they connected (which providers are available) and what is the current provider at the moment;
Hint! You might have to register all consumers as services in the OSGi ServiceRegistry.
- sets the charge of the `RechargableBattery` to a provided value;
- changes the configuration of the `TV`.
Hint! You can add console commands with the help of
[CommandProvider][CommandProvider]
Hint! Test the console commands in the OSGi runtime by starting the different Radio consumers and the TV consumer. Start only the `Battery` provider and wait until it is discharged. Check if the consumers have stopped as expected. Recharge the battery and check, if the consumers have started. You can try out different scenarios as well!
## References
Some of the tutorials listed below are not up-to-date with the latest versions of the Equinox framework or are written to be run in another implementation of the OSGi Runtime (e.g. Apache Felix). They can help you with solving the tasks above. This resources are divided in several groups.
### Multiple topics
{:.no_toc}
- This tutorials simply cover more than one from the topics below:
- [OSGi enRoute project](http://enroute.osgi.org/book/150-tutorials.html)
- [Lars Vogel: OSGi Modularity](http://www.vogella.com/tutorials/OSGi/article.html);
- [Lars Vogel: OSGi Services](http://www.vogella.com/tutorials/OSGiServices/article.html);
- [Hello, OSGi, Part 1: Bundles for beginners](http://www.javaworld.com/article/2077837/java-se/java-se-hello-osgi-part-1-bundles-for-beginners.html);
- [OSGi Getting Started](https://mnlipp.github.io/osgi-getting-started/);
- [Java OSGi Tutorial for Beginners](http://o7planning.org/en/10135/java-osgi-tutorial-for-beginners);
- [Understand OSGi Concepts. Try to Follow the Puzzle Approach](http://crunchify.com/understand-osgi-concepts-try-to-follow-the-puzzle-approach/).
### Writing basic OSGi bundle
{:.no_toc}
- This tutorials are focused on writing basic bundle, managing the dependencies between the bundles and running them in an OSGi container:
- [Getting started with OSGi: Your first bundle](http://www.eclipsezone.com/eclipse/forums/m92130843.html);
- [Getting started with OSGi: Interacting with the Framework](http://www.eclipsezone.com/eclipse/forums/m92131032.html);
- [Getting started with OSGi: Dependencies between Bundles](http://www.eclipsezone.com/eclipse/forums/t90544.html).
### Services
{:.no_toc}
- Registering and using Services with the low-level OSGi API:
- [Getting started with OSGi: Registering a Service](http://eclipsezone.com/eclipse/forums/t90688.html);
- [Getting started with OSGi: Consuming a Service](http://www.eclipsezone.com/eclipse/forums/t90796.html);
- [Apache Felix Tutorial: A bundle that listens for OSGi service events](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-1.html);
- [Apache Felix Tutorial: A bundle that implements a dictionary service](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-2.html);
- [Apache Felix Tutorial: A bundle that implements another dictionary service](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-2b.html);
- [Apache Felix Tutorial: A bundle that implements a simple dictionary service client](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-3.html);
- [Apache Felix Tutorial: A bundle that implements a more robust dictionary service client](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-4.html).
### Service Trackers
{:.no_toc}
- OSGi Service Tracker simplifies using services from the Framework's registry. You can find example usage in the links below:
- [Getting started with OSGi: Dynamic Service Tracking](http://www.eclipsezone.com/eclipse/forums/t91059.rhtml);
- [Apache Felix Tutorial: A bundle that implements a dictionary service client using the Service Tracker](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-5.html);
- [Apache Felix Tutorial: A bundle that implements a spell checker service using dictionary services.](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-6.html);
- [Apache Felix Tutorial: A bundle that implements a spell checker service client](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-7.html);
- [OSGi - ServiceTracker - ServiceTrackerCustomizer](http://stackoverflow.com/questions/28748052/osgi-servicetracker-servicetrackercustomizer).
### Declarative Services and Components
{:.no_toc}
- OSGi Declarative Services is a high level API for managing services in OSGi. Its functionality is widely used in the openHAB project. Tutorials about the topic:
- [Getting Started with OSGi Declarative Services](http://blog.vogella.com/2016/06/21/getting-started-with-osgi-declarative-services/)
- [Getting started with OSGi: Introducing Declarative Services](http://www.eclipsezone.com/eclipse/forums/t96740.html);
- [Getting started with OSGi: Declarative Services and Dependencies](http://www.eclipsezone.com/eclipse/forums/t97690.rhtml);
- [Apache Felix Tutorial: A bundle that implements a spell checker service using Declarative Services](http://felix.apache.org/documentation/tutorials-examples-and-presentations/apache-felix-osgi-tutorial/apache-felix-tutorial-example-9.html).
### Managed Services
{:.no_toc}
- [OSGi declarative managed services and configuration](http://nakvic-dev.blogspot.bg/2010/11/osgi-declarative-managed-services-and.html)
[BundleContext]: https://osgi.org/javadoc/r4v43/core/org/osgi/framework/BundleContext.html
[ManagedService]: https://osgi.org/javadoc/r4v42/org/osgi/service/cm/ManagedService.html
[ServiceTracker]: https://osgi.org/javadoc/r4v42/org/osgi/util/tracker/ServiceTracker.html
[SERVICE_RANKING]: https://osgi.org/javadoc/r4v42/org/osgi/framework/Constants.html#SERVICE%5FRANKING
[ds]: osgids.html
[CommandProvider]: http://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fosgi%2Fframework%2Fconsole%2FCommandProvider.html

211
developers/tests.md Normal file
View File

@ -0,0 +1,211 @@
---
layout: developersguide
title: Writing tests
---
{% include base.html %}
# Tests
There are two approaches for testing:
Unit tests and integration tests.
Integration tests start up parts of the openHAB framework and the test is performed with real OSGi services.
Tests of this kind should be used rarely as they increase the overall test runtime considerably.
But it usually makes sense to have at least one integration test for a complex extension,
to make sure everything (all your OSGi services) start up correctly.
## Unit tests
Each class inside the `src/main/test` folder will have all public methods with a `@Test` annotation automatically executed as a test.
Inside the class one can refer to all classes from the host bundle and all imported classes.
The following code snippet shows a simple JUnit test which tests the `toString` conversation of a PercentType.
```java
public class PercentTypeTest {
@Test
public void DoubleValue() {
PercentType pt = new PercentType("0.0001");
assertEquals("0.0001", pt.toString());
}
}
```
Using the [hamcrest matcher library](http://hamcrest.org/JavaHamcrest/) is a good way to write expressive assertions.
In contrast to the original assertion statements from JUnit the hamcrest matcher library allows to define the assertion in a more natural order:
```java
PercentType pt = new PercentType("0.0001");
assertThat(pt.toString(), is(equalTo("0.0001")));
```
### Mockito
In order to keep tests as focused as possible we use the mocking framework [https://github.com/mockito/mockito Mockito].
Mockito lets us verify interactions between supporting classes and the unit under test and additionally supports stubbing of method calls for these classes.
Please read the very short but expressive introduction on the [http://site.mockito.org/ Mockito homepage] in addition to this small example:
```java
public class MyBindingHandlerTest {
private ThingHandler handler;
@Mock
private ThingHandlerCallback callback;
@Mock
private Thing thing;
@Before
public void setUp() {
initMocks(this);
handler = new MyBindingHandler(thing);
handler.setCallback(callback);
}
@After
public void tearDown() {
// Free any resources, like open database connections, files etc.
handler.dispose();
}
@Test
public void initializeShouldCallTheCallback() {
// we expect the handler#initialize method to call the callback during execution and
// pass it the thing and a ThingStatusInfo object containing the ThingStatus of the thing.
handler.initialize();
// verify the interaction with the callback.
// Check that the ThingStatusInfo given as second parameter to the callback was build with the ONLINE status:
verify(callback).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
}
}
```
### Assertions
Here is small example on when to use Hamcrest or JUnit assertions.
In general Hamcrest should be favoured over JUnit as for the more advanced and detailed error output:
```java
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.StringContains.containsString;
...
@Test
public void assertionsToBeUsed() {
// use JUnit assertions for very basic checks:
assertNotNull(new Object());
assertNull(null);
boolean booleanValue = true;
assertTrue(booleanValue); // test boolean values only, no conditions or constraints
// use Hamcrest assertions for everything else:
assertThat("myString", is("myString"));
assertThat("myString", is(instanceOf(String.class)));
assertThat("myString", containsString("yS"));
assertThat(Arrays.asList("one", "two"), hasItem("two"));
assertThat(Arrays.asList("one", "two"), hasSize(2));
// also valuable for null/boolean checks as the error output is advanced:
assertThat(null, is(nullValue()));
assertThat(new Object(), is(not(nullValue())));
assertThat(true, is(not(false)));
}
```
## Integration tests
Some components of openHAB are heavily bound to the OSGi runtime,
because they use OSGi core services like the `EventAdmin` or the `ConfigurationAdmin` or the `ItemRegistry`
That makes it hard to test those components outside of the OSGi container.
Integration tests allow to run test classes that extend `JavaOSGiTest` inside an OSGi runtime.
They reside as separate bundles in the `itests/` directory.
A .bndrun file must be provided with your integration test to configure the runtime.
Those kind of tests should be used sparingly as the setup is more complex and introduces execution overhead.
Most situations can be tested using mocks (see [Mockito](#mockito)) and unit tests.
From maven one can execute the test with `mvn install` command from the folder of the test fragment bundle.
### Example
The base class `JavaOSGiTest` sets up a bundle context and has convenience methods for registering mocks as OSGi services and the retrieval of registered OSGi services.
Public methods with a @Test annotation will automatically be executed as OSGi tests, as long as the class-name ends with `Test`.
The following JUnit/Mockito test class shows how to test the `ItemRegistry` by providing a mocked `ItemProvider`.
```java
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.smarthome.core.items.Item;
import org.eclipse.smarthome.core.items.ItemProvider;
import org.eclipse.smarthome.core.items.ItemRegistry;
import org.eclipse.smarthome.core.library.items.SwitchItem;
import org.eclipse.smarthome.test.java.JavaOSGiTest;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import com.google.common.collect.Lists;
public class JavaItemRegistryOSGiTest extends JavaOSGiTest {
private static String ITEM_NAME = "switchItem";
private ItemRegistry itemRegistry;
@Mock
private ItemProvider itemProvider;
@Before
public void setUp() {
initMocks(this);
itemRegistry = getService(ItemRegistry.class);
when(itemProvider.getAll()).thenReturn(Lists.newArrayList(new SwitchItem(ITEM_NAME)));
}
@Test
public void getItemsShouldReturnItemsFromRegisteredItemProvider() {
assertThat(itemRegistry.getItems(), hasSize(0));
registerService(itemProvider);
List<Item> items = new ArrayList<>(itemRegistry.getItems());
assertThat(items, hasSize(1));
assertThat(items.get(0).getName(), is(equalTo(ITEM_NAME)));
unregisterService(itemProvider);
assertThat(itemRegistry.getItems(), hasSize(0));
}
}
```
In the `setUp` method all mocks (annotated with @Mock) are created.
This is `itemProvider` for this test.
Then the `ItemRegistry` OSGi service is retrieved through the method `getService` from the base class `OSGiTest` and assigned to a private variable.
Then the `ItemProvider` mock is configured to return a list with one SwitchItem when `itemProvider#getAll` gets called.
The test method first checks that the registry delivers no items by default.
Afterwards it registers the mocked `ItemProvider` as OSGi service with the method `registerService` and checks if the `ItemRegistry` returns one item now.
At the end the mock is unregistered again.
## Common errors
### Failed to execute goal org.eclipse.tycho:tycho-surefire-plugin:XXX:test (default-test) on project XXX: No tests found.
Maven might report this error when building your project, it means that the maven surefire plugin cannot find any tests to execute, please check the following details:
* Did you add any test classes with a class-name which ends with `Test` (singular)
* Did you annotate any methods with `@Test`

View File

@ -0,0 +1,198 @@
---
layout: developersguide
title: Transformations / Profiles
---
{% include base.html %}
# Developing a Transformation Service
Transformations and Profiles (see next section) are very similar in their nature. Usually
if you implement a transformation, you also provide a profile for the same matter in the same bundle.
A transformation in openHAB is generally speaking a mapping from a Java String to a Java String
with a given user configuration.
It does not keep any state, so it is expected to return the same output for the same input.
Transformations are used for generic bindings like MQTT, HTTP, COAP and any other direct
protocol interaction to transform an incoming wire string to something that a channel can process.
Let's assume that we want to write a transformation that appends the string ",bro!" to every
input.
First you want to create a new bundle for example via the skeleton.
You start by writing an OSGi service class that implements `TransformationService`.
Also check out the openHAB JavaDoc class documentation, there might be a more specialized class for
your needs like `AbstractFileTransformationService`.
```java
@NonNullByDefault
@Component(immediate = true, property = { "smarthome.transform=BRO" })
public class TheBroTransformationService implements TransformationService {
private final Logger logger = LoggerFactory.getLogger(TheBroTransformationService.class);
@Override
public @Nullable String transform(String config, String input) throws TransformationException {
}
}
```
Next you implement the `transform` method. You are given a user configuration and the input value.
In our case we do not use the `config` parameter.
Other services like the regex or map transformation are using this parameter for the regex ("`.*=(\\d*.\\d*).*`") or the map (`mapfile.map`) for example.
Our implementation is as simple as this:
```java
@Override
public @Nullable String transform(String config, String input) throws TransformationException {
return input + ",bro!";
}
```
# Developing a Profile
The communication between the framework and the Thing handlers can be influenced by "Profiles".
By their nature, profiles are correlated to links between Items and Channels (i.e. `ItemChannelLinks`),
just like transformations.
But in contrast to transformations, if one Channel is linked to several Items it also will have several profile instances.
Each instance handling the communication to exactly one of these Items.
The same applies for the situation where one Item is linked to multiple Channels.
Profiles are created by ProfileFactories and are retained for the lifetime of their link.
This means that they are, in contrast to transformations, allowed to retain a transient state,
like e.g. the timestamp of the the last event or the last state.
With this, it is possible to take into account the temporal dimension when calculating the appropriate action in any situation.
There exist two different kinds of profiles: state and trigger profiles.
## State Profiles
State profiles are responsible for communication between Items and their corresponding state Channels (`ChannelKind.STATE`).
Their purpose is to forward state updates and commands to and from the Thing handlers.
## Trigger Profiles
Trigger Channels (`ChannelKind.TRIGGER`) by themselves do not maintain a state (as by their nature they only fire events).
With the help of trigger profiles they can be linked to Items anyway.
Hence the main purpose of a trigger profile is to calculate a state based on the fired events.
This state then is forwarded to the linked Item by sending `ItemStateEvents`.
Trigger profiles are powerful means to implement some immediate, straight-forward logic without the need to write any rules.
Apart from that, they do not pass any commands or state updates to and from the Thing handler as by their nature trigger Channels are not capable of handling these.
This section explains how custom `Profile`s can be created.
First you want to create a new bundle for example via the skeleton.
## Profile Properties
A `Profile` is determined by its type, i.e. `StateProfileType` or `TriggerProfileType` and its `ProfileTypeUID`.
Both types are specified via interfaces `StateProfile` and `TriggerProfile`, respectively.
The `ProfileTypeUID` identifies one specific type of `Profile`.
Each `Profile` exists in a certain scope.
There are pre-defined `Profile`s that are defined in scope `ProfileTypeUID.SYSTEM_SCOPE`, which is "system".
Custom `Profiles` should be created in a different scope.
Thus a `ProfileTypeUID` can be created as follows: `new ProfileTypeUID("MyScope", "MyProfileName")`.
A `StateProfile` receives `Commands` and `StateUpdates` from the `ThingHandler` and from the `Item`.
It has to implement four methods that specify how the `Command`s or `StateUpdate`s should be handled.
A `TriggerProfile` makes it possible to link a `TriggerChannel` to an `Item`.
This `Profile` receives the `State` of the `Item` and the `Event` that has been triggered.
## ProfileTypeProvider
Custom `ProfileType`s have to be annouced by a `ProfileTypeProvider` to the framework via an OSGi service:
```java
@Component(service = { ProfileTypeProvider.class })
public class MyProfileTypeProvider implements ProfileTypeProvider {
@Override
public Collection<ProfileType> getProfileTypes(Locale locale) {
//return custom types
}
}
```
## ProfileFactory
The most important interface is the `ProfileFactory` which has to be implemented and announced as an OSGi service:
```java
@Component(service = { ProfileFactory.class })
public class MyProfileFactory implements ProfileFactory {
```
Such a factory is responsible for specific `ProfileTypeUID`s that should be returned by `Collection<ProfileTypeUID> getSupportedProfileTypeUIDs()`.
Further it is capable of creating these `Profile`s via `createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback, ProfileContext profileContext);`.
For convenience, the `ProfileFactory` and the `ProfileTypeProvider` can be put into one class and announce the two services:
```java
@Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
public class MyProfileFactory implements ProfileFactory, ProfileTypeProvider {
```
### ProfileCallback
`Profile`s need the opportunity to notify the framework about what they did with the `Command`s and `StateUpdate`s it received from the framework.
The `ProfileCallback` provides methods to forward the results of a `Profile`s processing to the `ThingHandler` and to the `Framework`.
It should be injected into the `Profile` upon its creation.
### ProfileContext
Some more advanced `Profile`s which can be configured need access to their `Configuration` object.
This is offered via the `ProfileContext`.
A `ScheduledExecutorService` can also be retrieved via the `ProfileContext` in order to schedule long running tasks in a separate Thread.
The `ProfileContext` may also be injected into the `Profile` upon its creation.
## ProfileAdvisor
A `ProfileAdvisor` is an optional component which can be used to suggest a specific `ProfileTypeUID` for a given `Channel` or `ChannelType`.
Two methods have to be implemented to achieve this:
`ProfileTypeUID getSuggestedProfileTypeUID(Channel channel, @Nullable String itemType);`
`ProfileTypeUID getSuggestedProfileTypeUID(ChannelType channelType, @Nullable String itemType);`
## Using Profiles
### .items file
`Profiles`s can be specified as a parameter for a given channel:
```java
<item-type> MyItem { channel="<bindingID>:<thing-typeID>:MyThing:myChannel"[profile="MyScope:MyProfile"]}
```
## Existing Profiles
A few profiles are provided by openHAB itself.
### FollowProfile
If one device should "follow" the actions of another device, the FollowProfile can be used.
The term "follow" in this case means that any state that is sent to an `Item` will be forwarded from this `Item` to any linked channel with the FollowProfile.
The FollowProfile takes state updates on an `Item` and sends them as a command onto the channel.
In the direction from the ThingHandler towards the `Item`, the FollowProfile ignores state updates.
```java
<itemType> <itemName> { channel="<channelUID>", channel="<followChannelUID>"[profile="follow"]}
```
### OffsetProfile
The `OffsetProfile` provides the possibility to adjust a value from a device before it arrives at the framework.
An offset can be specified via the parameter `offset` which has to be a `QuantityType` or `DecimalType`.
A positive offset is the amount of change from the device towards the framework, i.e. all values from the device are increased by this offset and values sent to the device are decreased by this offset.
A negative offset subtracts the offset from the value sent by the device to the framework and adds the offset to values sent from the framework to the device.
```java
Number <itemName> { channel="<bindingID>:<thing-typeID>:<thingName>:<channelName>"[profile="offset", offset="<value>"]}
```

337
developers/utils/events.md Normal file
View File

@ -0,0 +1,337 @@
---
layout: developersguide
title: Event Bus
---
{% include base.html %}
# Event Bus
{:.no_toc}
The openHAB framework provides an event bus for inter-component communication.
The communication is based on events which can be sent and received through the event bus in an asynchronous way.
Examples of openHAB event types are _ItemCommandEvent_, _ItemStateEvent_, _ItemAddedEvent_, _ThingStatusInfoEvent_, etc.
This section introduces the event API and illustrates how to receive such events.
Furthermore, the sending of events and the implementation of new event types will be described.
{::options toc_levels="2,3"/}
* TOC
{:toc}
## API Introduction
### The Interfaces
The `EventPublisher` posts `Event`s through the openHAB event bus in an asynchronous way.
The `EventSubscriber` defines the callback interface to receive events of specific types to which the event subscriber is subscribed.
The EventPublisher and the EventSubscribers are registered as OSGi services.
An event subscriber can provide an `EventFilter` in order to filter events based on the topic or the content.
If there is no filter all subscribed event types are received.
The event itself will be subclassed for each event type, which exists in the System (e.g. ItemCommandEvent, ItemUpdatedEvent, ThingStatusInfoEvent).
### The Core Events
This section lists the core events provided by openHAB which can be divided into the categories _Item Events_, _Thing Events_ and _Inbox Events_.
An event consists of a `topic`, a `type`, a `payload` and a `source`.
The payload can be serialized with any String representation and is determined by its concrete event type implementation (e.g. ItemCommandEvent, ItemUpdatedEvent).
The payloads of the openHAB core events are serialized with JSON.
Each event implementation provides the payload as high level methods as well, usually presented by a data transfer object (DTO).
A topic clearly defines the target of the event and its structure is similar to a REST URI, except the last part, the action.
The topics of openHAB events are divided into the following four parts: `{namespace}/{entityType}/{entity}/{action}`, e.g. `smarthome/items/{itemName}/command`.
The type of an event is represented by a string, usually the name of the concrete event implementation class, e.g. ItemCommandEvent, ItemUpdatedEvent.
This string type presentation is used by event subscribers for event subscription (see chapter "Receive Events") and by the framework for the creation of concrete event instances.
The event source is optional and represents the name of the source identifying the sender.
#### Item Events
| Event |Description |Topic |
|-----------------------|-------------------------------------------------|----------------------------------------|
| ItemAddedEvent |An item has been added to the item registry. |smarthome/items/{itemName}/added |
| ItemRemovedEvent |An item has been removed from the item registry. |smarthome/items/{itemName}/removed |
| ItemUpdatedEvent |An item has been updated in the item registry. |smarthome/items/{itemName}/updated |
| ItemCommandEvent |A command is sent to an item via a channel. |smarthome/items/{itemName}/command |
| ItemStateEvent |The state of an item is updated. |smarthome/items/{itemName}/state |
| ItemStateChangedEvent |The state of an item has changed. |smarthome/items/{itemName}/statechanged |
**Note:** The ItemStateEvent is always sent if the state of an item is updated, even if the state did not change.
ItemStateChangedEvent is sent only if the state of an item was really changed.
It contains the old and the new state of the item.
#### Thing Events
| Event |Description |Topic |
|-----------------------|-------------------------------------------------|-----------------------------------|
| ThingAddedEvent |A thing has been added to the thing registry. |smarthome/things/{thingUID}/added |
| ThingRemovedEvent |A thing has been removed from the thing registry.|smarthome/things/{thingUID}/removed|
| ThingUpdatedEvent |A thing has been updated in the thing registry. |smarthome/things/{thingUID}/updated|
| ThingStatusInfoEvent |The status of a thing is updated. |smarthome/things/{thingUID}/status |
| ThingStatusInfoChangedEvent |The status of a thing changed. |smarthome/things/{thingUID}/statuschanged |
**Note:** The ThingStatusInfoEvent is always sent if the status info of a thing is updated, even if the status did not change.
ThingStatusInfoChangedEvent is sent only if the status of a thing was really changed.
It contains the old and the new status of the thing.
#### Inbox Events
| Event |Description |Topic |
|-----------------------|---------------------------------------------------|-----------------------------------|
| InboxAddedEvent |A discovery result has been added to the inbox. |smarthome/inbox/{thingUID}/added |
| InboxRemovedEvent |A discovery result has been removed from the inbox. |smarthome/inbox/{thingUID}/removed |
| InboxUpdateEvent |A discovery result has been updated in the inbox. |smarthome/inbox/{thingUID}/updated |
#### Link Events
| Event |Description |Topic |
|-----------------------------|---------------------------------------------------------|------------------------------------------------|
| ItemChannelLinkAddedEvent |An item channel link has been added to the registry. |smarthome/links/{itemName}-{channelUID}/added |
| ItemChannelLinkRemovedEvent |An item channel link has been removed from the registry. |smarthome/links/{itemName}-{channelUID}/removed |
#### Channel Events
| Event |Description |Topic |
|-----------------------------|---------------------------------------------------------|------------------------------------------------|
| ChannelTriggeredEvent |A channel has been triggered. |smarthome/channels/{channelUID}/triggered |
## Receive Events
This section describes how to receive openHAB events in Java.
If you want to receive events "outside" openHAB, e.g. with JavaScript, please refer to the [Server Sent Events section](../features/rest.html).
An event subscriber defines the callback interface for receiving events from the openHAB event bus.
The following Java snippet shows how to receive `ItemStateEvent`s and `ItemCommandEvent`s from the event bus.
Therefore, the `EventSubscriber` interface must be implemented.
```java
public class SomeItemEventSubscriber implements EventSubscriber {
private final Set<String> subscribedEventTypes = ImmutableSet.of(ItemStateEvent.TYPE, ItemCommandEvent.TYPE);
private final EventFilter eventFiter = new TopicEventFilter("smarthome/items/ItemX/.*");
@Override
public Set<String> getSubscribedEventTypes() {
return subscribedEventTypes;
}
@Override
public EventFilter getEventFilter() {
return eventFilter;
}
@Override
public void receive(Event event) {
String topic = event.getTopic();
String type = event.getType();
String payload = event.getPayload();
if (event instanceof ItemCommandEvent) {
ItemCommandEvent itemCommandEvent = (ItemCommandEvent) event;
String itemName = itemCommandEvent.getItemName();
Command command = itemCommandEvent.getItemCommand();
// ...
} else if (event instanceof ItemStateEvent) {
ItemStateEvent itemStateEvent = (ItemStateEvent) event;
// ...
}
}
}
```
The `SomeItemEventSubscriber` is subscribed to the event types `ItemStateEvent` and `ItemCommandEvent`, provided by the method `getSubscribedEventTypes()`.
A string representation of an event type can be found by a public member `TYPE` which usually presents the name of the class.
To subscribe to all available event types, use the public member `ALL_EVENT_TYPES` of the event subscriber interface.
The event subscriber provides a `TopicEventFilter` which is a default openHAB `EventFilter` implementation that ensures filtering of events based on a topic.
The argument of the filter is a [Java regular expression](http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html).
The filter method `EventFilter.apply()` will be called for each event on the event bus to which the event subscriber is subscribed (in the example above ItemStateEvent and ItemCommandEvent).
If the filter applies (in the given example for all item events with the item name "ItemX"), the event will be received by the `EventSubscriber.receive()` method.
Received events can be cast to the event implementation class for further processing.
Each event subscriber must be registered via OSGi Declarative Services (DS) under the `org.eclipse.smarthome.event.EventSubscriber` interface.
```xml
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="SomeItemEventSubscriber">
<implementation class="org.eclipse.smarthome.core.items.events.SomeItemEventSubscriber"/>
<service>
<provide interface="org.eclipse.smarthome.core.events.EventSubscriber"/>
</service>
</scr:component>
```
The listing below summarizes some best practices in order to implement event subscribers:
- To subscribe to only one event type openHAB provides the `org.eclipse.smarthome.core.events.AbstractTypedEventSubscriber` implementation.
To receive an already cast event the `receiveTypedEvent(T)` method must be implemented.
To provide an event filter the method `getEventFilter()` can be overridden.
- openHAB provides an `AbstractItemEventSubscriber` class in order to receive ItemStateEvents and ItemCommandEvents (more information can be obtained in the next chapter).
- To filter events based on a topic the `org.eclipse.smarthome.core.events.TopicEventFilter` implementation from the openHAB core bundle can be used.
The filtering is based on [Java regular expression](http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html).
- The subscribed event types and the filter should be stored as class members (see example above) due to performance reasons.
- If the subscribed event types are sufficient in order to receive all interested events, do not return any filter (in that case the method getFilter() returns null) due to performance reasons.
- Avoid the creation of too many event subscribers.
Similar event types can be received in one event subscriber.
- Handle exceptions in event subscriber implementation and throw only serious exceptions.
Thrown exceptions will be handled in the framework by logging an error message with the cause.
- The receive method should terminate quickly, since it blocks other event subscribers.
Create a thread for long running operations.
### Receive ItemStateEvents and ItemCommandEvents
Due to the fact that receiving ItemStateEvents and ItemCommandEvents is a common use case, openHAB provides an abstract event subscriber implementation via the core bundle.
The class `org.eclipse.smarthome.core.items.events.AbstractItemEventSubscriber` provides two methods `receiveUpdate(ItemStateEvent)` and `receiveCommand(ItemCommandEvent)` which can be implemented in order to receive and handle such events.
```java
public class SomeItemEventSubscriber extends AbstractItemEventSubscriber {
private final EventFilter eventFiter = new TopicEventFilter("smarthome/items/ItemX/.*");
@Override
public EventFilter getEventFilter() {
return eventFilter;
}
@Override
protected void receiveCommand(ItemCommandEvent commandEvent) {
// do something
}
@Override
protected void receiveUpdate(ItemStateEvent stateEvent) {
// do something
}
}
```
## Send Events
Usually the core events are only sent by the openHAB framework.
However, it is possible to sent events explicitly, e.g. ItemCommandEvents and ItemStateEvents.
The Java snippet below illustrates how to send events via the EventPublisher.
The openHAB core events can only be created via the corresponding event factory.
```java
public class SomeComponentWantsToPost {
private EventPublisher eventPublisher;
public void postSomething() {
ItemCommandEvent itemCommandEvent = ItemEventFactory.createCommandEvent("ItemX", OnOffType.ON);
eventPublisher.post(itemCommandEvent);
}
protected void setEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
protected void unsetEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = null;
}
}
```
The EventPublisher will be injected via OSGi Declarative Services.
```xml
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="SomeComponentWantsToPost">
<!-- ... -->
<reference bind="setEventPublisher" cardinality="1..1" interface="org.eclipse.smarthome.core.events.EventPublisher"
name="EventPublisher" policy="static" unbind="unsetEventPublisher"/>
</scr:component>
```
## Define new Event Types
openHAB provides the possibility to easily implement new event types and event factories.
Events can be added by implementing the `Event` interface or extending the `AbstractEvent` class which offers a default implementation.
Both classes are located in the openHAB core bundle.
The following Java snippet shows a new event type extending the class `AbstractEvent`.
```java
public class SunriseEvent extends AbstractEvent {
public static final String TYPE = SunriseEvent.class.getSimpleName();
private final SunriseDTO sunriseDTO;
SunriseEvent(String topic, String payload, SunriseDTO sunriseDTO) {
super(topic, payload, null);
this.sunriseDTO = sunriseDTO;
}
@Override
public String getType() {
return TYPE;
}
public SunriseDTO getSunriseDTO() {
return sunriseDTO;
}
@Override
public String toString() {
return "Sunrise at '" + getSunriseDTO.getTime() + "'.";
}
}
```
The listing below summarizes some coding guidelines as illustrated in the example above:
- Events should only be created by event factories.
Constructors do not have any access specifier in order to make the class package private.
- The serialization of the payload into a data transfer object (e.g. `SunriseDTO`) should be part of the event factory and will be assigned to a class member via the constructor.
- A public member `TYPE` represents the event type as string representation and is usually the name of the class.
- The `toString()` method should deliver a meaningful string since it is used for event logging.
- The source of an event can be `null` if not required.
For more information about implementing an event, please refer to the Java documentation.
## Define new Event Factory
Event factories can be added by implementing the `EventFactory` interface or extending the `AbstractEventFactory` class.
The `AbstractEventFactory` provides some useful utility for parameter validation and payload serialization & deserialization with JSON.
The classes are located in the openHAB core bundle.
```java
public class SunEventFactory extends AbstractEventFactory {
private static final String SUNRISE_EVENT_TOPIC = "smarthome/sun/{time}/sunrise";
public SunEventFactory() {
super(Sets.newHashSet(SunriseEvent.TYPE);
}
@Override
protected Event createEventByType(String eventType, String topic, String payload, String source) throws Exception {
if (SunriseEvent.TYPE.equals(eventType)) {
return createSunriseEvent(topic, payload);
}
return null;
}
private Event createSunriseEvent(String topic, String payload) {
SunriseDTO sunriseDTO = deserializePayload(payload, SunriseDTO.class);
return new SunriseEvent(topic, payload, sunriseDTO);
}
public static SunriseEvent createSunriseEvent(Sunrise sunrise) {
String topic = buildTopic(SUNRISE_EVENT_TOPIC, sunrise.getTime());
SunriseDTO sunriseDTO = map(sunrise);
String payload = serializePayload(sunriseDTO);
return new SunriseEvent(topic, payload, sunriseDTO);
}
}
```
The listing below summarizes some guidelines as illustrated in the example above:
- Provide the supported event types (`SunriseEvent.TYPE`) via an `AbstractEventFactory` constructor call.
The supported event types will be returned by the `AbstractEventFactory.getSupportedEventTypes()` method.
- The event factory defines the topic (`SUNRISE_EVENT_TOPIC`) of the supported event types.
Please ensure that the topic format follows the topic structure of the openHAB core events, similar to a REST URI (`{namespace}/{entityType}/{entity}/{sub-entity-1}/.../{sub-entity-n}/{action}`).
The namespace must be `smarthome`.
- Implement the method `createEventByType(String eventType, String topic, String payload, String source)` to create a new event based on the topic and the payload, determined by the event type.
This method will be called by the framework in order to dispatch received events to the corresponding event subscribers.
If the payload is serialized with JSON, the method `deserializePayload(String payload, Class<T> classOfPayload)` can be used to deserialize the payload into a data transfer object.
- Provide a static method to create event instances based on a domain object (Item, Thing, or in the example above `Sunrise`).
This method can be used by components in order to create events based on domain objects which should be sent by the EventPublisher.
If the data transfer object should be serialized into a JSON payload, the method `serializePayload(Object payloadObject)` can be used.

447
developers/utils/i18n.md Normal file
View File

@ -0,0 +1,447 @@
---
layout: developersguide
title: Internationalization
---
{% include base.html %}
# Internationalization
In this chapter the openHAB support for internationalization is described.
All texts can be internationalized by using Java I18N properties files.
For each language a specific file with a postfix notation is used.
The postfix consists of a language code and an optional region code (country code).
```
Format: any_<language-code>_<country-code>.properties
Example: any_de_DE.properties
```
The language code is either two or three lowercase letters that are conform to the ISO 639 standard.
The region code (country code) consists of two uppercase letters that are conform to the ISO 3166 standard or to the UN M.49 standard.
The search order for those files is the following:
```
any_<language-code>_<country-code>.properties
any_<language-code>.properties
any.properties
```
You can find detailed information about Java internationalization support and a list of the ISO 639 and ISO 3166 codes at [The Java Tutorials](http://docs.oracle.com/javase/tutorial/i18n/locale/create.html) page.
The properties files have to be placed in the following directory of the bundle:
```
ESH-INF/i18n
```
Example files:
```
|- ESH-INF
|---- i18n
|------- yahooweather_de.properties
|------- yahooweather_de_DE.properties
|------- yahooweather_fr.properties
```
## Internationalize Binding XML files
In openHAB a binding developer has to provide different XML files.
In these XML files label and description texts must be specified.
To Internationalize these texts Java I18N properties files must be defined.
For the binding definition and the thing types XML files openHAB defines a standard key scheme, that allows to easily reference the XML nodes.
Inside the XML nodes the text must be specified in the default language English.
Typically all texts for a binding are put into one file with name of the binding, but they can also be split into multiple files.
### Binding Definition
The following snippet shows the binding XML file of the Yahoo Weather Binding and its language file that localizes the binding name and description for the German language.
XML file (binding.xml):
```xml
<binding:binding id="yahooweather">
<name>YahooWeather Binding</name>
<description>The Yahoo Weather Binding requests the Yahoo Weather Service
to show the current temperature, humidity and pressure.</description>
<author>Kai Kreuzer</author>
</binding:binding>
```
Language file (yahooweather_de.properties):
```ini
binding.yahooweather.name = Yahoo Wetter Binding
binding.yahooweather.description = Das Yahoo Wetter Binding stellt verschiedene Wetterdaten wie die Temperatur, die Luftfeuchtigkeit und den Luftdruck für konfigurierbare Orte vom yahoo Wetterdienst bereit.
```
So the key for referencing the name of a binding is `binding.<binding-id>.name` and `binding.<binding-id>.description` for the description text.
### Thing Types
The following snippet shows an excerpt of the thing type definition XML file of the Yahoo Weather Binding and its language file that localizes labels and descriptions for the German language.
XML file (thing-types.xml):
```xml
<thing:thing-descriptions bindingId="yahooweather">
<thing-type id="weather">
<label>Weather Information</label>
<description>Provides various weather data from the Yahoo service</description>
<channels>
<channel id="precipitation" typeId="precipitation" />
<channel id="temperature" typeId="temperature" />
<channel id="minTemperature" typeId="temperature">
<label>Min. Temperature</label>
<description>Minimum temperature in degrees celsius (metric) or fahrenheit (imperial).</description>
</channel>
</channels>
<config-description>
<parameter name="location" type="text">
<label>Location</label>
<description>Location for the weather information. Syntax is WOEID, see https://en.wikipedia.org/wiki/WOEID.
</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<channel-type id="precipitation">
<item-type>String</item-type>
<label>Precipitation</label>
<description>Current precipitation (dry, rain, snow).</description>
<state readOnly="true" pattern="%s">
<options>
<option value="dry">dry</option>
<option value="rain">rain</option>
<option value="snow">snow</option>
</options>
</state>
</channel-type>
<channel-type id="temperature">
<item-type>Number</item-type>
<label>Temperature</label>
<description>Current temperature in degrees celsius (metric) or fahrenheit (imperial).</description>
<state readOnly="true" pattern="%d Value" />
<config-description>
<parameter name="unit" type="text" required="true">
<label>Temperature unit</label>
<description>Select the temperature unit.</description>
<options>
<option value="C">Degree Celsius</option>
<option value="F">Degree Fahrenheit</option>
</options>
<default>C</default>
</parameter>
</config-description>
</channel-type>
<channel-type id="cmd-channel">
<item-type>String</item-type>
<label>Device Commands</label>
<description>Send one of the defined command options to the device.</description>
<command>
<options>
<option value="RESET">Reset</option>
<option value="CMD_1">Command 1</option>
<option value="CMD_2">Command 2</option>
</options>
</command>
</channel-type>
</thing:thing-descriptions>
```
Language file (yahooweather_de.properties):
```ini
thing-type.yahooweather.weather.label = Wetterinformation
thing-type.yahooweather.weather.description = Stellt verschiedene Wetterdaten vom Yahoo Wetterdienst bereit.
thing-type.config.yahooweather.weather.location.label = Ort
thing-type.config.yahooweather.weather.location.description = Ort der Wetterinformation. Syntax ist WOEID, siehe https://en.wikipedia.org/wiki/WOEID.
channel-type.yahooweather.precipitation.label = Niederschlag
channel-type.yahooweather.precipitation.description = Aktueller Niederschlag (Trocken, Regen, Schnee).
channel-type.yahooweather.precipitation.state.option.dry = Trocken
channel-type.yahooweather.precipitation.state.option.rain = Regen
channel-type.yahooweather.precipitation.state.option.snow = Schnee
channel-type.yahooweather.temperature.label = Temperatur
channel-type.yahooweather.temperature.description = Aktuelle Temperatur in Grad Celsius (Metrisch) oder Grad Fahrenheit (US).
channel-type.yahooweather.temperature.state.pattern = %d Wert
thing-type.yahooweather.weather.channel.minTemperature.label = Min. Temperatur
thing-type.yahooweather.weather.channel.minTemperature.description = Minimale Temperatur in Grad Celsius (Metrisch) oder Grad Fahrenheit (US).
channel-type.config.yahooweather.temperature.unit.label = Temperatur Einheit
channel-type.config.yahooweather.temperature.unit.description = Auswahl der gewünschten Temperatur Einheit.
channel-type.config.yahooweather.temperature.unit.option.C = Grad Celsius
channel-type.config.yahooweather.temperature.unit.option.F = Grad Fahrenheit
channel-type.yahooweather.cmd-channel.command.option.RESET = Reset Device
channel-type.yahooweather.cmd-channel.command.option.CMD1 = Command one
channel-type.yahooweather.cmd-channel.command.option.CMD2 = Command two
```
So the key for referencing a label of a defined thing type is `thing-type.<binding-id>.<thing-type-id>.label`.
A label of a channel type can be referenced with `channel-type.<binding-id>.<channel-type-id>.label` and a label of a channel definition with `thing-type.<binding-id>.<thing-type-id>.channel.<channel-id>.label`.
And finally the config description parameter key is `thing-type.config.<binding-id>.<thing-type-id>.<parameter-name>.label` or `channel-type.config.<binding-id>.<channel-type-id>.<parameter-name>.label`.
The following snippet shows an excerpt of the thing type definition XML file of the Weather Underground Binding and its language file that localizes labels and descriptions for the French language.
XML file (thing-types.xml):
```xml
<thing:thing-descriptions bindingId="weatherunderground">
<thing-type id="weather">
<label>Weather Information</label>
<description>Provides various weather data from the Weather Underground service</description>
<channel-groups>
<channel-group id="current" typeId="current" />
<channel-group id="forecastTomorrow" typeId="forecast">
<label>Weather Forecast Tomorrow</label>
<description>This is the weather forecast for tomorrow</description>
</channel-group>
<channel-group id="forecastDay2" typeId="forecast">
<label>Weather Forecast Day 2</label>
<description>This is the weather forecast in two days</description>
</channel-group>
</channel-groups>
<config-description>
<parameter name="apikey" type="text" required="true">
<context>password</context>
<label>API Key</label>
<description>API key to access the Weather Underground service</description>
</parameter>
<parameter name="location" type="text" required="true">
<label>Location of Weather Information</label>
<description>Multiple syntaxes are supported. Please read the binding documentation for more information</description>
</parameter>
<parameter name="language" type="text" required="false">
<label>Language</label>
<description>Language to be used by the Weather Underground service</description>
<options>
<option value="EN">English</option>
<option value="FR">French</option>
<option value="DL">German</option>
</options>
</parameter>
<parameter name="refresh" type="integer" min="5" required="false" unit="min">
<label>Refresh interval</label>
<description>Specifies the refresh interval in minutes.</description>
<default>30</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="current">
<label>Current Weather</label>
<description>This is the current weather</description>
<channels>
<channel id="conditions" typeId="currentConditions" />
<channel id="temperature" typeId="temperature" />
</channels>
</channel-group-type>
<channel-group-type id="forecast">
<label>Weather Forecast</label>
<description>This is the weather forecast</description>
<channels>
<channel id="temperature" typeId="temperature">
<label>Temperature</label>
<description>Forecasted temperature</description>
</channel>
<channel id="maxTemperature" typeId="maxTemperature" />
</channels>
</channel-group-type>
<channel-type id="currentConditions">
<item-type>String</item-type>
<label>Current Conditions</label>
<description>Weather current conditions</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="temperature">
<item-type>Number</item-type>
<label>Temperature</label>
<description>Current temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f" />
<config-description>
<parameter name="SourceUnit" type="text" required="true">
<label>Temperature Source Unit</label>
<description>Select the temperature unit provided by the Weather Underground service</description>
<options>
<option value="C">Degree Celsius</option>
<option value="F">Degree Fahrenheit</option>
</options>
<default>C</default>
</parameter>
</config-description>
</channel-type>
<channel-type id="maxTemperature">
<item-type>Number</item-type>
<label>Maximum Temperature</label>
<description>Maximum temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f" />
<config-description>
<parameter name="SourceUnit" type="text" required="true">
<label>Maximum Temperature Source Unit</label>
<description>Select the maximum temperature unit provided by the Weather Underground service</description>
<options>
<option value="C">Degree Celsius</option>
<option value="F">Degree Fahrenheit</option>
</options>
<default>C</default>
</parameter>
</config-description>
</channel-type>
</thing:thing-descriptions>
```
Language file (weatherunderground_fr.properties):
```ini
# binding
binding.weatherunderground.name = Extension WeatherUnderground
binding.weatherunderground.description = L'extension Weather Underground interroge le service Weather Underground pour récupérer des données météo.
# thing types
thing-type.weatherunderground.weather.label = Informations météo
thing-type.weatherunderground.weather.description = Présente diverses données météo fournies par le service Weather Underground.
# thing type configuration
thing-type.config.weatherunderground.weather.apikey.label = Clé d'accès
thing-type.config.weatherunderground.weather.apikey.description = La clé d'accès au service Weather Underground.
thing-type.config.weatherunderground.weather.location.label = Emplacement des données météo
thing-type.config.weatherunderground.weather.location.description = Plusieurs syntaxes sont possibles. Merci de consulter la documentation de l'extension pour plus d'information.
thing-type.config.weatherunderground.weather.language.label = Langue
thing-type.config.weatherunderground.weather.language.description = La langue à utiliser par le service Weather Underground.
thing-type.config.weatherunderground.weather.language.option.EN = Anglais
thing-type.config.weatherunderground.weather.language.option.FR = Français
thing-type.config.weatherunderground.weather.language.option.DL = Allemand
thing-type.config.weatherunderground.weather.refresh.label = Fréquence de rafraîchissement
thing-type.config.weatherunderground.weather.refresh.description = La fréquence de rafraîchissement des données en minutes.
# channel group types
channel-group-type.weatherunderground.current.label = Météo actuelle
channel-group-type.weatherunderground.current.description = La météo actuelle.
channel-group-type.weatherunderground.forecast.label = Météo prévue
channel-group-type.weatherunderground.forecast.description = La météo prévue.
# channel groups
thing-type.weatherunderground.weather.group.forecastTomorrow.label = Météo de demain
thing-type.weatherunderground.weather.group.forecastTomorrow.description = La météo prévue demain.
thing-type.weatherunderground.weather.group.forecastDay2.label = Météo dans 2 jours
thing-type.weatherunderground.weather.group.forecastDay2.description = La météo prévue dans 2 jours.
# channel types
channel-type.weatherunderground.currentConditions.label = Conditions actuelles
channel-type.weatherunderground.currentConditions.description = Les conditions météo actuelles.
channel-type.weatherunderground.temperature.label = Température
channel-type.weatherunderground.temperature.description = La température actuelle.
channel-type.weatherunderground.maxTemperature.label = Température maximale
channel-type.weatherunderground.maxTemperature.description = La température maximale.
# channels inside a channel group type
channel-group-type.weatherunderground.current.channel.temperature.label = Température
channel-group-type.weatherunderground.current.channel.temperature.description = La température prévue.
# channel type configuration
channel-type.config.weatherunderground.temperature.SourceUnit.label = Unité de température
channel-type.config.weatherunderground.temperature.SourceUnit.description = Choix de l'unité de température fournie par le service Weather Underground pour la température actuelle.
channel-type.config.weatherunderground.temperature.SourceUnit.option.C = Degrés Celsius
channel-type.config.weatherunderground.temperature.SourceUnit.option.F = Degrés Fahrenheit
channel-type.config.weatherunderground.maxTemperature.SourceUnit.label = Unité de température maximale
channel-type.config.weatherunderground.maxTemperature.SourceUnit.description = Choix de l'unité de température fournie par le service Weather Undergroundde pour la température maximale.
channel-type.config.weatherunderground.maxTemperature.SourceUnit.option.C = Degrés Celsius
channel-type.config.weatherunderground.maxTemperature.SourceUnit.option.F = Degrés Fahrenheit
```
So the label of a channel group type can be referenced with `channel-group-type.<binding-id>.<channel-group-type-id>.label` and the label of a channel group definition with `thing-type.<binding-id>.<thing-type-id>.group.<channel-group-id>.label`.
A label of a channel definition inside a channel group type can be translated with `channel-group-type.<binding-id>.<channel-group-type-id>.channel.<channel-id>.label`.
### Using custom Keys
In addition to the default keys the developer can also specify custom keys inside the XML file.
But with this approach the XML file cannot longer contain the English texts.
So it is mandatory to define a language file for the English language.
The syntax for custom keys is `@text/<key>`.
The keys are unique across the whole bundle, so a constant can reference any key in all files inside the `ESH-INF/i18n` folder.
The following snippet shows a binding XML that uses custom keys:
XML file (binding.xml):
```xml
<binding:binding id="yahooweather">
<name>@text/bindingName</name>
<description>@text/bindingName</description>
<author>Kai Kreuzer</author>
</binding:binding>
```
Language file (yahooweather_en.properties):
```ini
bindingName = Yahoo Weather Binding
offline.communication-error=The Yahoo Weather API is currently not available.
```
Language file (yahooweather_de.properties):
```ini
bindingName = Yahoo Wetter Binding
offline.communication-error=Die Yahoo Wetter API ist zur Zeit nicht verfügbar.
```
The custom keys are a very good practice to translate bundle dependent error messages.
```java
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "@text/offline.communication-error");
```
## I18n Text Provider API
To programmatically resolve texts for certain languages openHAB provides the OSGi service `TranslationProvider`.
The service parses every file inside the `ESH-INF/i18n` folder and caches all texts.
A localized text can be retrieved via the method `getText(Bundle bundle, String key, String default, Locale locale)` (or via the method `getText(Bundle bundle, String key, String default, Locale locale, Object... arguments)` if additional arguments are to be injected into the localized text), where bundle must be the reference to the bundle, in which the file is stored.
The BundleContext from the Activator provides a method to get the bundle.
```java
String text = i18nProvider.getText(bundleContext.getBundle(), "my.key", "DefaultValue", Locale.GERMAN);
```
## Locale Provider
To programmatically fetch the locale used by the openHAB system an OSGi service `LocaleProvider` is offered.
The service contains a `getLocale()` method that can be used to choose a configurable locale.
## Getting Thing Types and Binding Definitions in different languages
Thing types can be retrieved through the `ThingTypeRegistry` OSGi service.
Every method takes a `Locale` as last argument.
If no locale is specified the thing types are returned for the default locale which is determined by using the `LocaleProvider`, or the default text, which is specified in the XML file, if no language file for the default locale exists.
The following snippet shows how to retrieve the list of Thing Types for the German locale:
```java
List<ThingType> thingTypes = thingTypeRegistry.getThingTypes(Locale.GERMAN);
```
If one binding supports the German language and another does not, it might occur that the languages of the returned thing types are mixed.
For Binding Info and ConfigDescription, the localized objects can be retrieved via the `BindingInfoRegistry` and the `ConfigDescriptionRegistry` in the same manner as described for Thing Types.

116
developers/utils/tools.md Normal file
View File

@ -0,0 +1,116 @@
---
layout: developersguide
title: Framework Tools
---
# Framework Tools
Find a few framework utilities listed in this chapter.
# Unique Instance ID
When communicating with external systems, it is often desirable to have a unique identifier.
The `org.eclipse.smarthome.core.id` bundle is a mean to generate such an id, which is automatically persisted.
The persistence is done in the configured `userdata` directory as a file called `uuid`.
The id is provided through a static method and can be retrieved through
```java
String uuid = InstanceUUID.get();
```
# Network Address Service
The `NetworkAddressService` is an OSGi service that can be used like any other OSGi service by adding a service reference to it.
Its OSGi service name is `org.eclipse.smarthome.network`.
A user can configure his default network address via Paper UI under `Configuration -> System -> Network Settings`.
One can obtain the configured address via the `getPrimaryIpv4HostAddress()` method on the service.
This service is useful for example in the `ThingHandlerFactory` or an `AudioSink` where one needs a specific IP address of the host system to provide something like a `callback` URL.
Some static methods like `getAllBroadcastAddresses()` for retrieving all interface broadcast addresses or `getInterfaceAddresses()` for retrieving all assigned interface addresses might be usefull as well for discovery services.
## Network Address Change Listener
The `NetworkAddressChangeListener` is a consumer type OSGi service interface.
If listeners want to be notified about network interface address changes, they can implement `NetworkAddressChangeListener` and register as an OSGi service.
Please be aware that not all network interface changes are notified to the listeners, only "useful" network interfaces :--
When a network interface status changes from "up" to "down", it is considered as "removed".
When a "loopback" or "down" interface is added, the listeners are not notified.
# Caching
The framework provides some caching solutions for common scenarios.
A common usage case is in a `ThingHandler` to encapsulate one value of an internal state and attach an expire time on that value.
A cache action will be called to refresh the value if it is expired.
This is what `ExpiringCache` implements.
If `handleCommand(ChannelUID channelUID, Command command)` is called with the "RefreshType" command, you just return `cache.getValue()`.
It is a good practice to return as fast as possible from the `handleCommand(ChannelUID channelUID, Command command)` method to not block callers especially UIs.
Use this type of cache only, if your refresh action is a quick to compute, blocking operation.
If you deal with network calls, consider the asynchronously reloading cache implementation instead.
## Expiring and asynchronously reloading cache
If we refreshed a value of the internal state in a `ThingHandler` just recently, we can return it immediately via the usual `updateState(channel, state)` method in response to a "RefreshType" command.
If the state is too old, we need to fetch it first and this may involve network calls, interprocess operations or anything else that will would block for a considerable amout of time.
A common usage case of the `ExpiringCacheAsync` cache type is in a `ThingHandler` to encapsulate one value of an internal state and attach an expire time on that value.
A **handleCommand** implementation with the interesting *RefreshType* could look like this:
```java
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
switch (channelUID.getId()) {
case CHANNEL_1:
cache1.getValue(updater).thenAccept(value -> updateState(CHANNEL_1, value));
break;
...
}
}
}
```
The interesting part is the `updater`.
If the value is not yet expired, the returned CompletableFuture will complete immediately and the given code is executed.
If the value is expired, the updater will be used to request a refreshed value.
An updater can be any class or lambda that implements the funtional interface of `Supplier<CompletableFuture<VALUE_TYPE>>`.
In the following example the method `CompletableFuture<VALUE_TYPE> get()` is accordingly implemented.
The example assumes that we deal
with a still very common callback based device refreshing method `doSuperImportantAsyncStuffHereToGetRefreshedValue(listener)`.
The listener is the class
itself, which implements `DeviceStateUpdateListener`.
We will be called back with a refreshed device state in `asyncCallbackFromDeviceStateRefresh`
and mark the Future as *complete*.
```java
class FetchValueFromDevice implements Supplier<CompletableFuture<double>>, DeviceStateUpdateListener {
CompletableFuture<double> c;
@Override
CompletableFuture<double> get() {
if (c != null) {
c = new CompletableFuture<double>();
doSuperImportantAsyncStuffHereToGetRefreshedValue( (DeviceStateUpdateListener)this );
}
return c;
}
// Here you process the callback from your device refresh method
@Override
void asyncCallbackFromDeviceStateRefresh(double newValue) {
// Notify the future that we have something
if (c != null) {
c.complete(newValue);
c = null;
}
}
}
```
If you deal with a newer implementation with a CompletableFuture support, it is even easier.
You would just return your CompletableFuture.