Für meine Diagramme und so nutze ich eine Influx DB + Grafana. Die Daten hatte ich mal über den influxDb History Adapter von ioBroker gemacht, aber da gab´s immer mal Fehler. Also sende ich die Daten jetzt selbst. So wie ich sie brauche und nur die Daten die ich brauche. In den wenigen Tagen wo ich den ioBroker Adapter laufen lies sind auch über 1GB Daten angefallen.. das bedeutet allein schon knapp 50GB pro Jahr – das wäre Wahnsinn.
Jetzt hatte ich aber das Problem, dass die Influx wohl überlastet ist. Es gab immer wieder Error 500 oder Timeout Probleme. Also dachte ich mir, ich reduziere die Anzahl der Requests auf die DB. Bisher habe ich jeden Wert einzeln gesendet und auch gedrosselt gab es Probleme. Deswegen jetzt ein Subflow: Jetzt fasse ich eine Messeinheit als ein Objekt zusammen und sende das – 1x pro Minute.
Messwerte in die InfluxDB
Bisher: Also der Messwert kommt, wird per Funktion dem Feld zugeordnet und eventuell konvertiert und dann über eine Drosselung zur InfluxDB geschickt.
Jetzt neu gibt es ein Subflow der die Werte speichert und wenn welche vorhanden sind schickt er diese gebündelt ab. Aus einem Objekt mit einem Messwert wird also ein Objekt mit beliebig vielen Objekten. Das Measurement und die Haltezeit sind einstellbar.
Zum Ablauf: Die Werte kommen rein, werden in einem Javascript Objekt gespeichert. Jede Sekunde wird per Inject geprüft wie lange die letzte Ausgabe her ist und in einer Flow-Variable gespeichert. 300ms später prüft der Switch ob die Differenz größer ist als in den Eigenschaften definiert und wenn ja werden die Werte ausgegeben. Dabei wird auch das Objekt zurück gesetzt. Sprich geleert. Für das Senden hätte man auch das Gate aus dem letzten Beitrag nutzen können 🙂
Hier der Node Red Code für den Subflow:
[{"id":"b1ba376d.4bdd98","type":"subflow","name":"Payload Merge","info":"# Eingang\nmsg.payload muss ein Objekt sein. Alle Felder werden gespeichert und nach der definierten Zeit gesandt. \n\nKommt in der Zeit ein Messwert 2x wird der alte überschrieben!\n\n# Ausgang\nWenn werte eingelaufen sind kommt nach der definierten Zeit ein payload Objekt mit allen Feldern.\n\n# Reset\nwenn das Topic reset lautet wird alles zurück gesetzt - zum testen eher gedacht.","category":"","in":[{"x":40,"y":100,"wires":[{"id":"5dc702f5.f3a23c"}]}],"out":[{"x":880,"y":340,"wires":[{"id":"2014191d.a97366","port":0}]}],"env":[{"name":"SendInterval_in_sek","type":"num","value":"60"},{"name":"measurement","type":"str","value":""}],"color":"#DDAA99","inputLabels":["Messwerte"],"outputLabels":["zusammengefasstes Messwerte Objekt"],"status":{"x":360,"y":180,"wires":[{"id":"5dc702f5.f3a23c","port":1}]}},{"id":"c328ed2e.4af27","type":"function","z":"b1ba376d.4bdd98","name":"Werte speichern","func":"var data = flow.get('data') || \"{}\";\ndata = JSON.parse(data);\n\nvar payload = msg.payload;\n\nif (!(typeof payload === 'object' && payload !== null)){\n return msg;\n}\n\nvar now = Math.round((new Date()).getTime() / 1000);\n\nfor(var key in payload){\n data[key] = payload[key];\n}\n\n// speichern\nflow.set('data', JSON.stringify(data));\n\nreturn msg;","outputs":1,"noerr":0,"x":400,"y":120,"wires":[[]]},{"id":"99f17cad.7e5c8","type":"inject","z":"b1ba376d.4bdd98","name":"","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":260,"wires":[["3badf961.3ca726","f556411c.2ce3a"]]},{"id":"2014191d.a97366","type":"function","z":"b1ba376d.4bdd98","name":"werte ausgeben","func":"var data = flow.get('data') || \"{}\";\ndata = JSON.parse(data);\n\n\nvar now = Math.round((new Date()).getTime() / 1000);\n\nvar anzahl = 0;\nfor(var key2 in data){\n anzahl++;\n}\n\n\n// speichern\nflow.set('data', \"{}\"); // leeren\nflow.set('lastUpd', now);\n\nmsg.measurement = env.get('measurement')||\"\";\nmsg.payload = data;\n\nif (anzahl == 0) return;\n\nreturn msg;","outputs":1,"noerr":0,"x":700,"y":340,"wires":[[]]},{"id":"5dc702f5.f3a23c","type":"switch","z":"b1ba376d.4bdd98","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"reset","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":170,"y":100,"wires":[["3b41e132.44415e"],["c328ed2e.4af27"]]},{"id":"3b41e132.44415e","type":"function","z":"b1ba376d.4bdd98","name":"reset","func":"\n\n// speichern\nflow.set('data', \"{}\");\nflow.set('lastUpd', 0);\nnode.warn(\"RESET\");\n\nreturn msg;","outputs":1,"noerr":0,"x":370,"y":60,"wires":[[]]},{"id":"f6973c1a.70a6","type":"switch","z":"b1ba376d.4bdd98","name":"","property":"SendDiff","propertyType":"flow","rules":[{"t":"gt","v":"SendInterval_in_sek","vt":"env"}],"checkall":"true","repair":false,"outputs":1,"x":550,"y":340,"wires":[["2014191d.a97366"]]},{"id":"3badf961.3ca726","type":"function","z":"b1ba376d.4bdd98","name":"berechnet SendInterval_in_sek","func":"var lastUpd = parseInt( flow.get('lastUpd') || 0);\nvar now = Math.round((new Date()).getTime() / 1000);\n\nvar SendInterval_in_sek = parseInt(env.get('SendInterval_in_sek') || 30);\n\nvar diff = now - lastUpd;\n//node.warn(\"Diff: \"+ diff);\nflow.set('SendDiff', diff);\n\nreturn msg;","outputs":1,"noerr":0,"x":430,"y":260,"wires":[[]]},{"id":"f556411c.2ce3a","type":"delay","z":"b1ba376d.4bdd98","name":"","pauseType":"delay","timeout":"300","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":360,"y":340,"wires":[["f6973c1a.70a6"]]}]
Die Fehler sind nicht ganz weg – aber schon weniger. Man könnte nun noch die payloads die da kommen in einen Batch zusammen fügen 🙂 – das später.
Hat man Sensoren anderer Hersteller als Homematic möchte man diese vielleicht eben auch a) zur Steuerung nutzen oder b) diese “sehen” über HomeKit zum Beispiel.
Ich verwende von Xiaomi den Temperatur- und Feuchtigkeitssensor. Der Nebenbei auch noch den Luftdruck misst. Der Sensor ist gefühlt nur 3×3 cm groß 🙂 also super klein. Funken kann der Sensor mittels ZigBee. Und so wird das System aussehen:
Der Sensor wird über den ConBee II im Daemon DECONZ angemeldet. DECONZ bietet hier eine API an womit man den abfragen kann. DECONZ läuft bei mir als Docker-Container. Gleichzeitig läuft auf der CCU3 der CUx-Daemon der virtuelle Geräte erstellen kann ( ja der könnte auch zigbee 😉 ) Kernstück ist dann Node Red – was die ganzen Dienste verknüpft. Es holt sich die Daten von Deconz und schaufelt sie in die CCU3, in die InfluxDB und aus der CCU3 gelangen die Geräte ins HomeKit
Vorbereitungen
Über den CUxD legen wir ein Thermostat an
in der CCU3 muss das CUxD-Gerät konfiguriert und einem Raum zugewiesen werden.
Der Sensor muss im DECONZ angelernt werden.
Anlegen im CUxD
Dazu in der CCU3: Einstellung >> Gerätemanager >> CUx-Daemon auswählen Im CUx-Daemon dann im Menü oben “Geräte” auswählen und wie folgt einrichten:
Konfig in der CCU3
In der CCU3 erscheint das Gerät nun im Posteingang, kann einem Raum/Gewerke zugeordnet werden und mit Fertig in die Geräteliste übernommen werden.
hier sind nun einmal der Mode auf “Temp+HUM” zu setzen und “USE_HMDATAPT” ist zu deaktivieren.
Anlernen in der Phoscon App / DECONZ
Dazu einfach auf Einstellungen >> Sensoren gehen und “Neuen Sensor verbinden” anklicken. Am Sensor selbst musste ich die Taste nur einmal kurz betätigen. Der Sensor blinkte blau und zack waren beide verbunden.
Im Node RED
Für den Zugriff auf DECONZ benötigen wir das Modul: node-red-contrib-deconz. Und das Modul node-red-contrib-ccu.
Hier wird nun der Wert vom Sensor abgefragt, kurz umgewandelt (division durch 100) und dann a) der CCU3 gemeldet (dazu gleich mehr) und in meinem Fall sende ich das noch an die Influx DB.
Um den Wert in der CCU3 ist es wichtig das Gerät wie oben beschrieben zu konfigurieren und dann die SET_HUMIDITY bzw. SET_TEMPERATURE zu setzen und nicht HUMIDITY oder TEMPERATURE direkt!
Anzeigen im HomeKit
Das ist wohl der einfachste Teil. Mithilfe des Node Red Paketes redmatic-homekit muss man nur, wie in der Anleitung gut beschrieben, eine Home Bridge in einen Flow ziehen und schon werden alle CCU3 Geräte veröffentlicht – vollautomatisch. 🙂
Alternativ kann man das auch händig machen. Die DECONZ Adapter haben alle 2 Ausgänge. Der erste immer für das Datenobjekt bzw. den State (je nach Einstellung) und der 2. ist der Service Ausgang für ein Universalgerät im HomeKit. siehe https://github.com/rdmtc/RedMatic/wiki/Homekit#universal-accessory
Die Schüler der Bienenschule haben die folgende Aufgabe leider nicht lösen können und müssen nun in den Ferien üben. Kannst du die Aufgabe lösen? Wenn nicht hilft vielleicht etwas Honig 🙂
Es gibt 7 Bienenwabe mit je 6 Kanten. Jede Kante hat eine Zahl aus dem Bereich 1-6. Keine Zahl ist doppelt.
Die Waben sind so zu legen, dass eine Wabe in der Mitte liegt und die restlichen 6 Bienenwaben darum angelegt werden. Dabei soll die Zahl der angelegten Wabe zu der Zahl an der Kante passen wo es angelegt wird. Zusätzlich muss die rechte bzw. linke Kante der angelegten Wabe mit der benachbarten angelegten Wabe überein stimmen.
Eine Lösung – leider passt sie nicht.
Fast richtig – aber nicht die richtige Lösung.
So trival wie es ausschaut ist die Aufgabe bei weitem nicht! Es gibt 7 Waben die in der Mitte liegen können. Die restlichen 6 können beliebig verteilt liegen und haben dazu noch 6 Möglichkeiten eine Stellung einzunehmen.
Wenn ein Stein in der Mitte liegt kann man ihn an 6 verschiedenen Stellen anlegen und hat dafür 6 Stellungen. Bei der nächste Wabe hat man noch 5 Stellen wo man anlegen kann, aber immer noch 6 Stellungen. Bei der nächste Wabe hat man noch 4 Stellen usw. Diese Möglichkeiten multipliziert man miteinander = 36*30*24*18*12*6 = 33.592.320 Jetzt noch die 7 Möglichkeiten beachten ergibt eine Zahl von 235.146.240 Möglichkeiten für dieses Rätsel
Die Aufgabe
Finde die Kombination wo die oben genannte Bedingung richtig ist 🙂
Kleine Hilfe
List waben = new List()
{
new Spielstein(1, new[] {1, 2, 3, 4, 5, 6}),
new Spielstein(2, new[] {1, 6, 4, 2, 5, 3}),
new Spielstein(3, new[] {1, 4, 3, 6, 5, 2}),
new Spielstein(4, new[] {1, 4, 6, 2, 3, 5}),
new Spielstein(5, new[] {1, 6, 2, 4, 5, 3}),
new Spielstein(6, new[] {1, 6, 5, 4, 3, 2}),
new Spielstein(7, new[] {1, 6, 5, 3, 2, 4})
};
Ikea bietet für 6€ einen günstigen ZigBee-fähigen Dimmer an (https://www.ikea.com/de/de/p/tradfri-kabelloser-dimmer-weiss-70408595/). Diesen habe ich über einen ConBee II (Dresden Elektronik) angebunden. Ich möchte ihn über Node Red steuern können. Dafür nutze ich den Adapter node-red-contrib-deconz 1.1.10 und ioBroker mit dem Tradfri Adapter.
Der Taster liefert 6 verschiedene Statuscodes:
1002 – Eingeschalten (I)
2002 – Ausgeschalten (O)
1001 – Start von I wird gedrückt gehalten (Long PRESS)
1003 – Ende von I wird gedrückt gehalten
2001 – Start von O wird gedrückt gehalten (Long PRESS)
2003 Ende von O wird gedrückt gehalten
(c) IKEA 2020
Mit den Werten 1002 und 2002 kann man nun einfach über einen switch ein on/off Beispiel bauen.
hier ein Beispiel für den Heizstrahler der nach dem Anschalten maximal. 15min an sein darf. Das kann durch erneutes drücken wiederholt bzw. auf 15min zurück gesetzt werden. “Display Payload” ist ein Subflow der den msg.payload auf Status mit schickt und somit unter dem Element anzeigt. Nachfolgend der Code dafür
Wir wollen nun aber auch das lange drücken mit Auswerten. Dazu nutze ich zusätzlich noch node-red-contrib-simple-gate um ein Element zu haben, was ähnlich einem Tor steuerbar ist und den Inhalt einfach durchleitet.
Beispiel Gate
Die Idee zum dimmen ist nun folgende: Mit dem langen Drücken von I oder O (1001 bzw. 2001) wird das Gate geöffnet und mit 1003 bzw. 2003 (Loslassen von I bzw. O) wird das Gate geschlossen. Zyklich wird nun ein Inject (einmal pro Sekunde) auf das Gate geschickt. Kommt es durch wird der Helligkeitswert geändert.
Jetzt muss man vorab noch die “Richtung”, also hoch zählen oder runter zählen definieren. Dazu werten wir auch den Start des langen Drückens mit aus und setzen intern den Wert für die Zählrichtung.
Kommt nun ein Wert durch das Gate wird der aktuelle Wert plus gerechnet mit dem Produkt aus dem Wert wie stark es sich ändern soll und der Richtung (1 für hoch und -1 für runter) also wenn der Wert 50 ist und die Stufenbreite 10 wäre das beim Runter zählen: 50 + (10 * -1) = 40. Geht der Wert über die Grenze von 1-100 hinaus wird er zurück gesetzt auf den Grenzwert.
var brightness = parseInt(flow.get('Brightness')) || 1;
var step = parseInt(env.get('StepBrightness')) || 10;
var direction = parseInt(msg.payload) || 1;
var newBrightness = brightness + (step * direction);
if (newBrightness > 100) newBrightness = 100;
if (newBrightness < 1) newBrightness = 1;
flow.set('Brightness', newBrightness); // save new value
msg.payload = newBrightness;
return msg;
Anschließend wird der neue Helligkeitswert aus gegeben.
Doppelklick
Auch einen Doppelklick kann man realisieren. In der Ereignisgetriebenen Entwicklung kennen wir aber nicht die Dimension Zeit. Das heißt wir wissen nicht was man vorher geklickt hat, wie lange das her ist oder wie oft. Daher werden wir vorher ein Subflow bauen, welcher speichert wie oft ein Ereignis in einer bestimmten Zeit kam. Das geht ganz einfach, indem wir den Zeitstempel (Eintreffen eines Events) in ein Array legen und im Flow speichern. Danach werden alle Einträge entfernen, welche älter sind als unser Messzeitraum. Und am Ende zählen wir die Elemente und geben die Zahl aus.
Das sieht erstmal recht unspektakulär aus:
var now = Math.floor(Date.now() / 1000 ) || 0;
function RemoveOld(ts){
var diff = env.get('TimeRangeInSeconds') || 5;
var now = Math.floor(Date.now() / 1000 ) || 0;
var now2 = now - diff;
return Math.floor(ts) > now2;
}
var data = flow.get('data') || "[]";
data = JSON.parse(data);
data.push(now);
data=data.filter(RemoveOld);
msg.payload = data.length;
flow.set('data', JSON.stringify(data));
return msg;
Über Umgebungsvariablen wird hier die Zeit übergeben. Wird der Wert nicht gesetzt wird 5 als Standard angenommen.
Mit diesem Subflow können wir nun den Wert 1002 zählen lassen. Ist dieser größer/gleich 2 können wir ein Doppelklick-Ereignis ausführen. Ich will hier eine definierte Helligkeit setzen.
Der ganze Subflow gibt natürlich auch noch einen Status aus, weswegen die Linien etwas kreuz und quer gehen. Aber hier jetzt endlich der Überblick:
Im oberen Teil ist die simple on/off-Logik, in der Mitte das Dimmen und dadrunter der Doppelklick. Die Initialisierung der Helligkeit findet ganz unten statt wo der Wert einmalig in die Flow-Variable geschrieben wird.
Wie ihr seht, hat der Subflow 2 Ausgänge. Der 1. Ausgang steuert die Helligkeit und der 2. den Status an/aus. Diese müssen dann noch verbunden werden. Als Eingang dienst der Ikea-Taster mit seinen 6 Statuscodes.
Möchte man kontinuierlich einen Speedtest durchführen, konnte ich bis vor wenigen Monaten den Speedtest bei UniFi nutzen. Leider lassen die jetzt den Speedtest nur noch aller 12h zu – damit ist das ganze zum Monitoren etwas obsolet. Da muss was neues her
Speedtest.net bietet seit neustem eine speedtest-cli an womit man den Test auf einer bash durchführen kann. Das Ergebnis will ich später in einer InfluxDB übertragen. Und um das ganze zu kapseln verwende ich dabei Docker.
Visualisierung mittels Grafana aus der InfluxDB
Es geht also damit los, dass man das Dockerfile anlegen muss. Das muss man in zwei Schritten machen. Beim ersten Start von speedtest-cli wird ein Token abgerufen und lokal gespeichert. Den muss man später noch mit rein kopieren.
FROM arm32v7/debian
RUN apt-get -y update \
&& apt-get -y install gnupg1 apt-transport-https dirmngr curl
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61 \
&& echo "deb https://ookla.bintray.com/debian buster main" | tee /etc/apt/sources.list.d/speedtest.list
RUN apt-get update && apt-get -y install speedtest
RUN mkdir -p /root/.config/ookla/
ADD assert/speedtest-cli.json /root/.config/ookla/
ADD assert/start.sh /root/start.sh
ADD assert/createMeasure.sh /root/createMeasure.sh
WORKDIR /root
CMD ["./start.sh"]
Wir bauen uns also von einem Standard arm32 v7 Debian Image das Image. Dazu bbrauchen wir diverse Pakete zum installieren und fügen dann den Server von Ookla hinzu. Die Zeilen ab “mkdir…” lässt man am Anfang weg 😃 Nach dem ersten build startet mal mit diesem Image ein Container und startet “speedtest” – bestätigt die ganzen Fragen und es wird in /root/.config/ookla eine speedtest-cli.json erstellt. Diese muss man nun in einem 2. Terminal Fenster mit docker cp raus kopieren oder man lässt sich einfach den Inhalt anzeigen und erstellt lokal die Datei. Ich habbe diese ins Verzeichnis “assert” gelegt. Jetzt kann der Rest auch rein.
start.sh
Da der Speedtest kontinuierlich laufen soll gibt es den Einstiegspunkt start.sh wo nichts anderes passiert als Speedtest starten, warten und das ganze wiederholen:
#!/bin/bash
while true
do
./createMeasure.sh
sleep 600
done
Die eigentliche Messung und übertragung in die InfluxDB findet dann in der createMeasure.sh statt:
#!/bin/bash
# Ausgabe Datei löschen und neu anlegen
rm -f output.txt
touch output.txt
# Speedtest ausführen, Ausgabe als csv und umleiten in die output.txt
speedtest --format=csv > output.txt
# alle Ergebnisse speichere ich noch zusätzlich
FILENAME=html/`date +'%Y'`-`date +'%m'`_measure.csv
echo Schreibe nach $FILENAME
LASTLINE=`cat output.txt`
echo $LASTLINE >> $FILENAME
echo Ergebnis\r\n$LASTLINE
# Auslesen von PING, Download (DL) und Upload (UL) aus der CSV - Kommawerte werden gelöscht.
PING=`echo $LASTLINE |sed -r 's/"//g' | cut -d"," -f3 |cut -d"." -f1`
DL=`echo $LASTLINE |sed -r 's/"//g' | cut -d"," -f6 |cut -d"." -f1`
UL=`echo $LASTLINE |sed -r 's/"//g' | cut -d"," -f7 |cut -d"." -f1`
# DL und UL sind Bit/s und müssen in Byte/s umgerechnet werden
DL=`expr $DL \* 8`
UL=`expr $UL \* 8`
# Aufbereiten der Anfragen an Influx
DATA1="Speedtest Ping=$PING"
DATA2="Speedtest Download=$DL"
DATA3="Speedtest Upload=$UL"
# Senden. Meine Datenbank heißt hier Hausdaten und die Sammlung Speedtest
echo -e "Sending Data\n$DATA1"
curl -i -XPOST 'http://192.168.103.2:8086/write?db=Hausdaten' --data-binary "$DATA1"
echo -e "Sending Data\n$DATA2"
curl -i -XPOST 'http://192.168.103.2:8086/write?db=Hausdaten' --data-binary "$DATA2"
echo -e "Sending Data\n$DATA3"
curl -i -XPOST 'http://192.168.103.2:8086/write?db=Hausdaten' --data-binary "$DATA3"
Das Volume ist dafür da, dass ich die Messwerte nach Monat getrennt raus bekomme als CSV. Zusätzlich werden die Werte ja auch an die InfluxDB übertragen.