Speedtest in OpenHAB

Heute habe ich mich auf die Suche gemachte wie ich einen automatischen Speedtest in mein OpenHAB integrieren kann. Auf der Suche bin ich hier über den Beitrag in der OpenHAB Community gestoßen.
Es funktionierte auch alles so weit, nur der gemessene Upload war sehr langsam. Nach weiterem Suchen bin ich darauf gestoßen dass es möglicherweise an der Art wie ich speedtest-cli installiert habe. Nämlich über apt-get. Nun habe ich das python Script wie hier beschrieben geladen und ausführbar gemacht.

wget https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py
chmod +x speedtest.py

Danach das Script einmal testen

speedtest.py 

Nun muss man sich einen Server aus der Liste aussuchen weil die Auswertung im folgenden auf Grund unterschiedlicher Server-Namen nicht funktioniert

speedtest.py --list | grep "Germany"

Aus der Liste sucht man sich einen Server aus. Ich nehme einaml 4997

speedtest.py --server 4997
Retrieving speedtest.net configuration...
Testing from Vodafone Kabel Deutschland (XXX.XXX.XXX.XXX)...
Retrieving speedtest.net server list...
Retrieving information for the selected server...
Hosted by inexio (Saarlouis) [XX.XX km]: 51.842 ms
Testing download speed................................................................................
Download: 154.99 Mbit/s
Testing upload speed................................................................................................
Upload: 37.84 Mbit/s

Abhängig vom Server-Namen muss man dann den Split für den Ping machen und auch die richtigen Zeilen wählen (Siehe hier Code-Zeile 25,26 und 27). Bei mir ist es der 6. Teil der Textzeile 4 für den Ping sowie die Zeilen 6 bzw. 8 für Download bzw. Upload (Man beginnt beim Zählen jeweils mit 0). Ansonsten muss man nur noch in Zeile 23 das „Ping“ durch „Retrieving“ ersetzen.

rule "Speedtest"
when
    //Time cron "0 0 5,13 * * ?" or
    Time cron "0 0 * * * ?" or
    Item SpeedtestRerun received command ON
then
    logInfo("Speedtest", "--> speedtest executed...")
    SpeedtestRunning.postUpdate("Messung läuft...")

    // update timestamp for last execution
    SpeedtestResultDate.postUpdate(new DateTimeType())

    // execute the script, you may have to change the path depending on your system
    var String speedtestCliOutput = executeCommandLine("/usr/local/bin/speedtest.py --server 4997", 120*1000) //Inexio
    //var String speedtestCliOutput = executeCommandLine("/usr/local/bin/speedtest.py --server 1746", 120*1000)	//Vodafone

    // for debugging:
    //var String speedtestCliOutput = "Ping: 43.32 ms\nDownload: 21.64 Mbit/s\nUpload: 4.27 Mbit/s"
    //logInfo("Speedtest", "--> speedtest output:\n" + speedtestCliOutput + "\n\n")

    SpeedtestRunning.postUpdate("Datenauswertung...")

    // starts off with a fairly simple error check, should be enough to catch all problems I can think of
    if (speedtestCliOutput.startsWith("Retrieving") && speedtestCliOutput.endsWith("Mbit/s")) {
        var String[] results = speedtestCliOutput.split("\\r?\\n")
        var float ping = new java.lang.Float(results.get(4).split(" ").get(6)) //Inexio
        // var float ping = new java.lang.Float(results.get(4).split(" ").get(7)) //Vodafone
        var float down = new java.lang.Float(results.get(6).split(" ").get(1))
        var float up   = new java.lang.Float(results.get(8).split(" ").get(1))
        SpeedtestResultPing.postUpdate(ping)
        SpeedtestResultDown.postUpdate(down)
        SpeedtestResultUp.postUpdate(up)
        SpeedtestSummary.postUpdate(String::format("ᐁ%.1f  ᐃ%.1fMbit/s | %.0f ms", down, up, ping))
        SpeedtestRunning.postUpdate("-")
        logInfo("Speedtest", "--> speedtest finished.")
    } else {
        SpeedtestResultPing.postUpdate(0)
        SpeedtestResultDown.postUpdate(0)
        SpeedtestResultUp.postUpdate(0)
        SpeedtestSummary.postUpdate("(unbekannt)")
        SpeedtestRunning.postUpdate("Fehler bei der Ausführung")
        logError("Speedtest", "--> speedtest failed. Output:\n" + speedtestCliOutput + "\n\n")
    }
    SpeedtestRerun.postUpdate(OFF)
end

Umzug auf OpenHAB 2.5 und RasPi 4

