zurück zum Artikel

Wetterfühlig mit Eclipse SmartHome

Moritz Kammerer
Wetterfühlig mit Eclipse SmartHome

Wer beliebige Geräte im Smart Home einbinden möchte, findet mit Eclipse SmartHome ein passendes Framework. Das soll die Erstellung eines Plug-ins beziehungsweise Binding für die Netatmo-Wetterstation zeigen.

Eclipse SmartHome (ESH [1]) ist ein Framework für das vernetzte Zuhause, das sogenannte Smart Home. Wie der Name bereits vermuten lässt, wird das Framework von der Eclipse Foundation gesponsort und weiterentwickelt.

Ziel des Frameworks ist es, Geräte verschiedener Hersteller miteinander zu verbinden und eine einheitliche Schnittstelle zu diesen zu erschaffen, über die Anwender die Geräte steuern können. Sie können auch Automatisierungsregeln definieren wie folgende: Wenn sich der Anwender mit seinem Handy dem Haus nähert, geht automatisch das Licht an.

Dieser Artikel beschreibt die Entwicklung eines Binding für Eclipse SmartHome, das die Netatmo-Wetterstation integriert. Die Station kann die aktuelle Raumtemperatur, Luftfeuchte, den CO2-Gehalt und weitere Informationen auslesen.

Um das Binding in Eclipse SmartHome auszuführen, wird eine Laufzeitumgebung benötigt. Eine solche stellt openHAB 2 bereit. Der komplette Quelltext des Binding befindet sich auf GitHub [2].

Ein Binding ist für das Einbinden von Geräten in Eclipse SmartHome zuständig. Ein Gerät wird dabei als "Thing" bezeichnet und jedes Binding kann mehrere davon anbinden. Jedes Thing besteht aus mehreren "Channels", die die Funktionen des Geräts repräsentieren. Das Thing der Netatmo-Wetterstation verwendet drei Channels: einen für die aktuelle Temperatur, einen für die Luftfeuchtigkeit und einen für den CO2-Gehalt der Luft. Jeder Channel verwendet einen bestimmten Typ. Im Falle der Wetterstation sind es jeweils Zahlen, aber es gibt andere Typen wie Schalter, Dimmer und Farben (siehe Eclipse SmartHome Dokumentation [3]).

Die SmartHome-Benutzeroberfläche kann zu jedem Channel mehr Kontext, etwa ein passendes Icon, bereitstellen. Damit das funktioniert, muss der Entwickler des Binding dem Channel eine Kategorie zuordnen. Channels können les- und schreibbar oder nur lesbar sein. Bei der Netatmo-Wetterstation lassen sie sich nur lesen, da die Wetterstation nicht über eine Steuerungsfunktion verfügt. Bei einem Thermostat ist dagegen denkbar, dass ein schreibbarer Channel existiert, der die Zieltemperatur im Raum regelt. Um ein Thing zu verwalten, wird ein ThingHandler implementiert, der sich um das Aktualisieren der Channels kümmert und auf Kommandos reagiert. Abbildung 1 zeigt die Bestandteile eines Binding in einer Übersicht.

Architektur eines Binding mit Thing, ThingHandler und Channels (Abb. 1)

Architektur eines Binding mit Thing, ThingHandler und Channels (Abb. 1)

Java und das darauf aufsetzende OSGi-System bilden die Basis von Eclipe SmartHome. Jedes Binding ist ein eigenständiges OSGi-Bundle. Die gleichnamige Java-IDE des Eclipse-Projekts eignet sich hervorragend, um ein Binding zu entwickeln. In der Entwicklerdokumentation von Eclipse SmartHome [4] wird beschrieben, wie Eclipse für die Entwicklung eines Binding einzurichten ist.

Nachdem Entwickler die in der Dokumentation aufgeführten Schritte ausgeführt haben, befindet sich unter $INSTALLDIR/git/smarthome das aktuelle Eclipse SmartHome Framework. Zuerst müssen sie darin mvn clean install ausführen, was ein installiertes Maven voraussetzt. Dieser Schritt ist wichtig, da er unter anderem die später verwendete Benutzeroberfläche des Eclipse SmartHome baut. Der Set-up-Prozess legt einen Eclipse Workspace unter $INSTALL_DIR/ws an, der einfach in Eclipse geöffnet werden kann.

