Aktuelle Wetterdaten über Wunderground Service abrufen und in der Homematic CCU speichern – Homematic-Script

Update 30.07.2018: Wunderground vergibt inzwischen keine kostenlosen API-Keys mehr.

To improve our services and enhance our relationship with our users, we will no longer provide free weather API keys as part of our program. If you have been directed to download our Weather Underground free API key by a third party provider, please contact your vendor for resolution

Um immer Zugriff auf aktuelle Wetterdaten in der Homematic CCU zu haben, wird nicht unbedingt die entsprechende Hardware zur Messung benötigt. Man kann auch einfach über einen Wetterdienst die benötigten Daten aus dem Internet laden. Eine schöne API für diesen Zweck bietet der Dienst Wunderground.

Um die Abfrage der Wetterdaten zu automatisieren, habe ich ein kleines Homematic-Script entwickelt, welches sich die Daten über den angeboteten HTTP-Webservice herunterlädt und in eigene Systemvariablen auf der Homematic CCU abspeichert. Somit können diese Werte dann für unterschiedliche Aktionen weiterverarbeitet werden.

Installation

Das Script kann direkt in einem Programm eingebunden werden, welches z. B. alle 30 Minuten automatisch ausgeführt wird. Die Systemvariablen müssen nicht manuell erzeugt werden, dies erledigt das Homematic-Script automatisiert.

Voraussetzung

Neben der Homematic CCU, muss auf dieser der CUx-Daemon installiert und eingerichtet sein.
Zusätzlich wird zur Abfrage über den Wunderground HTTP-Webservice ein individueller API-Key benötigt, welcher kostenlos nach einer Registrierung über die Wunderground Webseite erstellt werden kann.

Konfiguration

Folgende Einstellungen müssen bei den entsprechenden Variablen individuell im Script vorgenommen werden, damit dies funktioniert:

CUXD_ID Seriennummer des CUx-Daemon Kanals
API_KEY Wunderground API Key
API_QUERY Ort für dem die Wetterdaten geladen werden sollen

Homematic-Script

Das aktuelle Script kann über GitHub heruntergeladen werden

Service – Weather Data – Wunderground auf GitHub

Code-Snippet: Homematic Systemvariablen über ein Script automatisch erzeugen

Viele Homematic Scripte greifen auf Systemvariablen zu, um einen Status auzulesen, Werte abzufragen oder zu hinterlegen. Um diese nicht manuell anlegen zu müssen, besteht die Möglichkeit im Script zu prüfen, ob eine Variable existiert. Falls nicht, kann diese dann direkt automatisch angelegt werden.

Leider ist auch hierzu die API Dokumentation nicht vollständig. Daher habe ich die möglichen Werte und Methoden (soweit möglich) mit aufgelistet.

Typen und Werte

ValueType

  • ivtBinary (2)
  • ivtFloat (4)
  • ivtInteger (16)
  • ivtString (20)

ValueSubType

  • istPresent (23) – Anwesenheit
  • istAlarm (6) – Alarm
  • istGeneric (0) – Zahl
  • istBool (2) – Logikwert
  • istEnum (29) – Werteliste
  • istChar8859 (11) – Zeichenkette

Beispiele – Systemvariable erstellen

Zeichenkette

string  svName = "testVarPresent";
object  svObj  = dom.GetObject(svName);

if (!svObj){   
    object svObjects = dom.GetObject(ID_SYSTEM_VARIABLES);

    svObj = dom.CreateObject(OT_VARDP);

    svObjects.Add(svObj.ID());

    svObj.Name(svName);   
    svObj.ValueType(ivtString);
    svObj.ValueSubType(istChar8859);
	
    svObj.DPInfo("Variable string for test");
    svObj.ValueUnit("");
    svObj.State("some text");
    svObj.Internal(false);
    svObj.Visible(true);

    dom.RTUpdate(false);
}

WriteLine(svObj.Value().ToString());

Zahl

string  svName = "testVarNumber";
object  svObj  = dom.GetObject(svName);

