. OpenHAB2 – Smarthome

bookmark_borderLogs im Browser mit frontail

Manchmal ist es praktisch, wenn man sich die Logs einfach kurz im Browser ansehen kann. Damit könnte man Sie dann z.b. auch in einer Visualisierung einbauen. Dies ist dank frontail auch relativ einfach umzusetzen. Zuerst muss nodejs und npm installiert werden. Danach kann dann frontail installiert werden

sudo apt-get install nodejs 
sudo apt-get install npm

npm i frontail -g

Anschließend erstellen wir einen Service

cd /lib/systemd/system/
sudo nano frontail.service

Hier das Script

#!/bin/sh -

[Unit]
Description=Frontail

[Service]
ExecStart=/usr/local/bin/frontail --ui-highlight -t dark /var/log/openhab2/openhab.log /var/log/openhab2/events.log
Restart=always
User=pi
Group=pi
Environment=PATH=/usr/bin/
Environment=NODE_ENV=production
WorkingDirectory=/usr/bin/

[Install]
WantedBy=multi-user.target
Alias=frontail.service

Nun machen wir das Script ausführbar und lassen systemctl die Konfiguration neu lesen. Anschließend setzen wir den Service auf enable damit er beim booten gestartet wird und starten ihn manuell

sudo chmod 644 /lib/systemd/system/frontail.service
sudo systemctl daemon-reload
sudo systemctl enable frontail.service
sudo systemctl start frontail.service

Frontail ist nun unter http://ip_des_rapsi:9001 erreichbar

Unter /usr/local/lib/node_modules/frontail/preset/ können wir noch das Design der Ausgabe beeinflussen. Dazu die Datei default.json entsprechend ändern.

{
  "words": {
    "[ERROR]": "color: red;",
    "[WARN ]": "color: orange;",
    "[INFO ]": "color: green;",
    "Node is DEAD": "color: red;",
    "GroupItemStateChangedEvent": "color: #0026FF; font-weight: bold;",
    "ItemStateChangedEvent": "color: #3F55D1;",
    "ItemCommandEvent]": "color: #00137F;",
    "hingStatusInfoChangedEvent": "color: #007F46;",
    "ON": "color: green; font-weight: bold;",
    "OFF": "color: red; font-weight: bold;",
    "received command": "color: yellow;"
  },
  "lines": {
    "": "font-size: 1.2em; color: #FBFBFB",
    "/var/log/openhab2/openhab.log": "text-align: right; font-size: 1.5em; font-weight: bold; color: #FBFBFB; border-top: 2px solid #F8F8F8;",
    "/var/log/openhab2/events.log": "text-align: right; font-size: 1.5em; font-weight: bold; color: #FBFBFB; border-top: 2px solid #F8F8F8;",
    "ERROR": "font-size: 1.2em; color: #FBFBFB; background-color: #990808;",
    "model.script": "font-size: 1.2em; color: #FBFBFB; background-color: #1B0CA6; font-weight: bold;"
  }
}

bookmark_borderSpeedtest mit OpenHAB – neu

Nach mehreren Versionen der Implementierung eines dauerhaften Speedtests bin ich im OpenHAB Forum wieder auf einen sehr guten Artikel gestoßen. Seit ich den Speedtest anhand dieses Artikels eingerichtet habe läuft nun alles super.
Man muss dazu nur ein paar Vorbereitungen treffen:

sudo apt-get install gnupg1 apt-transport-https dirmngr
export INSTALL_KEY=379CE192D401AB61
# Ubuntu versions supported: xenial, bionic
# Debian versions supported: jessie, stretch, buster
export DEB_DISTRO=$(lsb_release -sc)
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $INSTALL_KEY
echo "deb https://ookla.bintray.com/debian ${DEB_DISTRO} main" | sudo tee  /etc/apt/sources.list.d/speedtest.list
sudo apt-get update
# Other non-official binaries will conflict with Speedtest CLI
# Example how to remove using apt-get
# sudo apt-get remove speedtest-cli
sudo apt-get install speedtest

Anschließend kann man sich mit

speedtest -L