Nach langer Zeit der Funkstille hier ein paar kurze Infos zu meinem Umzug auf OH 2.5 und den neuen PI. Den RasPi 2B wollte ich schon seit längerem ablösen da er doch häufig zu langsam ist und selbst die simple OpenHAB Basic UI lange zum Laden braucht.
Der Umstieg hat auch sehr gut funktioniert. Ich habe den neuen PI neu installiert und mich dabei hieran gehalten. Nach der Installation von OH habe ich das das Backup-Script von OH genutzt um alles wieder auf dem bisherigen Stand zu haben. So weit so gut.
Anschließend wollte ich mein Backup wieder einrichten und plötzlich waren die Backups 3 mal so groß wie vorher. OK. Ich habe auch eine größere SD-Karte verbaut aber eigentlich ist da ja nicht mehr drauf?
Nur war die SD Karte vorher schonmal in Betrieb und da ich nicht richtig (alles mit 0en überschreiben) formatiert hatte führte das ganze dennoch zu einem großen Backup da dd ja direkt auf der Karte liest ohne das Dateisystem („Inhaltsverzeichnis“) zu beachten. Die Lösung war die Karte einmal mit 0en zu radieren und das dann wieder zu löschen

dd if=/dev/zero of=/home/pi/delete_me
rm -f /home/pi/delete_me

nginx / FRITZ!Box / IPv6

Ich wollte mir nun noch nginx als reverse proxy installieren um den Port 80 auf dem RaspPi direkt zu OpenHAB durchzuleiten. Gesagt getan und mit der IP-Adresse (IPv4) ging das auch sehr flott gemäß der Anleitung hier. Mein Eintrag in der nginx config sieht dann so aus:

server {
    listen                                    80;
    server_name                               IPV4_openhab;

    location / {
        proxy_pass                            http://localhost:8080/;
        proxy_set_header Host                 $http_host;
        proxy_set_header X-Real-IP            $remote_addr;
        proxy_set_header X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto    $scheme;
    }
}

Nur leider ging es dann nicht wenn ich den Hostnamen eingebe. Nach etwas Recherche und einem kurzen Test mit tracert konnte ich sehen, dass die FRITZ!Box den Hostname auf die IPv6-Adresse auflöst. Somit ist ein zusätzlicher Eintrag in der nginx config notwendig der wie folgt aussehen muss. Danach klappt es auch mit IPv6 und dem Hostnamen.

server {
    listen                                    [::]:80;
    server_name                               hostname_openhab;

    location / {
        proxy_pass                            http://localhost:8080/;
        proxy_set_header Host                 $http_host;
        proxy_set_header X-Real-IP            $remote_addr;
        proxy_set_header X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto    $scheme;
    }
}

Edit: Zunächst hat die obige Konfiguration funktioniert. Aber es scheint wohl nicht alles abgefackelt zu sein weshalb meine Konfig nun so aussieht. Damit habe ich hoffentlich alle möglichen Fälle abgedeckt. Zudem leite bei der Eingabe des Host Namen immer direkt auf die Basic UI

server {
    listen                                    80;
    listen                                    [::]:80;
    server_name                               IPV4_openhab;

    location / {
        proxy_pass                            http://localhost:8080/;
        proxy_set_header Host                 $http_host;
        proxy_set_header X-Real-IP            $remote_addr;
        proxy_set_header X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto    $scheme;
    }
}

server {
    listen                                    80;
    listen                                    [::]:80;
    server_name                               hostname_openhab;

    location / {
        proxy_pass                            http://localhost:8080/;
        proxy_set_header Host                 $http_host;
        proxy_set_header X-Real-IP            $remote_addr;
        proxy_set_header X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto    $scheme;
        proxy_redirect http://homecontrol/start/index /basicui/app;
        }

    }
}

SmartMeter ISKA MT681 / Optokoppler

Endlich konnte ich meine Optokoppler den ich bei Nils von Volkszähler bestellt habe in Betrieb nehmen. Bei mir hat es mit OH 2.4 und dem Einbinden des Optokopplers als Thing über die GUI auf Anhieb funktioniert. Aus unterschiedlichen Quellen habe ich hier aber auch schon von Problemen gehört von denen ich zum Glück verschont geblieben bin.

Nun zur Einrichtung. Ich möchte natürlich den aktuellen Verbrauch sowie den Gesamtverbrauch sehen. Dazu werden folgende Items angelegt und natürlich ins Sitemap eingebunden. Ich verwende hier auch das neue UOM Konzept:

Number:Energy STR_act   "Stromverbrauch akt [%.0f %unit%]" 	<energy>      					{channel="smartmeter:meter:6fd23207:1-0_16-7-0"} 
Number:Energy STR_sum   "Stromverbrauch ges [%.2f %unit%]" 	<energy>        {channel="smartmeter:meter:6fd23207:1-0_1-8-0"} 

Darüber hinaus will ich noch den Verbrauch der letzten 7 Tage (STR_D0 bis STR_D6) sowie pro Monat (STR_1 bis STR_12) sehen. Und ich hab mir noch zwei Hilfsvariablen angelegt. Diese habe ich als Item angelegt damit sie nicht weg sind wenn ich den PI einmal durchstarte. Um die Items muss ich mir nämlich keine Gedanken machen da die MAP DB diese speichert und wiederherstellt.