if (!svObj){   
    object svObjects = dom.GetObject(ID_SYSTEM_VARIABLES);

    svObj = dom.CreateObject(OT_VARDP);

    svObjects.Add(svObj.ID());

    svObj.Name(svName);   
    svObj.ValueType(ivtFloat);
    svObj.ValueSubType(istGeneric);
	
    svObj.DPInfo("Variable number for test");
    svObj.ValueUnit("°C");
    svObj.ValueMin(-100);
    svObj.ValueMax(100);
    svObj.State(15);
    svObj.Internal(false);
    svObj.Visible(true);

    dom.RTUpdate(false);
}

WriteLine(svObj.Value().ToString());

Alarm

string  svName = "testVarAlarm";
object  svObj  = dom.GetObject(svName);

if (!svObj){   
    object svObjects = dom.GetObject(ID_SYSTEM_VARIABLES);

    svObj = dom.CreateObject(OT_VARDP);

    svObjects.Add(svObj.ID());

    svObj.Name(svName);   
    svObj.ValueType(ivtBinary);
    svObj.ValueSubType(istAlarm);
    svObj.ValueName0("nicht ausgelöst");
    svObj.ValueName1("ausgelöst");    
    !svObj.AlType(atSystem);
    !svObj.AlArm(true);

    svObj.ValueUnit("");   

    dom.RTUpdate(false);
}

WriteLine(svObj.State().ToString());
WriteLine(svObj.ValueName());

! Alarm auslösen
svObj.State(true);
svObj.DPInfo("Alarm - aktiv");

WriteLine(svObj.State().ToString());
WriteLine(svObj.ValueName());

Logikwert

string  svName = "testVarBool";
object  svObj  = dom.GetObject(svName);

if (!svObj){   
    object svObjects = dom.GetObject(ID_SYSTEM_VARIABLES);

    svObj = dom.CreateObject(OT_VARDP);

    svObjects.Add(svObj.ID());

    svObj.Name(svName);   
    svObj.ValueType(ivtBinary);
    svObj.ValueSubType(istBool);
    svObj.ValueName0("nicht anwesend");
    svObj.ValueName1("anwesend");    
    svObj.State(true);

    svObj.DPInfo("Logikwert test");
    svObj.ValueUnit("");

    dom.RTUpdate(false);
}

WriteLine(svObj.State().ToString());
WriteLine(svObj.ValueName());

Werteliste

string  svName = "testVarEnum";
object  svObj  = dom.GetObject(svName);

if (!svObj){   
    object svObjects = dom.GetObject(ID_SYSTEM_VARIABLES);

    svObj = dom.CreateObject(OT_VARDP);

    svObjects.Add(svObj.ID());

    svObj.Name(svName);   
    svObj.ValueType(ivtInteger);
    svObj.ValueSubType(istEnum);
    svObj.ValueList("val1;val2;val3");
    svObj.State(1);

    svObj.DPInfo("Werteliste test");

    dom.RTUpdate(false);
}

var valList = svObj.ValueList();
var val = valList .StrValueByIndex(";",svObj.Value());
WriteLine(val);

Systemvariable löschen

Muss aus irgendeinem Grund eine Systemvariable gelöscht werden, kann dies auch direkt über Homematic-Script erledigt werden.

string  svName = "testVarAlarm";
object  svObj  = dom.GetObject(svName);

WriteLine(svObj);
dom.DeleteObject(svObj.ID());
WriteLine(svObj);

Code-Snippet: Alle Systemvariablen und Werte auslesen und auflisten

Um über Homematic-Script alle vorhandenen Systemvariablen und deren Werte aus der Homematic Zentrale auszulesen, kann die Konstante „ID_SYSTEM_VARIABLES“ verwendet werden.

Hier ein kleines Beispiel Script, welches über alle Systemvariablen iteriert um diese aufzulisten.

string svListStr = "";
string vid; 
var svIDs = dom.GetObject(ID_SYSTEM_VARIABLES).EnumIDs();

