Smart Mirror in OpenHAB einbinden

Smart Mirror in OpenHAB einbinden

Wie ihr euren Smart Mirror / Magic Mirror mit Openhab überwachen und sogar fernsteuern könnt, möchte ich kurz in diesem Beitrag erläutern. Das ganze setzten wir mit dem Modul MQTTbridge von den drei Entwicklern @bugsounet @sergge1 und @DanielHfnr um. In diesem Beitrag gehe ich auch ein wenig auf das Thema Troubleshooting beim Modden von Modulen ein.

Wie ich meinen eigenen Smart Mirror gebastelt habe, könnt ihr <hier/> nachlesen. Als Grundlage sind auch die anderen Logs über OpenHab und MQTT interessant.

TL;DR

Für diejenigen, die sich bereits gut mit der Materie auskennen: Ich habe das Modul SystemStats von BenRoe als Basis genommen und in der Datei MMM-SystemStats.js eine Notifikation und ein paar kleine Variablen-Verarbeitungen an das Modul MQTTbridge eingebaut.

Für das Swipen nutze ich das MMM-Gesture Modul bei dem ich einen kleinen Teil für das MMM-Pages Modul ergänzt habe. Außerdem nutze ich einen eigenen Arduino Sketch für die Auswertung der Sensoren. Nun habe ich einfach einen String als gemappten Switch in die OpenHAB Sitemap eingebaut, mit dem man auch Swipen kann.

Let’s go!

Voraussetzung ist natürlich ein Smarter Spiegel mit der Magic Mirror² Firmware. Wir wählen uns mit ssh ein oder loggen uns z.B. mit Teamviewer auf den Spiegel ein und öffen dann die Konsole.

Da die Firmware bei mir nur noch 20% RAM übrig lässt, beende ich zunächst den Prozess MagicMirror (läuft bei mir in pm2) mit dem Befehl:

pm2 stop MagicMirror

Als nächstes wollen wir ins Verzeichnis wechseln, in das wir unsere Magic Mirror Module speichern:

cd MagicMirror/modules

Kleiner Tipp am Rande: Wenn ihr nicht immer so viel tippen möchtet, könnt ihr mit der Tabulator Taste die Automatische Vervollständigung bemühen.

Jetzt können wir die Module die wir brauchen einfach aus den entsprechenden GitHub Repos klonen und die Installationsanweisungen befolgen.

SystemStats Modul:

git clone https://github.com/BenRoe/MMM-SystemStats
cd MMM-SystemStats
npm install
cd ..

MQTT-Bridge Modul:

git clone --depth=1 https://github.com/sergge1/MMM-MQTTbridge.git
cd MMM-MQTTbridge
npm install
cd ..

Der Befehl cd .. bringt uns immer ins Verzeichnis modules zurück.

Danach müssen wir noch die config.js aktualisieren und unsere beiden neuen Module einpflegen. Das kann man entweder auch direkt in der Konsole machen

cd
sudo nano MagicMirror/config/config.js

Alternative:

Wer nicht so gern mit der Konsole arbeitet, kann sich auch die Repos per Browser auf seinen Rechner herunter laden und mit einem FTP Programm auf den RaspberryPi übertragen. Dazu braucht ihr die IP, die Log-In Daten und den Port. Standardmäßig wird für FTP der Port 22 genutzt.

Config.js aktualisieren

Egal wie ihr es macht, als Beispiel kopiere ich meine ergänzungen für die beiden Module mal kurz hier rein:

MQTT-Bridge:

//---------- MQTT Bridge -------------

		{
			module: 'MMM-MQTTbridge',
			disabled: false,
			config: {
				mqttServer: "mqtt://openhabian:SuperSafePW@192.168.x.xxx:1883",
				mqttConfig:
				{
					listenMqtt: true,
					interval: 1000, //default was 300000 ms
				},
				notiConfig:
				{
					listenNoti: true,
					ignoreNotiId: ["CLOCK_SECOND", "CLOCK_MINUTE", "NEWS_FEED"],
					ignoreNotiSender: ["system", "NEWS_FEED"],
				},
				// set "NOTIFICATIONS -> MQTT" dictionary at /dict/notiDictionary.js
				// set "MQTT -> NOTIFICATIONS" dictionary at /dict/mqttDictionary.js
			},
		},