Im Ordner extensions/binding liegt ein Skript namens create_binding_skeleton (Endung .bat bzw. .sh), das Boilerplate-Code für ein Binding generiert. Entwickler müssen es mit dem Namen des Binding und des Autors aufrufen, also beispielsweise create_binding_skeleton.cmd Netatmo "Moritz Kammerer". Das Skript erzeugt darauf zwei neue Ordner mit dem Quelltext- beziehungsweise dem Testfallgerüst des Binding. Im Quelltext-Ordner generiert es folgende Dateien und Unterordner:

Das neu angelegte Binding lässt sich nun in Eclipse importieren.

Für den Zugriff auf die Wetterstation müssen Entwickler den Netatmo-Webservice in der Cloud nutzen, denn die Daten der Station sind über das lokale Netzwerk nicht verfügbar. Der Webservice ist mit OAuth 2 vor unberechtigten Zugriffen geschützt. Zum Abruf der Daten müssen sich Entwickler zunächst bei Netatmo registrieren [5] und eine neue App anlegen. Jede App besitzt eine Client ID und ein Client Secret. Beide werden später für die Erstellung eines OAuth-2-Tokens benötigt.

Da verschiedene Stellen des Binding die Verbindung zum Netatmo-Webservice erfordern, bietet sich die Verwendung des Singleton-Entwurfsmusters an. Der Webservice wird in der Klasse NetatmoWebservice im Package org.eclipse.smarthome.binding.netatmo implementiert, die sich mithilfe des Apache HttpClients mit dem Webservice verbindet und sich ein OAuth-2-Token ausstellen lässt. Um den Apache HttpClient und den JSON-Deserialisierer verwenden zu können, ist die Erweiterung der Import-Package-Klausel im OSGi-Manifest (MANIFEST.MF) um folgende Pakete nötig:

com.google.gson,
com.google.gson.annotations,
org.apache.http,
org.apache.http.conn,
org.apache.http.conn.ssl,
org.apache.http.impl.conn,
org.apache.http.conn.scheme,
org.apache.http.client,
org.apache.http.client.entity,
org.apache.http.client.methods,
org.apache.http.impl.client,
org.apache.http.message, org.apache.http.util

Folgendes Listing zeigt die Authentifizierung am Netatmo-Webservice:

public class NetatmoWebservice {
public static final NetatmoWebservice INSTANCE =
new NetatmoWebservice();
  private static final String CLIENT_ID = "...";
private static final String CLIENT_SECRET = "...";
private static final String USERNAME = "...";
private static final String PASSWORD = "...";
  private static final String SCOPES = "read_station";
private static final String REQUEST_TOKEN_URL =
"https://api.netatmo.net/oauth2/token";
private static final String STATION_DATA_URL =
"https://api.netatmo.net/api/getstationsdata";
  private final Logger logger =
LoggerFactory.getLogger(NetatmoWebservice.class);
private final CloseableHttpClient client;
private final Gson gson;
  private OAuthToken token;
  private NetatmoWebservice() {
this.client = HttpClients.createDefault();
this.gson = new Gson();
}
  public void authenticate() throws IOException {
HttpPost request = new HttpPost(REQUEST_TOKEN_URL);
List<NameValuePair> formData = new ArrayList<>();
formData.add(new BasicNameValuePair("grant_type",
"password"));
formData.add(new BasicNameValuePair("client_id",
CLIENT_ID));
formData.add(new BasicNameValuePair("client_secret",
CLIENT_SECRET));
formData.add(new BasicNameValuePair("username",
USERNAME));
formData.add(new BasicNameValuePair("password",
PASSWORD));
formData.add(new BasicNameValuePair("scope", SCOPES));
    request.setEntity(new UrlEncodedFormEntity(formData));
    try (CloseableHttpResponse response =
client.execute(request))
{
checkStatus(response);
String json =
EntityUtils.toString(response.getEntity());
this.token = gson.fromJson(json, OAuthToken.class);
}
}
  public StationData fetchStationData() throws IOException {
HttpGet request = new HttpGet(STATION_DATA_URL);
request.setHeader("Authorization",
"Bearer " + token.getAccessToken());
    try (CloseableHttpResponse response =
client.execute(request))
{
checkStatus(response);
String json =
EntityUtils.toString(response.getEntity());
return gson.fromJson(json, StationData.class);
}
}
}