Number 		STR_help_d  "Stromverbruach helper Tag [%.2f]"
Number 		STR_help_m  "Stromverbruach helper Monat [%.2f]"
Number 		STR_D0		"heute [%.2f kWh]"
Number 		STR_D1		"-1 [%.2f kWh]"
Number 		STR_D2		"-2 [%.2f kWh]"
Number 		STR_D3		"-3 [%.2f kWh]"
Number 		STR_D4		"-4 [%.2f kWh]"
Number 		STR_D5		"-5 [%.2f kWh]"
Number 		STR_D6		"-6 [%.2f kWh]"
Number 		STR_1		"Januar [%.2f kWh]"	(gStrom)
Number 		STR_2		"Februar [%.2f kWh]"	(gStrom)	
Number 		STR_3		"März [%.2f kWh]"	(gStrom)
Number 		STR_4		"April [%.2f kWh]"	(gStrom)
Number 		STR_5		"Mai [%.2f kWh]"	(gStrom)
Number 		STR_6		"Juni [%.2f kWh]"	(gStrom)
Number 		STR_7		"Juli [%.2f kWh]"	(gStrom)
Number 		STR_8		"August [%.2f kWh]"	(gStrom)
Number 		STR_9		"September [%.2f kWh]"	(gStrom)
Number 		STR_10		"Oktober [%.2f kWh]"	(gStrom)
Number 		STR_11		"November [%.2f kWh]"	(gStrom)	
Number 		STR_12		"Dezember [%.2f kWh]"	(gStrom)

Kommen wir nun zu den Regeln und beginnen mit dem was wir für die Werte der letzten 7 Tage brauchen. Immer um Mitternacht reichen wir die Variablen eins weiter. Somit ist in D0 immer der Wert des aktuellen Tags und in den anderen Variablen absteigend die letzten Tage.

rule "Strom_taegl"
   when
		Time is midnight 
   then
		STR_D6.postUpdate(STR_D5.state)
		STR_D5.postUpdate(STR_D4.state)
		STR_D4.postUpdate(STR_D3.state)
		STR_D3.postUpdate(STR_D2.state)
		STR_D2.postUpdate(STR_D1.state)
		STR_D1.postUpdate(STR_D0.state)
		STR_help_d.postUpdate(STR_sum.state)  
		STR_D0.postUpdate(0.0)
end

Das ganze ist mit den labels leider etwas unschön weshalb die Regel auch die Tage in die labels setzt. Abhängig vom aktuellen Wochentag werden die labels absteigend entsprechend umbenannt:

switch now.getDayOfWeek{
case 1: { STR_D0.label = "Montag" STR_D1.label = "Sonntag" STR_D2.label = "Samstag" STR_D3.label = "Freitag" STR_D4.label = "Donnerstag" STR_D5.label = "Mittwoch" STR_D6.label = "Dienstag" }
case 2: { STR_D0.label = "Dienstag" STR_D1.label = "Montag" STR_D2.label = "Sonntag" STR_D3.label = "Samstag" STR_D4.label = "Freitag" STR_D5.label = "Donnerstag" STR_D6.label = "Mittwoch" }
case 3: { STR_D0.label = "Mittwoch" STR_D1.label = "Dienstag" STR_D2.label = "Montag" STR_D3.label = "Sonntag" STR_D4.label = "Samstag" STR_D5.label = "Freitag" STR_D6.label = "Donnerstag" }
case 4: { STR_D0.label = "Donnerstag" STR_D1.label = "Mittwoch" STR_D2.label = "Dienstag" STR_D3.label = "Montag" STR_D4.label = "Sonntag" STR_D5.label = "Samstag" STR_D6.label = "Freitag" }
case 5: { STR_D0.label = "Freitag" STR_D1.label = "Donnerstag" STR_D2.label = "Mittwoch" STR_D3.label = "Dienstag" STR_D4.label = "Montag" STR_D5.label = "Sonntag" STR_D6.label = "Samstag" }
case 6: { STR_D0.label = "Samstag" STR_D1.label = "Freitag" STR_D2.label = "Donnerstag" STR_D3.label = "Mittwoch" STR_D4.label = "Dienstag" STR_D5.label = "Montag" STR_D6.label = "Sonntag" }
case 7: { STR_D0.label = "Sonntag" STR_D1.label = "Samstag" STR_D2.label = "Freitag" STR_D3.label = "Donnerstag" STR_D4.label = "Mittwoch" STR_D5.label = "Dienstag" STR_D6.label = "Montag" }
}

Und hier die Regel komplett