Also hier solltet ihr in der URL des mqtt Server einfach eure Daten entsprechend der Form Username:Password@IP:Port eingeben. Ich hab das Update Interval noch auf 1 Sekunde herunter gesetzt. Man hat hier Außerdem eine Ignore Liste, da im Hintergrund ständig viele Notifications gesendet werden. Damit entlastet man die CPU.

SystemStats:

//--------- System-Stats--------
		{
			module: 'MMM-SystemStats',
			position: 'top_left', // This can be any of the regions.
			// classes: 'small dimmed', // Add your own styling. OPTIONAL.
			//header: 'System Stats', // Set the header text OPTIONAL
			config: {
				updateInterval: 10000, // every 10 seconds
				align: 'right', // align labels
				header: 'System Stats', // This is optional
				units: 'metric', // default, metric, imperial
				view: 'textAndIcon',
			},
		},	

Hier habe ich eigentlich nichts verändert. Jetzt starten wir den Spiegel wieder mit

cd
pm2 start MagicMirror

Modifikationen

Da die ganze Bastelei hier selbst mein Projekt zum lernen der Materie ist, freue ich mich gern über Hinweise, was ggf. einfacher oder besser gehen könnte.

Für das Modifizieren in den .js Dateien der Module nutze ich VS Code. Die aktualisierten Daten schiebe ich dann per FileZilla rüber auf den Pi. Gleichzeitig habe ich das WebInterface des Smart Mirrors einfach im Browser geöffnet (zu finden unter der IP des Spiegels und Port 8080 – 192.168.x.xxx:8080).

Also wie geht man vor wenn man noch keine Ahnung hat? Ganz genau: Abgucken. Mein Ziel ist es ja, die beiden Module miteinander reden zu lassen. Das hab ich beim hacken des Gesten Modules ja auch schon gemacht. Also habe ich mir zur Hilfe die Dateien der Module ebenfalls noch einmal geöffnet. In VS Code kann man schön mehrere Dateien nebeneinander anordnen. So kann man auch die Struktur der Dateien erkennen und einiges lernen. Hier mal ein Auszug aus der .js Datei (bitte nicht in euren Code einfügen):

Ganz oben steht immer etwas wie:

Module.register('MMM-Gestures', {

Bei meinen ersten Änderungen dachte ich, ich hätte versehentlich die zweite Klammer gelöscht, aber nein, das ist Absicht. Ohne Inhalt würde diese Zeile nämlich so aussehen:

Module.register('MMM-Gestures', {});

Also sollte ganz am Ende eines Modules immer der folgende Code stehen:

});

Dann folgt meistens eine start: function deklaration

start: function () {

		Log.info('MMM-Gestures start invoked.');

		// notifications are only received once the client (this file) sends the first message to the server (node_helper.js)
		this.sendSocketNotification('INIT');

	},

Hier seht ihr eine Log.info – die sind für das entwickeln sehr wichtig. Mit der Zeile this.sendSocketNotification wird eine Nachricht mit dem Inhalt “INIT” an die Hilfs Datei node_helper.js gesendet. Die MM-Module sind oft so aufgebaut, dass es eine solche Datei gibt, die bestimmte Aufgaben übernimmt. Wir wollen aber nicht mit der node_helper reden, sondern mit einem anderen Modul. Dazu finden wir zwei Abschnitte später einen Hinweis:

