Octoprint & OpenHAB & MQTT

Octoprint und OpenHAB und MQTT

Ich habe ja hier bereits beschrieben, wie ich meine TUYA-Geräte auf Tasmota geflasht und OpenHAB eingerichtet habe. Das ganze läuft nun schon sehr gut. Heute geht es darum, wie ich OctoPrint mit dem OpenHAB kommunizieren lasse, Werte auslese und diese zum Schalten von Aktoren bzw. zur Visualisierung in Grafana nehme.

Warum ist das wichtig?

Ich lasse auch gerne mal längere Drucke in die Nacht hinein laufen. Die Beleuchtung für die Kamera und der Drucker selbst müssen nach Fertigstellung des Druckes aber nicht unnötig an bleiben. Also ist es ganz praktisch, diese Verbraucher nach Fertigstellung des Printjobs automatisch abzuschalten. Vorher hatte ich über IFTTT meine OctoPrint Instanz eingebunden und so eine smarte Steckdose automatisiert ausschalten lassen, sobald der Druck zu Ende war. Diese Funktion kann man auch mit MQTT (mit aber auch ohne OpenHAB) umsetzen.

Das Problem bei IFTTT ist, dass man keine logischen Verknüpfungen erstellen kann. Es gibt nur einfache Wenn-Dann-Funktionen. Mit OpenHAB kann ich aber beispielsweise die Beleuchtung im Keller anschalten, wenn ich den Drucker anschalte, aber nur so lange es Dunkel ist. Oder das Licht automatisch abschalten, sobald Tageslicht da ist. So können die Zeitrafferaufnahmen auch bei langen Drucken laufen und das Licht muss nicht über Tag an bleiben.

MQTT Broker

Bei mir läuft OpenHAB ja auf dem RaspberryPi. Daher ist es für diesen Blog auch die Prämisse, dass ihr ebenfalls openhabian habt. Im OpenHAB habe ich MQTT über Mosquitto eingerichtet. Dies könnt ihr nachträglich Installieren in dem ihr über das Terminal in die openhabian-config geht und dann mqtt und grafana auswählt. Im Detail öffnet ihr also entweder eine ssh Verbindung oder habt halt Tastatur & Monitor am Raspi. Sobald ihr drin seid, gebt ihr

sudo openhabian-config

ein. Danach müsst ihr das root Password eingeben. Wenn ihr länger nicht in der OpenHab Config wart, könnt ihr vermutlich einige Updates installieren.