rule "Strom_taegl"
   when
		Time is midnight 
   then
		STR_D6.postUpdate(STR_D5.state)
		STR_D5.postUpdate(STR_D4.state)
		STR_D4.postUpdate(STR_D3.state)
		STR_D3.postUpdate(STR_D2.state)
		STR_D2.postUpdate(STR_D1.state)
		STR_D1.postUpdate(STR_D0.state)
		STR_help_d.postUpdate(STR_sum.state)  
		STR_D0.postUpdate(0.0)
		switch now.getDayOfWeek{
			case 1: { STR_D0.label = "Montag" STR_D1.label = "Sonntag" STR_D2.label = "Samstag" STR_D3.label = "Freitag" STR_D4.label = "Donnerstag" STR_D5.label = "Mittwoch" STR_D6.label = "Dienstag" }
			case 2: { STR_D0.label = "Dienstag" STR_D1.label = "Montag" STR_D2.label = "Sonntag" STR_D3.label = "Samstag" STR_D4.label = "Freitag" STR_D5.label = "Donnerstag" STR_D6.label = "Mittwoch" }
			case 3: { STR_D0.label = "Mittwoch" STR_D1.label = "Dienstag" STR_D2.label = "Montag" STR_D3.label = "Sonntag" STR_D4.label = "Samstag" STR_D5.label = "Freitag" STR_D6.label = "Donnerstag" }
			case 4: { STR_D0.label = "Donnerstag" STR_D1.label = "Mittwoch" STR_D2.label = "Dienstag" STR_D3.label = "Montag" STR_D4.label = "Sonntag" STR_D5.label = "Samstag" STR_D6.label = "Freitag" }
			case 5: { STR_D0.label = "Freitag" STR_D1.label = "Donnerstag" STR_D2.label = "Mittwoch" STR_D3.label = "Dienstag" STR_D4.label = "Montag" STR_D5.label = "Sonntag" STR_D6.label = "Samstag" }
			case 6: { STR_D0.label = "Samstag" STR_D1.label = "Freitag" STR_D2.label = "Donnerstag" STR_D3.label = "Mittwoch" STR_D4.label = "Dienstag" STR_D5.label = "Montag" STR_D6.label = "Sonntag" }
			case 7: { STR_D0.label = "Sonntag" STR_D1.label = "Samstag" STR_D2.label = "Freitag" STR_D3.label = "Donnerstag" STR_D4.label = "Mittwoch" STR_D5.label = "Dienstag" STR_D6.label = "Montag" }
		}
end

Für die Anzeige der monatlichen Werte habe ich einen etwas anderen Ansatz gewählt. Dazu muss man sich zunächst am Monatsersten den aktuellen Wert speichern und zwar mit folgender Regel:

rule "Strom_monatl"
   when
		Time cron "0 0 0 1 * ?" 
   then
		STR_help_m.postUpdate(STR_sum.state)
end

Und damit haben wir dann auch unser Rüstzeug und können die Werte schließlich ermitteln und updaten. Der Teil für die täglichen Werte ist ganz einfach. Mit ein paar kleinen Helferlein wird die Differenz berechnet und dann schließlich in STR_D0 geschrieben.

var float helper_d = 0
var float diff_d = 0
var float sum = 0

rule "Strom_aktualisiert"
   when
		Item STR_sum changed
   then 
		helper_d = (STR_help_d.state as Number).floatValue
		sum	 = (STR_sum.state as QuantityType<Number>).floatValue
		diff_d 	 = ( sum - helper_d ) / 1000
		STR_D0.postUpdate(diff_d)

end

Der Teil für die Monate ist etwas kniffeliger aber sobald man das System raus hat auch nicht wirklich schwierig. Zunächst ermitteln wir auch hier die Differenz zum Monatsanfang. Dann folgt die Magie indem wir den dynamisch ermittelten Namen in der Gruppe gStrom, welche wir oben den Items zugewiesen haben suchen und somit das aktuelle Monatsitem an der Hand haben um diesem dann den ermittelten Wert zuzuweisen.

var float helper_m = 0
var float diff_d = 0
var float sum = 0

rule "Strom_aktualisiert"
   when
		Item STR_sum changed
   then 
		helper_m = (STR_help_m.state as Number).floatValue
		sum	 = (STR_sum.state as QuantityType<Number>).floatValue
		diff_d 	 = ( sum - helper_d ) / 1000
		diff_m 	 = ( sum - helper_m ) / 1000

		val monthStrom = gStrom.members.findFirst[name.equals("STR_"+now.getMonthOfYear)]
		monthStrom.postUpdate(diff_m)
end

Die Regel sieht dann fertig so aus:

var float helper_d = 0
var float helper_m = 0
var float diff_d = 0
var float diff_m = 0
var float sum = 0

rule "Strom_aktualisiert"
   when
		Item STR_sum changed
   then 
		helper_d = (STR_help_d.state as Number).floatValue
		helper_m = (STR_help_m.state as Number).floatValue
		sum	 = (STR_sum.state as QuantityType<Number>).floatValue
		diff_d 	 = ( sum - helper_d ) / 1000
		diff_m 	 = ( sum - helper_m ) / 1000
		STR_D0.postUpdate(diff_d)
		val monthStrom = gStrom.members.findFirst[name.equals("STR_"+now.getMonthOfYear)]
		monthStrom.postUpdate(diff_m)
end

Und jetzt mal sehen ob sich das bewährt. Klar kann man hier auch viel mit Graphen etc. machen aber ich bin ein Freund der nackten Zahlen und möchte einfach und schnell eine Übersicht über den Verbrauch haben.

Off Topic: Da ich zu faul war das USB Kabel zu verlegen und noch ein nicht genutztes Netzwerkkabel in der Nähe des Zählerschranks verfügbar war habe ich mir bei Aliexpress für wenige € ein USB RJ45 Extern geschossen. Funktioniert super!

Raspberry PI – Backup Strategie

Bisher habe ich immer nur meine Config Files von OpenHAB gebackupt. Jedoch möchte ich nun immer ein komplettes Image als Backup haben damit ich im Fehlerfall einfach eine SD-Karte auf Basis des Images erzeugen kann um direkt wieder alles lauffähig zu haben.