socketNotificationReceived: function (notification, payload) {

		Log.info('Received message from gesture server: ' + notification + ' - ' + payload);

		// forward gesture to other modules
		this.sendNotification('GESTURE', { gesture: payload });

		// interact with compliments module upon PRESENT and AWAY gesture
		var complimentModules = MM.getModules().withClass('compliments');

		if (complimentModules && complimentModules.length === 1) {

			var compliment = complimentModules[0];

			if (payload === 'PRESENT') {

				Log.info('Showing compliment after having received PRESENT gesture.');
				compliment.show();

			} else if (payload === 'AWAY') {

				Log.info('Hiding compliment after having received AWAY gesture.');
				compliment.hide();

			} else if (payload === 'FAR') {

				Log.info('Reloading page after having received FAR gesture.');
				location.reload();

			} else if (payload === 'RIGHT') {

				Log.info('Showing next page after having received RIGHT gesture.');
				this.sendNotification("COMPLIMENT_INCREMENT");

			} else {

				Log.info('Not handling received gesture in this module directly:');
				Log.info(payload);

			}
		}

Dieser Abschnitt liest empfangene SocketNotifications der node_helper.js und verteilt die Informationen unter bestimmten Voraussetzungen weiter. Genau das was ich erreichen will. Der gesuchte Befehl lautet also in etwa:

this.sendNotification(notification, payload);

Im Modul SystemStats finden wir einen ganz ähnlichen Abschnitt:

 socketNotificationReceived: function(notification, payload) {

Hier wird mit der entsprechenden Node_helper.js kommuniziert, die die eigentlich Systemwerte abgreift. Die Nachricht wird betrachtet und zerlegt.

if (notification === 'STATS') {
      this.stats.cpuTemp = payload.cpuTemp;
      //console.log("this.config.useSyslog-" + this.config.useSyslog + ', this.stats.cpuTemp-'+parseInt(this.stats.cpuTemp)+', this.config.thresholdCPUTemp-'+this.config.thresholdCPUTemp);
      if (this.config.useSyslog) {
        var cpuTemp = Math.ceil(parseFloat(this.stats.cpuTemp));
        //console.log('before compare (' + cpuTemp + '/' + this.config.thresholdCPUTemp + ')');
        if (cpuTemp > this.config.thresholdCPUTemp) {
          console.log('alert for threshold violation (' + cpuTemp + '/' + this.config.thresholdCPUTemp + ')');
          this.sendSocketNotification('ALERT', {config: this.config, type: 'WARNING', message: this.translate("TEMP_THRESHOLD_WARNING") + ' (' + this.stats.cpuTemp + '/' + this.config.thresholdCPUTemp + ')' });
        }
      }

      this.stats.sysLoad = payload.sysLoad[0];
      this.stats.freeMem = Number(payload.freeMem).toFixed() + '%';
      upTime = parseInt(payload.upTime[0]);
      this.stats.upTime = moment.duration(upTime, "seconds").humanize();
      this.stats.freeSpace = payload.freeSpace;
      this.updateDom(this.config.animationSpeed);

Die Variablen this.stats.XXX werden benutzt, um im div Containern auf dem Smart Mirror angezeigt zu werden. Hier habe ich die entsprechenden umwandlungen einefügt, da der String auch Zeichen enthält, die ich nicht an OpenHab senden möchte.

Mein Fork hat also folgenden Code in der socketNotificationReceived Funktion:

  socketNotificationReceived: function(notification, payload) {
    //Log.log('MMM-SystemStats: socketNotificationReceived ' + notification);
    //Log.log(payload);
    if (notification === 'STATS') {
      this.stats.cpuTemp = payload.cpuTemp;
      //console.log("this.config.useSyslog-" + this.config.useSyslog + ', this.stats.cpuTemp-'+parseInt(this.stats.cpuTemp)+', this.config.thresholdCPUTemp-'+this.config.thresholdCPUTemp);
      if (this.config.useSyslog) {
        var cpuTemp = Math.ceil(parseFloat(this.stats.cpuTemp));
        //console.log('before compare (' + cpuTemp + '/' + this.config.thresholdCPUTemp + ')');
        if (cpuTemp > this.config.thresholdCPUTemp) {
          console.log('alert for threshold violation (' + cpuTemp + '/' + this.config.thresholdCPUTemp + ')');
          this.sendSocketNotification('ALERT', {config: this.config, type: 'WARNING', message: this.translate("TEMP_THRESHOLD_WARNING") + ' (' + this.stats.cpuTemp + '/' + this.config.thresholdCPUTemp + ')' });
        }
      }
      //---- customized part to broadcast to mqtt-bridge Module ----
      //checks if the Module MQTTbridge is loaded and functional
      this.stats.cpuTempMQTT = parseInt(payload.cpuTemp);
      this.stats.sysLoad = payload.sysLoad[0];
      this.stats.freeMem = Number(payload.freeMem).toFixed() + '%';
      this.stats.freeMemMQTT = Number(payload.freeMem).toFixed();
      upTime = parseInt(payload.upTime[0]);
      this.stats.upTime = moment.duration(upTime, "seconds").humanize();
      this.stats.upTimeMQTT = parseInt(payload.upTime);
      this.stats.freeSpace = payload.freeSpace;
      this.stats.freeSpaceMQTT = parseInt(payload.freeSpace);
    
      
      var mqttModules = MM.getModules().withClass('MMM-MQTTbridge');
      console.log(new Date() + ': Found Module: ' + mqttModules);
  
      if (mqttModules) {
  
        var notification = "UNKNOWN";  
        
        Log.info("Broadcasting CPU Temperature to MQTT-Bridge - Payload: " + this.stats.cpuTempMQTT)
        this.sendNotification("CPU_TEMP", this.stats.cpuTempMQTT);

        Log.info("Broadcasting Systemload to MQTT-Bridge - Payload: " +  this.stats.sysLoad)
        this.sendNotification("SYSLOAD", this.stats.sysLoad);

        Log.info("Broadcasting Free Memory to MQTT-Bridge - Payload: " + this.stats.freeMemMQTT)
        this.sendNotification("FREE_MEM", this.stats.freeMemMQTT);

        Log.info("Broadcasting UpTime to MQTT-Bridge - Payload: " + this.stats.upTimeMQTT)
        this.sendNotification("UPTIME", this.stats.upTimeMQTT);

        Log.info("Broadcasting Free Space to MQTT-Bridge - Payload: " + this.stats.freeSpaceMQTT)
          this.sendNotification("FREE_SPACE", this.stats.freeSpaceMQTT);
        
      }      
      
      this.updateDom(this.config.animationSpeed);
      //----- End of Custom Code ----

      // Original Code:
      // this.stats.sysLoad = payload.sysLoad[0];
      // this.stats.freeMem = Number(payload.freeMem).toFixed() + '%';
      // upTime = parseInt(payload.upTime[0]);
      // this.stats.upTime = moment.duration(upTime, "seconds").humanize();
      // this.stats.freeSpace = payload.freeSpace;
      // this.updateDom(this.config.animationSpeed);
    }
  },

Wieder sehen wir zahlreiche Logeinträge, die mir helfen zu erkennen ob alles funktioniert. Denn natürlich habe ich diesen Code nicht auf Anhieb so hinbekommen. Da ich mir bei vielen Sachen unsicher bin, ist es oft ein Trial and Error Prozess. Am Ende des Eintrages zähle ich ein paar der Fehler auf, die ich gemacht habe. Aber nun eine kurze Erläuterung zu Logs:

Logs

Wenn ihr Module modifiziert sollet ihr in jedem Fall Log Nachrichten einbauen, um zu sehen, ob bestimmte Abschnitte des Code überhaupt erreicht, bzw. ausgefüht werden. Diese Log-Nachrichten kann man sich auf verschiedenen Angucken:

pm2 log

Eine Möglichkeit ist direkt am Spiegel oder per SSH. In der Konsole könnt ihr den Befehl

pm2 log

oder besser

pm2 logs MagicMirror

benutzen, um euch die Logs anzusehen. Hier tauchen aber leider nicht alle Nachrichten auf, sondern nur die der node_helper Scripte.

npm start dev

Eine weitere Möglichkeit ist den Prozess im Development Modus zu starten. Wenn ihr den MagicMirror² also gestoppt habt (z.B. mit pm2 stop MagicMirror) könnt ihr mit cd in das Verzeichnis navigieren und von da aus im Entwicklermodus starten.

cd MagicMirror
npm start dev

Jetzt wird der Browser, in dem das Interface angezeigt wird, zusätzlich mit geöffneter Entwicklerkonsole gestartet. Hier sind so ziemlich alle Notifications zu sehen, die so im Hintergrund herum geschickt werden.

Browser Entwicklermodus

Noch viel bequemer finde ich aber die dritte Variante. Wir entwickeln ja ohnehin lieber am Tisch als dort wo der Spiegel hängt und nutzen dafür ssh, FTP und was nicht alles. Also gehen wir ganz einfach auf die Website, die der Spiegel bereitstellt und öffnen hier den Entwicklermodus im Browser.

Also einfach SpiegelIP:8080 und dann F12. So habt ihr die gleiche Konsole, wie bei Variante zwei, nur viel bequemer am Arbeitsplatz. Hier kann man außerdem leicht mit F5 die Seite aktualisieren, wenn man Code geändert hat.

Der Code im Detail

Was habe ich gemacht?

Unter dem Kommentar, dass hier mein Modifizierte Code beginnt (das mache ich, damit ich später leichter wiederfinde was ich gepfuscht habe) steht:

this.stats.cpuTempMQTT = parseInt(payload.cpuTemp);

Das ist eine einfache JavaScript funktion, die ich durch Google gefunden habe. Sie wandelt den String in einen Int um und wird so das % Zeichen los. Den Wert schreibe ich in eine neue Variable, die ich nur für den MQTT Transport auch so benannt habe. Das gleiche mache ich mit anderen Werten, bei denen nicht nur reine Zahlen im String stehen.

Im Abschnitt danach:

var mqttModules = MM.getModules().withClass('MMM-MQTTbridge');
      console.log(new Date() + ': Found Module: ' + mqttModules);
  
      if (mqttModules) {

prüfe ich, ob das Modul MQTT Bridge registriert ist. Der nächste Code muss nur ausgeführt werden, wenn das auch der Fall ist. Die Vorlage dazu stammt aus dem Gesture-Modul.

Log.info("Broadcasting CPU Temperature to MQTT-Bridge - Payload: " + this.stats.cpuTempMQTT)
        this.sendNotification("CPU_TEMP", this.stats.cpuTempMQTT);

Der Befehl Log.info postet was ich tue in das Logbuch, damit ich sehen kann ob und was ich versende. Danach wird der eigentliche Notification Befehl ausgeführt. Der Erste Teil ist die NotificationID und lautet in diesem Fall “CPU_TEMP”. Sie ist wichtig für das MQTT-Bridge Modul, damit es weiß, dass diese Nachricht weiter verarbeitet werden soll. Danach wird der Inhalt der Variablen cpuTempMQTT eingefügt.

Im Log sollte nun in etwa die folgende Abfolge auftauchen. Naja, zumindest die Broadcasting … Nachrichten und die Meldung, falls ihr das Admin-Interface Modul habt, dass es diese Nachricht empfangen hat. Der MQTT Teil folgt als nächstes.

MQTT

Das MQTT Bridge Modul bekommt jetzt Nachrichten vom SystemStats Modul. Soweit so gut. Jetzt soll es diese per MQTT an unseren Broker schicken, der sie an OpenHAB weiterleitet.

Dazu müssen wir die beiden JS-Scripte zur Übersetzung editieren. Diese liegen im Modul-Verzeichnis unter dict. In diesem Fall brauchen wir eigentlich nur die Datei notiDictionary.js.

Wir definieren hier auf welche Schlagwörter das Modul hören soll und was genau es dann machen soll. Wir möchten alle fünf Systemstatus Variablen übertragen.

Der unberührte Code sieht in etwa so aus:

var notiHook = [
  //when notification "xyz" appeears, the command inside notiMqttCmd will call the same in the var notiMqttCommands section
  {
    notiId: "USER_PRESENCE",
    notiPayload: [
      {
        payloadValue: true, 
        notiMqttCmd: ["SCREENON"]
      },
      {
        payloadValue: false, 
        notiMqttCmd: ["SCREENOFF"]
      },
    ],
  },
];

Das ist ein vorgefertigtes Beispiel des Autors. Hier ersetzen wir die notiId mit unserer eigenen, die wir in die Nachricht im SystemStat Modul benutzt haben. Werden die Anführungszeichen bei PayloadValue frei gelassen, wird der tatsächliche Payload übertragen. Man kann hier aber auch Werte überschreiben. In unserem Fall ist der obere Abschnitt in var notiHook mit den folgenden Abschnitten befüllt:

//--- Raspi System Stats
  //--- CPU Temperatur
  {
    notiId: "CPU_TEMP",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["CPU_TEMP"]
      },
    ],
  },
  //--- System Load
  {
    notiId: "SYSLOAD",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["SYSLOAD"]
      },
    ],
  },
  //--- Free Memory
  {
    notiId: "FREE_MEM",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["FREE_MEM"]
      },
    ],
  },
  //--- UpTime
  {
    notiId: "UPTIME",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["UPTIME"]
      },
    ],
  },
  //--- Free Space
  {
    notiId: "FREE_SPACE",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["FREE_SPACE"]
      },
    ],
  },//Hook controls the command