Der Konstruktor der Klasse erstellt einen HttpClient und einen JSON-Deserialisierer. Die Methode authenticate fordert ein OAuth-2-Token an und speichert es in der Instanzvariable token. Dazu ist die Client ID und das Client Secret der angelegten App sowie der Benutzername und das Passwort des Netatmo-Accounts erforderlich. Mit diesem Token lassen sich anschließend Anfragen an den Netatmo-Webservice stellen, um beispielsweise alle Wetterstationen und deren Werte zu lesen, wie in der Methode fetchStationData implementiert.

Eclipse SmartHome hat das Konzept einer "Inbox": Dort befinden sich Things, die das Framework gefunden, aber noch nicht gekoppelt hat. Ein sogenannter "Discovery Service" füllt die Inbox. Wie die Dienste technisch die Things finden, ist im dazugehörigen Binding geregelt. Das Hue-Binding von Eclipse SmartHome verwendet UPnP (Universal Plug and Play)zum Auffinden der Hue-Lampen im Netzwerk. Das zu entwickelnde Netatmo-Binding verwendet die gerade geschaffene Klasse NetatmoWebservice, um sich vom Netatmo-Webservice die verfügbaren Wetterstationen zu holen. Zur Implementierung eines Discovery-Services müssen Entwickler eine Klasse erstellen, die das Interface DiscoveryService von Eclipse SmartHome implementiert. Im Netatmo-Binding erbt die Klasse NetatmoDiscoveryService im Package org.eclipse.smarthome.binding.netatmo.discovery von der abstrakten Basisklasse AbstractDiscoveryService aus dem Eclipse SmartHome Framework und implementiert dadurch das geforderte Interface. Der Konstruktor der Basisklasse erwartet zwei Argumente: Eine Liste der Things, die der Discovery Service finden kann, und einen Timeout in Sekunden, nachdem das Discovery spätestens beendet ist. Entwickler müssen die abstrakte Methode startScan() implementieren. Das Framework ruft sie jedes Mal auf, wenn der Benutzer über die Oberfläche nach neuen Things sucht.

StationData weatherStations =
NetatmoWebservice.INSTANCE.fetchStationData();
for (Device device :
weatherStations.getBody().getDevices()) {
ThingUID uid = new ThingUID(NetatmoBindingConstants
.THING_TYPE_WEATHER_STATION,
cleanId(device.getId()));
  DiscoveryResult discovery = DiscoveryResultBuilder
.create(uid)
.withLabel(device.getName())
.withProperty("id", device.getId())
.build();
  thingDiscovered(discovery);
}


Zum Auflisten der Wetterstationen kommt der NetatmoWebservice zum Einsatz. Für jede gefundene Wetterstation wird eine ThingUID erstellt. Dabei handelt es sich um eine eindeutige Identifikation eines Thing bestehend aus dessen Typbezeichnung und ID. Da die IDs vom Netatmo-Webservice einen Doppelpunkt enthalten, der aber in einer ThingUID nicht erlaubt ist, ersetzt die Methode cleanId die Doppelpunkte durch Bindestriche.

Die Klasse DiscoveryResultBuilder erstellt ein DiscoveryResult für die ThingUID. Außerdem setzt sie das Thing-Label, das der Benutzer in der Inbox sieht, und speichert die ID der Wetterstation als Property in DiscoveryResult. Zu guter Letzt übergibt die Basisklasse das erstellte DiscoveryResult per thingDiscovered() an die Inbox. Damit die Discovery funktioniert, muss der entsprechende Service noch beim Framework registriert werden. Dazu wird im Ordner OSGI-INF eine neue Datei namens NetatmoDiscovery.xml angelegt. Durch diese Datei registriert das OSGi-System die Klasse NetatmoDiscoveryService als Discovery Service:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component
xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
immediate="true"
modified="modified"
name=
"de.qaware.esh.netatmo.discovery.NetatmoDiscoveryService">
<implementation class=
"de.qaware.esh.netatmo.discovery.NetatmoDiscoveryService"/>
<service>
<provide interface=
"org.eclipse.smarthome.config.discovery.DiscoveryService"/>
</service>
</scr:component>

