3D Printer Status from Octoprint via MQTT

Tags: #<Tag:0x00007fc3fc4d8538> #<Tag:0x00007fc3fc4d8448> #<Tag:0x00007fc3fc4d8290>

Hi all,

I succeeded getting the status of my 3D Printer via MQTT (OH MQTT 2.4) which is controlled by Octoprint on a Raspberry Pi.

This is maybe a more elegant implementation than the one over HTTP/REST I shared earlier but lacks the commanding part:

What you need:

  • openHAB2
  • New MQTT Binding installed in OH2 (aka MQTT 2.4)
  • JSONPATH Transformation installed in OH2
  • Octoprint running with MQTT Plugin (minimum v0.8.0) installed

In Octoprint, go to Settings -> MQTT and configure your broker and Topics (I basically activated everything, temperature with 1°C setting)

Create a new *.things file (e.g. octoprint.things) and put the following mqtt-things:
Make sure you have already configured your MQTT broker connection from OH2!

Thing mqtt:topic:octoprint "Octoprint MQTT" (mqtt:broker:127_0_0_1_1883) {
    Channels:
        Type string : connected "Connected" [ stateTopic="octoprint/mqtt" ]
        Type string : state "State" [ stateTopic="octoprint/event/PrinterStateChanged", transformationPattern="JSONPATH:$.state_string" ]
        Type string : event "Event" [ stateTopic="octoprint/event/+", transformationPattern="JSONPATH:$._event" ]
        Type string : filename "Filename" [ stateTopic="octoprint/progress/printing", transformationPattern="JSONPATH:$.printer_data.job.file.name" ]
        Type number : progress "Progress" [ stateTopic="octoprint/progress/printing", transformationPattern="JSONPATH:$.printer_data.progress.completion" ]
        Type number : printTime "Time Printed" [ stateTopic="octoprint/progress/printing", transformationPattern="JSONPATH:$.printer_data.progress.printTime" ]
        Type number : printTimeLeft "Time Left" [ stateTopic="octoprint/progress/printing", transformationPattern="JSONPATH:$.printer_data.progress.printTimeLeft" ]
        Type number : layerheight "Layerheight" [ stateTopic="octoprint/progress/printing", transformationPattern="JSONPATH:$.printer_data.currentZ" ]
        Type number : temperature-bed-actual "Temperature Bed Actual" [ stateTopic="octoprint/temperature/bed", transformationPattern="JSONPATH:$.actual" ]
        Type number : temperature-bed-target "Temperature Bed Target" [ stateTopic="octoprint/temperature/bed", transformationPattern="JSONPATH:$.target" ]
        Type number : temperature-hotend-actual "Temperature Hotend Actual" [ stateTopic="octoprint/temperature/tool0", transformationPattern="JSONPATH:$.actual" ]
        Type number : temperature-hotend-target "Temperature Hotend Target" [ stateTopic="octoprint/temperature/tool0", transformationPattern="JSONPATH:$.target" ]
}

Create a new *.items file (e.g. octoprint.items) and put the following code:

Group gOctoprint "Octoprint" (gAll)

// MQTT
String OctoprintConnected                           "Connected [%s]"		    <network>       (gOctoprint)    {channel="mqtt:topic:octoprint:connected"} 
String OctoprintState	                            "State [%s]"		        <office>        (gOctoprint)	{channel="mqtt:topic:octoprint:state"}
String OctoprintEvent   	                        "Event [%s]"		        <office>        (gOctoprint)	{channel="mqtt:topic:octoprint:event"}
String OctoprintJobFileName	                        "Filename [%s]"		        <office>        (gOctoprint)	{channel="mqtt:topic:octoprint:filename"}
Number:Dimensionless OctoprintJobProgressCompletion	"Completion [%.0f %%]"	    <battery>       (gOctoprint)	{channel="mqtt:topic:octoprint:progress"}
Number OctoprintPrintTime                           "Time Printed [%.0f s]"	    <time>          (gOctoprint)	{channel="mqtt:topic:octoprint:printTime"}
String OctoprintPrintTimeString                     "Time Printed [%s]"	        <time>          (gOctoprint)
Number OctoprintPrintTimeLeft                       "Time Left [%.0f s]"	    <time>          (gOctoprint)	{channel="mqtt:topic:octoprint:printTimeLeft"}
String OctoprintPrintTimeLeftString                 "Time Left [%s]"	        <time>          (gOctoprint)
String OctoprintPrintETAString                      "ETA [%s]"			                            <time>	        (gOctoprint)    
DateTime OctoprintPrintETADateTime                  "ETA [%1$tA, %1$td.%1$tm.%1$tY %1$tH:%1$tM]"    <time>          (gOctoprint)