var notiHook = [
  //when notification "xyz" appeears, the command inside notiMqttCmd will call the same in the var notiMqttCommands section
  {
    notiId: "USER_PRESENCE",
    notiPayload: [
      {
        payloadValue: true, 
        notiMqttCmd: ["SCREENON"]
      },
      {
        payloadValue: false, 
        notiMqttCmd: ["SCREENOFF"]
      },
    ],
  },
  //--- Raspi System Stats
  //--- CPU Temperatur
  {
    notiId: "CPU_TEMP",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["CPU_TEMP"]
      },
    ],
  },
  //--- System Load
  {
    notiId: "SYSLOAD",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["SYSLOAD"]
      },
    ],
  },
  //--- Free Memory
  {
    notiId: "FREE_MEM",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["FREE_MEM"]
      },
    ],
  },
  //--- UpTime
  {
    notiId: "UPTIME",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["UPTIME"]
      },
    ],
  },
  //--- Free Space
  {
    notiId: "FREE_SPACE",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["FREE_SPACE"]
      },
    ],
  },
  //--- End---
];//--- Raspi System Stats
  //--- CPU Temperatur
  {
    notiId: "CPU_TEMP",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["CPU_TEMP"]
      },
    ],
  },
  //--- System Load
  {
    notiId: "SYSLOAD",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["SYSLOAD"]
      },
    ],
  },
  //--- Free Memory
  {
    notiId: "FREE_MEM",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["FREE_MEM"]
      },
    ],
  },
  //--- UpTime
  {
    notiId: "UPTIME",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["UPTIME"]
      },
    ],
  },
  //--- Free Space
  {
    notiId: "FREE_SPACE",
    notiPayload: [
      {
        payloadValue: '', 
        notiMqttCmd: ["FREE_SPACE"]
      },
    ],
  },