Schritt 1: Backup Verzeichnis mounten

Ich habe einen entsprechenden Ordner auf meinem NAS erstellt. Dieser kann gemountet werde. Darüber hinaus muss man auch einen Ordner auf dem PI erstellen: z.B.:

sudo mkdir /mnt/backup

Dies wäre dann der LOCAL_FOLDER. 

sudo mount -t cifs //NAS_IP/NAS_FOLDER LOCAL_FOLDER -o username=USER,password=PASSWORD,vers=1.0

Vers=1.0 war bei meiner Synology notwendig. Ihr könnt aber auch zuerst mal ohne probieren.
Hat alles geklappt kann man das mount in fstab einbinden damit bei jedem Start automatisch das Verzeichnis eingebunden wird.

sudo nano /etc/fstab

Danach am Ende folgendes einfügen und die Änderungen speichern

//NAS_IP/NAS_FOLDER LOCAL_FOLDER cifs username=USER,password=PASSWORD,vers=1.0 0 0

Der mount erfolgt damit automatisch beim Neustart. Man kann aber auch mit folgendem Befehl ohne Neustart mounten.

sudo mount -a

Manchmal kommt es vor, dass beim Neustart der mount nicht klappt da das Netzwerk noch nicht verfügbar ist oder ähnliches. Dagegen hilf es via Crontab nochmal mount -a auszuführen. Sollte das Laufwerk bereits da sein passiert nichts und sonst erfolgt dann der mount. Dazu crontab aufrufen

sudo crontab -e

und am Ende folgendes anfügen und speichern.

# Mount zur Sicherheit falls FSTAB nicht funktioniert hat
@reboot sleep 120 && mount -a

Schritt 2: Backup Scripte

Ich möchte ein mit gzip gepacktes Image mit Hilfe des Standard Befehls dd erstellen. Der Befehl sieht dann wie folgt aus. Zunächst einmal müsst ihr herausfinden wie euer Laufwerk heißt welches Ihr sichern wollt. Das geht mit dem Befehl:

lsblk

Meine SD- Karte heißt beispielsweise /dev/mcblk0 . Dann könnt ihr das ganze einmal testen. Achtung, je nach belegtem Speicher und Systemperformance kann das eine ganze weile dauern da auch direkt mit gzip verkleinert wird.

sudo dd if=/dev/mmcblk0 | gzip > LOCAL_FOLDER/raspi_backup_$(date +%Y%m%d_%H%M%S).img.gz

Hat dies funktioniert sind wir bereit das Skript anzulegen. Dazu verwenden wir nano

sudo nano /home/pi/backup.sh

Und kopieren den folgenden Teil in die Datei:

#!/bin/bash
 
# Backup mit Hilfe von dd erstellen und im angegebenen Pfad speichern
dd if=/dev/mmcblk0 bs=1M | gzip > LOCAL_FOLDER/raspi_backup_$(date +%Y%m%d_%H%M%S).img.gz
 
# Alte Sicherungen nach X (hier 5) neuen Sicherungen entfernen
pushd LOCAL_FOLDER; ls -tr LOCAL_FOLDER/raspi_backup_* | head -n -5 | xargs rm; popd

Das ganze dann noch nach /usr/local/bin/ verschieben und ausführbar machen

sudo chmod 755 /home/pi/backup.sh
sudo mv /home/pi/backup.sh /usr/local/bin/backup.sh

So weit so gut. Zusätzlich möchte ich noch die OpenHAB Einstellungen um schneller an ein Backup z. B. des Sitemaps zu kommen. OpenHAB liefert bereits ein passendes Script mit welches wir nur noch aufrufen müssen (liegt bei mir hier: /usr/share/openhab2/runtime/bin/ ) . Dazu erstellen wir analog zum obigen vorgehen die Datei backup_oh.sh mit folgendem Inhalt verschieben diese entsprechend und machen sie ausführbar

sudo nano /home/pi/backup_oh.sh
#!/bin/bash

# Backup mit Hilfe von OH Script erstellen und im angegebenen Pfad speichern
/usr/share/openhab2/runtime/bin/backup LOCAL_FOLDER/openhab_backup_$(date +%Y%m%d_%H%M%S).zip

# Alte Sicherungen nach X (hier 10) neuen Sicherungen entfernen
pushd LOCAL_FOLDER; ls -tr LOCAL_FOLDER/openhab_backup_* | head -n -10 | xargs rm; popd
sudo chmod 755 /home/pi/backup_oh.sh
sudo mv /home/pi/backup_oh.sh /usr/local/bin/backup_oh.sh

Schritt 3: Backup einplanen (Cronjob)

Zu Schluss planen wir die beiden Backups noch ein. Das Image erstelle ich einmal wöchentlich immer Sonntags um 1 Uhr und das schlankere OpenHAB Backup einmal täglich um 0:30 Uhr. Dazu wieder die Crontab bearbeiten

sudo crontab -e

Hier dann die Skripte einplanen

# Image Datei 1 mal pro Woche erstellen
00 01 * * 0 /usr/local/bin/backup.sh
# Tägliches OpenHBA Backup
30 00 * * * /usr/local/bin/backup_oh.sh

