WetterfĂĽhlig mit Eclipse SmartHome

Seite 2: Einrichtung

Inhaltsverzeichnis

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 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:

  • ESH-INF/
    • binding/binding.xml – Metadaten des Binding: Name, Autor, Beschreibung
    • thing/thing-types.xml – Definition von Things und Channels
    • i18n – Internationalisierung der Channelbeschreibungen und ähnliche Infosrmationen
  • src/.../:
    • NetatmoBindingConstants.java – Konstanten, die unter anderem den Namen des Binding und der Channels enthalten. Stellt Verbindung zwischen dem Java-Code und thing-types.xml her
    • handler/NetatmoHandler.java – der ThingHandler des Binding
    • internal/NetatmoHandlerFactory.java – Factory fĂĽr den ThingHandler
  • META-INF/MANIFEST.MF – OSGi-Manifest
  • OSGI-INF/NetatmoHandlerFactory.xml – Registrierung der NetatmoHandlerFactory als OSGi-Komponente
  • target/ – Artefakte des Builds
  • build.properties – Einstellungen fĂĽr den Build (vom Maven-Tycho-Plug-in verwendet)
  • pom.xml – Maven Project Model

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