Powerinterface für Nedap Powerrouter

Wer eine Solaranlage auf dem Dach hat und zufällig vor ein paar Jahren einen Powerrouter der Firma Nedap gekauft hat, kennt das Problem: Die Firma ist pleite gegangen und seitdem ist der Support irgendwo zwischen katastrophal und nicht vorhanden. Am schlimmsten ist, dass die Nedap Powerrouter ihre aktuellen Produktionsdaten nur an eine spezielle Internetseite unter www.mypowerrouter.com senden und per se keine Möglichkeit bieten, diese lokal zu empfangen, bzw. einzusehen. Zu allem Übel scheint es sich seit Januar 2021 abzuzeichnen, dass auch diese Internetseite früher oder später den Betrieb ganz einstellen wird, womit die ehemaligen Kunden der Firma Nedap vollends im Regen stehen.

In Anbetracht dieser Umstände, haben sich seit einiger Zeit Betroffene intensiv Gedanken gemacht, wie man die Daten lokal abfangen, in einer Datenbank aufzeichnen und über ein passendes Interface graphisch darstellen kann. Es gibt auf photovoltaikforum.com einen längeren Thread zu diesem Thema, in dem der aktuelle Stand der Erkenntnisse verfolgt werden kann.

Nicklas Grießer (ngrie) hat einen Webdienst entwickelt, der in der Lage ist, die Daten des Powerrouters lokal zu empfangen und anzuzeigen. Dave Mallou hat darum herum ein fertiges Image für den Raspberry Pi gebaut hat, das die Daten in einer InfluxDB speichert und mit Grafana anzeigt. Aufbauend auf dieser hervorragenden Vorarbeit habe ich hier eine Weiterentwicklung dieses Ansatzes zusammengestellt, bei dem ich im wesentlichen 2 Dinge geändert habe:

  1. Keine zusätzliche externe USB-Netzwerkkarte nötig
  2. AdGuard ist durch Pi-Hole ersetzt. Dies ist meine subjektive Präferenz, da ich Pi-Hole schon lange im Einsatz habe und einfach kenne und liebe. Soweit ich das überblicke, gibt es kaum echte objektive Argumente, die AdGuard hinter Pi-Hole zurückfallen lassen. Ist wohl eher frei nach dem Motto “Was der Bauer net kennt, …” 😉
  3. Ich stelle (bisher) kein fertiges Image zur Verfügung, da ich der Meinung bin, dass die Schritte zur Installation nicht weiter kompliziert sind. Außerdem hat man durch das Selbermachen den großes Vorteil, dass man sein System genau kennt und leicht in einzelnen Details an seine Bedürfnisse/sein Umfeld anpassen kann.

Grundkonzept

Im lokalen LAN wird ein eigener DNS-Server eingerichtet (z.B. Pi-Hole, AdGuard, dnsmasq, bind9, o.ä.), der für die Webadresse “logging1.powerrouter.com” (an die der Nedap Powerrouter seine Daten schicken möchte) eine lokale IP-Adresse ausgibt, unter der der Webdienst von Niklas läuft, der die Daten des Powerrouters entgegennimmt und in eine Datenbank (InfluxDB) schreibt. Auf diese DB kann dann ein graphisches Anzeigesystem (Grafana) zugreifen, um die Daten wieder im lokalen LAN als Webpage zur Verfügung zu stellen. All diese Dienste können auf einem einzigen kleinen Linux-Rechner (z.B. ein Raspberry Pi, oder eine VM) laufen.

Im folgenden führe ich Schritt für Schritt durch die Installation.

Installation und Einrichtunbg

Raspberry OS installieren

Das kleinste Image, ohne Desktop reicht völlig aus, der Server läuft eh im Prinzip “headless”, d.h. ohne Monitor, Maus und Tastatur. Sollte SSH aber nicht auf Anhieb funktionieren, dann ist zur Installation ein Monitor und eine Tastatur extrem hilfreich! In meinem Fall war der Raspi etwas wählerisch mit dem Netzwerkkabel…🥴

  • aktuelles RaspberryOS-Image installieren
    • am besten mit dem “Raspberry Pi Imager” die SD-Karte/USB-Stick vorbereiten
    • Das Image kann über das verstecktes Menü (Shift+Cmd+X) vorkonfiguriert werden:
      • WLAN und SSH-Passwort ändern bzw. anpassen
      • Rechnername auf “powerinterface” setzen
    • Dann Image auf die SD-Karte schreiben
  • Raspi booten und aktualisieren
    • apt update
    • apt full-install
  • feste LAN-IP-Adresse vergeben
    • Entweder im Router-DHCP die IP fest auf die MAC-Adresse des Raspi eintragen
    • Oder DHCP im Router komplett deaktivieren und den DHCP-Dienst im Pi-Hole aktivieren (s.u.)
      • In diesem Fall dann hier eine feste IP für die MAC des Raspi eintragen
    • Oder dem Raspi selbst über /etc/interfaces eine feste IP-Adresse geben
    • Auf jeden fall sicherstellen, dass keine IP-Adresse doppelt vorkommen kann, das wäre absolut tödlich für das ganze LAN!!!
      • => Die feste IP-Adresse darf nicht im Adressbereich liegen, den der DHCP an Clients verteilt!!!

Pi-Hole

Pi-Hole ist ein DNS-Server, der eigentlich zum filtern von Werbung im ganzen LAN gebaut ist. Er kann über den DHCP-Service auch die Verteilung der IP-Adressen übernehmen.

Details siehe hier: https://pi-hole.net

installieren

curl -sSL https://install.pi-hole.net | bash

..und den Anweisungen folgen.

konfigurieren:

  1. Passwort für Pi-Hole Admin-Site löschen: pihole -a -p
  2. Unter “Local DNS” die Domain “logging1.powerrouter.com” auf die IP des Raspi setzen
  3. Dafür sorgen, dass der Pi-Hole als DNS-Server im LAN per DHCP verteilt wird:
    1. Entweder in den DHCP-Einstellungen des Routers die IP des Raspi als lokalen DNS-Server eintragen
    2. Oder DHCP im Router deaktivieren und im Pi-Hole aktivieren.

Pi-Hole Port 80 ändern

Standardmäßig läuft die Admin-Seite des Pi-Hole auf Port 80. Da aber unter diesem Port das Webinterface für den Nedap Powerrouter laufen muss (der schickt die Daten nämlich nur an Port 80), müssen wir das Admin-Interface des Pi-Hole auf was anderes legen. Glücklicherweise installiert der Pi-Hole mit lighttpd einen vollwertigen Web-Server. Über den bieten sich gleich mehrere Lösungen:

  1. Gesamten lighttpd server auf einem anderen Port laufen lassen, z.B. 8080
    • nano /etc/lighttpd/lighttpd.conf
    • Zeile “server.port = 80” ändern auf z.B. 8080
    • systemctl restart lighttpd
      • Die Pi-Hole Admin-Seite wird damit somit so aufgerufen: http://<ip-vom-Raspi>:8080/admin
  2. Für die Admin-Seite einen Vhost mit Subdomäne einrichten, in der Form “http://pihole.powerinterface”
    • (bitte separat googeln)

Am Schluß muss der Nedap Powerrouter neu gestartet werden, damit er sich den geänderten DNS-Server per DHCP holt. Über das lokale Menü am Gerät sicherstellen, dass der DNS-Server jetzt die IP-Adresse des powerinterface ist.