Der Hook sendet dann ein notiMqttCmd an den nächsten Abschnitt, wo dann die MQTT Nachricht erzeugt wird, die in der node_helper.js dann tatsächlich gesendet wird. In unserem Fall sieht das so aus:

var notiMqttCommands = [
    {
    commandId: "SCREENON",
    mqttTopic: "magicmirror/state",
    mqttMsgPayload: '{"state":"ON"}'
    },
    {
    commandId: "SCREENOFF",
    mqttTopic: "magicmirror/state",
    mqttMsgPayload: '{"state":"OFF"}'
  },
    //--- Raspi System Stats-----
    //--- CPU Temperatur ---
  {
    commandId: "CPU_TEMP",
    mqttTopic: "stat/magicmirror/cpuTemp",
    mqttMsgPayload: ''
  },
   //--- System Load---
  {
    commandId: "SYSLOAD",
    mqttTopic: "stat/magicmirror/sysLoad",
    mqttMsgPayload: ''
  },
  //--- Free Memory---
  {
    commandId: "FREE_MEM",
    mqttTopic: "stat/magicmirror/freeMem",
    mqttMsgPayload: ''
  },
  //--- Up Time---
  {
    commandId: "UPTIME",
    mqttTopic: "stat/magicmirror/upTime",
    mqttMsgPayload: ''
  },
  //--- Free Space---
  {
    commandId: "FREE_SPACE",
    mqttTopic: "stat/magicmirror/freeSpace",
    mqttMsgPayload: ''
  },

  // {
  //   commandId: "Command 2",
  //   mqttTopic: "magicmirror/state",
  //   mqttMsgPayload: ''
  // },
];