foreach(vid, svIDs){
    var sysVar = dom.GetObject(vid);
    svListStr = svListStr # "Name: " # sysVar.Name() # " Value: " +  sysVar.Value() # "\n";
}

WriteLine(svListStr);

Bluetooth Adapter (Dongle) am Raspberry Pi einrichten und Verbindung (Pairing) herstellen

Um auch über den Raspberry eine Bluetooth-Verbindung herstellen zu können, habe ich mir einen einfachen USB Bluetooth® 4.0 USB Mini Adapter von CSL besorgt. Die Installation und Einrichtung unter Raspbian ist relativ einfach und mit folgenden Schritten durchzuführen.

1. Installation der notwendigen Pakete

Bluez ist der offizielle Linux-Bluetooth-Protokollstack. Diese Pakete sind Open-Source und werden aktuell unter der GNU General Public License (GPL) verteilt.

sudo apt-get install bluetooth bluez-utils 

Optional können noch folgende Pakete installiert werden

sudo apt-get install blueman bluez-alsa

blueman: Blueman ist eine GTK+-Bluetooth-Verwaltung mit Bluez-D-Bus-Backend.
bluez-alsa: Dieses Paket enthält einen Treiber für die Anwendung der ALSA-Softwaresammlung.

Hinweis: Raspbian – Jessie

Hier steht das Paket „bluez-utils“ nicht mehr zur Verfügung. Es wird empfohlen das Paket „bluez“ zu installieren.

sudo apt-get install bluez

Zu beachten ist, dass dann auch die Befehle „bluez-simple-agent“ oder „bluez-test-input“ nicht mehr funktionierten.
Anstelle dieser Befehle kann dann die „bluetoothctl“-Anwendung verwendet werden.
Diese kann mit folgenden Befehl gestartet werden.

sudo bluetoothctl

2. Bluetooth Service prüfen

Es muss der entsprechende Service gestartet sein. Dies kann folgendermaßen geprüft werden

sudo service bluetooth status 

Sollte der Dienst nicht laufen, muss dieser gestartet werden.

sudo service bluetooth start

3. Benutzer der Gruppe „bluetooth“ hinzufügen

Damit ein Benutzer die Bluetooth-Funktionen benutzen kann, muss dieser in der entsprechenden Gruppe aufgenommen werden.

sudo gpasswd -a pi bluetooth 

(Benutzername „pi“ muss natürlich ggf. angepasst werden.)

Welche Benutzer sich in der Gruppe „bluetooth“ befinden, kann mittels folgenden Befehl geprüft werden.

cat /etc/group | grep bluetooth

4. Prüfen ob Bluetooth-Gerät erkannt wurde

Um sicherzustellen, dass das Bluetooth-Gerät erfolgreich erkannt wurde, sollte dies geprüft werden.

lsusb | grep -i bluetooth

Als Ausgabe sollte der Name des Gerätes erscheinen.

Bus 001 Device 004: ID 0b13:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)

Die Installation des Dongles ist dann hiermit abgeschlossen und es kann mit der Einrichtung der Bluetooth-Verbindung begonnen werden.

5. Bluetooth-Geräte suchen und Verbindung prüfen

Um alle Bluetooth-Geräte die sich in Reichweite befinden zu suchen, muss ein Scan durchgeführt werden.

sudo hcitool scan

Als Ergebnis sollten die Mac-Adressen der Bluetooth-Geräte aufgelistet werden.

Scanning ...
      AB:12:CD:45:EF:67       BTS-06

Soll nach Bluetooth Low Energy (LE) kurz BLE Geräten gesucht werden, gibt es hierfür einen eigenen Befehl

sudo hcitool lescan

Alternative unter „bluetoothctl“

„bluetoothctl“-Anwenung mit folgendem Befehl starten

sudo bluetoothctl

und nach Geräte scannen

scan on