eine Liste der Server anzeigen lassen die in der Nähe sind. Um sich einen festen Server auszusuchen. Diesen merken wir uns. Geht natürlich auch ohne aber ich mache es lieber so. Nun noch die Items anlegen. Die sehen bei mir so aus

String      SpeedtestSummary        "FRITZ!Box [%s]"             <network>       
Number      SpeedtestResultPing     "Ping [%.3f ms]"             <"speedtest_ping">                
Number      SpeedtestResultDown     "Downlink [%.2f Mbit/s]"     <"speedtest_download">  
Number      SpeedtestResultUp       "Uplink [%.2f Mbit/s]"       <"speedtest_upload"> 
String      SpeedtestRunning        "Speedtest running ... [%s]" <"speedtest_run">           
String      SpeedtestTestServer     "Testserver [%s]"            <"speedtest_summary">             
Switch      SpeedtestRerun          "Start manuell"              <"speedtest_reload">      
DateTime    SpeedtestResultDate     "letzter Test [%1$td.%1$tm.%1$tY, %1$tR Uhr]"   <"speedtest_date">
String      SpeedtestResultError    "Error Message [%s]"         <"speedtest_error">   

Danach kommt dann das anlegen der eigentlichen Regel, die bei mir so aussieht. In Zeile 12 „–accept-license –accept-gdpr“ muss man nicht unbedingt jedesmal mitgeben. Klappt bei mir aber Problemlos wenn ich das einfach dauerhaft mitgebe und dann kann ich es bei Updates etc auch nicht vergessen.

rule "Speedtest"
when
 
    Time cron "0 0 * * * ?" or
    Item SpeedtestRerun changed from OFF to ON or
    Item SpeedtestRerun received command ON

then
    SpeedtestRunning.postUpdate("Messung läuft...")

 system
	val speedtestExecute = "speedtest --accept-license --accept-gdpr -s 33264 -f json"	//Hochschule Trier
    var speedtestCliOutput = executeCommandLine(speedtestExecute, 120*1000)

    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("{\"type\":\"result\",") && speedtestCliOutput.endsWith("}}"))
    {
        var ping = Float::parseFloat(transform("JSONPATH", "$.ping.latency", speedtestCliOutput))
        SpeedtestResultPing.postUpdate(ping)

        var float down = Float::parseFloat(transform("JSONPATH", "$.download.bandwidth", speedtestCliOutput))
        down = (down / calc)
        SpeedtestResultDown.postUpdate(down)

        var float up = Float::parseFloat(transform("JSONPATH", "$.upload.bandwidth", speedtestCliOutput)) 
        up = (up / calc)
        SpeedtestResultUp.postUpdate(up)
		
		var String server = transform("JSONPATH", "$.server.name", speedtestCliOutput) + ', ' + transform("JSONPATH", "$.server.location", speedtestCliOutput)
		SpeedtestTestServer.postUpdate(server)

        SpeedtestSummary.postUpdate(String::format("ᐁ%.1f  ᐃ%.1f Mbit/s | %.0f ms", down, up, ping))

        SpeedtestRunning.postUpdate("-")

        // update timestamp for last execution
        val String ResultDate = "" + new DateTimeType()
        SpeedtestResultDate.postUpdate(ResultDate)
    }
    else
    {
        SpeedtestResultPing.postUpdate(0)
        SpeedtestResultDown.postUpdate(0)
        SpeedtestResultUp.postUpdate(0)
        SpeedtestSummary.postUpdate("(unbekannt)")
        SpeedtestRunning.postUpdate("Fehler")

        logError(ruleId, "--> speedtest failed. Output:\n" + speedtestCliOutput + "\n\n")
    }

    SpeedtestRerun.postUpdate(OFF)
end

bookmark_borderSpeedtest in OpenHAB

Es gibt einen neuen Artikel zum Thema Speedtest mit OpenHAB – neu

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

bookmark_borderUmzug 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

bookmark_bordernginx / 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 = / {
        return 301 /basicui/app;
        }

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

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

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

bookmark_borderOpenHAB2 – 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.

bookmark_borderOpenHAB2 – Ü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.

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