module.exports = { notiHook, notiMqttCommands };

Bei der Struktur der Topics habe ich mich an das Tasmota Schema gehalten, damit es Einheitlich bleibt.

Damit wir das ganze auch schön im Log sehen können, habe ich noch ein paar Zeilen in der MMM-MQTTbridge.js ergänzt. Dort wo entschieden wird, ob mit originalem oder neuen Payload gesendet wird, kann man die Funktion für die Zusammensetzung des mqtt-String sehen. Genau die möchte ich auch im Log sehen. Also sieht es bei mir so aus:

// Decision: Send payload specified in the mqttDictionary oder send the actual payload of mqtt message
                  if (this.config.mqttDictionary.mqttHook[i].mqttPayload[k].mqttPayload == '') 
                  {
                    this.sendNotification(this.config.mqttDictionary.mqttNotiCommands[x].notiID, payload.data);
                    Log.info("Publishing Notification: " + this.config.mqttDictionary.mqttNotiCommands[x].notiID, payload.data);
                    this.sendSocketNotification("LOG","[MQTT bridge] MQTT -> NOTI issued: " + this.config.mqttDictionary.mqttNotiCommands[x].notiID + ", payload: "+ payload.data);
                  } 
                  else 
                  {
                    this.sendNotification(this.config.mqttDictionary.mqttNotiCommands[x].notiID, this.config.mqttDictionary.mqttNotiCommands[x].notiPayload);
                    Log.info("Publishing Notification: " + this.config.mqttDictionary.mqttNotiCommands[x].notiID, this.config.mqttDictionary.mqttNotiCommands[x].notiPayload);
                    this.sendSocketNotification("LOG", "[MQTT bridge] MQTT -> NOTI issued: " + this.config.mqttDictionary.mqttNotiCommands[x].notiID + ", payload: " + this.config.mqttDictionary.mqttNotiCommands[x].notiPayload);
                  }  
                  break;