InfluxDB installieren

  • Repository einbinden
    • wget -qO- https://repos.influxdata.com/influxdb.key | sudo apt-key add -
    • source /etc/os-release
    • echo "deb https://repos.influxdata.com/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/influxdb.list
  • Influx installieren
    • apt update
    • apt install influxdb
    • systemctl unmask influxdb.service
    • systemctl start influxdb.service
    • systemctl enable influxdb.service
  • Datenbank vorbereiten (jede Zeile ein neuer Befehl)
    • influx
      create database home
      use home
      create user grafana with password '<eigenes Passwort vergeben>'
      grant all on home to grafana

      show databases
      show users
      show grants for grafana

      quit

Webdienst (ngrie)

Node.js installieren

RaspberryOS kommt entweder mit gar keinem Node.js installiert, oder mit einer alten Version, die man erst deinstallieren muss.

apt remove node nodejs

  • Repository einbinden (alternativ kann auch gleich die neue Version 16 genommen werden mit “…/setup_16.x
    • curl -sSL https://deb.nodesource.com/setup_14.x | sudo bash -
  • Node.js installieren
    • apt install nodejs
    • node -v
    • npm -v

installieren

cd /srv
git clone https://github.com/ngrie/powerinterface.git
cd powerinterface
npm install

Konfiguration

nano config.yml

Folgenden Inhalt in die Datei kopieren, danach speichern und schließen:

forwardRequests: true
actions:
  - type: influxdb
    host: 127.0.0.1
    database: home
    username: grafana
    password: <Passwort von oben eintragen>
    port: 8086

Autostart-Dienst erstellen

nano /etc/systemd/system/powerinterface.service

folgenden Inhalt in die Datei kopieren:

[Unit]
Description=Powerinterface
Documentation=https://github.com/ngrie/powerinterface
After=network.target [Service]
Type=simple
User=root
WorkingDirectory=/srv/powerinterface
ExecStart=/usr/bin/node /srv/powerinterface/server.js
Restart=on-failure

[Install]
WantedBy=multi-user.target

Datei speichern und schließen.

systemctl daemon-reload
systemctl enable powerinterface
systemctl start powerinterface

Grafana

installieren

  • Repository hinzufügen
    • wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
    • echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
  • installieren
    • sudo apt update
    • sudo apt install grafana
    • systemctl enable grafana-server

konfigurieren

Im Browser aufrufen: (Port 3000 auf der IP-Adresse des Raspi)
http://powerinterface.local:3000

Benutzer: admin
Passwort: admin

Man wird aufgefordert, das Passwort zu ändern.

Configuration -> Add Datasource -> InfluxDB
HTTP -> URL: http://powerinterface:8086/

Database: home
User: grafana
Password: <siehe oben>

Dashboards importieren

Die drei Dashboards von Dave Mallou runterladen und entpacken.

Aktuelle Daten
Powerrouterdaten
Statistik

Unter Dashboards -> Manage Dashboards -> Import die drei json-Dateien importieren.

Anwendung

Interface

Es gibt zwei Interfaces, die von Interesse sind. Das native Webinterface von ngrie, sowie die Diagramme, die über Grafana angezeigt werden.

natives Webinterface von ngrie

Hier werden die wichtigesten Live-Daten angezeigt, ähnlich wie auf der Übersichtsseite von mypowerrouter.com.

Aufruf über:

http://powerinterface.local/
http://<ip-vom-Raspi>:80/

Port 80 kann auch weggelassen werden.

Im Hintergrund schreibt das Interface die Daten auch in die Influx-Datenbank.

Grafana-Interface

Auf diesem Interface werden die angespeicherten Daten aus der Datenbank dargestellt.

Aufruf über:

http://powerinterface.local:3000/
http://<ip-vom-Raspi>:3000/

Troubleshooting

Bitte der Versuchung widerstehen, das ganze Setup in einem anderen LAN als dem endgültigen vorzubereiten, da u.a. der Pi-Hole bei der Installation sich die aktuelle IP-Adresse Raspberry Pi in die Konfiguration schreibt.

Wenn dennoch etwas nicht funktioniert, am besten schrittweise den Fehler eingrenzen:

Sicherstellen, dass die Dienste laufen

service pihole-FTL status
service influxdb status
service powerinterface status
service grafana-server status

Der Status sollte jeweils grün sein und die Dienste sollten laufen. Wenn nicht, genau die Fehlermeldungen lesen und die entsprechenden Probleme beheben. Mit tail -fn1000 /var/log/syslog sieht man ev. mehr Details.

Sicherstellen, dass der Powerrouter den Pi-Hole als DNS-Server verwendet

Dazu lokal am Gerät über das Menü Wartung -> Status -> Internetanschluß -> DNS-Server prüfen, ob die IP des Raspi angezeigt wird.

Sicherstellen, dass der Powerrouter den Pi-Hole als DNS-Server verwendet

Dazu im Admin-Webinterface vom Pi-Hole im Menü “Query Log” prüfen, ob es ca. 1 mal pro Minute eine DNS-Anfrage vom Client “PowerRouter” an die Domäne “logging1.powerrouter.com” gibt.

Außerdem in einer Dos-box mit ping logging1.powerrouter.com prüfen, ob der Pi-Hole die “richtige” IP-Adresse ausliefert – nämlich seine eigene!

Prüfen, ob der Webdienst Daten bekommt

Dazu den Webdienst im Browser aufrufen http://powerinterface, er sollte die live-Daten des Powerrouter anzeigen.

Prüfen, ob die Daten in die InfluxDB gespeichert werden

influx
use home

show series
show measurements

Bei den letzten beiden Befehlen sollte jeweils eine längere Liste von Daten angezeigt werden.

Prüfen, ob Grafana auf die richtigen Daten zugreift

Unter http://powerinterface:3000 -> Configuration -> Datasource -> InfluxDB alle Werte genauestens prüfen, insbesondere den Datenbank-Namen, Benutzer und Passwort.

Ausblick

Ihr dürft gerne in den Kommentaren schreiben, wenn irgend etwas nicht gut beschrieben ist, oder nicht funktioniert. Ich kann zwar keinen echten Support für jeden liefern, aber ich versuche schon, die Anleitung aktuell zu halten.




Beschattung mit sonnengeführten elektrischen Rollläden

Schön langsam entwickeln sich die Temperaturen auch in Deutschland in Richtung Sommer. Da sich aufgrund von Corona immer noch viele die meiste Zeit zu Hause aufhalten, mag der Wunsch entstehen, seine elektrischen Rollläden zur Beschattung der Wohnung zu verwenden. Die einfachste Möglichkeit ist, alle Rollos auf 80% schließen und den ganzen Tag im Dunkeln sitzen. Da das aber nicht wirklich schön ist, wäre eine Beschattung mit sonnengeführten elektrischen Rollläden wesentlich cooler. Wie das geht, zeige ich hier.

Als erstes braucht man natürlich neben den elektrischen Rollläden, eine bereits funktionierende Haussteuerung. Ich verwende bereits seit vielen Jahren FHEM dafür.

Beschattung

Die Idee ist, sich mit einem notify-device an ein Twilight-modul zu hängen, um damit alle paar Minuten den aktuellen Sonnenstand zu bekommen. Im notify-device wird dann ein Makro aufgerufen, das die neue Position jedes Rollladens anhand des aktuellen Sonnenstandes berechnet und diesen auf die neue Position fährt. Im Ergebnis bewegen sich alle paar Minuten alle Rollläden um einige Millimeter, sodass sie genau richtig abschatten — nicht zu viel (-> nicht zu dunkel) und nicht zu wenig (-> zu viel Sonneneinstrahlung).

Sonnengeführt

Zur Berechnung verwenden wir zwei Vektoren im Raum:

  • Der Sonneneinfallsvektor bestehend aus Azimuth und Elevation (Himmelsrichtung und Höhe der Sonne)
  • Der Rollo-Bewegungsvektor, der den Fahrweg des Rollos beschreibt

Anschließend berechnen wir den Winkel der beiden Vektoren, der dem Winkel der Sonne zur Fensterfläche entspricht. Durch einfache Winkelberechnungen können wir nun die Rolloposition bestimmen, die für eine optimale Abschattung sorgt.

elektrische Rollläden

Für jeden Rollladen sollten eine Reihe von Werten berücksichtigt werden, die Einfluß auf die Beschattung haben:

  • Breite und Höhe des Fensters
  • Tiefe der Fensterlaibung (bzw. wie weit die Sonne “rein” scheinen darf)
  • Dachwinkel (90 ° bei senkrechten Fenstern)
  • Höhe der Auf-/Vorbauten (z.b. außen liegender Rollokasten, oder ein Mauervorsprung
  • Fensterausrichtung (Himmelsrichtung)
  • Min-/Max Azimuth und Min-/Max Elevation für die Beschattung
  • Min-/Max schattenrelevante Position

Umsetzung

Die Logik des Makros läßt sich sicherlich in fast jeder Haussteuerung umsetzen, aber ich beschreibe hier wie es mit FHEM geht.

Als erstes das Twilight-Modul. Gemäß der Dokumentation, hier die allgemeine Definition:

 

define <name> Twilight <latitude> <longitude> [<indoor_horizon> [<Weather_Position>]]

Die Parameter dürften selbstredend sein, latitude und longitude ist die eigene geographische Position, der indoor_horizon beschreibt, bei wieviel Grad Sonnenstand das Event indoor Sunset ausgelöst werden soll. Für die Beschattung brauchen wir ihn nicht. Eine fertige Definition wäre somit:

define MyTwilight 49.123456 8.123456 -3

Das notify-device (Dokumentation) ist einfach definiert:

MyTwilight:azimuth:.* {
}

Somit wird es immer dann ausgelöst, wenn das Twilight-Device die Eigenschaft azimuth aktualisiert. Dies geschieht alle paar Minuten. Innerhalb der geschweiften Klammern kommt das eigentliche Steuerungsmakro zur sonnengeführten Beschattung mit elektrischen Rollläden:

MyTwilight:azimuth:.* {
	######### Rolladensteuerung
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	my $Elev = ReadingsVal('MyTwilight', 'elevation', '0');
	my $Azim = ReadingsVal('MyTwilight', 'azimuth', '0');
	Log 1, "Elev: $Elev";
	Log 1, "Azim: $Azim";
	# finales hochfahren, nach Sonnenuntergang, Deaktivierung (Aktivierung erfolgt ausserhalb kurz vor Sonnenaufgang)
	if ($Elev < 22 && $Azim > 180) {
		fhem("set BU_RolloS up");
		fhem("set BU_RolloN up");
		fhem("set KU_Rollo up");
		fhem("set WC_Rollo up");
		if ($Elev < 15) {
			fhem("set GZ_RolloW up");
			fhem("set notRolloSteuerung inactive");
			fhem("set Rollos up");
		}
	}
	# Fensterdefinitionen (DevName, Breite, Höhe, Tiefe, Dachwinkel, Aufbauhöhe, Fensterausrichtung, Aktiv)
		# 0, DeviceName des Rollos
		# 1, 2: Breit, Höhe (cm) = sichtbare Breite, Höhe des Fensters (Glas innerhalb des Rahmens)
		# 3: Tiefe (cm) = Wie weit darf die Sonne "ins Fenster" scheinen (vom Fensteraussenscheibe messen!)
		# 4: Dachwinkel (Grad) = native Neigung der Fensterscheibe (=Dach), bzw. 90° bei "normalen" Wandfenstern
		# 5: Aufbauhöhe (cm) = Wieviel cm tragen Rollläden, Wandvorsprünge, etc. zur Fensterscheibe auf?
		# 6: Fensterausrichtung (Grad) = In welche Himmelsrichtung "schaut" das Fenster? -> Senkrechte auf die Scheibe, Nord = 0°, Süd = 180°
		# 7: Min Azim. (Grad) = ab welchem Azimut wird beschattet?
		# 8: Max Azim. (Grad) = bis welchem Azimut wird beschattet?
		# 9: Min Elev. (Grad) = ab welcher Elevation wird beschattet?
		# 10: Max Elev. (Grad) = bis welcher Elevation wird beschattet?
		# 11: Min Schatten (%) = minimale schattenrelevante Rollo-Position (z.B. 12%)
		# 12: Max Schatten (%) = maximale schattenrelevante Rollo-Position (z.B. 98%)
		# 13: Aktiv = 0 -> Deaktiviert, 1 -> Aktiv
	my @BU_S = ("BU_RolloS", 77, 98, 15, 43, 0, 276, 90, 360, 29, 90, 16, 100, 1);
	my @BU_N = ("BU_RolloN", 77, 98, 15, 43, 0, 276, 90, 360, 22, 90, 16, 100, 1);
	my @KU = ("KU_Rollo", 77, 100, 15, 43, 0, 276, 90, 360, 21, 90, 16, 100, 1);
	my @WC = ("WC_Rollo", 60, 90, 15, 43, 0, 276, 90, 360, 20, 90, 16, 100, 1);
	my @SZ = ("SZ_Rollo", 300, 200, 35, 90, 15, 96, 0, 186, -10, 90, 14, 100, 1);
	my @WZ = ("WZ_Rollo", 300, 200, 35, 90, 15, 96, 0, 186, 0, 90, 14, 100, 1);
	my @GA = ("GA_Rollo", 105, 130, 15, 43, 0, 96, 0, 270, -10, 90, 13, 100, 1);
	my @GZ_O = ("GZ_RolloO", 105, 130, 15, 43, 0, 96, 0, 270, 0, 90, 13, 100, 1);
	my @GZ_W = ("GZ_RolloW", 125, 130, 15, 43, 0, 276, 90, 360, 15, 90, 14, 100, 1);
	my @ArrFenster = (@BU_S, @BU_N, @KU, @WC, @SZ, @WZ, @GA, @GZ_O, @GZ_W);
	# Array-Zugriffskonstanten
	my $C_DEVNAME = 0;
	my $C_BREITE = 1;
	my $C_HOEHE = 2;
	my $C_TIEFE = 3;
	my $C_DACHWINKEL = 4;
	my $C_AUFBAU = 5;
	my $C_F_AUSRICHTUNG = 6;
	my $C_MINAZIM = 7;
	my $C_MAXAZIM = 8;
	my $C_MINELEV = 9;
	my $C_MAXELEV = 10;
	my $C_MINPOS = 11;
	my $C_MAXPOS = 12;
	my $C_AKTIV = 13;
	# allgemeine Variablen/Konstanten
	my $pi = 3.1415926535897932;
	my @V_Rollo = (0, 0, 0);	# Vektor Rollo
	my @V_Sonne = (0, 0, 0);	# Vektor Sonne
	my $Aufbauwinkel = 0;
	my $Gesamtneigung = 0;
	my $Theta = 0;
	my $Phi = 0;
	my $Winkel = 0;
	my $RolloPos = 0;
	# rad = grad * pi / 180; 		grad = rad * 180 / pi
	# Sonnenvektor berechnen
	$Theta = (90 + $Elev) * ($pi / 180);
	$Phi = $Azim * ($pi / 180);
	$V_Sonne[0] = sin($Theta) * sin($Phi);
	$V_Sonne[1] = cos($Theta);
	$V_Sonne[2] = sin($Theta) * cos($Phi);
	foreach (@ArrFenster) {
		my @Fenster = @$_;
		# Gesamtneigung berechnen
		$Aufbauwinkel = atan($Fenster[$C_AUFBAU] / $Fenster[$C_HOEHE]) * (180 / $pi);
		$Gesamtneigung = $Fenster[$C_DACHWINKEL] + $Aufbauwinkel;
		# Rollo-Vektor berechnen
		$Theta = (90 + $Gesamtneigung) * ($pi / 180);
		$Phi = $Fenster[$C_F_AUSRICHTUNG] * ($pi / 180);
		$V_Rollo[0] = sin($Theta) * sin($Phi) * -1;
		$V_Rollo[1] = cos($Theta);
		$V_Rollo[2] = sin($Theta) * cos($Phi) * -1;
		# Sonnenwinkel auf Fensterfläche berechnen (=Winkel der beiden Vektoren)
		$Winkel = acos(($V_Rollo[0] * $V_Sonne[0] + $V_Rollo[1] * $V_Sonne[1] + $V_Rollo[2] * $V_Sonne[2]) /
				  (sqrt($V_Rollo[0] ** 2 + $V_Rollo[1] ** 2 + $V_Rollo[2] ** 2) * 
				  sqrt($V_Sonne[0] ** 2 + $V_Sonne[1] ** 2 + $V_Sonne[2] ** 2)));
		# Rollo-Position bestimmen
		if ($Fenster[$C_MINAZIM] < $Azim && $Azim < $Fenster[$C_MAXAZIM] && 
			$Fenster[$C_MINELEV] < $Elev && $Elev < $Fenster[$C_MAXELEV]) {
			$RolloPos = ((($Fenster[$C_TIEFE] + $Fenster[$C_AUFBAU]) / tan($Winkel)) / $Fenster[$C_HOEHE]) * 100;
		} else {
			$RolloPos = 100;
		}
		if ($RolloPos < 0) {$RolloPos = 0};
		if ($RolloPos > 100) {$RolloPos = 100};
		# Rollo-Pos auf schattenrelevanten Bereich skalieren
		$RolloPos = ($Fenster[$C_MAXPOS] - $Fenster[$C_MINPOS]) * ($RolloPos / 100) + $Fenster[$C_MINPOS];
		# Rollo auf Position fahren
		if ($Fenster[$C_AKTIV] == 1) {
			fhem("sleep 1; set ".$Fenster[$C_DEVNAME]." pct ".sprintf("%.0f", $RolloPos));
			Log 1, "Rollo ".$Fenster[$C_DEVNAME]." auf ".sprintf("%.0f", $RolloPos);
			#Log 1, "Rollo ".$Fenster[$C_DEVNAME].": Winkel ".($Winkel * 180 / $pi)." Theta ".($Theta*(180/$pi))." Phi ".($Phi*(180/$pi));
			#Log 1, "Rollo ".$Fenster[$C_DEVNAME].": V_Sonne ".$V_Sonne[0]."; ".$V_Sonne[1]."; ".$V_Sonne[2];
			#Log 1, "Rollo ".$Fenster[$C_DEVNAME].": V_Rollo ".$V_Rollo[0]."; ".$V_Rollo[1]."; ".$V_Rollo[2];
			#Log 1, "Rollo ".$Fenster[$C_DEVNAME].": Tiefe ".$Fenster[$C_TIEFE]." Höhe ".$Fenster[$C_HOEHE];
		}
	}
}



Eigenen DynDNS Server mit dynamischer IP Adresse betreiben

Einen eigenen DynDNS Server mit dynamischer IP Adresse zu betreiben stellt eine grundsätzliche Herausforderung dar. Man hat ein handfestes Henne-vor-Ei-Problem: Wenn sich die IP-Adresse ändert, muss die Adresse im DNS geändert werden, obwohl der DynDNS Server unter dieser Adresse nicht mehr erreichbar ist…

Detailprobleme

Meine Konstellation dürfte im privaten Umfeld vermutlich des öfteren vorkommen. Ich habe mir irgendwann einmal günstig eine Domain registrieren lassen — nennen wir sie example.com. Diese liegt bei einem günstigen Registrar, der mir per Webfrontend Zugriff auf die cPanel-Administration erlaubt, worüber ich die DNS-Einträge für example.com editieren kann. Um jetzt zu Hause einen kleinen Server betreiben zu können (z.B. für Nextcloud oder einen kleinen Blog) sollte der A-Record für example.com auf die gerade aktuelle IP-Adresse des DSL-Anschlusses zeigen. Die Subdomains für blog oder cloud können per CNAME-Record einfach auf example.com verweisen. Analog dazu sollte der DynDNS Dienst unter ddns.example.com erreichbar sein. Somit müssen wir also erstmal eine Lösung finden, um unseren zentralen A-Record von example.com dynamisch aktualisieren zu können.

DynDNS-Client für cPanel-DNS

Die Lösungen hier hängen ausschließlich an den Möglichkeiten, die unser Registrar für example.com anbietet. Im Idealfall bietet er eine DynDNS Option an, die vom DSL-Router angesprochen werden kann. Mein Anbieter hat es mir leider nicht ganz so einfach gemacht. Immerhin gibt es aber die Möglichkeit auf das cPanel-API zuzugreifen. Damit kann man also programmatisch DNS-Einträge anzeigen und manipulieren.

Konzept

Somit haben wir also folgendes Konzept: Ein Skript prüft regelmäßig, ob sich die IP-Adresse geändert hat. Falls ja, macht es per cPanel-API ein Update des A-Records von example.com. Somit kann unser DynDNS-Dienst als “normaler” Webserver unter ddns.example.com laufen und ist immer erreichbar.

Tipp: Die TTL für “ddns.example.com” sollte sehr klein (z.B. 60 Sekunden) gesetzt werden.

Meine Anforderungen an einen DynDNS Server sind sehr gering. Ich habe im Familien- und Freundeskreis eine Handvoll Fritzboxen, die per VPN immer erreichbar sein sollen und somit einen DDNS-Eintrag brauchen. Es ist für mich (und die beteiligten anderen) völlig akzeptabel, dass ihre Fritzbox unter familienname.example.com erreichbar ist. Hätten sie einen kommerziellen DynDNS Dienst im Internet wäre es ja schließlich auch nicht anders.

Somit kann ich also ohne Probleme das gleiche Skript für meinen eigentlichen DynDNS Server verwenden, wie ich selbst brauche, um meine eigene IP-Adresse aktuell zu halten.

Umsetzung

Unter https://github.com/raceybe/cpanel-ddns habe ich bereits eine passende Lösung hierfür gefunden. Also im wesentlichen ein PHP-Skript, das per cPanel-API einen beliebigen DNS-Eintrag aktualisiert. Das Readme ist bereits sehr ausführlich und die Umsetzung sollte eigentlich keine Probleme machen. Deswegen beschreibe ich jetzt nur kurz die Einbettung in den Apache-Server und das Zusammenspiel aller Komponenten.

Das zentrale Update.php braucht eine Config.php an seiner Seite mit den Zugangsdaten, um auf das cPanel-API zugreifen zu können. Ausserdem gibt es eine Testclient.php, das ebenfalls eine Config.php braucht, um einen bestimmten Eintrag zu aktualisieren. Der Testclient spricht also mit Update.php auf der Serverseite. Ich nutze den Testclient, um per cron-job meine eigene IP aktuell zu halten. Dafür muß Update.php als Vhost im Apache-server sowohl über Port 80 intern erreichbar sein, als auch über Port 443 unter ddns.example.com aus dem Internet.

Apache Server

Datei /etc/apache2/sites-available/ddns.example.com

Um die Konfiguration des eigentlichen Vhosts nicht für Port 80 und 443 doppelt zu pflegen, habe ich sie ausgelagert:

<VirtualHost *:80>
        Include /etc/apache2/ddns.vhost
</VirtualHost>

<VirtualHost *:443>
        Include /etc/apache2/ddns.vhost

        SSLCertificateFile /etc/letsencrypt/live/ddns.example.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/ddns.example.com/privkey.pem
        Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>

Auf die Anforderung und Einbindung des Let’s-Enctrypt Zertifikats gehe ich jetzt an der Stelle nicht gesondert ein, dafür gibt es genügend Dokumentation im Internet.

Datei ddns.vhost

ServerName ddns.example.com
DocumentRoot /var/www/vhosts/ddns/
DirectoryIndex index.php index.html
ErrorLog /var/log/apache2/ddns.example.com-error.log
TransferLog /var/log/apache2/ddns.example.com-access.log

<Directory /var/www/vhosts/ddns/>
    AllowOverride All
    Options FollowSymLinks
    <IfVersion < 2.3>
        Order allow,deny
        Allow from all
    </IfVersion>
    <IfVersion >= 2.3>
        Require all granted
    </IfVersion>
</Directory>

Datei /var/www/vhosts/ddns/.htaccess

CGIPassAuth on
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

<IfModule mod_rewrite.c>
   RewriteEngine On
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteRule ^(.+)$ $1.php [QSA,L]
</IfModule>

Unter /var/www/vhosts/ddns/ kommt dann die Update.php. Da es mir etwas zu unsicher erschien die Konfigurationsdatei mit den Zugangsdateien im selben Verzeichnis neben die aus dem Internet zugängliche Update.php zu legen, habe ich den Pfad angepaßt und die Config.php unter /var/www/ abgelegt:

require "../../ddns.config.php";

Datei /var/www/ddns.config.php

<?php
/*********************************************************************************
*Config Example
*
*Make sure this file is in the same folder as update.php and is called config.php
*********************************************************************************/
//cPanel API user, password, server, and port
$cpUser = 'username';
$cpPassword = 'MySuperSecretPassword';
$cpServer = 'https://cp188.myregistrar.net';
$cpPort = '2083';

//Authorized ddns users, passwords, and hostnames
$authUsers=array('user1','user2');
$authPasswords=array('Password1','Password2');
$authHostnames=array('example.com','host1.example.com');

Hier können also für jeden zu aktualisierenden Host unterschiedliche Zugangsdaten hinterlegt werden. Der erste Eintrag ist für unseren cron-job, da er den A-Record für die Domain selbst aktualisiert. Jeder weitere Host braucht auch einen Benutzer und ein Passwort.

An der Stelle merkt man auch, dass das ganze nicht für größere Installationen geeignet ist. Aber für meinen Bedarf reicht es locker.

cron-job

Analog und passend zu den Zugangsdaten, die in der ddns.config.php hinterlegt wurden, muss natürlich auch der Testclient konfiguriert werden:

Datei /opt/updateIP/config.php

<?php
/*********************************************************************************
*Config Example
*
*Make sure this file is in the same folder as testclient.php and is called config.php
*********************************************************************************/
//ddns client configuration details
$myip='1.2.3.4';
$hostname='example.com';
$login='user1';
$password='Password1';
$server='ddns.example.com';

Die Variable $myip= wird vom cron-job gesetzt und enthält die aktuelle IP-Adresse, die nach cPanel geschrieben werden soll.

Datei /opt/updateIP/update.sh

#!/bin/bash

oldip=`grep myip= /opt/updateIP/config.php |sed 's/;.*//' |grep -v ^$ |sed s/.*=\ *//`
ip=`curl -4s http://my.ip.fi`

if [ "$oldip" != "$ip" ]
then
        sed -i "s/myip=$oldip/myip='$ip'/" /opt/updateIP/config.php
        cd /opt/updateIP
        php testclient.php
fi

Der Skript holt sich als erstes die bisherige IP-Adresse aus der Config.php. Dann holt er sich über http://my.ip.fi die aktuelle externe IP-Adresse des Anschlusses. Wenn der Router hierfür ein API anbietet, oder das ganze Script vielleicht eh auf dem Router läuft, kann man sich die externe IP-Adresse eventuell auch direkt vom DSL-Router holen. Hat sich die Adresse geändert, trägt er die neue Adresse in der Config.php ein und ruft den Testclient auf.

Damit das funktioniert, muss lediglich sichergestellt sein, dass der cron-job unseren DynDNS-Server auch intern (d.h. wirklich ohne Internet!) unter ddns.example.com erreichen kann. Sollte sich die IP-Adresse nämlich geändert haben, würde ein externer DNS ja auf die alte IP-Adresse auflösen, womit der Update in die Hose geht! Das ganze läßt sich durch einen Eintrag in der /etc/hosts Datei lösen.

Die Datei /opt/updateIP/update.sh kann jetzt per cron, z.B. über /etc/crontab eingebunden werden, sodaß sie beispielsweise zu jeder Stunden in Minute 13 läuft:

  13 *  *  *  * root    /opt/updateIP/update.sh >/dev/null 2>&1

Zusammenfassung

Damit sollten wir jetzt am Ziel sein: Der cron-job hält den A-Record für die ganze Domäne aktuell, womit der DynDNS-Server immer unter ddns.example.com erreichbar ist. Jetzt fehlt nur noch, externe Hosts, wie z.B. eine Fritzbox so zu konfigurieren, dass sie diesen DynDNS-Server verwendet. Die dafür nötige URL ist:

https://user1:Passwort1@ddns.example.com/update?hostname=$hostname&myip=$myip

User1 und Passwort1 müssen natürlich den hinterlegten Zugangsdaten in der Datei ddns.config.php entsprechen. Die genaue Bezeichnung der Variablen $hostname und $myip hängt vom verwendeten Router ab, bei der Fritzbox lauten sie z.B. <domain> und <ipaddr>. Am Besten in der Doku des Routers nachsehen.




Asterisk ConfBridge und Zoom-Meeting

Zoom-Meetings sind in letzter Zeit sehr populär geworden und werden für sehr viele geschäftliche und private Konferenzen und Besprechungen verwendet. Zoom bietet auch die direkte Einwahl und Teilnahme per Telefon an. Die zugehörigen Einwahlnummern sind aber nur in den kostenpflichtigen Accounts sichtbar. Die Option, externe Teilnehmer aus einem Zoom-Meeting heraus anzurufen und mit hereinzuholen, kostet beträchtlichen Aufpreis. Da bei mir aber bereits ein Asterisk mit ConfBridge läuft, kam schnell der Gedanke, die Asterisk ConfBridge mit einem Zoom-Meeting zu verbinden.

Voraussetzung: Asterisk ConfBridge

Wie man einen Asterisk Telefonieserver installiert und konfiguriert würde an der Stelle viel zu weit führen, da dies ein sehr komplexes Thema ist. Ich verweise hierfür auf die offizielle Dokumentation, sowie auf www.voip-info.org, die sehr viele hilfreiche Informationen zum Thema bereit hält.

Asterisk sollte also bereits mit dem Modul ConfBridge installiert sein und laufen. Interne Telefone sollten eingerichtet sein und von innen und außen erreichbar sein. Die Telefonkonferenz sollte ebenfalls von innen und außen erreichbar sein.

Damit kann’s dann mit der Zoom-Anbindung losgehen:

Konzept

Im Groben funktioniert das Verbinden von Asterisk ConfBridge und Zoom-Meeting so, dass man in der Telefonkonferenz eine bestimmte Taste drückt. Asterisk fragt dann die Einwahlnummer, die Meeting-ID, sowie das Passwort über die Telefontastatur ab. Nach Eingabe all dieser Nummern wählt Asterisk sich in das Zoom-Meeting ein und verbindet das Telefonat dann mit der Telefonkonferenz.

Asterisk ConfBridge erweitern

Eine bisher nicht belegte Taste wird verwendet, um mithilfe der Funktion “dialplan_exec” in den Dialplan zu verzweigen. Hier ein Beispiel:

 [sample_user_menu]
type=menu
*=playback_and_continue(conf-usermenu)
*1=toggle_mute
1=toggle_mute
*2=dialplan_exec(addcaller,1,1)
2=dialplan_exec(addcaller,1,1)
*3=dialplan_exec(addcaller,2,1)
3=dialplan_exec(addcaller,2,1)
*4=decrease_listening_volume
4=decrease_listening_volume
*6=increase_listening_volume
6=increase_listening_volume
*7=decrease_talking_volume
7=decrease_talking_volume
;*8=leave_conference
;8=leave_conference
*9=increase_talking_volume
9=increase_talking_volume

Ich habe hier sogar 2 Tasten für die Einbindung externer Teilnehmer verwendet. Eine für Zoom-Meetings, die andere für normale externe Telefonnummern.

Die Funktion dialplan_exec(addcaller,1,1) verzweigt in den Kontext “addcaller” mit der Extension “1” und der Priorität “1”.

dialplan_exec(context,exten,priority)

The dialplan_exec action allows a user to escape from the conference and execute commands in the dialplan. Once the dialplan exits the user will be put back into the conference. The possibilities are endless!

Asterisk Doku

Dialplan für Zoom-Meeting

Im Dialplan passiert nun folgendes:

[addcaller]
; add Zoom-Meeting   
exten => 1,1,NoOp(${CALLERID})
 same = n,Read(CALL_NUMBER,enter-conf-call-number,25,2,60)
 same = n,Playback(auth-thankyou)
 same = n,Read(GLOBAL(MEETING_ID),enter-conf-call-number,13,2,30)
 same = n,Playback(auth-thankyou)
 same = n,Read(GLOBAL(PWD),enter-conf-call-number,13,2,30)
 same = n,Playback(auth-thankyou)
 same = n,Originate(PJSIP/${CALL_NUMBER}@T_9331206,exten,Features,92,1)

; add Mobile-Number    
exten = 2,1,NoOp(${CALLERID})
 same = n,Read(CALL_NUMBER,enter-conf-call-number,25,2,60)
 same = n,Playback(auth-thankyou)
 same = n,Originate(PJSIP/${CALL_NUMBER}@VM_tom,exten,Features,91,1)
  • Read() (Doku) Liest mit der Aufforderung aus Enter-conf-call-number eine Zahlenfolge über die Telefontastatur ein und speichert sie in der Variablen CALL_NUMBER.
  • GLOBAL() (Doku) Ist um die Variable herum ist nötig, da CALL_NUMBER eine globale Variable ist.
  • Originate() (Doku) Macht den eigentlichen Anruf und verzweigt in den Kontext “Features” mit der Extension “91”, sobald der Gegenpart (Zoom) den Anruf annimmt.
[Features]
exten = 91,1,Verbose(1, "Zoom-Meeting wird der Konferenz hinzugefügt.")
 same = n,Answer()
 same = n,Wait(3)
 same = n,Confbridge(conf1,,,sample_user_menu)
 same = n,Hangup()

Im Kontext “Features” (die Kontexte können übrigens heißen wie sie wollen) wird der Anruf dann über die Funktion Confbridge() (Doku) der bestehenden Konferenz hinzugefügt. Hierbei ist nur wichtig, dass mit “conf1” die selbe Konferenz genannt wird, die ja bereits besteht.

Die Konferenz hat jetzt somit mindestens 2 Teilnehmer, der interne Anrufer, die per Tastendruck die Anwahl des Zoom-Meetings veranlaßt hat, sowie das Zoom-Meeting als weiteren Teilnehmer. Natürlich können sich parallel noch beliebig viele andere Telefonteilnehmer in die Konferenz einwählen.




LED-Leiste als Hue-Lampe ansprechen

Nachdem ich mir eine hübsche RGB-LED Leiste mit einem ESP32 zusammengebaut habe und diverse Arduino-Sketche mit allen möglichen Lichteffekten zusammen kopiert habe, entstand nach einiger Zeit doch der Wunsch, die LED-Leiste als Hue-Lampe ansprechen zu können.Der Vorteil ist eine einheitliche Bedienung über die Hue-App, sowie die Möglichkeit alle Lichter auf einen Schlag auszuschalten.

Wie funktioniert das?

Um eine LED-Leiste als Hue-Lampe ansprechen zu können, muss zunächst eine virtuelle Hue-bridge auf einem Host (z.B. als Docker Container) installiert werden. Diese emulierte Hue-bridge gibt sich gegenüber dem LAN und somit auch gegenüber der Hue-App als originale Bridge aus. Auf der anderen Seite ist sie aber in der Lage auch andere (nicht-Hue-)Lampen einzubinden. Ich habe ein paar verschiedener solcher Hue-Emulationen gefunden, aber am besten und flexibelsten ist meiner Meinung nach “diyHue”, zu finden auf GitHub (https://github.com/diyhue/diyHue). Man muss aber sagen, dass die Dokumentation ziemlich schlecht ist. Ich habe die Schritte zur Installation und Konfiguration deshalb hier zusammen gestellt: diyHue bridge emulation installieren

Das schöne ist aber, dass man die LED-Leiste in beliebig viele “virtuelle” Hue-Lampen aufteilen kann, die alle einzeln über die Hue-App wie klassische Hue-Lampen steuerbar sind. Dabei kann man angeben, wie viele “Pixel” (=Leds) jede Lampe “breit” sein soll und wie viele Pixel zwischen den Lampen “frei” bleiben sollen.

LED-Leiste vorbereiten

Hier gehe ich jetzt nicht auf die Hardwareseite ein, das würde zu weit führen. Es gibt viele Tutorials im Netz, wie man eine RGB-Led-Leiste an einen Arduino-kompatiblen Microcontroller anschließt und verkabelt. Ich habe mich für SK9822-Leds entschieden, das sind billige Clone der APA102, die aus einzeln steuerbaren RGB-Leds bestehen. Als Controller verwende ich ein ESP32-Dev-Board von Espressif, weil es leistungsfähig ist (240 MHz, 4 MB Flash) und mit Bluetooth und Wifi alles mitbringen, was man braucht.

Zum Programmieren habe ich die Arduino-IDE verwendet. Ein Beispiel-Sketch ist unter https://github.com/linuzer/Arduino-HueAPI zu finden.

Natürlich müssen ein paar Dinge an die entsprechenden Gegebenheiten angepasst werden:

NUM_LEDS Gesamtzahl der physikalischen LEDs in der Leiste
DATA_PIN
CLOCK_PIN
Arduino-Pins, an denen die LEDs angeschlossen sind
COLOR_ORDER
LED_TYPE
Reihenfolder der Farben. In der Doku der LEDs nachsehen
HUE_(…) Der interessanteste Teil, konfiguriert die Anzahl und Größe der einzelnen virtuellen Hue-Lampen.
useDhcp selbstredend. Wenn True, dann muss keine IP-Adresse konfiguriert werden.
ConnectMQTT() Hier müssen IP, Port und Topic, sowie Client-Name angepasst werden.
class HueApi Hier wird die Kommunikation mit der diyHue gekapselt, sowie Hue-spezifische Routinen abgelegt.
StateGet()
StatePut()
Hier passiert die eigentliche Kommunikation mit der diyHue.
Detect() Diese Methode produziert die richtige Antwort auf die Suchanfrage der diyHue,
sodass die LED-Leiste als Hup-Lampe gefunden werden kann.

Am Ende den Sketch einfach per USB-Kabel auf den ESP flashen. Ist das erfolgreich geschehen, sucht man mit dem Handy nach dem unverschlüsseltem WLAN des ESP und verbindet sich. Die SSID sollte die sein, die man mit HUE_Name festgelegt hat.

Sodann kann man unter http://192.168.4.1 auf die Lampe zugreifen und das eigentliche WLAN konfigurieren. Nach einem Neustart, sollte die Lampe im WLAN auftauchen.

Am besten testet man jetzt auch gleich das Arduino OTA-Update (Over-the-air Flashing). Wenn das auch funktioniert, kann man den ESP an seinem endgültigen Bestimmungsort verbauen.

LED-Leiste als Hue-Lampe ansprechen

Wenn bis hierhin alles geklappt hat, öffnet man die Hue-App auf dem Handy und sucht nach einer neuen Bridge. Eventuell muss man die IP-Adresse der diyHue bridge manuell eingeben. Ist die App mit der diyHue verbunden, sucht man nach neuen Lampen. Es kann einen Moment dauern (der sogar länger als der Timeout der App-Suche sein kann) und dann sollte die App eine (oder mehrere) neue Hue-Lampen vermelden. Diese kann man jetzt in gewohnter Art und Weise konfigurieren, sie Räumen, Szenen und Gruppen zuordnen, umbenennen, etc. … und natürlich steuern.

An dieser Stelle ist man dann am Ziel. Jetzt kann man die selbstgebaute LED-Leiste als Hue-Lampe ansprechen. Jede virtuelle Hue-Lampe ist einzeln in der App zu sehen und kann entsprechend gesteuert werden.

Troubleshooting / Debugging

Bei Arduino Compiler-Fehlern genau lesen und alle Fehler beheben. Dabei immer mit dem obersten (!) Fehlern beginnen, der Rest können (verwirrende) Folgefehler sein. Am Anfang ist es wahrscheinlich, dass noch benötigte Bibliotheken nicht installiert sind (z.B. die ArduinoJson v6).

Die Funktionen zum Laden und Wiederherstellen der gespeicherten Settings sind bewußt auskommentiert (in Setup() ), weil sie am Anfang Probleme machen können, wenn es noch keine gespeicherte Konfiguration auf dem ESP gibt.

Wenn das Flashen geklappt hat, die Lampe aber nicht gefunden wird, dann mit dem Arduino Serial-Monitor (nur per USB-Kabel möglich) prüfen, ob es Fehlermeldungen gibt. Allerdings muss man echt Geduld haben, die Lampen tauchen häufig erste nach (viel-)maligem suchen in der App auf. Hintergrund ist, die diyHue sucht alle IP-Adressen im Netz ab und schaut, ob eine Lampe antwortet. Dabei ist das Zeitfenster pro IP-Adresse sehr klein… für einen ESP, der im WLAN hängt u.U. zu klein, sodass er nicht beim ersten Ping gefunden wird.

Sobald das Wifi konfiguriert ist, am Router prüfen, welche IP-Adresse vergeben wurde.

Per Serial-Monitor prüfen, ob die Lampe sich am MQTT-Broker anmelden konnte. Falls ja, ist man einen großen Schritt weiter: Ab jetzt kann man sich die wichtigsten Log-Meldungen auch per MQTT-Client auf dem Topic “iot/ledcontroller/log” abholen. Damit ist auch ein remote Debugging per WLAN möglich.

Jetzt kann man auch zu Debug-Zwecken weitere Log()-Kommandos in den Arduino-Code einbauen, um sich per MQTT über den Status informieren zu lassen.

Zur weiteren Fehleranalyse empfiehlt es sich den Debug-Modus auf der diyHue einzuschalten und mit tail -f ...log | grep "z.b. IP-Adresse" nach relevanten messages filtern zu lassen. Details hierzu siehe meinen Artikel, oder die offizielle Doku. Mit den MQTT-Messages (was die Led-Leiste abschickt) und mit dem diyHue-Log (was dort ankommt und die Bridge damit macht) sollte es dann möglich sein allen Problemen auf die Schliche zu kommen.




diyHue installieren

diyHue (GitHub) ist eine virtuelle Hue-bridge, die nicht nur eine originale Philips Hue-bridge mit ihren verbundenen Hue-Lampen ansprechen kann, sondern über eigene APIs eine breite Palette von anderen Lampen einbinden kann. Da sie aber netzwerkseitig das originale Hue-API spricht, kann sich die originale Hue-App (und alle Alternativen) an diyHue anmelden, und alle eingebundenen Lampen genau so steuern, wie die original Hue-Lampen. Wie man diyHue installieren kann, zeige ich in dieser Anleitung.

Das Schaubild auf GitHub gibt einen guten Überblick, was alles eingebunden werden kann.

diyHue installieren

Leider ist die Dokumentation des Projekts ziemlich schlecht, sodass es nicht besonders leicht ist, alles richtig hinzukriegen. Deshalb dokumentiere ich hier die Schritte, mit denen ich Erfolg hatte. Natürlich kann man die virtuell Hue-Bridge diyHue auch anders installieren, meine Lösung erhebt weder den Anspruch, die einzig Richtige, noch die Beste zu sein.

Ich habe mich für eine Installation in Docker entschlossen, da somit alles recht sauber gekapselt ist. Wer Docker noch nicht installiert hat, sollte sich erstmal ein bisschen einlesen, das führt jetzt hier zu weit, aber die Installation ist denkbar einfach.

Netzwerk

Dann muss man sich über das Netzwerk kurz Gedanken machen. Der diyHue container muss ja direkt aus dem Netz erreichbar sein. Ich habe mich entschlossen, meine Docker-container über eine macvlan-bridge direkt ins LAN zu hängen, ohne das sonst bei Docker übliche NAT Subnetz.

Mein LAN hat die Adresse 192.168.3.x mit Subnet 255.255.255.0. Die oberen 32 Adressen, von 192.168.3.224 – 192.168.3.255 möchte ich für meine Container reservieren.

Achtung! Diesen Bereich vom DHCP-Server des LAN ausklammern!

Die entsprechende network bridge erzeugt dann dieser Befehl:

docker network create -d macvlan --subnet=192.168.3.0/24 --ip-range=192.168.3.224/27 --aux-address 'host=192.168.3.224' --gateway=192.168.3.1 -o parent=eth0 pub_net

--aux-address 'host=192.168.3.224' Diese Adresse wird vom Docker-DHCP ausgenommen. Sie dient der Verbindung zwischen Host und Docker-Netzwerk.

-o parent=eth0 Das normale network interface des Hosts, bitte entsprechend anpassen.

pub_net Der Name der Bridge an den persönlichen Geschmack anpassen.

Als nächstes müssen wir eine Eigenheit von Docker, bzw. dem macvlan Treiber umgehen. Docker filtert standardmäßig den Traffic zwischen dem Host und den Containern heraus. Umgehen lässt sich das mit einem 2. Netzwerk-interface, welches wir unter /etc/network/interfaces eintragen. Die Befehle gelten alle für Debian/Ubuntu, auf anderen Distributionen könnte es anders sein.

auto docker-bridge
iface docker-bridge inet static
        address 192.168.3.224
        netmask 255.255.255.224
        pre-up ip link add docker-bridge link eth0 type macvlan mode bridge
        post-down ip link del docker-bridge link eth0 type macvlan mode bridge
        up route add -net 192.168.3.224 netmask 255.255.255.224 gw 192.168.3.1
        down route del -net 192.168.3.224 netmask 255.255.255.224 gw 192.168.3.1

Wir verwenden hierfür die oben ausgeklammerte Adresse 192.168.3.224. Der Name “docker-bridge” kann beliebig sein. Das Haupt-Interface “eth0” muss entsprechend angepasst werden. Es wird auch gleich die nötige Route mit angelegt.

Nach dem Speichern einmal service networking restart ausführen (oder analog auf anderen Systemen), um alles zu aktivieren.

Container starten

Damit können wir nun den eigentlichen Container installieren:

docker run --name=diyHue --env="IP=192.168.3.225" --env="DEBUG=true" --env="MAC=dc:fe:07:e1:80:e6" --volume="/mnt/hue-emulator/:/opt/hue-emulator/export/:rw" --network=pub_net -p 1982:1982/udp -p 1900:1900/udp -p 443:443 -p 80:80 -p 2100:2100/udp --restart=always --detach=true diyhue/core:latest

Tipp: Wer sich den Befehl eines bereits laufenden Containers nicht aufgeschrieben hat und wieder anzeigen möchte, findet hier eine Lösung!

--env="IP=192.168.3.225" Sofern man dem Container eine feste IP zuweisen möchte.

--env="DEBUG=true" Aktiviert den Debug-Modus. Details siehe https://diyhue.readthedocs.io/en/latest/AddFuncts/debug.html. Ansehen kann man sich das Logfile mit tail -f /var/lib/docker/containers/(ID)/(ID).log. Die (ID) bekommt man mit docker ps -a. Wenn alles läuft, nicht vergessen den Debug-Mode wieder auszuschalten! …sonst wird das Logfile ziemlich groß…!

--network=pub_net Das oben konfigurierte Netzwerk, ggf. den Namen anpassen.

-p 1982:1982/udp sowie die anderen: Diese Ports müssen freigegeben werden.

diyhue/core:latest Name des Containers, neueste Version.

diyHue konfigurieren

Wenn alles läuft, sollte sich die diyHue unter http://192.168.2.225 (oder die IP, die sie bekommen hat) erreichen lassen.

(Screenshot von installierter diyHue

originale Hue-bridge koppeln

Zunächst wird die Anzeige leer sein, denn als erstes muss die echte Hue-bridge gekoppelt werden. Wie das genau geht, steht unter https://diyhue.readthedocs.io/en/latest/configuration.html

In Kurz: Unter “Import from bridge” die IP-Adresse der echten Hue-bridge eintragen, den Hardware-Button auf der echten Bridge drücken, dann OK drücken. Wenn alles klappt, bestätigt eine kurze Meldung, wie viele Hue-Lampen importiert wurden.

Hue-App koppeln

Sehr wahrscheinlich werden jedoch immer noch keine Lampen angezeigt, denn es fehlt noch ein Raum, oder eine Gruppe, in die die Lampen zugeordnet werden. Leider bietet diyHue hier keine Möglichkeit das zu erledigen. Aber über die originale Philips-Hue App kann man diesen Schritt ausführen. Zum koppeln, läßt man die Hue-App nach einer neuen Bridge suchen. Wird sie nicht von alleine gefunden, gibt man über “Hilfe” die IP-Adresse direkt ein. Zum finalen Verbinden, muss man sich auf der diyHue-Seite unter “Link device” mit Benutzer “Hue” und Passwort “Hue” anmelden. Der anschließende Klick auf “Activate” stellt den Hardware-button dar. Jetzt kann man innerhalb 30 Sekunden die Hue-App koppeln. Wenn auch dies geklappt hat, sollten in der Hue-App alle Lampen wie bisher zu sehen sein. Auch die diyHue-Seite sollte jetzt alle Lampen anzeigen.

Wenn jetzt in der diyHue andere (nicht-Hue-)Lampen importiert werden, werden diese sofort auch in der Hue-App angezeigt und können über diese gesteuert werden.

mögliche Probleme

Die Hue-App findet die diyHue nicht

Über “Hilfe” die IP-Adresse von Hand eingeben

Die Hue-App kann nicht Verbunden werden

Es kann eine Hilfe sein, die Hue-App komplett zu löschen (mit allen Daten) und neu zu installieren.

Die diyHue-Seite zeigt keine Lampen an

Eine Möglichkeit ist, dass noch keine Lampen von der originalen Hue-Bridge importiert worden sind.

Eine andere Möglichkeit ist, dass die Hue-App noch nicht gekoppelt ist und somit noch kein einziger Raum oder Gruppe angelegt werden konnte.




Docker run command rekonstruieren

Ich hatte bereits mehrfach das Problem, dass ich mir den konkreten Befehl zum Starten eines bestimmten Docker-Containers nicht dokumentiert hatte, ihn aber hinterher wieder gebraucht habe. Aber es gibt einen Trick, mit dem man den Docker run command rekonstruieren kann.

Als erstes mit diesem Befehl alle laufenden Container anzeigen lassen:

docker ps -a

Hier die “Container ID” des gewünschten Containers kopieren.

Dann:

docker run --rm -v /var/run/docker.sock:/var/run/docker.sock assaflavie/runlike e0aced1e4011

Hierbei die Container ID hinten durch die oben kopierte ersetzen.

Der Befehl lädt einen speziellen Docker-Container temporär herunter und führt ihn aus. Als Ausgabe wird der run-Befehl des gefragten Containers angezeigt.