openHAB - From Vision to Architecture
In our previous post, an introduction was given on how openHAB operates and came to be. In this post, we will dive more into the openHABs distribution. At the end of this post, you should have a good understanding of the system and its architectural elements and relationships.
Table of Contents
- Main Architectural Style
- Containers View
- Components View
- Connectors View
- Development View
- Run-time View
- Realizing Key Qualities
- API Design Principles
Main Architectural Style
The standard on which openHAB is being developed is called OSGi. OSGi stands for Open Services Gateway initiative and it is an organization that has defined the OSGi standard. The OSGi specification describes a dynamic component system for Java, in which small modules can be loaded and shared dynamically to create larger applications.
As you can see in Figure 11, OSGi is made up of multiple layers. The gray layers constitute the system on which everything runs. The purple layers are defined within the OSGi standard and together form the OSGi container. The red security layer is also part of the OSGi standard, but is an optional layer and is thus not always available in OSGi implementations.
Bundles are the smallest unit of modularization in OSGi. Bundles are separate pieces of functionality that, when combined, can form an application. They have their own lifecycle and can be dynamically loaded and unloaded by the OSGi container.
The entire openHAB system is implemented as separate bundles. These bundles allow for a very flexible and extensible architecture. It is also perfect for open-source development since its architecture is easy to understand, and a developer-only needs to know a small part of the system. Add-ons, including bindings, are also implemented as bundles. They reside in a separate repository.
A few examples of bundles are:
- org.openhab.core.persistence - a bundle which allows settings to be stored persistently.
- org.openhab.core.model.script - runs automations scripts.
- org.openhab.binding.pioneeravr - provides support for controlling Pioneer AVRs.
- org.openhab.binding.samsungtv - provides support for controlling Samsung TVs.
These bundles can register Services, which can provide functionality and communication between bundles.
To run the bundles for the application you need an OSGi implementation called a container. The container can provide hardware support and other functionality. The most popular open-source containers are:
- Equinox - the reference implementation of OSGi, developed by the Eclipse Foundation.
- Apache Felix - developed by the Apache Software Foundation.
- Apache Karaf - a distribution based on Apache Felix. Also developed by the Apache Software Foundation.
- Concierge - specifically aimed at embedded devices
openHAB uses Equinox for the runtime and Karaf specifically for runtime dependency installation.
The idea of the containers view comes from the C4 model created by Simon Brown, where a system’s context (level 1), containers (level 2), components (level 3), and code (level 4) are visualized in abstract diagrams. In this section we explore the openHAB’s containers and in the next section we dig deeper into openHAB’s components. We refer our readers to our previous essay to learn more about openHAB’s context.
A container represents an application or data store and is an essential element to deploy the system, as is mentioned on the C4 model website2. They are static structures of a software system. An OSGi container can also be viewed as a container in the C4 model. openHAB’s OSGi containers Equinox and Apache Karaf are needed to run the bundles making up openHAB’s system. Without these containers the main system could not run (so yes essential they are). There are also other containers which represent web, mobile and desktop applications to enable user interaction with the main system. However, we are mainly interested in openHAB’s core system and therefore the Equinox container.
- Main system
- Apache Karaf
- Android mobile app
- iOS mobile app
- Windows desktop app
- UI Web app
A container is made up of several components. Each component is defined as “a grouping of related functionality encapsulated behind a well-defined interface”2. We previously described how the openHAB system is made up of many bundles within the OSGi standard. Such a bundle can also be viewed as a component within the C4 model. They are units of code, which together can form an application and can be reused. Note that the terminology can be quite confusing, as OSGi alliance also uses the term “component” to describe Java classes within a bundle3.
There are many bundles within the Equinox container, which can be grouped within core and add-ons bundles. We choose to highlight some of them in the tables below. The others are: audio, config, ephemeris, id, karaf, semantics, storage, test, thing, transform, and UI. Note that for instance, nine bundles concern configuration, thirty concern IO services, and just one concerns add-ons. Also there is one bundle for every binding, and there are approximately 300 bindings.
|Add-on||Implementation of an Add-on Service used as a dummy service for testing functionality|
|Authentication||Secures access to openHAB|
|Automation||Allows to use for instance triggers to automate rules|
|Binding||Baseline for binding implementations that connect to external services or devices|
|IO service||Displays openHAB internals via a defined interface (e.g. multicast DNS, Swagger)|
|Persistence service||Allows items to be stored and retrieved over time|
|Model||Representations for items, rules, things, etc.|
|Bindings||HP printer, Chromecast, Somfy|
|System integrations||HomeKit, openHAB Cloud Connector|
|Voice||Google Cloud Text-to-Speech|
The main connections between the different components is based on the internal OSGi communication. Bundles register as OSGi service, which can be seen as separate Java classes. Using OSGi services, communication between bundles is rather easy. OSGi Services allow to subscribe and publish data to other services, this inter-process communication can be implemented after they have been initialized. The figure underneath visualizes the relations between the OSGi services in the OSGi Framework and the other connectors.
Besides the internal communication, openHAB’s bundles has to communicate to devices, this is mostly implemented using either APIs, TCP connection, or the MQTT protocol. These external communication protocols between Things and the binding is initialized in the binding and varies for each manufacturer and/or binding.
At last, for saving the different configurations and other data, openHAB creates JsonDB files. These files contain the full configurations and history data of the openHAB instance and can be transferred between different openHAB instances.
The openHAB distribution is almost fully written in Java and consists of a set of OSGi services and components. The system decomposition consists of the openHAB core components, Add-ons and the OSGi Framework, as can see in the figure below4.
OSGi Framework All components of openHAB come as a binding to the OSGi framework. This framework can access every other service within the openHAB runtime5. The OSGi runtime serves as the message bus where others services can access data stored from other services. openHAB uses the reference implementation Equinox. Equinox gives a set of bundles for the OSGi framework6:
|Logback / SLF4||General purpose message logger for the OSGi environment.|
|Declarative Services||Simplifies the task of authoring and only allows service components to be active when needed.|
|Event Admin||Provides inter-bundle communication mechanism based on an event publish and subscribe model|
|Configuration Admin||Configuration admin service|
|HTTP Service||Provides embedded HTTP server provided by Jetty|
openHAB Core On top of the OSGi framework stands the core of openHAB. The build infrastructure is Maven and contains the core bundles for the openHAB runtime.
openHAB Add-ons The add-ons are implemented on top of the openHAB Core APIs. The bindings, automation logic, UI and Item Provider also fall into this category. Users can add and use these add-ons, depending on their preference.
Most add-ons for the openHAB distribution are first developed as a standalone, not part of the openHAB repository. After an add-on has been approved, it will be maintained and become adapted to the new core API.
openHAB uses Equinox as its OSGi container. For run-time dependency installation, Karaf is used. This means that at startup the core bundle is loaded, which triggers the loading of all dependent bundles. At some point the configuration bundle reads the systems configuration using the persistence bundle, this allows the system to know which add-ons are also installed by the user and it starts loading those into the Equinox container using Karaf.
When a user selects a new add-on to be installed during run-time, the system downloads the bundle from the main repository and loads it into the container. Equinox is then responsible for the bundle’s lifecycle. This lifecycle has the following states:
- INSTALLED: The bundle is installed in the container, but not all of its dependencies are resolved.
- RESOLVED: The bundle is installed and all of the dependencies are resolved and connected to it.
- STARTING: A state where the dependencies are resolved and the bundle is able to set itself up.
- ACTIVE: The bundle has set itself up and is running.
- STOPPING: The bundle is trying to gracefully stop execution.
- UNINSTALLED: The bundle has been removed from the container.
Realizing Key Qualities
As mentioned in the previous post, one of the key features of openHAB is the ability to integrate different technologies into the openHAB system. The OSGi framework plays a large role in enabling this possibility. Bundles can easily be added to this framework, which could be a binding to an external system of some sort.
As openHAB is meant for use by anyone, the system is fully built with Java and therefore only depends on a Virtual Machine (VM). These VMs are available on most machines, increasing the avail ability to the users. To accomplish usability, the system must be easy to setup and easy to work with.
API Design Principles
As openHAB is designed as a bus, API accessibility is an important feature. Using the API, external programs can access most of the openHAB features. All data related to Bidings, Things, and Items can be read or changed using the API. The API is based on the HTTP protocol and is implemented as an add-on. The standard REST HTTP protocol is used for openHAB’s API. REST is a well-known subset of the HTTP protocol and is used for most interactive web services.
To explore and validate the REST API calls, openHAB implemented a REST API Explorer in the main UI, based on Swagger7, an open-source toolset for designing and documenting APIs.
When looking at API design principles for REST API design8, we can see a few principles directly implemented by openHAB. Starting with the standard accept and response in JSON format, both the
accept headers default to
Application/json. The following principle uses the different HTTP request methods and uses nouns rather than verbs in endpoint paths. This is also the case for openHAB, looking at the API Explorer. Furthermore, openHAB is nesting resources for hierarchical objects. Every entry point in openHAB gets a unique UID, which can be traced back to the API. The next principle openHAB is applying is the standard error HTTP status codes and returns valuable information in case of an exception. Lastly, openHAB allows for filtering, sorting, and pagination using standards on some API functions9.
The REST API can be protected with authentication. openHAB enables both Basic and OAuth authentication mechanisms, but the usage of external API tokens is often recommended and can be easily implemented in the main UI.
Source: https://docs.osgi.org/download/r7/osgi.cmpn-7.0.0.pdf (page 320) ↩︎