Number:Length OctoprintJobCurrentLayerHeight        "Layer Height [%.1f mm]"    <flowpipe>      (gOctoprint)    {channel="mqtt:topic:octoprint:layerheight"}

Number:Temperature OctoprintPrinterHotEndTemp		"Nozzle temp [%.1f °C]"     <temperature>   (gOctoprint)	{channel="mqtt:topic:octoprint:temperature-hotend-actual"} 
Number:Temperature OctoprintPrinterHotEndTempTarget	"Nozzle target [%.1f °C]"   <temperature>	(gOctoprint)	{channel="mqtt:topic:octoprint:temperature-hotend-target"} 
Number:Temperature OctoprintPrinterBedTemp			"Bed temp [%.1f °C]"        <temperature>	(gOctoprint)	{channel="mqtt:topic:octoprint:temperature-bed-actual"} 
Number:Temperature OctoprintPrinterBedTempTarget	"Bed target [%.1f °C]"      <temperature>	(gOctoprint)	{channel="mqtt:topic:octoprint:temperature-bed-target"}

Switch OctoprintPower                               "Power"                                     (gOctoprint)    {channel="mqtt:topic:sonoff-plug-1:power"}
Switch OctoprintShutdownAfterPrint                  "Turn Off after Print"                      (gOctoprint)

Create a new *.rules file (e.g. octoprint.rules) and put the follwing code:

rule "Time Printed String"
when
	Item OctoprintPrintTime changed
then
    val seconds = (OctoprintPrintTime.state as DecimalType).intValue
    val int totalMinutes = seconds/60
    val int remainderSecs = seconds%60
    val int totalHours = totalMinutes/60
    val int remainderMins = totalMinutes%60
    val formattedTime = String::format("%02d", totalHours) + ":" + String::format("%02d", remainderMins) + ":" + String::format("%02d", remainderSecs)
    OctoprintPrintTimeString.postUpdate(formattedTime)
end

rule "Time Left String"
when
	Item OctoprintPrintTimeLeft changed
then
    val seconds = (OctoprintPrintTimeLeft.state as DecimalType).intValue
    val int totalMinutes = seconds/60
    val int remainderSecs = seconds%60
    val int totalHours = totalMinutes/60
    val int remainderMins = totalMinutes%60
    val formattedTime = String::format("%02d", totalHours) + ":" + String::format("%02d", remainderMins) + ":" + String::format("%02d", remainderSecs)
    OctoprintPrintTimeLeftString.postUpdate(formattedTime)

    val DateTime OctoprintETA = now.plusSeconds(seconds)
    //logInfo("Testing" ,"Octoprint ETA: " + OctoprintETA.toString)
    OctoprintPrintETAString.postUpdate(OctoprintETA.toString)
    OctoprintPrintETADateTime.postUpdate(new DateTimeType(OctoprintETA.toString))

end

rule "Shutdown after finish"
when
    Item OctoprintState changed from Printing to Operational
then
	if (OctoprintShutdownAfterPrint.state == ON && OctoprintJobProgressCompletion.state == 100){
		createTimer(now.plusSeconds(60),  [ |
            sendCommand(OctoprintPower, OFF)
        ])
        
        createTimer(now.plusSeconds(70),  [ |
            sendCommand(OctoprintShutdownAfterPrint, OFF)
        ])
	}
end

Example Sitemap:

Text label="3D Printer" icon="3dprinter" {
		Text	item=OctoprintState
		Text	item=OctoprintEvent
		Text	item=OctoprintJobFileName
		Text	item=OctoprintPrintTimeString
		Text	item=OctoprintPrintTimeLeftString
		Text	item=OctoprintPrintETADateTime
		Text	item=OctoprintJobProgressCompletion
		Text	item=OctoprintJobCurrentLayerHeight
		Text	item=OctoprintPrinterHotEndTemp
		Text	item=OctoprintPrinterBedTemp
		Image	url="http://octopi.local:8080/?action=snapshot" refresh=30000 // Camera Feed if you have
		Chart 	item=OctoprintJobProgressCompletion refresh=600000 period=12h // Plot progress over last 12h
		Text label="Power" {
			Switch 	item=OctoprintPower // Hidden On-Off Plug I use (to not accidentally click it)
		}
	}