Um zu prüfen, ob die Verbindung zu einem Gerät problemlos funktioniert, kann dies durch einen Ping an die Mac-Adresse sichergestellt werden.

sudo l2ping -c 1 AB:12:CD:45:EF:67

Ausgabe:

Ping: AB:12:CD:45:EF:67 from 00:1A:33:BC:31:1C (data size 44) ...
      0 bytes from AB:12:CD:45:EF:67 id 0 time 10.79ms
      1 sent, 1 received, 0% loss

6. Paarung der Geräte (Pairing)

Um das Pairing für die beiden Bluetooth-Geräte durchzuführen, wird neben der MAC-Adresse auch noch die Bluetooth-Adapter-Nummer (z. B. hci0) benötigt.

Diese kann wie folgt ausgelesen werden

hciconfig

Bevor die Paarung durchgeführt werden kann, muss sich das entsprechende Geräte im „Paarungsmodus“ bzw. „Pairing-Mode“ befinden.

Dann kann die Paarung wie folgt durchgeführt werden

bluez-simple-agent hci0 AB:12:CD:45:EF:67

Hier muss natürlich die entsprechende Mac-Adresse und Bluetooth-Adapter-Nummer angepasst werden.

Ist zur Herstellung eine Pin-Nummer notwendig, werden Sie aufgefordert diese einzugeben. Oft wird hier einfach „0000“ verwendet.

Sobald eine Aufforderung zur Bestätigung für den Passkey erscheint, muss „yes“ eingegeben und mit Enter bestätigt werden.

Hinweis: Dieser Schritt muss nur einmalig durchgeführt werden.

Alternative unter „bluetoothctl“

pair AB:12:CD:45:EF:67

Zur Prüfung, ob die Paarung erfolgreich durchgeführt wurde, kann dies durch die Auflistung der gepaarten Geräte getan werden.

bluez-test-device list

Alternative unter „bluetoothctl“

info AB:12:CD:45:EF:67

Fehlerbehebung

Bei diesem Schritt kommt es leider öfters zu Problemen. Im vielen Fällen sind diese Fehler aber einfach zu beheben.

Fehler:
Creating device failed: org.bluez.Error.ConnectionAttemptFailed: Page Timeout
Lösung:
Prüfen ob sich das Gerät auch wirklich im Pairing-Mode befindet und einfach noch mal versuchen.

Fehler:
Creating device failed: org.bluez.Error.AuthenticationRejected: Authentication Rejected
Lösung:
Wenn dieser Fehler auftritt, kann es an einem kleinen Fehler (Bug) im bluez-simple-agent Script liegen. Hier muss eine Anpassung vorgenommen werden, damit eine Ja/Nein Abfrage auf der Kommandozeile ausgegeben wird.

Vorab sollte sichergestellt werden, dass die falsche Einstellung „KeyboardDisplay“ vorliegt.

grep KeyboardDisplay /usr/bin/bluez-simple-agent 

Die Ausgabe sollte capability = „KeyboardDisplay“ sein.

Das Python-Script kann dann mit folgenden Befehlen angepasst werden.

sudo perl -i -pe 's/KeyboardDisplay/DisplayYesNo/' /usr/bin/bluez-simple-agent

Jetzt kann der vorherige Befehl zur Prüfung nochmals ausgeführt werden.
Dieser sollte jetzt capability = „DisplayYesNo“ ausgeben.

Die Schritt zur Paarung können jetzt erneut (erfolgreich) ausgeführt werden.

7. Verbindung herstellen

Je nachdem um welche Art von Gerät es sich handelt, muss eine Verbindung mit unterschiedlichen Befehlen durchgeführt werden.

Tastatur:

bluez-test-input connect AB:12:CD:45:EF:67

Lautsprecher:

bluez-test-audio connect AB:12:CD:45:EF:67

Hinweis: Es sollten ein paar Töne ausgegeben werden, wenn die Verbindung hergestellt wird.

Alternative unter „bluetoothctl“

connect AB:12:CD:45:EF:67