Um im Image auf einzelne Dateien in Windows zugreifen zu können kann man die Datei mit z. B. 7ZIP entpacken und anschließend diesen Linux Reader verwenden.

Da ich mich nicht mit fremden Lorbeeren schmücken möchte hier die Quellen aus denen die meisten Infos stammen welche ich hier nur passend zusammengebaut habe:

OpenHAB2 – Sonos und FRITZ!Box

Ups, da war die Musik wohl mal wieder zu laut und man hat den Anruf verpasst. Das sollte doch eigentlich mit einem SmartHome lösbar sein. Für viele bedeutet SmartHome ja einfach toll Lichter und sonstige Dinge vom Handy an und ausknipsen können, für mich ist der Fokus aber ein anderer. Die Automatisierung soll automatisch für mehr Komfort sorgen ohne, dass ich etwas dafür tun muss. So z.B. auch meine Steuerung des Frühstücksradios. Genauso soll hier TV und Sonos im Bedarfsfall still sein, wenn ein Anruf kommt. Die Erkennung läuft über die FRITZ!Box gemeinsam mit dem TR064 Binding. Dazu braucht man ein Item welches das Klingeln abfragt:

Switch  FBX_ringing "Anruf eingehend [%s]" {fritzboxtr064="callmonitor_ringing" }

Für die Sonos und das Samsung TV habe ich jeweils ein Item zum Muten angelegt

Switch  SON_mute "Sonos Mute" {channel="sonos:PLAY1:RINCON_XXXXXXXXXXX:mute"}
Switch	SAM_mute "TV Mute"    {channel="samsungtv:tv:XXXXXXXXXXXXXXXXX:mute"}

Und hier die einfache Regel dazu. Für mich war nur wichtig, dass das Muten nur rückgängig gemacht wird, wenn es auch aus der Regel kommt, deshalb merke ich mir das in den Variablen sonos und tv.

var sonos = 0
var tv = 0

rule "Anruf an"
  when
    Item FBX_ringing changed from OFF to ON
  then
    if ( SON_status.state == "PLAYING") {
      logWarn("Anruf", "Sonos Mute ON")
      sendCommand(SON_mute, ON)
      sonos = 1
    }
    if ( SAM_on.state == ON && SAM_mute.state == OFF) {
      logWarn("Anruf", "TV Mute ON")
      sendCommand(SAM_mute, ON)
      tv = 1
    }
end

rule "Anruf aus"
  when
    Item FBX_ringing changed from ON to OFF
  then
    if ( sonos === 1 ) {
      logWarn("Anruf", "Sonos Mute OFF")
      sendCommand(SON_mute, OFF)
      sonos = 0
    }
    if ( tv === 1 ) {
      logWarn("Anruf", "TV Mute OFF")
      sendCommand(SAM_mute, OFF)
      tv = 0
    }		
end

Das Ganze geht mit Sicherheit auch noch deutlich komplexer da es in meinem Fall aber genau ein Festnetztelefon im Haus gibt und ich somit nur die Sonos und den Fernseher in diesem Raum muten muss reicht das völlig für mich.

OpenHAB2 – Überwachung der Waschmaschine

Meine Ausgangssituation ist eine Waschmaschine, welche im Keller steht. Keiner möchte mehrmals hinlaufen um festzustellen ob der Waschvorgang abgeschlossen ist.

Was wird benötigt? Im Endeffekt nur eine Schaltsteckdose mit Energiemessfunktion die auch die Leistung der Waschmaschine verträgt. In meinem Fall habe ich nun eine Devolo Home Control Schaltsteckdose/Messsteckdose 2.0 im Einsatz welche bis zu 3000 Watt verträgt. Zunächst wollte ich einen Aeon Smart Switch 6 verwenden, welcher jedoch eine schlechte Funkreichweite und somit leider hinter der Waschmaschine nicht funktioniert hat.

Folgende Items habe ich angelegt:

Switch  SD3_on       "WaMa Steckdose"               {channel="zwave:xxx:switch_binary"}
Number  SD3_kwh      "Verbrauch Gesamt [%.2f kWh]"  {channel="zwave:xxx:meter_kwh"}	
Number  SD3_volt     "Spannung [%.0f Volt]"	    {channel="zwave:xxx:meter_voltage"}	
Number  SD3_watt     "Leistung [%.1f Watt]"	    {channel="zwave:xxx:meter_watts"}
Number  SD3_kwh_akt  "Verbrauch aktuell [%.2f kWh]"
Number  SD3_kwh_save "Verbrauch Speicher [%.2f kWh]"	

Da meter_reset aktuell noch nicht implementiert ist habe ich mit mit SD3_kwh_akt und SD3_kwh_save eine Krücke gebaut und speichere beim Einschalten den bisherigen Gesamtverbrauch aus SD3_kwh in die save Variable und berechne bei jeder Änderung von SD3_kwh die akt Variable. Dazu dienen die folgenden Regeln:

rule "WaMa Verbrauchsdaten 1"
  when 
    Item SD3_on changed from OFF to ON
  then
    SD3_kwh_save.postUpdate(SD3_kwh.state)
    SD3_kwh_akt.postUpdate(0.0)
end