Das machen wir zunächst einmal. Kann ja nicht schaden. (Navigation erfolgt mit den Pfeil-Tasten. Wer möchte kann auch die Verbesserungen installieren. Danach gehen wir auf den Punkt “Optional Components” und wählen Mosquitto aus.

Klickt euch durch den Dialog und stellt alles so ein wie ihr es braucht. Wenn alles installiert ist gehen wir wieder rüber auf die Paper UI in OpenHAB. Dazu braucht ihr die IP des Raspi im Netzwerk und geht auf Port 8080. Ggf. funktioniert auch openhab.local.

Unter AddOns – Bindings installieren wir das MQTT Binding. Danach legen wir unter Configuration – Things ein MQTT Bridge Thing an. Dazu gehen wir entweder in die Inbox oder unter Config – Things und drücken das große blaue Plus oben. Ggf. müsst ihr auf Add Manually klicken, damit die Option erscheint. Hier geben wir unsere MQTT Daten an, die wir vorhin im SetUp festgelegt haben.

Danach erstellen wir ein Generisches MQTT Thing und nennen es z.B. MQTT: Octoprint (Anycubic i3). Dazu gehen wir entweder nochmal in die Inbox oder unter Config – Things und drücken das große blaue Plus oben. Ggf. müsst ihr auf Add Manually klicken, damit die Option erscheint. Ich habe mehrere 3D-Drucker und zur eindeutigen Indentifizierung, habe ich den Druckernamen hinzugefügt.

In diesem Thing legen wir die Channels fest. MQTT ist im Prinzip ein kleiner Chat, bei dem die Variablen hin und her getextet werden. Für alle möglichen Dinge gibt es Topics. Diese können wir üblicherweise frei benennen, doch auf der Octoprint-Seite gibt uns das Plug-In die Namensgebung vor. Bei mir Sieht das MQTT Thing für OctoPrint so aus, dazu aber später mehr.

Octoprint

Wir gehen in den PlugIn Manager und tippen einfach MQTT ein. Es erscheint eine Auswahl an PlugIns. Wenn ihr nicht nur Daten auslesen möchtet, sondern auch Octoprint selbst per MQTT steuern wollt, gibt es das PlugIn MQTTSubscribe. Ansonsten nutze ich einfach das MQTT PlugIn.

Dieses Konfigurieren wir wie folgt:

  • Host: Hier braucht ihr eure OpenHAB IP
  • Port: 1883 ist der Standard MQTT Port. Falls ihr den geändert habt, müsst ihr das hier auch eintragen.
  • Protokoll: Habe ich auf v31 gelassen
  • Username & Password: Natürlich eure Daten, falls Notwendig

Im Reiter Topics könnt ihr auswählen, welche Informationen für euch Interessant sind. Ich habe hier einfach die Standardwerte gelassen.

Soweit so gut. Ab hier hatte ich dann kleine Schwierigkeiten, da das PlugIn nicht genau angibt, wie die Topics benannt sind. Dazu habe ich einfach MQTTfx genommen und ebenfalls mithören lassen was so los ist.

Das kleine Programm kann nämlich nach allen Topics im MQTT-Broker schnüffeln.

Für mich waren die folgenden Topics interessant:

octoPrint/temperature/bed
octoPrint/temperature/tool0
octoPrint/progress/printing
octoPrint/event/PrinterStateChanged

Wie genau dann der String aufgebaut ist, der versendet wird, seht ihr wenn ihr das Topic Subscribed habt. Fürchterliches Denglish….

Alternativ dazu kann man sich auch die Doku auf der GitHub Repo des PlugIns angucken. Um den String an der richtigen Stelle auszulesen müssen wir außerdem ein weiteres AddOn installieren. Die JSONPATH Transformierung. Das machen wir auch in der PaperUI.

Düsentemperatur auslesen

Zurück in der PaperUI können wir also in unserem Thing ein paar Channels anlegen. Hier mal als Beispiel die Nozzle Ist Temperatur

Wichtig ist tatsächlich die Incoming Value Transformation. Diese guckt an der richtigen Stelle im String nach und speichert den Wert in unser Item. Dazu dient die Eben erwähnte JSONPATH Transformierung.

Also Channel:

octoPrint/temperature/tool0

JSONPATH:

JSONPATH:$.actual

Ich persönlich lege meine Items über Visual Studio Code und der OpenHAB erweiterung in entsprechende Item Dateien an. Das ist etwas schöner zum Editieren, da man ja doch öfter mal an Items editiert. Things lege ich einfach über das PaperUi an. Ist aber einfach Geschmacksache.

Also nicht vergessen noch ein richtiges Itetm hinter den Channel zu legen, so, dass ein weißer Punkt im kleinen weißen Kreis auf dem blauen Kreis ist (Verstanden?!).

Der Code für die Item-File für das Nozzle Ist Temperatur Item:

Number OctoPrintNozzleTempIst  "MQTT: Octoprint Nozzle Temperatur IST"   { channel="mqtt:topic:Octoprint_AnyCubic:NozzleTempIst"}

So lese ich die Soll und Ist Temperaturen vom Druckbett und Nozzle aus. Diese lasse ich mir in meiner OpenHAB Sitemap auch Anzeigen.

Text item=OctoPrintNozzleTempIst    label="Nozzle IST [%.1f °C]"    icon="temperature"

Das ganze sieht in der BasicUI (auch Sitemap genannt) dann so aus:

Druckzeit & Progress

Außerdem lasse ich mir den Druckfortschritt und die Restzeit ausgeben. Dazu habe ich den Channel (Druckfortschritt) wie folgt eingestellt:

Für die verstrichene Zeit des Druckes gilt:

Topic:

octoPrint/progress/printing

JSON-Transformation:

JSONPATH:$.printer_data.progress.printTime

Items:

Number OctoPrintNozzleTempIst  "MQTT: Octoprint Nozzle Temperatur IST"   { channel="mqtt:topic:Octoprint_AnyCubic:NozzleTempIst"}
Number OctoPrintNozzleTempZiel  "MQTT: Octoprint Nozzle Temperatur Ziel" { channel="mqtt:topic:Octoprint_AnyCubic:NozzleTempTarget"}
Number OctoPrintBedTempIst  "MQTT: Octoprint Nozzle Temperatur IST "     { channel="mqtt:topic:Octoprint_AnyCubic:BedTempIst"}
Number OctoPrintBedTempZiel  "MQTT: Octoprint Nozzle Temperatur Ziel"    { channel="mqtt:topic:Octoprint_AnyCubic:BedTempTarget"}
Number OctoPrintPrintTime "MQTT: Octoprint Druckzeit [%.0f s]"           { channel="mqtt:topic:Octoprint_AnyCubic:PrintTime"}
Number OctoPrintPrintTimeLeft "MQTT: Octoprint Druckzeit Rest [%.0f s]"  { channel="mqtt:topic:Octoprint_AnyCubic:PrintTimeLeft"}

Sitemap:

Text item=OctropPrintPrinterState   label="Status"                   icon="screen"
            Text item=OctropPrintPrinterProgress label="Fortschritt  [%d %%]"    icon="batterylevel"
            Text item=OctoPrintNozzleTempIst    label="Nozzle IST [%.1f °C]"     icon="temperature"
            Text item=OctoPrintNozzleTempZiel   label="Nozzle SOLL [%.1f °C]"    icon="heating"
            Text item=OctoPrintBedTempIst       label="Druckbett IST [%.1f °C]"  icon="temperature"
            Text item=OctoPrintBedTempZiel      label="Druckbett SOLL [%.1f °C]" icon="heating"

Wie ihr seht, die meisten interessanten Sachen werden im Channel

octoPrint/progress/printing 

übertragen. Die verstrichene und restliche Druckzeit zum Beispiel auch. Achtet einfach auf die entsprechende JSONPATH Angabe! Im Zweifel guckt euch den ganzen String im MQTTfx an.

Drucker Statusänderung

Aber der wohl interessanteste Teil ist die Statusänderung des Druckers:

Item:

String OctropPrintPrinterStateChanged "MQTT: Octoprint Drucker Status Änderung" { channel="mqtt:topic:Octoprint_AnyCubic:PrinterStateChanged"}

Hier bekomme ich einen String in dem steht was der Drucker gerade macht. Diesen lese ich in einer openHAB rule aus.

rule "Drucker Status Änderungen"
    when
        Item OctropPrintPrinterStateChanged changed 
    then 
        if(OctropPrintPrinterStateChanged.state == "Finishing"){
            logInfo("keller.rules", "Anycubic - Druck ist Abgeschlossen");
            mqttAnycubicLampe.sendCommand(OFF);
            mqttAnycubic.sendCommand(OFF);
        }

        if(OctropPrintPrinterStateChanged.state == "Operational"){
            logInfo("keller.rules", "Anycubic - Drucker ist Bereit");
            mqttAnycubicLampe.sendCommand(ON);
       }

       if(OctropPrintPrinterStateChanged.state == "Offline"){
            logInfo("keller.rules", "Anycubic - Drucker ist Offline");
       }
        
end

Meine SiteMap hat folgenden Code für den Druckerbereich. Der obere Teil steuert meine MQTT-Gosund-Tasmota-Steckdosen.

Frame label="Keller" icon="light"{
        Switch item=gKeller           label="Alle"             icon="light"
        Switch item=mqttAnycubic      label="Anycubic i3 Mega" icon="poweroutlet_eu"
        Switch item=mqttAnycubicLampe label="Anycubic Lampe"   icon="poweroutlet_eu"
        Switch item=mqttKudoBean      label="Kudo Bean"        icon="poweroutlet_eu"
        Switch item=mqttWerkbankLampe label="Werkbank Lampe"        icon="poweroutlet_eu"
        Text label="Octoprint AnyCubic" icon="screen" {
            Text item=OctropPrintPrinterState   label="Status"                  icon="screen"
            Text item=OctropPrintPrinterProgress label="Fortschritt  [%d %%]"            icon="batterylevel"
            Text item=OctoPrintNozzleTempIst    label="Nozzle IST [%.1f °C]"    icon="temperature"
            Text item=OctoPrintNozzleTempZiel   label="Nozzle SOLL [%.1f °C]"   icon="heating"
            Text item=OctoPrintBedTempIst       label="Druckbett IST [%.1f °C]" icon="temperature"
            Text item=OctoPrintBedTempZiel      label="Druckbett SOLL [%.1f °C]" icon="heating"
            Text label="" icon="None"
            Text item=OctoPrintPrintTime     label="Druckzeit [JS(minstohours.js):%s]"           icon="time"
            Text item=OctoPrintPrintTimeLeft label="Restliche Druckzeit [JS(minstohours.js):%s]" icon="time"
            Text item=OctropPrintPrinterStateChanged label="Statusänderung" icon="Settings"
        }
        
    }

Für die Lesbare Umwandlung der Druckzeit habe ich eine Transformation Rule als .js angelegt. Das original wandelt Minuten in ein lesbares Format um, ich habe lediglich den Bereich für Sekunden hinzugefügt. Damit das funktioniert braucht ihr das AddOn JavaScript Transformation.

/*
Javascript transform function to change the number
of minutes of CPU time from the System Info Binding
into a more readable format
eg: 2365 into '1 day 15 hours 25 minutes

The item in the items file is defined as follow:
Number LocalComputer_Cpu_SystemUptime "[JS(CPUTime.js):%s]"
and linked via PaperUI to the System uptime channel
of the System Info Thing

- modified to apply to seconds
*/

(function(i) {
    if (i == 'NULL') { return i; }
    if (i == '-') { return 'Undefined'; }
    var val = parseInt(i); // The value sent by OH is a string so we parse into an integer
    var days = 0; // Initialise variables
    var hours = 0;
    var minutes = 0;
    var seconds =0;

    if (val >= (1440*60)) { // 1440 minutes in a days 
        days = Math.floor(val / (1440*60)); // Number of days
        val = val - (days * (1440*60)); // Remove days from val
    }
    if (val >= (60*60)) { // 60 minutes in an hour
       hours = Math.floor(val /(60*60)); // Number of hours
        val = val - (hours * (60*60)); // Remove hours from val
    }
    if (val >= 60) { // 60 seconds in a minute
        minutes = Math.floor(val /60); // Number of hours
         val = val - (minutes * 60); // Remove minutes from val
     }
    seconds = Math.floor(val); // Number of minutes

    var stringDays = ''; // Initialse string variables
    var stringHours = '';
    var stringMinutes = '';
    var stringSeconds = '';
    if (days === 1) {
        stringDays = '1 D '; // Only 1 day so no 's'
    } else if (days > 1) {
        stringDays = days + ' Ds '; // More than 1 day so 's'
    } // If days = 0 then stringDays remains ''

    if (hours === 1) {
        stringHours = '1 h '; // Only 1 hour so no 's'
    } else if (hours > 1) {
        stringHours = hours + ' hs '; // More than 1 hour so 's'
    } // If hours = 0 then stringHours remains ''

    if (minutes === 1) {
        stringMinutes = '1 Min '; // Only 1 minute so no 's'
    } else if (minutes > 1) {
        stringMinutes = minutes + ' Mins '; // More than 1 minute so 's'
    } // If minutes = 0 then stringMinutes remains ''

    if (seconds === 1) {
        stringSeconds = '1 s'; // Only 1 second so no 's'
    } else if (seconds > 1) {
        stringSeconds = seconds + ' s'; // More than 1 second so 's'
    } // If seconds = 0 then stringSeconds remains ''

    var returnString =  stringDays + stringHours + stringMinutes + stringSeconds
    return returnString.trim(); // Removes the extraneous space at the end

})(input)

Und natürlich meine Items File:

Number OctoPrintNozzleTempIst  "MQTT: Octoprint Nozzle Temperatur IST"   { channel="mqtt:topic:Octoprint_AnyCubic:NozzleTempIst"}
Number OctoPrintNozzleTempZiel  "MQTT: Octoprint Nozzle Temperatur Ziel" { channel="mqtt:topic:Octoprint_AnyCubic:NozzleTempTarget"}
Number OctoPrintBedTempIst  "MQTT: Octoprint Nozzle Temperatur IST "     { channel="mqtt:topic:Octoprint_AnyCubic:BedTempIst"}
Number OctoPrintBedTempZiel  "MQTT: Octoprint Nozzle Temperatur Ziel"    { channel="mqtt:topic:Octoprint_AnyCubic:BedTempTarget"}
Number OctoPrintPrintTime "MQTT: Octoprint Druckzeit [%.0f s]"           { channel="mqtt:topic:Octoprint_AnyCubic:PrintTime"}
Number OctoPrintPrintTimeLeft "MQTT: Octoprint Druckzeit Rest [%.0f s]"  { channel="mqtt:topic:Octoprint_AnyCubic:PrintTimeLeft"}

String OctropPrintPrinterState "MQTT: Octoprint Drucker Status"           { channel="mqtt:topic:Octoprint_AnyCubic:PrinterState"}
Number OctropPrintPrinterProgress "MQTT: Octoprint Drucker Status"        { channel="mqtt:topic:Octoprint_AnyCubic:PrinterProgress"}
String OctropPrintPrinterStateChanged "MQTT: Octoprint Drucker Status Änderung" { channel="mqtt:topic:Octoprint_AnyCubic:PrinterStateChanged"}

InfluxDB

Damit ich die Temperatur in Grafana anzeigen kann, muss ich diese werte auch in meine Persistence einpflegen. Wie ihr Influx und Grafana einstellt könnt ihr hier nachlesen.

//Octoprint
    OctoPrintNozzleTempIst : strategy = everyChange, restoreOnStartup
    OctoPrintNozzleTempZiel : strategy = everyMinute, restoreOnStartup
    OctoPrintBedTempIst : strategy = everyChange, restoreOnStartup
    OctoPrintBedTempZiel : strategy = everyMinute, restoreOnStartup

Grafana

So sieht mein Dashboard für den Keller aus.

Zusammenfassung

Wenn der String OctropPrintPrinterStateChanged sich ändert, werden bei mir sowohl Drucker als auch Lampe ausgeschaltet. Als nächstes kann man noch weitere Regeln ergänzen. Zum Beispiel dass die Tageszeit (z.B. mit AstroBinding) berücksichtigt werden soll.

Wer nicht nur Daten aus Octoprint Abrufen will sondern auch hinschicken, kann im nächsten Teil weiterlesen:

Drucker über OpenHAB und Octoprint steuern

Schreibe einen Kommentar