Nun können Entwickler das Binding das erste Mal ausprobieren. Dazu öffnen sie in Eclipse das Run-Menü, klicken auf Run Configurations und wählen die Konfiguration SmartHome Runtime aus. Unter dem Reiter Plug-ins muss für das Netatmo-Binding das Start Level auf 0 und Auto-Start auf true stehen. Nun lässt sich Eclipse SmartHome mit dem Binding über die Schaltfläche Run starten. Die Web-Benutzeroberfläche ist anschließend unter http://localhost:8080/ui/index.html erreichbar. Dort können Anwender im Bereich Inbox eine Discovery starten und die Netatmo-Wetterstationen finden. Um dessen Daten anzuzeigen, ist das Anlegen eines Thing und seiner Channels erforderlich.

Ein Thing besteht aus den folgenden drei Dateien:

Das Skript hat diese Dateien bereits angelegt. Entwickler definieren zu Beginn die Channels in der Datei ESH-INF/thing/thing-types.xml. Die Definition steht im Element <channels>. Jeder Channel hat einen Typ (<thing-type>-Element).

Die folgende XML-Datei zeigt die angepasste Definition des Thing. Sie verwendet nur die drei Channels temperature, humidity und co2. Die Definition des jeweiligen Channeltyps erfolgt darunter. Im Beispiel hat temperature den Typ Number und die Kategorie Temperature. Die Formatierung erfolgt mit einer Nachkommastelle und in Grad Celsius. Die Definition der anderen beiden Channeltypen erfolgt auf die gleiche Weise.

<thing-type id="weatherstation">
<label>Netatmo Weather Station</label>
<description>The Netatmo Weatherstation.</description>
  <channels>
<channel id="temperature" typeId="temperature"/>
<channel id="humidity" typeId="humidity"/>
<channel id="co2" typeId="co2"/>
</channels>
</thing-type>
<channel-type id="temperature">
<item-type>Number</item-type>
<label>Temperature</label>
<description>The current temperature.</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f C" />
</channel-type>
<channel-type id="humidity">
<item-type>Number</item-type>
<label>Humidity</label>
<description>The current humidity.</description>
<category>Humidity</category>
<state readOnly="true" pattern="%.1f %%" />
</channel-type>
<channel-type id="co2">
<item-type>Number</item-type>
<label>CO2</label>
<description>The current CO2 saturation.</description>
<category>CarbonDioxide</category>
<state readOnly="true" pattern="%d ppm" />
</channel-type>

Die Datei NetatmoBindingConstants verlangt noch nach einer Anpassung: Die Verknüpfung von XML und Java erfolgt über Strings, die den gleichen Wert haben müssen und die in der Klasse NetamoBindingConstants definiert sind:

public class NetatmoBindingConstants {
public final static String CHANNEL_TEMPERATURE
= "temperature";
public final static String CHANNEL_HUMIDITY
= "humidity";
public final static String CHANNEL_CO2
= "co2";
}

Wenn der Anwender in der Inbox bei einer Wetterstation auf den Approve-Button klickt, wird in der Klasse NetatmoHandlerFactory die Methode supportsThingType mit dem Wetterstationtypen aufgerufen. Wenn die Methode true zurückgibt, erstellt die Methode createHandler den Thing-Handler. Das Tool hat den Code bereits generiert. Für die Wetterstation wird eine Instanz der Klasse NetatmoHandler erstellt.

Diese erbt von BaseThingHandler und enthält zwei Methoden: initialize() wird von Eclipse SmartHome nach dem Erstellen des Handlers aufgerufen. Der Aufruf von handleCommand() erfolgt, wenn der Benutzer den Wert eines Channels ändert. Das Wetterstation-Thing verwendet Letzteres nicht, da es nur lesbare Channels enthält. Nur Things, die schreibbare Channels enthalten, müssen die Methode implementieren.