rule "WaMa Verbrauchsdaten 2"
  when 
    Item SD3_kwh changed
  then
    SD3_kwh_akt.postUpdate((SD3_kwh.state as DecimalType).doubleValue  - (SD3_kwh_save.state as DecimalType).doubleValue)
end

Für die eigentliche Regel musste ich zunächst den Standby Verbrauch der Waschmaschine ermitteln. Dieser liegt bei mir bei ca. 1,2 Watt, weshalb ich die Schwelle ab der die Waschmaschine als fertig erkannt wird auf 2 Watt festgelegt habe.  Die Regel wird natürlich bei jeder Änderung der Wattzahl ausgeführt. Wenn die Steckdose an, der Timer WAMAoff null und die aktuelle Leistung in Watt kleiner als der Schwellwert (2.0 Watt) ist, so wird ein Timer erzeugt. Der Timer läuft 5 Minuten, damit etwaige Pause der Waschmaschine die Benachrichtigung nicht auslösen und auch das Finale freigeben der Tür am Ende des Waschgangs erfolgt ist. Steig der Energieverbrauch wieder an während der Timer noch läuft, so wird der Timer gecancelt und auf den Initialwert null gesetzt.

Sobald der Timer abgelaufen ist wird die Schaltsteckdose ausgeschaltet, die Lautstärke der Sonos auf 40% gestellt und ein Text, welcher auch die benötigte Energie in kWh enthält über die Sonos ausgegeben. Schlussendlich noch eine Push Nachricht auf die Handys, falls gerade niemand die Sprachausgabe hört.

var Timer WAMAoff = null

rule "WaMa"
  when 
    Item SD3_watt changed
  then
    if (SD3_on.state == ON && SD3_watt.state < 2.0 && WAMAoff == null) {
      logWarn("Waschmaschine","Set Timer")
      WAMAoff = createTimer(now.plusMinutes(5), [|
        logWarn("Waschmaschine","aus")
        sendCommand(SD3_on, OFF)
        sendCommand(SON_volinfo, 40)
        say("Die Waschmaschine ist fertig und hat" + (SD3_kwh_akt.state as DecimalType).doubleValue.toString.replaceAll("\\.",",") + "Kilowattstunden verbraucht")
        sendBroadcastNotification("Waschmaschine fertig")
        ])		
    }
    if (SD3_on.state == ON && SD3_watt.state > 2.0 && WAMAoff != null) {
        logWarn("Waschmaschine","Reset Timer")
        WAMAoff.cancel
        WAMAoff = null
    }
end

Nun ist abwarten und Tee trinken angesagt ob sich die Regel auch bewährt.

OpenHAB2 und Sonos – Projekt Frühstücksradio

Wäre es nicht schön, wenn morgens auf dem Weg zum Frühstückstisch das Radio automatisch angeht? Was für eine Frage. Natürlich wäre es das. Beschlossene Sache und los geht es. Zunächst muss also Sonos Binding installiert werden um die Sonos Box dann über die Inbox als „Thing“ hinzuzufügen. Danach legen wir das Volume und das Playuri item an. Über das Volume Item setzen wir später die Wunschlautstärke, dass die Musik uns nicht gleich umhaut falls z.B. am Abend vorher Musik auch etwas lauter gehört wurde. Über das Playuri Item setzen wir den Radio-Stream. Es gibt auch ein Radio Item aber damit wollte es bei mir nicht klappen.

//Sonos Wohnzimmer
Dimmer  SON_vol   "Volume [%.1f %%]" soundvolume>   (gSonos)	{channel="sonos:PLAY1:RINCON_XXX:volume"}
String  SON_uri		                                        {channel="sonos:PLAY1:RINCON_XXX:playuri"}

Nun fehlt nur noch die passende Regel.

var Timer SONRun = null
rule "Fruehstuecksradio"
   when
    Item MS1_mot changed from OFF to ON
   then
    if(SONRun == null && (now.getHourOfDay() >= 6 && now.getHourOfDay() < 9)){
      logWarn("Fruehstuecksradio", "Trigger!")
      sendCommand(SON_vol, 16)
      sendCommand(SON_uri, "x-rincon-mp3radio://http://sr.audiostream.io/sr/1009/mp3/128/sr1")
      SONRun = createTimer(now.plusHours(4), [|logWarn("Fruehstuecksradio","Timer Off")])	
    }
    else {
      logWarn("Fruehstuecksradio", "Job already done")
    }
end

Grundsätzlich würde es reichen, wenn die Box loslegt sobald mein Bewegungsmelder (MS1_mot) im Wohnzimmer auslöst und die aktuelle Uhrzeit in einem gewissen Bereich wie hier z.B. 6 und 9 Uhr liegt. Dies hätte aber zur Folge, dass es quasi nicht möglich ist die Box in dieser Zeit wieder stumm zu bekommen oder etwas anderes zu hören, da jede erneute Bewegung das Radio startet. Deshalb definiere ich den Timer SONRun. Dieser wird beim Auslösen auf 4 Stunden gestellt und erst wenn der Timer abgelaufen kann die Regel erneut ausgeführt werden. Da der Timer länger läuft als der mögliche Auslösezeitraum groß ist kann das Radio somit nur einmal gestartet werden.