8. Gerät als „vertrauenswürdig“ (trusted device) einstellen

Um die Verbindung zu diesem Gerät nicht jedes Mal manuell durchführen zu müssen (z. B. nach einem Neustart), kann das Gerät als „vertrauenswürdig“ eingestellt werden.

bluez-test-device trusted AB:12:CD:45:EF:67 yes

Alternative unter „bluetoothctl“

trust AB:12:CD:45:EF:67

Zur Abfrage des „Trusted“ Status kann folgender Befehl verwendet werden.

bluez-test-device trusted AB:12:CD:45:EF:67

9. Gerät trennen und entfernen (unpair)

Die Trennung eines Gerätes kann mit diesem Befehl durchgeführt werden.

bluez-test-device disconnect AB:12:CD:45:EF:67

Alternative unter „bluetoothctl“

disconnect AB:12:CD:45:EF:67

Soll das Gerät komplett entfernt werden, kann folgender Befehl verwendet werden.

bluez-test-device remove AB:12:CD:45:EF:67

Manchmal kann es dann vorkommen, dass ein erneute Paarung fehlschlägt. Eventuell liegt es daran, dass sich im Ordner /var/lib/bluetooth noch eine Datei befindet, die genau so benannt ist wie die Mac-Adresse.

Diese muss dann manuell entfernt werden.

sudo rm -f /var/lib/bluetooth/AB:12:CD:45:EF:67

Alternative unter „bluetoothctl“

remove AB:12:CD:45:EF:67

Dynamische Sprachausgabe mittels Pi-To-Speech Service – Mein Raspberry Pi lernt sprechen (mit Hilfe von Google TTS und Pico TTS)

Begriffe wie Smart Home, Hausautomatisierung und Internet of Things (IoT) tauchen mittlerweile überall auf und man kommt eigentlich nicht mehr an diesen Themen vorbei. Im Rahmen der Hausautomatisierung habe ich nach einer unabhängigen Lösung gesucht, um eine dynamische Sprachausgabe zu realsieren. Diese soll unabhängig von der eingesetzten Hausautomatisierungsanlage wie z.B. Homematic, KNX o. ä. lauffähig sein.

Lösungsansatz

Ein möglicher Lösungsansatz war relativ schnell gefunden. Da bereits ein Raspberry Pi für andere Aufgaben läuft, sollte dieser auch diese Aufgabe übernehmen.

Voraussetzungen

  • Raspberry Pi (mit z. B. Raspbian)
  • HTTP Webserver (z. B. Lighttpd und PHP Suport)
  • Programmiersprachen: Python & PHP

Realisierung

1. Text-to-Speech Dienst auswählen

Um über den Raspberry Pi eine dynamische Sprachausgabe zu ermöglichen, muss dieser um eine Text-to-Speech (TTS) Funktionalität erweitert werden. Es gibt einige Anwendungen und Online-Services die dafür verwendet werden können. Diese unterscheiden sich aber oft deutlich in der Qualität der Aussprache. Aus diesem Grund ist meine Wahl auf folgende zwei TTS-Services gefallen

Google Text-to-Speech (Online Dienst)
Der Online TTS Dienst des Google Übersetzer ist vielen bekannt. Dieser bietet die Möglichkeit, einen eingegeben Text vorzulesen. Zusätzlich funktioniert es auch, die Sprachausgabe über eine einfache HTTP-Anfrage (GET-Request) anzufordern. Es wird dann eine MP3 Datei zurückgeliefert, welche die Sprachausgabe enthält.

SVOX Pico TTS (Offline Anwendung)
Weitere Informationen zu diesem Sprachsynthesizer findet Ihr in einem anderen Blog-Eintrag von mir.
Text mit SVOX Pico TTS direkt von der Kommandozeile oder aus einer Datei durch Sprachausgabe wiedergeben

2. Pi-To-Speech Service (Python-Script)

Um die Erzeugung der Audio-Datei für die Sprachausgabe zu automatisieren, habe ich ein kleines Phython-Script entwickelt, welches aktuell die genannten TTS-Dienste unterstützt.

Den Sourcecode für das Python-Script gibt es auf GitHub:
pi-to-speech-service.py

Cache
Das Script bietet die Möglichkeit die erzeugte Audiodatei lokal zu speichern. Wird erneut der gleiche Text angefragt, muss dann die Audiodatei nicht neu erzeugt werden. Hier wird dann einfach die zuvor gespeicherte Datei verwendet. Dies bietet neben der Performance-Optimierung auch den Vorteil, dass nicht unnötig der einer Text-to-Speech Services angefragt werden muss.

Google Limitierung
Bei der Verwendung des Google Text-to-Speech Dienstes ist die Limitierung auf max. 100 Zeichen pro Anfrage. Im Script wurde das Problem gelöst, indem ein längerer Text in jeweils 100 Zeichen aufgeteilt wird und mehrere Anfragen erfolgen.

Script ausführen
Um über das Pyhton-Script den Text „Hallo“ in eine Sprachausgabe umzuwandeln, kann dies wie folgt ausgeführt werden:

 python tts-service.py --text "Hallo" --provider "google" 

Hinweis: Weitere mögliche Parameter und deren Beschreibungen findet man im GitHub Repository.

3. Webservice für Zugriff im Netzwerk

Um das Script auch von „außerhalb“ der Kommandozeile aufrufen zu können, habe ich zusätzlich ein kleines PHP-Script erstellt. Da auf meinem Raspberry Pi bereits ein HTTP-Webserver (Lighttpd) mit PHP Unterstützung läuft, konnte ich das PHP-Script einfach darüber bereitstellen und innerhalb des lokalen Netzwerkes aufrufen.

Den Sourcecode für das PHP-Script gibt es auf GitHub:
tts-service.php

Benutzerberechtigung
Zu beachten sind hierbei die Benutzerberechtigungen. Diese Einstellungen können natürlich je nach Konfiguration, Pfad, Benutzernamen etc. abweichen und müssen individuell angepasst (auch im PHP-Script) werden.

Möglickeit 1:
Mein Benutzer für den Webserver hat keine Berechtigung um das Pyhton-Script auszuführen.
Hierfür wurde ein eigener Benutzer „tts“ erstellt. Zusätzlich muss dann in der Datei „/etc/sudoers“ eine Anpassung für die Berechtigung vorgenommen werden.

 www-data ALL=(tts) NOPASSWD: /opt/tts/pi-to-speech-service.py 

Möglickeit 2:
Will man keinen Benutzer anlegen, dann kann auch für Python das Set User ID (SetUID) Bit gesetzt werden.

sudo chmod u+s /usr/bin/python

Dann ist es nicht mehr notwendig im PHP-Script das Python-Script mittels ’sudo -u‘ zu starten.
Diese kleine Anpassung muss dann im PHP-Script noch vorgenommen werden.

Sicherheit
Alle internen Webservices befinden sich im Ordner „internal“. Da solche Services nicht von überall aufgerufen werden dürfen, habe ich zusätzlich die Lighttpd Konfigurationsdatei „/etc/lighttpd/lighttpd.conf“ erweitert. Folgende Bedingung prüft, ob es sich beim Aufrufer um ein Gerät im internen Netzwerk handelt, ansonsten wird der Zugriff verweigert.

$HTTP["remoteip"] !~ "192\.168\.1\.*" {
  $HTTP["url"] =~ "^/(internal)/" {
    url.access-deny = ( "" )
  }
}

Script ausführen
Ist alles korrekt eingerichtet, kann das PHP-Script folgendermaßen aufgerufen werden.

http://<SERVER-IP>/internal/tts.php?text=hallo&provider=google

Hierbei können auch alle durch das Pyhton-Script unterstützte Parameter verwendet werden.

Längere Texte
Da es für die URL eine maximale Länge gibt, besteht für längere Texte die Möglichkeit, diesen über den Body als POST-Request zu senden.