@Override
public void initialize() {
scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
refresh();
}
}, 0, 10, TimeUnit.MINUTES);
}

Der Eclipse SmartHome Scheduler führt den Code alle zehn Minuten aus

Die initialize()-Methode erstellt über den ESH-Scheduler einen Task, dessen Ausführung alle zehn Minuten erfolgt und der die folgende refresh()-Methode aufruft:

private void refresh() {
String id = getThing().getProperties().get("id");
  StationData data = NetatmoWebservice.INSTANCE
.fetchStationData();
for (Device device : data.getBody().getDevices()) {
if (device.getId().equals(id)) {
DashboardData dashboard = device.getDashboardData();
      updateState(NetatmoBindingConstants.
CHANNEL_TEMPERATURE,
new DecimalType(dashboard
.getTemperature()));
updateState(NetatmoBindingConstants.CHANNEL_HUMIDITY,
new DecimalType(dashboard.getHumidity()));
updateState(NetatmoBindingConstants.CHANNEL_CO2,
new DecimalType(dashboard.getCo2()));
return;
}
}
}

In der refresh-Methode erfolgt die Abfrage der aktuellen Daten der Wetterstation über den Netatmo-Webservice. Da er die Daten aller Wetterstationen liefert, muss das Programm die richtige Wetterstation herausfiltern. Im Discovery Service wurde die ID der Wetterstation, die zum Thing gehört, in der Eigenschaft id gespeichert. Diese lässt sich im ThingHandler mit der Methode getThing().getProperties().get("id") auslesen.

Nach dem Filtern der Webservice-Daten auf die richtige Wetterstation lassen sich mit der von der Basisklasse geerbten Methode updateState die neuen Werte der Channels setzen.

Die neuste Version von openHAB 2 ist direkt aus dem Jenkins verfügbar [6]. Die Property ui in conf/services/addons.cfg muss den Wert paper haben, damit die Benutzeroberfläche aktiviert ist. Nun können Nutzer openHAB 2 über start.bat (bzw. .sh) starten und das Binding per mvn clean install bauen. Die dabei erzeugte JAR-Datei müssen sie anschließend in den Ordner addons von openHAB 2 kopieren. Die Benutzeroberfläche von openHAB 2 ist unter http://localhost:8080/ui/index.html zu erreichen.

Die Integration eines neuen Geräts in Eclipse SmartHome ist nicht mit viel Aufwand verbunden. Durch die Abstraktionsschicht der Things und Channels lassen sich die Funktionen der unterschiedlichen Geräte auf eine einheitliche Schnittstelle abbilden. Durch die aktive Open-Source-Entwicklung unter der Eclipse Foundation hat es gute Chancen, das führende Framework im Bereich Smart Home zu werden. Entwickler können die stabilen und ausgereiften Technologien Java und OSGi verwenden und müssen keine exotischen neuen lernen.

OpenHAB 2 ist eine Smart-Home-Plattform auf Basis des Eclipse SmartHome Frameworks. Durch die Plattformunabhängigkeit von Java lässt sich openHAB 2 auf verschiedenen Plattformen wie einem Raspberry Pi ausführen und bietet damit einen kostengünstigen Einstieg in das vernetzte Zuhause.

Moritz Kammerer [7]
ist Softwareentwickler bei der QAware GmbH. Er hat bereits diverse Bindings für Eclipse SmartHome entwickelt, an Eclipse SmartHome mitgearbeitet und interessiert sich von Anfang an für das Internet der Dinge. Nebenbei ist er Autor diverser Open-Source-Projekte.


(rme [8])


URL dieses Artikels:
https://www.heise.de/-3346356

Links in diesem Artikel:
[1] https://eclipse.org/smarthome/documentation/index.html
[2] https://github.com/qaware/netatmo-esh
[3] http://www.eclipse.org/smarthome/documentation/development/bindings/xml-reference.html
[4] https://eclipse.org/smarthome/documentation/development/ide.html
[5] https://dev.netatmo.com/
[6] https://openhab.ci.cloudbees.com/job/openHAB-Distribution
[7] mailto:moritz.kammerer@qaware.de
[8] mailto:rme@ix.de