Note: If you haven't already done so, reading the Hobson Home page will provide helpful context.
Being an automation hub, it is Hobson's job to expose disparate types of smart devices (e.g. lights, thermostats, etc.) in a consistent way. Hobson is designed around a completely modular architecture that allows third-parties to easily create plugins to integrate with new types of hardware and sources of data.
The Hobson Hub is comprised of multiple plugins, each of which performs a discrete piece of functionality. Here's an example of some of the plugins you might find installed in a Hub:
Note that plugins are used not only for device control (e.g. Foscam Camera, Philips Hue) but for core Hub capabilities such as scheduling and the web management console.
At it's simplest level, creating a Hobson Hub plugin involves creating a HobsonPlugin implementation that creates one or more HobsonDeviceProxy implementations. Each HobsonDeviceProxy is responsible for monitoring and/or controlling an external entity – a light switch, a weather API, etc. Everything is packaged as a Hobson Plugin Bundle which can then be installed on and used by the Hobson Hub.
Note: There should only ever be one HobsonPlugin implementation per plugin bundle.
Plugin/Device Class Hierarchy
Both HobsonPlugin and HobsonDeviceProxy are interfaces that can, in theory, be implemented directly. However, the Hobson API provides two abstract classes that make the developer's job quite a bit easier and it's recommended to use them as a starting point.
Note that the diagram above shows the MyHobsonPlugin creating 0 or more MyHobsonDevice instances. It is not mandatory for a plugin to create devices (see the next section for an example).
Variables provide a way to obtain and/or change the state of an entity. Variables are appropriate when:
- The state can be represented as a simple primitive value such as boolean, string, etc.
- A change to the state can be applied very quickly (let's say less than a second).
Each HobsonPlugin can publish zero or more global variables that aren't specific to a particular device. For example, the OpenWeatherMap plugin publishes a global variable that represents the current external temperature. Another example of a global variable is whether the Hub is in "vacation mode".
Each HobsonDeviceProxy can publish zero or more device variables that provide the capability to obtain the state of and/or exert control of a device. For example, a light will usually publish an "on" variable that provides state information (is the light on?) when it is read and provides control when it is set (turn the light off).
There are three types of variables that can be published:
- Read-write - provides both state and control
- Read-only - provides state only
- Write-only - provides control only
The presence of variables, through a documented naming convention, indicates the capabilities of a device. For example, the presence of a read-write "on" variable means that a device is switchable. This information is used, among other things, to drive user interfaces.
Actions provide a mechanism for more complex control of plugins and devices. Actions are appropriate when:
- A change of state or control request cannot be applied very quickly or requires a way to obtain current status of the change.
- There is more than one parameter needed for the change of state or control request.
- It is desirable to allow Hobson to use the change of state or control request in the context of user-defined tasks.
Both HobsonPlugin and HobsonDeviceProxy classes can publish actions. Plugin actions are appropriate for actions that are not device-specific and Device actions are appropriate for actions that are.
The Hobson Hub is built on the OSGi framework which in turn runs on a Java Virtual Machine (JVM). However, as a plugin developer, the details of OSGi are hidden by several manager interfaces as shown below:
These manager interfaces (action, device, disco, etc.) hide the OSGi-specific details from your HobsonPlugin. This separation makes it much easier to test the core Hobson code and simplifies dependencies for HobsonPlugin implementations.
The Plugin Lifecycle
As mentioned above, Hobson plugins are deployed to the Hobson Hub as OSGi bundles. A bundle is simply a Java JAR file that includes some additional, OSGi-specific metadata.
Here is an overview of a typical plugin bundle lifecycle:
- Upon bundle activation, the bundle meta-data is used to determine which subclass of HobsonPlugin to instantiate. Again, there should only be one HobsonPlugin implementation per plugin bundle.
- The HobsonPlugin implementation is instantiated. Note that very little if anything should be done in the HobsonPlugin implementation's constructor as dependency injection has not yet taken place.
- The OSGi runtime dependency injects the manager implementations (e.g. config, device, disco, etc.) that the HobsonPlugin will use.
- The HobsonPlugin onStartup() method is called and passed the current plugin configuration. The plugin can then begin initializing using the provided configuration information and manager implementations.
- Once the HobsonPlugin is initialized, it will likely want to instantiate one or more HobsonDeviceProxy subclasses and publish them to the runtime.
- Each created HobsonDeviceProxy will publish zero or more DeviceProxyVariable objects to the runtime. As indicated above, variables provide a mechanism to monitor and/or control the state of a device.
- If a plugin indicates it has a refresh interval, its onRefresh() method will be periodically invoked at the indicated interval. This allows a plugin to poll external devices (if a poll mechanism is necessary) as well as perform housekeeping tasks.
- When a HobsonDeviceProxy detects that the external entity it is monitoring (e.g. a light) has changed state (e.g. the light turned off), the HobsonDevice should fire a DeviceVariablesUpdateEvent event to let all interested parties know that something has changed. The setVariableValue() method in AbstractHobsonDeviceProxy is an easy way to do this.
- When an incoming device state change request is received (e.g. a user turns on a light through the web console or mobile app), the HobsonDeviceProxy will receive a callback via its onSetVariable() method. The HobsonDeviceProxy should attempt to alter the state of the external device it controls. When the HobsonDevice determines the state has successfully changed (e.g. the light has successfully turned on), it should fire a DeviceVariablesUpdateEvent event (this can often be taken care of by step 6 if implemented as such).
- When the plugin is stopped (either by a Hub shutdown or explicitly via the API), the HobsonPlugin onShutdown() method is called which gives it the opportunity to clean up. This should include stopping all of its published devices.