bzw. ergänzend im zweiten Block:

// if NOTI matched in the Dictionary, send respective MQTT message
                  // If payloadValue is empty in notiDictionary --> send payload of Notification to MQTT - Otherwise send payload defined in notiDictionary
                  if (this.config.notiDictionary.notiHook[i].notiPayload[j].payloadValue == '') 
                  {
                    this.publishNotiToMqtt(this.config.notiDictionary.notiMqttCommands[x].mqttTopic, payload.toString());
                    Log.info("Publishing Message: " + this.config.notiDictionary.notiMqttCommands[x].mqttTopic, payload.toString())
                  } 
                  else 
                  {
                    this.publishNotiToMqtt(this.config.notiDictionary.notiMqttCommands[x].mqttTopic, this.config.notiDictionary.notiMqttCommands[x].mqttMsgPayload.toString());
                    Log.info("Publishing Message: " + this.config.notiDictionary.notiMqttCommands[x].mqttTopic, this.config.notiDictionary.notiMqttCommands[x].mqttMsgPayload.toString())
                  }
                  break;

Nun sollten eure Logs auch so aussehen.

Ob die Nachrichten tatsächlich beim MQTT-Broker ankommen, können wir mit MQTTfx überprüfen.

OpenHAB

In Openhab erzeugen wir einfach ein generisches MQTT Thing das wir zum Beispiel MagicMirror nennen. Hier werden auch die Channels angelegt.

Die Topics müssen natürlich mit denen im MM² Modul übereinstimmen, sonst funktioniert das ganze nicht. Außerdem müssen wieder die Items am Ende der Channels angelegt werden. Das mache ich wieder in einer .items File.

Number MagicMirrorCPUTemp "MQTT: Magic Mirror CPU Temperatur"    { channel="mqtt:topic:MQTTMagicMirror:MMcpuTemp"}
Number MagicMirrorSysLoad "MQTT: Magic Mirror System Load"       { channel="mqtt:topic:MQTTMagicMirror:SysLoad"}
Number MagicMirrorFreeMemory "MQTT: Magic Mirror Free memory"    { channel="mqtt:topic:MQTTMagicMirror:FreeMem"}
Number MagicMirrorUpTime "MQTT: Magic Mirror Up Time"            { channel="mqtt:topic:MQTTMagicMirror:UpTime"}
Number MagicMirrorFreeSpace "MQTT: Magic Mirror Free Space"      { channel="mqtt:topic:MQTTMagicMirror:FreeSpace"}
String MagicMirrorSwipe "MQTT Magic Mirror Swipe"                { channel="mqtt:topic:MQTTMagicMirror:Swipe"}

Und jetzt?

Jetzt habe wir die Werte im System und können diese wieder überprüfen. Besonders die CPU-Temperatur ist bei mir sehr hoch. Hier möchte ich eine Notfall abschaltung mit einer Smarten Steckdose umsetzen. Außerdem kann man nun auch Befehle an den MagicMirror senden. Zum Beispiel bei kritischer Temperatur den Monitor auszuschalten. Ich will zunächst nur ein paar Tage die Temperatur loggen und sehen ob sich der Spiegel in der Nacht abkühlt. Denn da wird er nicht genutzt und der Monitor bleibt lange aus. Wenn das nicht der Fall ist, werde ich wohl noch ein paar Luftlöcher und mehr Lüfter einbauen müssen. Bei der Gelegenheit kann ich auch eine neue Scheibe einsetzen.

Sitemap

Ich stelle die Werte unter anderem in einer Sitemap dar und habe hier auch zwei Knöpfe zum Swipen angelegt. Zum Senden von Befehlen scrollt einfach weiter zum nächsten Abschnitt.

Text   label="Magic Mirror" icon="settings"{
            Switch item=MagicMirrorSwipe label="Swipe" mappings=[LEFT="<", RIGHT=">"]     icon="movecontrol"
            Text item=MagicMirrorCPUTemp label="CPU Temperatur [%.1f °C]"   icon="temperature"
            Text item=MagicMirrorSysLoad label="System Load"                icon="Settings"
            Text item=MagicMirrorFreeMemory label="Free Memory  [%d %%]"    icon="batterylevel"
            Text item=MagicMirrorFreeSpace label="Free Space  [%d %%]"      icon="batterylevel"
            Text item=MagicMirrorUpTime label="Laufzeit [JS(minstohours.js):%s]" icon="time"
        }