ToDo:

  • See if it is possible to issue commands over MQTT as well

Enjoy!

7 Likes

Nice writeup, thx a lot.
Will try as soon I find the time to connect my printer to Octoprint. :+1::+1:

The two timer are created at the same time and will both execute after 60 seconds
It will not be in sequence
To retain your original sequence you need 60 and 70 seconds
I have also used the recommended syntax of item.sendCommand(Command) instead of sendCommand(item, command)

rule "Shutdown after finish"
when
    Item OctoprintState changed from Printing to Operational
then
    if (OctoprintShutdownAfterPrint.state == ON && OctoprintJobProgressCompletion.state == 100){
        createTimer(now.plusSeconds(60),  [ |
            OctoprintPower.sendCommand(OFF)
        ])
        createTimer(now.plusSeconds(70),  [ |
            OctoprintShutdownAfterPrint.sendCommand(OFF)
        ])
     }
end

Nice, I’ll have to go install the MQTT Plugin into my OctoPrint now :grin:

Something similiar to this:

val brokerName = "mqtt:broker:mosquitto"
val actions = getActions("mqtt", brokerName)
// logInfo(filename, "Actions are: " + actions)
actions.publishMQTT("octopring/sometopic", "somemessage")
// logInfo(filename, "action published...")

@sihui @vzorglub Thanks for the suggestions!

@hakan Looks like the Octoprint MQTT implementation currently does not support command execution

Ah, sorry, I thought you wanted to push commands from openHAB to Octoprint… My mistake.

Hi @hakan,
I think we misunderstood each other.

Yes I would like to control Octoprint from openHAB.
But the octoprint-mqtt plugin does not have an option to receive commands.

Nice work, was just installing Octoprint and was basically 50% on the way starting to build something like this/a binding, guess I can do something else useful now. :+1:

Well an actual octoprint binding would be amazing, but I have no Java skills for that…

Hooray, today, I had my first real print with Octoprint connected to openHAB. Things start to become real interesting. Soon, I’ll be able to switch on the light in the room so I can have better timelampses, and notifiy myself via nanoleafs / hues / fog horns that I should go to the basement and pick up my print :wink:

One thing though, I cannot obtain the data for Z Layer height. How did you convince octoprint to send you this item:

    Type number : layerheight "Layerheight" [
         stateTopic="octoprint/progress/printing",
         transformationPattern="JSONPATH:$.printer_data.currentZ" ]

my octoprint/progess/printing looks like this (linefeeds added for readibility):

  octoprint/progress/printing 
  {"progress": 28, 
    "_timestamp": 1547383285, 
    "location": "local", 
    "path": "yaddayaddayadda.gcode"}

You need at least version 0.8.0 of the MQTT Plugin, then you have the option:

I found just the OctoPrint-MQTTSubscribe plugin:

After my current print finishes (another 30 minutes or so), I’ll install this plugin as well and see what I can do with it.

Interesting. Let me know your progress!

Thank you, @SpaceGlider - works like a charm. Now you’re one of my heroes thumbsup

Thanks for your explanation. But unfortunately it’s not working for me. I’ve the same items and things file.
In my Octoprint-log it shows:

MQTT (0.8.1) = /home/pi/oprint/local/lib/python2.7/site-packages/octoprint_mqtt
2019-03-28 09:06:21,268 - octoprint.plugins.mqtt - INFO - Connected to mqtt broker

The version is right. If I use MQTT.fx I see the topics with values:

But in openhab I don’t get any value. MQTT is working in openhab with the esp8266 I have.

In OpenHab I have the binding-mqtt - 2.4.0 installed

Any Ideas?

So something is missing. Did you configure the connection from openHAB (binding-mqtt) to your MQTT broker correctly?

Yes I did. I does work with my esp-boards

Hello, works very well. Except for the automatic switch off when the PrintJob is done.
This line makes mor worry “OctoprintJobProgressCompletion.state == 100”
On:

logInfo (filename, “Progress:” + OctoprintJobProgressCompletion.state)
returns: “Progress: 100.0%” back. and is not true.
Do I have to compare this as a string?

Chris

Hi Chris

This works for me:

rule "Octoprint finish"
when
    Item OctoprintJobProgressCompletion changed to 100
then
    logInfo("Octoprint","Print finished")
end