Z-Wave Versuchsaufbau – Aeotec Multi Sensor 6

Der Multisensor ist das zentrale Element in meinem Versuchsaufbau. Er liefert mir Temperatur, Luftfeuchte, Helligkeit sowie einen UV-Index. Den UV-Index lasse ich im weiteren Verlauf einmal unter den Tisch fallen, da ich dafür noch keine Verwendung habe.

Sensor

Temperatur, Feuchtigkeit und Helligkeit habe ich an die openHAB rd4j-Persistenz gekoppelt und erhalte somit mehr oder weniger schöne Graphen. Die Persistenz hat den Vorteil, dass nur sehr wenig Speicherplatz benötigt wird, dazu aber in einem späteren Artikel mehr. Hier seht ihr nun das Ergebnis welches sich ohne großen Aufwand erzeugen lässt:

Graphen

Das Coding dazu sieht wie folgt aus:

Chart item=Sensor_Living_Temp service=rrd4j period=D refresh=30000
Chart item=Sensor_Living_Hum service=rrd4j period=D refresh=30000
Chart item=Sensor_Living_Lum service=rrd4j period=D refresh=30000

Wie gesagt, es gibt deutlich ansprechendere Lösungen aber für meinen ersten Test ist es völlig ausreichend. Man muss lediglich das Item benennen, den Service (zumindest, wenn man mehr als eine Persistenz aktiv hat), die Periode sowie das Aktualisierungsintervall. Die Messwerte habe ich für Temperatur und Feuchte mit zwei anderen Wetterstationen abgeglichen und die Werte passen im Rahmen einer gewissen Toleranz. Man darf nicht vergessen, dass die relative Luftfeuchte in Relation zur gemessenen Temperatur steht und auch dadurch Abweichungen zu Stande kommen.

Des Weiteren ist die Bewegungserkennung für mich von zentraler Bedeutung. Hiermit schalte ich eine Schaltsteckdose mit angeschlossener Lampe sobald die Helligkeit unter einen gewissen Schwellwert (es haben sich 4 Lux bewährt) fällt. Darüber hinaus ist der Bewegungsmelder auch für die Aktivierung des Alarms bei scharf geschalteter Alarmanlage verantwortlich. Die Bewegungserkennung ist für einen solch günstigen Multisensor sehr gut. Man muss wie immer bei PIR-Bewegungsmelder darauf achten, dass diese nicht in der Nähe einer Wärmequelle angebracht werden und auch Wärmequellen im Raum Bewegungen überlagern können. Dies ist aber Systembedingt und kein Nachteil des Produkts.

Alles in allem ein sehr zuverlässiger Sensor den ich aktuell per USB mit Strom versorge weshalb ich leider nichts zur Batterielebensdauer sagen kann.

Z-Wave Versuchsaufbau

In meinem heutigen Artikel möchte ich euch einmal kurz meinen Versuchsaufbau beschreiben.

Ursprünglich hatte ich den Fibaro Rauchmelder sowie den passenden Multi Sensor von Fibaro. Die Kaufentscheidung hatte ich auf Grund des Designs getroffen, was sich dann leider schnell rächte. Der Multisensor ließ sich zunächst ganz einfach mit dem Controller verbinden, da ich aber aus anderen Gründen den Controller resetten musste und so anschließend den Sensor wieder anlernen wollte war es jedoch vorbei mit der schönen heilen Welt. Trotz mehrfachen Reset des Sensors wollte er sich einfach nicht mehr mit meinem Controller verbinden, er wurde lediglich als unbekanntes Gerät angezeigt. Dies hat mich einiges an Nerven gekostet und hat mich fast dazu bewegt meine Z-Wave Experimente ad Acta zu legen. Bestärkt wurde dies durch den Fibaro Rauchmelder, welcher sich noch nicht mal in den „pairing Mode“ versetzen lies. Gut, so habe ich die Komponenten zurückgeschickt und mich ein paar Tage später dazu durchgerungen doch nochmal einen Anlauf zu nehmen und diesmal auf den Popp Rauchmelder sowie den Aeotec Multi Sensor zu setzen.

Was wollte ich nun mit meinem minimalen Versuchsaufbau alles bezwecken.

  1. Der Multi Sensor soll mir erstmal alle Standardwerte wie Helligkeit, Temperatur, Luftfeuchte liefern. Darüber hinaus erfasst er natürlich auch wenn sich vor seiner Linse etwas bewegt.
  2. Der Micro Smart Plug dient dazu eine Lampe zu schalten, es muss sich ja auch was tun, wenn ich mit dem Multi Sensor schon so tolle Werte wie Helligkeit und Bewegungserkennung habe.
  3. Eine Alarmierung, wenn jemand sich Zugang zu meiner Wohnung verschafft wäre darüber hinaus auch sehr toll. Damit er sich auch noch etwas erschrickt dient der Rauchmelder als Sirene.
  4. Darüber hinaus möchte ich auch auf meinem Handy alarmiert werden wenn sich in Sachen Feuer oder Einbruchsalarm etwas tut.

Mehr zu den einzelnen Teilbereichen gibt es dann bald zu lesen.