InfluxDB und Grafana

Natürlich habe ich die Variablen auch zu meiner Persistence hinzugefügt.

MagicMirrorCPUTemp : strategy = everyMinute, restoreOnStartup
    MagicMirrorSysLoad : strategy = everyMinute, restoreOnStartup
    MagicMirrorFreeMemory : strategy = everyMinute, restoreOnStartup
    magicMirrorUpTime : strategy = everyMinute, restoreOnStartup
    MagicMirrorFreeSpace : strategy = everyMinute, restoreOnStartup

Und natürlich habe ich mir auch wieder ein schönes Grafana-Layout erstellt.

Mal sehen wie sich die Temperatur so entwickelt.

Befehle an den MagicMirror mit MQTT senden

In der Sitemap habt ihr ja den folgenden Code gesehen:

Switch item=MagicMirrorSwipe label="Swipe" mappings=[LEFT="<", RIGHT=">"]     icon="movecontrol"

Dieser Speichert in das Item

String MagicMirrorSwipe "MQTT Magic Mirror Swipe"                { channel="mqtt:topic:MQTTMagicMirror:Swipe"}

Dieses hängt am Channel:

Mit der Konfiguration:

Im MQTT Bridge Modul müssen wir diesmal die mqttDictionary.js mit unseren Befehlen füttern. Die Struktur ist die gleiche wie in der notiDictionary, wie weiter oben beschrieben.

var mqttHook = [
    {
      mqttTopic: "cmnd/magicmirror/swipe",
      mqttPayload: [
        {
          payloadValue: '{"swipe":"LEFT"}',
          mqttNotiCmd: ["SWIPE_LEFT"]
        },
        {
          payloadValue: '{"swipe":"RIGHT"}',
          mqttNotiCmd: ["SWIPE_RIGHT"]
        },
      ],
    },
    // {
    //   mqttTopic: "magicmirror/state",
    //   mqttPayload: [
    //     {
    //       payloadValue: "1",
    //       mqttNotiCmd: ["Command 1", "Command 2"]
    //     },
    //   ],
    // },
  ];
var mqttNotiCommands = [
    {
      commandId: "SWIPE_LEFT",
      notiID: "PAGE_DECREMENT",
      notiPayload: 1
    },
    {
      commandId: "SWIPE_RIGHT",
      notiID: "PAGE_INCREMENT",
      notiPayload: 1
    },
  ];

  module.exports = { mqttHook,  mqttNotiCommands};

Im Detail wird einfach im MQTT Topic auf left oder right im channel gewartet und wenn ja, wird der selbe Befehl, den auch das Gesture Modul senden muss genutzt. Und so einfach haben wir eine Smartphone Fernbedienung für den SmartMirror.

Lessons learned

Für mich ist diese ganze JS Welt neu. Was mich verwundert ist die nicht vorhandene Variablen Deklaration, das bin ich nicht gewohnt. Ich habe vorher viel mit Arduino gemacht.

Schaut euch genau die Syntax an, dann passieren euch nicht die Fehler, dass ein ganzes Modul einfach leer geladen wird (Klammer direkt im Header, siehe oben).

Ein weiterer dummer Fehler war zu Anfang, dass ich in der Config für die MQTT Bridge übersehen hatte localhost zu meiner MQTT-Broker IP zu ändern. Das hab ich lange übersehen aber bei der Fehlersuche andere Bugs gefunden. Darunter ein paar Schreibfehler, so dass Variablen nicht übereingestimmt haben.

Ich war mir zu Anfang nicht sicher wie die MQTT Nachricht aufgebaut werden muss. Bzw. nicht mal wie die Nachricht an das MQTT Modul aufgebaut werden muss. Ich hatte die NotificationID an der falschen stelle.

Als ich vermeintlich alles am laufen hatte, musste ich feststellen, dass Buchstaben mit im String waren. Aber hier war die parseInt Lösung überraschend einfach.

Naja, jetzt läuft es! Als nächstes werde ich einen toggle Button für den Monitor in die SiteMap einbauen und ein kleines eigenes Modul zusammen basteln. Das soll mir Anzeigen wer Zuhause ist (Handy im WLAN) und wer nicht. Außerdem will ich das PlugIn OpenHabFlorrPlan einrichten, weshalb ich den ganzen Spiegel überhaupt erst gebaut habe.

1 Kommentar

Schreibe einen Kommentar