[modules/octoprint] added new octoprint module
* shows printer bed + tools temperature in status bar * opens popup on left click to show temperature details and webcam stream (if enabled)
This commit is contained in:
parent
9b63345b44
commit
5402a0b16f
2 changed files with 228 additions and 1 deletions
224
modules/contrib/octoprint.py
Normal file
224
modules/contrib/octoprint.py
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
# pylint: disable=C0111,R0903
|
||||||
|
|
||||||
|
"""Displays the Octorpint status and the printer's bed/tools temperature in the status bar.
|
||||||
|
|
||||||
|
Left click opens a popup which shows the bed & tools temperatures and additionally a livestream of the webcam (if enabled).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
* octoprint.address : Octoprint address (e.q: http://192.168.1.3)
|
||||||
|
* octoprint.apitoken : Octorpint API Token (can be obtained from the Octoprint Webinterface)
|
||||||
|
* octoprint.webcam : Set to True if a webcam is connected (default: False)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import urllib
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from io import BytesIO
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import simplejson
|
||||||
|
|
||||||
|
import core.module
|
||||||
|
import core.widget
|
||||||
|
import core.input
|
||||||
|
|
||||||
|
def get_frame(url):
|
||||||
|
img_bytes = b""
|
||||||
|
stream = urllib.request.urlopen(url)
|
||||||
|
while True:
|
||||||
|
img_bytes += stream.read(1024)
|
||||||
|
a = img_bytes.find(b'\xff\xd8')
|
||||||
|
b = img_bytes.find(b'\xff\xd9')
|
||||||
|
if a != -1 and b != -1:
|
||||||
|
jpg = img_bytes[a:b+2]
|
||||||
|
img_bytes = img_bytes[b+2:]
|
||||||
|
img = Image.open(BytesIO(jpg))
|
||||||
|
return img
|
||||||
|
return None
|
||||||
|
|
||||||
|
class WebcamImagesWorker(threading.Thread):
|
||||||
|
def __init__(self, url, queue):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
self.__url = url
|
||||||
|
self.__queue = queue
|
||||||
|
self.__running = True
|
||||||
|
def run(self):
|
||||||
|
while self.__running:
|
||||||
|
img = get_frame(self.__url)
|
||||||
|
self.__queue.put(img)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.__running = False
|
||||||
|
|
||||||
|
class Module(core.module.Module):
|
||||||
|
@core.decorators.every(minutes=1)
|
||||||
|
def __init__(self, config, theme):
|
||||||
|
super().__init__(config, theme, core.widget.Widget(self.octoprint_status))
|
||||||
|
|
||||||
|
self.__octoprint_state = "Unknown"
|
||||||
|
self.__octoprint_address = self.parameter("address", "")
|
||||||
|
self.__octoprint_api_token = self.parameter("apitoken", "")
|
||||||
|
self.__octoprint_webcam = self.parameter("webcam", False)
|
||||||
|
|
||||||
|
self.__webcam_images_worker = None
|
||||||
|
self.__webcam_image_url = self.__octoprint_address + "/webcam/?action=stream"
|
||||||
|
self.__webcam_images_queue = None
|
||||||
|
|
||||||
|
self.__printer_bed_temperature = "-"
|
||||||
|
self.__tool1_temperature = "-"
|
||||||
|
|
||||||
|
self.update_status()
|
||||||
|
|
||||||
|
core.input.register(self, button=core.input.LEFT_MOUSE,
|
||||||
|
cmd=self.__show_popup)
|
||||||
|
|
||||||
|
def octoprint_status(self, widget):
|
||||||
|
if self.__octoprint_state == "Offline" or self.__octoprint_state == "Unknown":
|
||||||
|
return self.__octoprint_state
|
||||||
|
return self.__octoprint_state + " | B: " + str(self.__printer_bed_temperature) + "°C" + " | T1: " + str(self.__tool1_temperature) + "°C"
|
||||||
|
|
||||||
|
def __get(self, endpoint):
|
||||||
|
url = self.__octoprint_address + "/api/" + endpoint
|
||||||
|
headers = {"X-Api-Key": self.__octoprint_api_token}
|
||||||
|
resp = requests.get(url, headers=headers)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return resp.json(), resp.status_code
|
||||||
|
except simplejson.errors.JSONDecodeError:
|
||||||
|
return None, resp.status_code
|
||||||
|
|
||||||
|
def __get_printer_bed_temperature(self):
|
||||||
|
printer_info, status_code = self.__get("printer")
|
||||||
|
if status_code == 200:
|
||||||
|
return printer_info["temperature"]["bed"]["actual"], printer_info["temperature"]["bed"]["target"]
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def __get_octoprint_state(self):
|
||||||
|
job_info, status_code = self.__get("job")
|
||||||
|
return job_info["state"] if status_code == 200 else "Unknown"
|
||||||
|
|
||||||
|
def __get_tool_temperatures(self):
|
||||||
|
tool_temperatures = []
|
||||||
|
|
||||||
|
printer_info, status_code = self.__get("printer")
|
||||||
|
if status_code == 200:
|
||||||
|
temperatures = printer_info["temperature"]
|
||||||
|
|
||||||
|
tool_id = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
tool = temperatures["tool" + str(tool_id)]
|
||||||
|
tool_temperatures.append((tool["actual"], tool["target"]))
|
||||||
|
except KeyError:
|
||||||
|
break
|
||||||
|
tool_id += 1
|
||||||
|
return tool_temperatures
|
||||||
|
|
||||||
|
def update_status(self):
|
||||||
|
try:
|
||||||
|
self.__octoprint_state = self.__get_octoprint_state()
|
||||||
|
|
||||||
|
actual_temp, _ = self.__get_printer_bed_temperature()
|
||||||
|
if actual_temp is None:
|
||||||
|
actual_temp = "-"
|
||||||
|
self.__printer_bed_temperature = str(actual_temp)
|
||||||
|
|
||||||
|
tool_temps = self.__get_tool_temperatures()
|
||||||
|
if len(tool_temps) > 0:
|
||||||
|
self.__tool1_temperature = tool_temps[0][0]
|
||||||
|
else:
|
||||||
|
self.__tool1_temperature = "-"
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("Couldn't get data")
|
||||||
|
|
||||||
|
def __refresh_image(self, root, webcam_image, webcam_image_container):
|
||||||
|
try:
|
||||||
|
img = self.__webcam_images_queue.get()
|
||||||
|
webcam_image = ImageTk.PhotoImage(img)
|
||||||
|
webcam_image_container.config(image=webcam_image)
|
||||||
|
except queue.Empty as e:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("Couldn't refresh image")
|
||||||
|
|
||||||
|
root.after(5, self.__refresh_image, root, webcam_image, webcam_image_container)
|
||||||
|
|
||||||
|
def __refresh_temperatures(self, root, printer_bed_temperature_label, tools_temperature_label):
|
||||||
|
actual_bed_temp, target_bed_temp = self.__get_printer_bed_temperature()
|
||||||
|
if actual_bed_temp is None:
|
||||||
|
actual_bed_temp = "-"
|
||||||
|
if target_bed_temp is None:
|
||||||
|
target_bed_temp = "-"
|
||||||
|
|
||||||
|
bed_temp = "Bed: " + str(actual_bed_temp) + "/" + str(target_bed_temp) + " °C"
|
||||||
|
printer_bed_temperature_label.config(text=bed_temp)
|
||||||
|
|
||||||
|
|
||||||
|
tool_temperatures = self.__get_tool_temperatures()
|
||||||
|
tools_temp = "Tools: "
|
||||||
|
|
||||||
|
if len(tool_temperatures) == 0:
|
||||||
|
tools_temp += "-/- °C"
|
||||||
|
else:
|
||||||
|
for i, tool_temperature in enumerate(tool_temperatures):
|
||||||
|
tools_temp += str(tool_temperature[0]) + "/" + str(tool_temperature[1]) + "°C"
|
||||||
|
if i != len(tool_temperatures)-1:
|
||||||
|
tools_temp += "\t"
|
||||||
|
tools_temperature_label.config(text=tools_temp)
|
||||||
|
|
||||||
|
root.after(500, self.__refresh_temperatures, root, printer_bed_temperature_label, tools_temperature_label)
|
||||||
|
|
||||||
|
def __show_popup(self, widget):
|
||||||
|
root = tk.Tk()
|
||||||
|
root.attributes('-type', 'dialog')
|
||||||
|
root.title("Octoprint")
|
||||||
|
frame = tk.Frame(root)
|
||||||
|
if self.__octoprint_webcam:
|
||||||
|
|
||||||
|
#load first image synchronous before popup is shown, otherwise tkinter isn't able to layout popup properly
|
||||||
|
img = get_frame(self.__webcam_image_url)
|
||||||
|
webcam_image = ImageTk.PhotoImage(img)
|
||||||
|
webcam_image_container = tk.Button(frame, image=webcam_image)
|
||||||
|
webcam_image_container.pack()
|
||||||
|
|
||||||
|
self.__webcam_images_queue = queue.Queue()
|
||||||
|
|
||||||
|
self.__webcam_images_worker = WebcamImagesWorker(self.__webcam_image_url, self.__webcam_images_queue)
|
||||||
|
self.__webcam_images_worker.start()
|
||||||
|
else:
|
||||||
|
logging.debug("Not using webcam, as webcam is disabled. Enable with --webcam.")
|
||||||
|
frame.pack()
|
||||||
|
|
||||||
|
temperatures_label = tk.Label(frame, text="Temperatures", font=('', 25))
|
||||||
|
temperatures_label.pack()
|
||||||
|
|
||||||
|
printer_bed_temperature_label = tk.Label(frame, text="Bed: -/- °C", font=('', 15))
|
||||||
|
printer_bed_temperature_label.pack()
|
||||||
|
|
||||||
|
tools_temperature_label = tk.Label(frame, text="Tools: -/- °C", font=('', 15))
|
||||||
|
tools_temperature_label.pack()
|
||||||
|
|
||||||
|
root.after(10, self.__refresh_image, root, webcam_image, webcam_image_container)
|
||||||
|
root.after(500, self.__refresh_temperatures, root, printer_bed_temperature_label, tools_temperature_label)
|
||||||
|
root.bind("<Destroy>", self.__on_close_popup)
|
||||||
|
|
||||||
|
root.eval('tk::PlaceWindow . center')
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
def __on_close_popup(self, event):
|
||||||
|
self.__webcam_images_queue = None
|
||||||
|
self.__webcam_images_worker.stop()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.update_status()
|
||||||
|
|
||||||
|
def state(self, widget):
|
||||||
|
return []
|
||||||
|
|
||||||
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -266,5 +266,8 @@
|
||||||
"work": { "prefix": "" },
|
"work": { "prefix": "" },
|
||||||
"break": { "prefix": "" }
|
"break": { "prefix": "" }
|
||||||
},
|
},
|
||||||
"hddtemp": { "prefix": "" }
|
"hddtemp": { "prefix": "" },
|
||||||
|
"octoprint": {
|
||||||
|
"prefix": " "
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue