def test_cancelled_callback(self): countdown = Countdown(5) timer_task = mock.MagicMock() timer_task.side_effect = countdown.step on_cancelled = mock.MagicMock() on_condition_false = mock.MagicMock() timer = RepeatedTimer( 10, timer_task, condition=lambda: countdown.counter > 0, on_condition_false=on_condition_false, on_cancelled=on_cancelled, ) timer.start() # give it some time to run time.sleep(1) # then cancel it and wait for the thread to really finish timer.cancel() timer.join() self.assertEqual(0, on_condition_false.call_count) self.assertEqual(1, on_cancelled.call_count)
class DisplayETAPlugin(octoprint.plugin.ProgressPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.EventHandlerPlugin): def __init__(self): self.activated = True self.timer = RepeatedTimer( 5.0, DisplayETAPlugin.fromTimer, args=[self], run_first=False, ) self.timer.start() self.currentFile = None def on_event(self, event, payload): if event == "FileSelected": self.currentFile = payload["file"] def fromTimer(self): self._logger.info("The Timer Has Fired") self._logger.info(self._printer.get_current_job()) self._printer.select_file( '/home/pablo/.octoprint/uploads/20mm_hollow_cube.gcode', False, printAfterSelect=True) self.timer.cancel() #self._plugin_manager.send_plugin_message(self._identifier, dict(eta_string=self.eta_string)) def get_assets(self): return {"js": ["js/displayeta.js"]}
def test_not_run_first(self): timer_task = mock.MagicMock() timer = RepeatedTimer(60, timer_task) timer.start() # give it some time to run - should hang in the sleep phase though time.sleep(1) # then cancel it and wait for the thread to really finish timer.cancel() timer.join() self.assertEqual(0, timer_task.call_count)
def test_not_run_first(self): timer_task = mock.MagicMock() timer = RepeatedTimer(60, timer_task) timer.start() # give it some time to run - should hang in the sleep phase though time.sleep(1) # then cancel it and wait for the thread to really finish timer.cancel() timer.join() self.assertEqual(0, timer_task.call_count)
def test_run_first(self): timer_task = mock.MagicMock() timer = RepeatedTimer(60, timer_task, run_first=True) timer.start() # give it some time to run time.sleep(1) # then cancel it and wait for the thread to really finish timer.cancel() timer.join() # should have run once self.assertEqual(1, timer_task.call_count)
def test_run_first(self): timer_task = mock.MagicMock() timer = RepeatedTimer(60, timer_task, run_first=True) timer.start() # give it some time to run time.sleep(1) # then cancel it and wait for the thread to really finish timer.cancel() timer.join() # should have run once self.assertEqual(1, timer_task.call_count)
def test_cancelled_callback(self): countdown = Countdown(5) timer_task = mock.MagicMock() timer_task.side_effect = countdown.step on_cancelled = mock.MagicMock() on_condition_false = mock.MagicMock() timer = RepeatedTimer(10, timer_task, condition=lambda: countdown.counter > 0, on_condition_false=on_condition_false, on_cancelled=on_cancelled) timer.start() # give it some time to run time.sleep(1) # then cancel it and wait for the thread to really finish timer.cancel() timer.join() self.assertEqual(0, on_condition_false.call_count) self.assertEqual(1, on_cancelled.call_count)
class NavBarPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SettingsPlugin): def __init__(self): self.isRaspi = False self.debugMode = False # to simulate temp on Win/Mac self.displayRaspiTemp = True self._checkTempTimer = None def on_after_startup(self): self.displayRaspiTemp = self._settings.get(["displayRaspiTemp"]) self._logger.debug("displayRaspiTemp: %s" % self.displayRaspiTemp) if sys.platform == "linux2": with open('/proc/cpuinfo', 'r') as infile: cpuinfo = infile.read() # Match a line like 'Hardware : BCM2709' match = re.search('^Hardware\s+:\s+(\w+)$', cpuinfo, flags=re.MULTILINE | re.IGNORECASE) if match is None: # Couldn't find the hardware, assume it isn't a pi. self.isRaspi = False elif match.group(1) == 'BCM2708': self._logger.debug("Pi 1") self.isRaspi = True elif match.group(1) == 'BCM2709': self._logger.debug("Pi 2") self.isRaspi = True elif match.group(1) == 'BCM2835': self._logger.debug("Pi 3") self.isRaspi = True if self.isRaspi and self.displayRaspiTemp: self._logger.debug("Let's start RepeatedTimer!") self.startTimer(30.0) elif self.debugMode: self.isRaspi = True if self.displayRaspiTemp: self.startTimer(5.0) self._logger.debug("is Raspberry Pi? - %s" % self.isRaspi) def startTimer(self, interval): self._checkTempTimer = RepeatedTimer(interval, self.checkRaspiTemp, None, None, True) self._checkTempTimer.start() def checkRaspiTemp(self): from sarge import run, Capture self._logger.debug("Checking Raspberry Pi internal temperature") if sys.platform == "linux2": p = run("/opt/vc/bin/vcgencmd measure_temp", stdout=Capture()) p = p.stdout.text elif self.debugMode: import random def randrange_float(start, stop, step): return random.randint(0, int((stop - start) / step)) * step + start p = "temp=%s'C" % randrange_float(5, 60, 0.1) self._logger.debug("response from sarge: %s" % p) match = re.search('=(.*)\'', p) if not match: self.isRaspi = False else: temp = match.group(1) self._logger.debug("match: %s" % temp) self._plugin_manager.send_plugin_message(self._identifier, dict(israspi=self.isRaspi, raspitemp=temp)) ##~~ SettingsPlugin def get_settings_defaults(self): return dict(displayRaspiTemp = self.displayRaspiTemp) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.displayRaspiTemp = self._settings.get(["displayRaspiTemp"]) if self.displayRaspiTemp: interval = 5.0 if self.debugMode else 30.0 self.startTimer(interval) else: if self._checkTempTimer is not None: try: self._checkTempTimer.cancel() except: pass self._plugin_manager.send_plugin_message(self._identifier, dict()) ##~~ TemplatePlugin API def get_template_configs(self): if self.isRaspi: return [ dict(type="settings", template="navbartemp_settings_raspi.jinja2") ] else: return [] ##~~ AssetPlugin API def get_assets(self): return { "js": ["js/navbartemp.js"], "css": ["css/navbartemp.css"], "less": ["less/navbartemp.less"] } ##~~ Softwareupdate hook def get_update_information(self): return dict( navbartemp=dict( displayName="Navbar Temperature Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-NavbarTemp", current=self._plugin_version, # update method: pip w/ dependency links pip="https://github.com/imrahil/OctoPrint-NavbarTemp/archive/{target_version}.zip" ) )
class shutdownprinterPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.StartupPlugin): def __init__(self): self.url = "" self._typeNotifShow = "" self._wait_temp = "" self.previousEventIsCancel = False self._abort_all_for_this_session = False self.gcode = "M81" self._mode_shutdown_gcode = True self._mode_shutdown_api = False self._mode_shutdown_api_custom = False self.api_custom_GET = False self.api_custom_POST = False self.api_custom_PUT = False self.api_custom_url = "" self.api_custom_json_header = "" self.api_custom_body = "" self.api_key_plugin = "" self.api_json_command = "" self.api_plugin_name = "" self.api_plugin_port = 5000 self.extraCommand = "" self.abortTimeout = 0 self.temperatureValue = 0 self.temperatureTarget = False self.printFailed = False self.printCancelled = False self.rememberCheckBox = False self.lastCheckBoxValue = False self._shutdown_printer_enabled = True self._timeout_value = None self._abort_timer = None self._abort_timer_temp = None self.ctx = ssl.create_default_context() self.ctx.check_hostname = False self.ctx.verify_mode = ssl.CERT_NONE def initialize(self): self.gcode = self._settings.get(["gcode"]) self._logger.debug("gcode: %s" % self.gcode) self.url = self._settings.get(["url"]) self._logger.debug("url: %s" % self.url) self.api_key_plugin = self._settings.get(["api_key_plugin"]) self._logger.debug("api_key_plugin: %s" % self.api_key_plugin) self._mode_shutdown_gcode = self._settings.get_boolean( ["_mode_shutdown_gcode"]) self._logger.debug("_mode_shutdown_gcode: %s" % self._mode_shutdown_gcode) self._mode_shutdown_api = self._settings.get_boolean( ["_mode_shutdown_api"]) self._logger.debug("_mode_shutdown_api: %s" % self._mode_shutdown_api) self._mode_shutdown_api_custom = self._settings.get_boolean( ["_mode_shutdown_api_custom"]) self._logger.debug("_mode_shutdown_api_custom: %s" % self._mode_shutdown_api_custom) self.api_custom_POST = self._settings.get_boolean(["api_custom_POST"]) self._logger.debug("api_custom_POST: %s" % self.api_custom_POST) self.api_custom_GET = self._settings.get_boolean(["api_custom_GET"]) self._logger.debug("api_custom_GET: %s" % self.api_custom_GET) self.api_custom_PUT = self._settings.get_boolean(["api_custom_PUT"]) self._logger.debug("api_custom_PUT: %s" % self.api_custom_PUT) self.api_custom_url = self._settings.get(["api_custom_url"]) self._logger.debug("api_custom_url: %s" % self.api_custom_url) self.api_custom_json_header = self._settings.get( ["api_custom_json_header"]) self._logger.debug("api_custom_json_header: %s" % self.api_custom_json_header) self.api_custom_body = self._settings.get(["api_custom_body"]) self._logger.debug("api_custom_body: %s" % self.api_custom_body) self.api_json_command = self._settings.get(["api_json_command"]) self._logger.debug("api_json_command: %s" % self.api_json_command) self.api_plugin_name = self._settings.get(["api_plugin_name"]) self._logger.debug("api_plugin_name: %s" % self.api_plugin_name) self.api_plugin_port = self._settings.get_int(["api_plugin_port"]) self._logger.debug("api_plugin_port: %s" % self.api_plugin_port) self.temperatureValue = self._settings.get_int(["temperatureValue"]) self._logger.debug("temperatureValue: %s" % self.temperatureValue) self.temperatureTarget = self._settings.get_boolean( ["temperatureTarget"]) self._logger.debug("temperatureTarget: %s" % self.temperatureTarget) self.extraCommand = self._settings.get(["extraCommand"]) self._logger.debug("extraCommand: %s" % self.extraCommand) self.abortTimeout = self._settings.get_int(["abortTimeout"]) self._logger.debug("abortTimeout: %s" % self.abortTimeout) self.printFailed = self._settings.get_boolean(["printFailed"]) self._logger.debug("printFailed: %s" % self.printFailed) self.printCancelled = self._settings.get_boolean(["printCancelled"]) self._logger.debug("printCancelled: %s" % self.printCancelled) self.rememberCheckBox = self._settings.get_boolean( ["rememberCheckBox"]) self._logger.debug("rememberCheckBox: %s" % self.rememberCheckBox) self.lastCheckBoxValue = self._settings.get_boolean( ["lastCheckBoxValue"]) self._logger.debug("lastCheckBoxValue: %s" % self.lastCheckBoxValue) if self.rememberCheckBox: self._shutdown_printer_enabled = self.lastCheckBoxValue self.shutdown_printer = self._plugin_manager.get_hooks( "octoprint.plugin.ShutdownPrinter.shutdown") self.enclosure_screen_hook = self._plugin_manager.get_hooks( "octoprint.plugin.external.event") self.hookEnclosureScreenfct() def on_after_startup(self): self.hookEnclosureScreenfct() def hookEnclosureScreenfct(self, data=dict()): self._logger.error("send status off 1") if self.enclosure_screen_hook is not None: for name, hook in self.enclosure_screen_hook.items(): # first sd card upload plugin that feels responsible gets the job try: # hook(self.statusManualStop) self._logger.error("send status off 2") hook( dict(shutdownPrinter=dict( offAfterPrintEnd=self._shutdown_printer_enabled, data=data))) except Exception as e: self._logger.error("Failed get hook: %s" % e.message) else: self._logger.error("hook does not exist") def hook_event_enclosureScreen(self, data): if "shutdownPrinter" in data: self._logger.info(str(data["shutdownPrinter"])) if "offAfterPrintEnd" in data["shutdownPrinter"]: if self._shutdown_printer_enabled: self._shutdown_printer_enabled = False else: self._shutdown_printer_enabled = True self.lastCheckBoxValue = self._shutdown_printer_enabled if self.rememberCheckBox: self._settings.set_boolean(["lastCheckBoxValue"], self.lastCheckBoxValue) self._settings.save() eventManager().fire(Events.SETTINGS_UPDATED) self._plugin_manager.send_plugin_message( self._identifier, dict(shutdownprinterEnabled=self._shutdown_printer_enabled, type=self._typeNotifShow, timeout_value=self._timeout_value, wait_temp=self._wait_temp, time=time.time())) if "abort" in data["shutdownPrinter"]: self.forcedAbort = True if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._timeout_value = None self._typeNotifShow = "destroynotif" self._timeout_value = -1 self._wait_temp = "" self._logger.info("Shutdown aborted.") self._destroyNotif() def get_assets(self): return dict(js=["js/shutdownprinter.js"], css=["css/shutdownprinter.css"]) def get_template_configs(self): return [ dict(type="sidebar", name="Shutdown Printer", custom_bindings=False, icon="power-off"), dict(type="settings", custom_bindings=False) ] def get_api_commands(self): return dict(enable=["eventView"], status=[], update=["eventView"], disable=["eventView"], shutdown=["mode", "eventView"], abort=["eventView"]) def get_additional_permissions(self): return [ dict(key="ADMIN", name="shutdown printer plugin", description=gettext( "Allows to access to api of plugin shutdownprinter."), default_groups=[ADMIN_GROUP], roles=["admin"], dangerous=True) ] def on_api_command(self, command, data): # if not user_permission.can(): # return make_response("Insufficient rights", 403) try: plugin_permission = Permissions.PLUGIN_SHUTDOWNPRINTER_ADMIN.can() except: plugin_permission = user_permission.can() if not plugin_permission: return make_response("Insufficient rights", 403) # user_id = current_user.get_name() # if not user_id or not admin_permission.can(): # return make_response("Insufficient rights", 403) if command == "status": return make_response(str(self._shutdown_printer_enabled), 200) elif command == "enable": self._shutdown_printer_enabled = True self.hookEnclosureScreenfct() elif command == "disable": self._shutdown_printer_enabled = False self.hookEnclosureScreenfct() elif command == "shutdown": def process(): try: self._shutdown_printer_API_CMD( data["mode"] ) #mode 1 = gcode, mode 2 = api, mode 3 = custom api except: # exc_type, exc_obj, exc_tb = sys.exc_info() self._logger.error("Failed read tty screen : %s" % str(traceback.format_exc())) thread = threading.Thread(target=process) thread.daemon = True self._logger.info("start thread") thread.start() elif command == "abort": self.forcedAbort = True if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._timeout_value = None self._typeNotifShow = "destroynotif" self._timeout_value = -1 self._wait_temp = "" self._logger.info("Shutdown aborted.") self._destroyNotif() if command == "enable" or command == "disable": self.lastCheckBoxValue = self._shutdown_printer_enabled if self.rememberCheckBox: self._settings.set_boolean(["lastCheckBoxValue"], self.lastCheckBoxValue) self._settings.save() eventManager().fire(Events.SETTINGS_UPDATED) self._logger.info("eventView") if data["eventView"] == True: self.sendNotif(True) return make_response("ok", 200) def sendNotif(self, skipHook=False): if self.forcedAbort == False: if skipHook == False: self.hookEnclosureScreenfct( dict(type=self._typeNotifShow, timeout_value=self._timeout_value, wait_temp=self._wait_temp, time=time.time())) self._plugin_manager.send_plugin_message( self._identifier, dict(shutdownprinterEnabled=self._shutdown_printer_enabled, type=self._typeNotifShow, timeout_value=self._timeout_value, wait_temp=self._wait_temp, time=time.time())) def on_event(self, event, payload): # if event == Events.CLIENT_OPENED: # self._plugin_manager.send_plugin_message(self._identifier, dict(shutdownprinterEnabled=self._shutdown_printer_enabled, type="timeout", timeout_value=self._timeout_value)) # return if not self._shutdown_printer_enabled: return if event == Events.PRINT_STARTED: # self._logger.info("Print started") self.previousEventIsCancel = False self._abort_all_for_this_session = False if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._destroyNotif() self._logger.info( "ShutdownPrinter Events.PRINT_STARTED aborted because PRINTING detect" ) if event not in [ Events.PRINT_DONE, Events.PRINT_FAILED, Events.PRINT_CANCELLED ]: return return if event == Events.PRINT_DONE: # self._logger.info("Print done") self._temperature_target() return elif event == Events.PRINT_CANCELLED and self.printCancelled: # self._logger.info("Print cancelled") self.previousEventIsCancel = True self._temperature_target() return elif event == Events.PRINT_CANCELLED: # self._logger.info("Print cancelled") self.previousEventIsCancel = True return elif (event == Events.ERROR or event == Events.PRINT_FAILED) and self.printFailed: if self.previousEventIsCancel == True: self.previousEventIsCancel = False return # self._logger.info("Print failed") self._temperature_target() return else: self.previousEventIsCancel = False return def _temperature_target(self): self.forcedAbort = False if self._abort_timer_temp is not None: # self._logger.info("_abort_timer_temp_destroyNotif") self._destroyNotif() return if self._abort_all_for_this_session == True: # self._logger.info("_abort_all_for_this_session_destroyNotif") if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._destroyNotif() return if self.temperatureTarget: self._abort_timer_temp = RepeatedTimer(2, self._temperature_task) self._abort_timer_temp.start() else: self._timer_start() def _temperature_task(self): try: if self._abort_all_for_this_session == True: if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._destroyNotif() return if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._logger.info( "ShutdownPrinter _temperature_task aborted because PRINTING detect" ) self._destroyNotif() return self._temp = self._printer.get_current_temperatures() self._logger.info(str(self._temp)) tester = 0 number = 0 self._wait_temp = "" self._typeNotifShow = "waittemp" for tool in self._temp.keys(): if not tool == "bed" and not tool == "chamber": if self._temp[tool]["actual"] is None: continue if self._temp[tool]["actual"] <= self.temperatureValue: tester += 1 number += 1 self._wait_temp = " - " + str(tool) + ": " + str( self._temp[tool]["actual"]) + "/" + str( self.temperatureValue) + "°C\n" if tester == number: if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._timer_start() else: self.sendNotif() except: self._logger.error("Failed to connect to call api: %s" % str(traceback.format_exc())) def _timer_start(self): if self._abort_timer is not None: self._destroyNotif() return if self._abort_all_for_this_session == True: if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._destroyNotif() return self._logger.info("Starting abort shutdown printer timer.") self._timeout_value = self.abortTimeout self._abort_timer = RepeatedTimer(1, self._timer_task) self._abort_timer.start() def _timer_task(self): if self._timeout_value is None: self._destroyNotif() return if self._abort_all_for_this_session == True: self._destroyNotif() if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None return if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._logger.info( "ShutdownPrinter _timer_task aborted because PRINTING detect") self._destroyNotif() return self._timeout_value -= 1 self._typeNotifShow = "timeout" self._wait_temp = "" self.sendNotif() if self._timeout_value <= 0: if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None if self.forcedAbort == False: self._shutdown_printer() def _destroyNotif(self): self._timeout_value = -1 self._wait_temp = "" self.hookEnclosureScreenfct( dict(type="destroynotif", timeout_value=-1, wait_temp="", time=time.time())) self._plugin_manager.send_plugin_message( self._identifier, dict(shutdownprinterEnabled=self._shutdown_printer_enabled, type="destroynotif", timeout_value=-1, wait_temp="", time=time.time())) def _shutdown_printer(self): if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: time.sleep(2) if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: self._logger.info( "ShutdownPrinter aborted because PRINTING detect") return if self.forcedAbort: self._logger.info( "ShutdownPrinter aborted because force abort is True") return if self._mode_shutdown_gcode == True: self._shutdown_printer_by_gcode() elif self._mode_shutdown_api == True: self._shutdown_printer_by_API() else: self._shutdown_printer_by_API_custom() def _shutdown_printer_API_CMD(self, mode): if mode == 1: self._shutdown_printer_by_gcode() elif mode == 2: self._shutdown_printer_by_API() elif mode == 3: self._shutdown_printer_by_API_custom() def _extraCommand(self): if self.shutdown_printer is not None: for name, hook in self.shutdown_printer.items(): # first sd card upload plugin that feels responsible gets the job try: hook() except Exception as e: self._logger.error("Failed get hook: %s" % e.message) else: self._logger.error("hook does not exist") if self.extraCommand != "": process = subprocess.Popen(self.extraCommand, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() self._logger.info("extraCommand stdout: %s" % stdout.rstrip().strip()) self._logger.info("extraCommand stderr: %s" % stderr.rstrip().strip()) def _shutdown_printer_by_API(self): if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: time.sleep(2) if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: self._logger.info( "ShutdownPrinter by API aborted because PRINTING detect") return if self.forcedAbort: self._logger.info( "ShutdownPrinter by API aborted because force abort is True") return url = "http://127.0.0.1:" + str( self.api_plugin_port) + "/api/plugin/" + self.api_plugin_name headers = { 'Content-Type': 'application/json', 'X-Api-Key': self.api_key_plugin } data = self.api_json_command.encode() self._logger.info("Shutting down printer with API") try: request = urllib2.Request(url, data=data, headers=headers) request.get_method = lambda: "POST" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE contents = urllib2.urlopen(request, timeout=30, context=ctx).read() self._logger.debug("call response (POST API octoprint): %s" % contents) self._extraCommand() except: self._logger.error("Failed to connect to call api: %s" % str(traceback.format_exc())) return def _shutdown_printer_by_API_custom(self): if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: time.sleep(2) if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: self._logger.info( "ShutdownPrinter by Custom API aborted because PRINTING detect" ) return if self.forcedAbort: self._logger.info( "ShutdownPrinter by Custom API aborted because force abort is True" ) return headers = {} if self.api_custom_json_header != "": headers = eval(self.api_custom_json_header) if self.api_custom_PUT == True: data = self.api_custom_body.encode() self._logger.info("Shutting down printer with API custom (PUT)") try: request = urllib2.Request(self.api_custom_url, data=data, headers=headers) request.get_method = lambda: "PUT" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE contents = urllib2.urlopen(request, timeout=30, context=ctx).read() self._logger.debug("call response (PUT): %s" % contents) self._extraCommand() except Exception as e: self._logger.error("Failed to connect to call api: %s" % e.message) return if self.api_custom_POST == True: data = self.api_custom_body self._logger.info("Shutting down printer with API custom (POST)") try: request = urllib2.Request(self.api_custom_url, data=data, headers=headers) request.get_method = lambda: "POST" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE contents = urllib2.urlopen(request, timeout=30, context=ctx).read() self._logger.debug("call response (POST): %s" % contents) self._extraCommand() except Exception as e: self._logger.error("Failed to connect to call api: %s" % e.message) return elif self.api_custom_GET == True: self._logger.info("Shutting down printer with API custom (GET)") try: request = urllib2.Request(self.api_custom_url, headers=headers) ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE contents = urllib2.urlopen(request, timeout=30, context=ctx).read() self._logger.debug("call response (GET): %s" % contents) self._extraCommand() except Exception as e: self._logger.error("Failed to connect to call api: %s" % e.message) return def _shutdown_printer_by_gcode(self): if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: time.sleep(2) if self._printer.get_state_id( ) == "PRINTING" and self._printer.is_printing() == True: self._logger.info( "ShutdownPrinter by GCODE aborted because PRINTING detect") return if self.forcedAbort: self._logger.info( "ShutdownPrinter by GCODE aborted because force abort is True") return self._printer.commands(self.gcode + " " + self.url) self._logger.info("Shutting down printer with command: " + self.gcode + " " + self.url) self._extraCommand() def powersupplyCancelAutoShutdown(self, status): self._logger.info("Shutdown aborted status " + str(status)) if status == 0: self.forcedAbort = True self._abort_all_for_this_session = True if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None if self._abort_timer_temp is not None: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._timeout_value = None self._logger.info("Shutdown aborted.") self._destroyNotif() def get_settings_defaults(self): return dict(gcode="M81", url="", api_key_plugin="", abortTimeout=30, extraCommand="", _mode_shutdown_gcode=True, _mode_shutdown_api=False, _mode_shutdown_api_custom=False, api_custom_POST=False, api_custom_GET=False, api_custom_PUT=False, api_custom_url="", api_custom_json_header="", api_custom_body="", api_plugin_port=5000, temperatureValue=110, _shutdown_printer_enabled=True, printFailed=False, printCancelled=False, rememberCheckBox=False, lastCheckBoxValue=False) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.gcode = self._settings.get(["gcode"]) self.url = self._settings.get(["url"]) self.api_key_plugin = self._settings.get(["api_key_plugin"]) self._mode_shutdown_gcode = self._settings.get_boolean( ["_mode_shutdown_gcode"]) self._mode_shutdown_api = self._settings.get_boolean( ["_mode_shutdown_api"]) self._mode_shutdown_api_custom = self._settings.get_boolean( ["_mode_shutdown_api_custom"]) self.api_custom_POST = self._settings.get_boolean(["api_custom_POST"]) self.api_custom_GET = self._settings.get_boolean(["api_custom_GET"]) self.api_custom_PUT = self._settings.get_boolean(["api_custom_PUT"]) self.api_custom_url = self._settings.get(["api_custom_url"]) self.api_custom_json_header = self._settings.get( ["api_custom_json_header"]) self.api_custom_body = self._settings.get(["api_custom_body"]) self.api_json_command = self._settings.get(["api_json_command"]) self.api_plugin_name = self._settings.get(["api_plugin_name"]) self.api_plugin_port = self._settings.get_int(["api_plugin_port"]) self.temperatureValue = self._settings.get_int(["temperatureValue"]) self.temperatureTarget = self._settings.get_int(["temperatureTarget"]) self.extraCommand = self._settings.get(["extraCommand"]) self.printFailed = self._settings.get_boolean(["printFailed"]) self.printCancelled = self._settings.get_boolean(["printCancelled"]) self.abortTimeout = self._settings.get_int(["abortTimeout"]) self.rememberCheckBox = self._settings.get_boolean( ["rememberCheckBox"]) self.lastCheckBoxValue = self._settings.get_boolean( ["lastCheckBoxValue"]) def get_update_information(self): return dict(shutdownprinter=dict( displayName="Shutdown Printer", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-ShutdownPrinter", current=self._plugin_version, # update method: pip w/ dependency links pip= "https://github.com/devildant/OctoPrint-ShutdownPrinter/archive/{target_version}.zip" ))
class Display_panelPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.ShutdownPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.ProgressPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin): _area_offset = 3 _cancel_requested_at = 0 _cancel_timer = None _colored_strip_height = 16 # height of colored strip on top for dual color display _debounce = 0 _display_init = False _displaylayerprogress_current_height = -1.0 _displaylayerprogress_current_layer = -1 _displaylayerprogress_total_height = -1.0 _displaylayerprogress_total_layer = -1 _display_timeout_active = False _display_timeout_option = 0 # -1 - deactivated, 0 - printer disconnected, 1 - disconnected/connected but idle, 2 - always _display_timeout_time = 0 _display_timeout_timer = None _etl_format = "{hours:02d}h {minutes:02d}m {seconds:02d}s" _eta_strftime = "" _gpio_init = False _image_rotate = False _last_debounce = 0 _last_display_timeout_option = 0 # -1 - deactivated, 0 - printer disconnected, 1 - disconnected/connected but idle, 2 - always _last_display_timeout_time = 0 _last_i2c_address = "" _last_image_rotate = False _last_pin_cancel = -1 _last_pin_mode = -1 _last_pin_pause = -1 _last_pin_play = -1 _last_printer_state = 0 # 0 - disconnected, 1 - connected but idle, 2 - printing _pin_cancel = -1 _pin_mode = -1 _pin_pause = -1 _pin_play = -1 _printer_state = 0 # 0 - disconnected, 1 - connected but idle, 2 - printing _progress_on_top = False _screen_mode = ScreenModes.SYSTEM _system_stats = {} _timebased_progress = False ##~~ StartupPlugin mixin def on_after_startup(self): """ StartupPlugin lifecycle hook, called after Octoprint startup is complete """ self.setup_display() self.setup_gpio() self.configure_gpio() self.clear_display() self.check_system_stats() self.start_system_timer() self.update_ui() self.start_display_timer() ##~~ ShutdownPlugin mixin def on_shutdown(self): """ ShutdownPlugin lifecycle hook, called before Octoprint shuts down """ self.stop_display_timer() self.clear_display() self.clean_gpio() ##~~ EventHandlerPlugin mixin def on_event(self, event, payload): """ EventHandlerPlugin lifecycle hook, called whenever an event is fired """ # self._logger.info("on_event: %s", event) self.set_printer_state(event) # Connectivity if event == Events.DISCONNECTED: self._screen_mode = ScreenModes.SYSTEM if event in (Events.CONNECTED, Events.CONNECTING, Events.CONNECTIVITY_CHANGED, Events.DISCONNECTING): self.update_ui() # Print start display if event == Events.PRINT_STARTED: self._screen_mode = ScreenModes.PRINT self.update_ui() # Print end states if event in (Events.PRINT_FAILED, Events.PRINT_DONE, Events.PRINT_CANCELLED, Events.PRINT_CANCELLING): self._displaylayerprogress_current_height = -1.0 self._displaylayerprogress_current_layer = -1 self._displaylayerprogress_total_height = -1.0 self._displaylayerprogress_total_layer = -1 self.update_ui() # Print limbo states if event in (Events.PRINT_PAUSED, Events.PRINT_RESUMED): self.update_ui() # Mid-print states if event in (Events.Z_CHANGE, Events.PRINTER_STATE_CHANGED): self.update_ui() # Get progress information from DisplayLayerProgress plugin if event in ("DisplayLayerProgress_heightChanged", "DisplayLayerProgress_layerChanged"): if payload.get('currentHeight') != "-": self._displaylayerprogress_current_height = float(payload.get('currentHeight')) else: self._displaylayerprogress_current_height = -1.0 if payload.get('currentLayer') != "-": self._displaylayerprogress_current_layer = int(payload.get('currentLayer')) else: self._displaylayerprogress_current_layer = -1 self._displaylayerprogress_total_height = float(payload.get('totalHeight')) self._displaylayerprogress_total_layer = int(payload.get('totalLayer')) self.update_ui() ##~~ ProgressPlugin mixin def on_print_progress(self, storage, path, progress): """ ProgressPlugin lifecycle hook, called when print progress changes, at most in 1% incremements """ self.update_ui() def on_slicing_progress(self, slicer, source_location, source_path, destination_location, destination_path, progress): """ ProgressPlugin lifecycle hook, called whenever slicing progress changes, at most in 1% increments """ self._logger.info("on_slicing_progress: %s", progress) # TODO: Handle slicing progress bar self.update_ui() ##~~ TemplatePlugin mixin def get_template_configs(self): """ TemplatePlugin lifecycle hook, called to get templated settings """ return [dict(type="settings", custom_bindings=False)] ##~~ SettingsPlugin mixin def initialize(self): """ Prepare variables for setting modification detection """ self._debounce = int(self._settings.get(["debounce"])) self._display_init = False self._display_timeout_option = int(self._settings.get(["display_timeout_option"])) self._display_timeout_time = int(self._settings.get(["display_timeout_time"])) self._eta_strftime = str(self._settings.get(["eta_strftime"])) self._gpio_init = False self._i2c_address = str(self._settings.get(["i2c_address"])) self._image_rotate = bool(self._settings.get(["image_rotate"])) self._pin_cancel = int(self._settings.get(["pin_cancel"])) self._pin_mode = int(self._settings.get(["pin_mode"])) self._pin_pause = int(self._settings.get(["pin_pause"])) self._pin_play = int(self._settings.get(["pin_play"])) self._progress_on_top = bool(self._settings.get(["progress_on_top"])) self._screen_mode = ScreenModes.SYSTEM self._last_debounce = self._debounce self._last_display_timeout_option = self._display_timeout_option self._last_display_timeout_time = self._display_timeout_time self._last_i2c_address = self._i2c_address self._last_image_rotate = False self._last_pin_cancel = self._pin_cancel self._last_pin_mode = self._pin_mode self._last_pin_pause = self._pin_pause self._last_pin_play = self._pin_play self._timebased_progress = bool(self._settings.get(["timebased_progress"])) def get_settings_defaults(self): """ SettingsPlugin lifecycle hook, called to get default settings """ return dict( debounce = 250, # Debounce 250ms display_timeout_option = -1, # Default is never display_timeout_time = 5, # Default is 5 minutes eta_strftime = "%-m/%d %-I:%M%p", # Default is month/day hour:minute + AM/PM i2c_address = "0x3c", # Default is hex address 0x3c image_rotate = False, # Default if False (no rotation) pin_cancel = -1, # Default is disabled pin_mode = -1, # Default is disabled pin_pause = -1, # Default is disabled pin_play = -1, # Default is disabled progress_on_top = False, # Default is disabled timebased_progress = False, # Default is disabled ) def on_settings_save(self, data): """ SettingsPlugin lifecycle hook, called when settings are saved """ octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self._debounce = int(self._settings.get(["debounce"])) self._display_timeout_option = int(self._settings.get(["display_timeout_option"])) self._display_timeout_time = int(self._settings.get(["display_timeout_time"])) self._eta_strftime = str(self._settings.get(["eta_strftime"])) self._i2c_address = str(self._settings.get(["i2c_address"])) self._image_rotate = bool(self._settings.get(["image_rotate"])) self._pin_cancel = int(self._settings.get(["pin_cancel"])) self._pin_mode = int(self._settings.get(["pin_mode"])) self._pin_pause = int(self._settings.get(["pin_pause"])) self._pin_play = int(self._settings.get(["pin_play"])) pins_updated = 0 try: if self._i2c_address.lower() != self._last_i2c_address.lower() or \ self._image_rotate != self._last_image_rotate: self.clear_display() self._display_init = False self._last_i2c_address = self._i2c_address self._last_image_rotate = self._image_rotate self.setup_display() self.clear_display() self.check_system_stats() self.start_system_timer() self._screen_mode = ScreenModes.SYSTEM self.update_ui() if self._pin_cancel != self._last_pin_cancel: pins_updated = pins_updated + 1 self.clean_single_gpio(self._last_pin_cancel) if self._pin_mode != self._last_pin_mode: pins_updated = pins_updated + 2 self.clean_single_gpio(self._last_pin_mode) if self._pin_pause != self._last_pin_pause: pins_updated = pins_updated + 4 self.clean_single_gpio(self._last_pin_pause) if self._pin_play != self._last_pin_play: pins_updated = pins_updated + 8 self.clean_single_gpio(self._last_pin_play) self._gpio_init = False self.setup_gpio() if pins_updated == (pow(2, 4) - 1) or self._debounce != self._last_debounce: self.configure_gpio() pins_updated = 0 if pins_updated >= pow(2, 3): self.configure_single_gpio(self._pin_play) pins_updated = pins_updated - pow(2, 3) if pins_updated >= pow(2, 2): self.configure_single_gpio(self._pin_pause) pins_updated = pins_updated - pow(2, 2) if pins_updated >= pow(2, 1): self.configure_single_gpio(self._pin_mode) pins_updated = pins_updated - pow(2, 1) if pins_updated >= pow(2, 0): self.configure_single_gpio(self._pin_cancel) pins_updated = pins_updated - pow(2, 0) if pins_updated > 0: self.log_error("Something went wrong counting updated GPIO pins") if self._display_timeout_option != self._last_display_timeout_option or \ self._display_timeout_time != self._last_display_timeout_time: self.start_display_timer(self._display_timeout_time != self._last_display_timeout_time) self._last_debounce = self._debounce self._last_display_timeout_option = self._display_timeout_option self._last_display_timeout_time = self._display_timeout_time self._last_pin_cancel = self._pin_cancel self._last_pin_mode = self._pin_mode self._last_pin_play = self._pin_play self._last_pin_pause = self._pin_pause except Exception as ex: self.log_error(ex) pass ##~~ Softwareupdate hook def get_update_information(self): """ Softwareupdate hook, standard library hook to handle software update and plugin version info """ # Define the configuration for your plugin to use with the Software Update # Plugin here. See https://docs.octoprint.org/en/master/bundledplugins/softwareupdate.html # for details. return dict( display_panel=dict( displayName="OctoPrint Micro Panel", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-DisplayPanel", current=self._plugin_version, # update method: pip pip="https://github.com/sethvoltz/OctoPrint-DisplayPanel/archive/{target_version}.zip" ) ) ##~~ Helpers def bcm2board(self, bcm_pin): """ Function to translate bcm pin to board pin """ board_pin = -1 if bcm_pin != -1: _bcm2board = [ -1, -1, -1, 7, 29, 31, -1, -1, -1, -1, -1, 32, 33, -1, -1, 36, 11, 12, 35, 38, 40, 15, 16, 18, 22, 37, 13 ] board_pin=_bcm2board[bcm_pin-1] return board_pin def start_system_timer(self): """ Function to check system stats periodically """ self._check_system_timer = RepeatedTimer(5, self.check_system_stats, None, None, True) self._check_system_timer.start() def check_system_stats(self): """ Function to collect general system stats about the underlying system(s). Called by the system check timer on a regular basis. This function should remain small, performant and not block. """ if self._screen_mode == ScreenModes.SYSTEM: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) self._system_stats['ip'] = s.getsockname()[0] s.close() self._system_stats['load'] = psutil.getloadavg() self._system_stats['memory'] = psutil.virtual_memory() self._system_stats['disk'] = shutil.disk_usage('/') # disk percentage = 100 * used / (used + free) self.update_ui() elif self._screen_mode == ScreenModes.PRINTER: # Just update the UI, the printer mode will take care of itself self.update_ui() def setup_display(self): """ Intialize display """ try: self.i2c = busio.I2C(SCL, SDA) self.disp = adafruit_ssd1306.SSD1306_I2C(128, 64, self.i2c, addr=int(self._i2c_address,0)) self._logger.info("Setting display to I2C address %s", self._i2c_address) self._display_init = True self.font = ImageFont.load_default() self.width = self.disp.width self.height = self.disp.height self.image = Image.new("1", (self.width, self.height)) self.draw = ImageDraw.Draw(self.image) self._screen_mode = ScreenModes.SYSTEM except Exception as ex: self.log_error(ex) pass def setup_gpio(self): """ Setup GPIO to use BCM pin numbering, unless already setup in which case fall back and update globals to reflect change. """ self.BCM_PINS = { self._pin_mode: 'mode', self._pin_cancel: 'cancel', self._pin_play: 'play', self._pin_pause: 'pause' } self.BOARD_PINS = { self.bcm2board(self._pin_mode) : 'mode', self.bcm2board(self._pin_cancel): 'cancel', self.bcm2board(self._pin_play): 'play', self.bcm2board(self._pin_pause): 'pause' } self.input_pinset = self.BCM_PINS try: current_mode = GPIO.getmode() set_mode = GPIO.BCM if current_mode is None: GPIO.setmode(set_mode) self.input_pinset = self.BCM_PINS self._logger.info("Setting GPIO mode to BCM numbering") elif current_mode != set_mode: GPIO.setmode(current_mode) self.input_pinset = self.BOARD_PINS self._logger.info("GPIO mode was already set, adapting to use BOARD numbering") GPIO.setwarnings(False) self._gpio_init = True except Exception as ex: self.log_error(ex) pass def configure_gpio(self): """ Setup the GPIO pins to handle the buttons as inputs with built-in pull-up resistors """ if self._gpio_init: for gpio_pin in self.input_pinset: self.configure_single_gpio(gpio_pin) def configure_single_gpio(self, gpio_pin): """ Setup the GPIO pins to handle the buttons as inputs with built-in pull-up resistors """ if self._gpio_init: try: if gpio_pin != -1: GPIO.setup(gpio_pin, GPIO.IN, GPIO.PUD_UP) GPIO.remove_event_detect(gpio_pin) self._logger.info("Adding GPIO event detect on pin %s with edge: FALLING", gpio_pin) GPIO.add_event_detect(gpio_pin, GPIO.FALLING, callback=self.handle_gpio_event, bouncetime=self._debounce) except Exception as ex: self.log_error(ex) def clean_gpio(self): """ Remove event detection and clean up for all pins (`mode`, `cancel`, `play` and `pause`) """ if self._gpio_init: for gpio_pin in self.input_pinset: self.clean_single_gpio(gpio_pin) def clean_single_gpio(self, gpio_pin): """ Remove event detection and clean up for all pins (`mode`, `cancel`, `play` and `pause`) """ if self._gpio_init: if gpio_pin!=-1: try: GPIO.remove_event_detect(gpio_pin) except Exception as ex: self.log_error(ex) pass try: GPIO.cleanup(gpio_pin) except Exception as ex: self.log_error(ex) pass self._logger.info("Removed GPIO pin %s", gpio_pin) def handle_gpio_event(self, channel): """ Event callback for GPIO event, called from `add_event_detect` setup in `configure_gpio` """ try: if channel in self.input_pinset: if self._display_timeout_active: self.start_display_timer() return else: self.start_display_timer() label = self.input_pinset[channel] if label == 'cancel': self.try_cancel() else: if self._cancel_timer is not None: self.clear_cancel() else: if label == 'mode': self.next_mode() if label == 'play': self.try_play() if label == 'pause': self.try_pause() except Exception as ex: self.log_error(ex) pass def next_mode(self): """ Go to the next screen mode """ self._screen_mode = self._screen_mode.next() self.update_ui() def try_cancel(self): """ First click, confirm cancellation. Second click, trigger cancel """ if not self._printer.is_printing() and not self._printer.is_paused(): return if self._cancel_timer: # GPIO can double-trigger sometimes, check if this is too fast and ignore if (time.time() - self._cancel_requested_at) < 1: return # Cancel has been tried, run a cancel self.clear_cancel() self._printer.cancel_print() else: # First press self._cancel_requested_at = time.time() self._cancel_timer = RepeatedTimer(10, self.clear_cancel, run_first=False) self._cancel_timer.start() self.update_ui() def clear_cancel(self): """ Clear a pending cancel, something else was clicked or a timeout occured """ if self._cancel_timer: self._cancel_timer.cancel() self._cancel_timer = None self._cancel_requested_at = 0 self.update_ui() def try_play(self): """ If possible, play or resume a print """ # TODO: If a print is queued up and ready, start it if not self._printer.is_paused(): return self._printer.resume_print() def try_pause(self): """ If possible, pause a running print """ if not self._printer.is_printing(): return self._printer.pause_print() def clear_display(self): """ Clear the OLED display completely. Used at startup and shutdown to ensure a blank screen """ if self._display_init: self.disp.fill(0) self.disp.show() def set_printer_state(self, event): """ Set printer state based on latest event """ if event in (Events.DISCONNECTED, Events.CONNECTED, Events.PRINT_STARTED, Events.PRINT_FAILED, Events.PRINT_DONE, Events.PRINT_CANCELLED, Events.PRINT_PAUSED, Events.PRINT_RESUMED): if event == Events.DISCONNECTED: self._printer_state = 0 if event in (Events.CONNECTED, Events.PRINT_FAILED, Events.PRINT_DONE, Events.PRINT_CANCELLED, Events.PRINT_PAUSED): self._printer_state = 1 if event in (Events.PRINT_STARTED, Events.PRINT_RESUMED): self._printer_state = 2 if self._printer_state != self._last_printer_state: self.start_display_timer(True) self._last_printer_state = self._printer_state return def start_display_timer(self, reconfigure=False): """ Start timer for display timeout """ do_reset = False if self._display_timeout_timer is not None: if reconfigure: self.stop_display_timer() else: do_reset = True if do_reset: self._display_timeout_timer.reset() else: if self._printer_state <= self._display_timeout_option: self._display_timeout_timer = ResettableTimer(self._display_timeout_time * 60, self.trigger_display_timeout, [True], None, True) self._display_timeout_timer.start() if self._display_timeout_active: self.trigger_display_timeout(False) return def stop_display_timer(self): """ Stop timer for display timeout """ if self._display_timeout_timer is not None: self._display_timeout_timer.cancel() self._display_timeout_timer = None return def trigger_display_timeout(self, activate): """ Set display off on activate == True and on on activate == False """ self._display_timeout_active = activate if self._display_init: if activate: self.stop_display_timer() self.disp.poweroff() else: self.disp.poweron() return def update_ui(self): """ Update the on-screen UI based on the current screen mode and printer status """ if self._display_init: try: current_data = self._printer.get_current_data() if self._cancel_timer is not None and current_data['state']['flags']['cancelling'] is False: self.update_ui_cancel_confirm() elif self._screen_mode == ScreenModes.SYSTEM: self.update_ui_system() elif self._screen_mode == ScreenModes.PRINTER: self.update_ui_printer() elif self._screen_mode == ScreenModes.PRINT: self.update_ui_print(current_data) self.update_ui_bottom(current_data) # Display image. if self._image_rotate: self.disp.image(self.image.rotate(angle=180)) else: self.disp.image(self.image) self.disp.show() except Exception as ex: self.log_error(ex) def update_ui_cancel_confirm(self): """ Show a confirmation message that a cancel print has been requested """ top = (self._colored_strip_height) * int(self._progress_on_top) bottom = self.height - (self._colored_strip_height * int(not self._progress_on_top)) left = 0 offset = self._area_offset * int(self._progress_on_top) if self._display_init: try: self.draw.rectangle((0, top, self.width, bottom), fill=0) display_string = "Cancel Print?" text_width = self.draw.textsize(display_string, font=self.font)[0] self.draw.text((self.width / 2 - text_width / 2, top + offset + 0), display_string, font=self.font, fill=255) display_string = "Press 'X' to confirm" text_width = self.draw.textsize(display_string, font=self.font)[0] self.draw.text((self.width / 2 - text_width / 2, top + offset + 9), display_string, font=self.font, fill=255) display_string = "Press any button or" text_width = self.draw.textsize(display_string, font=self.font)[0] self.draw.text((self.width / 2 - text_width / 2, top + offset + 18), display_string, font=self.font, fill=255) display_string = "wait 10 sec to escape" text_width = self.draw.textsize(display_string, font=self.font)[0] self.draw.text((self.width / 2 - text_width / 2, top + offset + 27), display_string, font=self.font, fill=255) except Exception as ex: self.log_error(ex) def update_ui_system(self): """ Update three-fourths of the screen with system stats collected by the timed collector """ if self._display_init: top = (self._colored_strip_height) * int(self._progress_on_top) bottom = self.height - (self._colored_strip_height * int(not self._progress_on_top)) left = 0 offset = self._area_offset * int(self._progress_on_top) # Draw a black filled box to clear the image. self.draw.rectangle((0, top, self.width, bottom), fill=0) try: mem = self._system_stats['memory'] disk = self._system_stats['disk'] # Center IP ip = self._system_stats['ip'] tWidth, tHeight = self.draw.textsize(ip) self.draw.text((left + ((self.width - tWidth) / 2), top + offset + 0), ip, font=self.font, fill=255) # System Stats self.draw.text((left, top + offset + 9), "L: %s, %s, %s" % self._system_stats['load'], font=self.font, fill=255) self.draw.text((left, top + offset + 18), "M: %s/%s MB %s%%" % (int(mem.used/1048576), int(mem.total/1048576), mem.percent), font=self.font, fill=255) self.draw.text((left, top + offset + 27), "D: %s/%s GB %s%%" % (int(disk.used/1073741824), int((disk.used+disk.total)/1073741824), int(10000*disk.used/(disk.used+disk.free))/100), font=self.font, fill=255) except: self.draw.text((left, top + offset + 9), "Gathering System Stats", font=self.font, fill=255) def update_ui_printer(self): """ Update three-fourths of the screen with stats about the printer, such as temperatures """ if self._display_init: top = (self._colored_strip_height) * int(self._progress_on_top) bottom = self.height - (self._colored_strip_height * int(not self._progress_on_top)) left = 0 offset = self._area_offset * int(self._progress_on_top) try: self.draw.rectangle((0, top, self.width, bottom), fill=0) self.draw.text((left, top + offset + 0), "Printer Temperatures", font=self.font, fill=255) if self._printer.get_current_connection()[0] == "Closed": self.draw.text((left, top + offset + 9), "Head: no printer", font=self.font, fill=255) self.draw.text((left, top + offset + 18), " Bed: no printer", font=self.font, fill=255) else: temperatures = self._printer.get_current_temperatures() tool = temperatures['tool0'] or None bed = temperatures['bed'] or None self.draw.text((left, top + offset + 9), "Head: %s / %s\xb0C" % (tool['actual'], tool['target']), font=self.font, fill=255) self.draw.text((left, top + offset + 18), " Bed: %s / %s\xb0C" % (bed['actual'], bed['target']), font=self.font, fill=255) except Exception as ex: self.log_error(ex) def update_ui_print(self, current_data): """ Update three-fourths of the screen with information about the current ongoing print """ if self._display_init: top = (self._colored_strip_height) * int(self._progress_on_top) bottom = self.height - (self._colored_strip_height * int(not self._progress_on_top)) left = 0 offset = self._area_offset * int(self._progress_on_top) try: self.draw.rectangle((0, top, self.width, bottom), fill=0) self.draw.text((left, top + offset + 0), "State: %s" % (self._printer.get_state_string()), font=self.font, fill=255) if current_data['job']['file']['name']: file_name = current_data['job']['file']['name'] self.draw.text((left, top + offset + 9), "File: %s" % (file_name), font=self.font, fill=255) print_time = self._get_time_from_seconds(current_data['progress']['printTime'] or 0) self.draw.text((left, top + offset + 18), "Time: %s" % (print_time), font=self.font, fill=255) filament = current_data['job']['filament']['tool0'] if "tool0" in current_data['job']['filament'] else current_data['job']['filament'] filament_length = self.float_count_formatter((filament['length'] or 0) / 1000, 3) filament_mass = self.float_count_formatter(filament['volume'] or 0, 3) self.draw.text((left, top + offset + 27), "Filament: %sm/%scm3" % (filament_length, filament_mass), font=self.font, fill=255) # Display height if information available from DisplayLayerProgress plugin height = "{:>5.1f}/{:>5.1f}".format(float(self._displaylayerprogress_current_height), float(self._displaylayerprogress_total_height)) layer = "{:>4d}/{:>4d}".format(self._displaylayerprogress_current_layer, self._displaylayerprogress_total_layer) height_text = "" if self._displaylayerprogress_current_height != -1.0 and self._displaylayerprogress_current_layer != -1: height_text = layer + ";" + height elif self._displaylayerprogress_current_layer != -1: height_text = layer elif self._displaylayerprogress_current_height != -1.0: height_text = height self.draw.text((left, top + offset + 36), height_text, font=self.font, fill=255) else: self.draw.text((left, top + offset + 18), "Waiting for file...", font=self.font, fill=255) except Exception as ex: self.log_error(ex) def update_ui_bottom(self, current_data): """ Update one-fourths of the screen with persistent information about the current print """ if self._display_init: top = (self.height - self._colored_strip_height) * int(not self._progress_on_top) bottom = self.height - ((self.height - self._colored_strip_height) * int(self._progress_on_top)) left = 0 try: # Clear area self.draw.rectangle((0, top, self.width, bottom), fill=0) if self._printer.get_current_connection()[0] == "Closed": # Printer isn't connected display_string = "Printer Not Connected" text_width = self.draw.textsize(display_string, font=self.font)[0] self.draw.text((self.width / 2 - text_width / 2, top + 4), display_string, font=self.font, fill=255) elif current_data['state']['flags']['paused'] or current_data['state']['flags']['pausing']: # Printer paused display_string = "Paused" text_width = self.draw.textsize(display_string, font=self.font)[0] self.draw.text((self.width / 2 - text_width / 2, top + 4), display_string, font=self.font, fill=255) elif current_data['state']['flags']['cancelling']: # Printer paused display_string = "Cancelling" text_width = self.draw.textsize(display_string, font=self.font)[0] self.draw.text((self.width / 2 - text_width / 2, top + 4), display_string, font=self.font, fill=255) elif current_data['state']['flags']['ready'] and (current_data['progress']['completion'] or 0) < 100: # Printer connected, not printing display_string = "Waiting For Job" text_width = self.draw.textsize(display_string, font=self.font)[0] self.draw.text((self.width / 2 - text_width / 2, top + 4), display_string, font=self.font, fill=255) else: percentage = int(current_data['progress']['completion'] or 0) # Calculate progress from time if current_data['progress']['printTime'] and self._timebased_progress: percentage = int((current_data['progress']['printTime'] or 0) / ((current_data['progress']['printTime'] or 0) + current_data['progress']['printTimeLeft']) * 100) time_left = current_data['progress']['printTimeLeft'] or 0 # Progress bar self.draw.rectangle((0, top + 0, self.width - 1, top + 5), fill=0, outline=255, width=1) bar_width = int((self.width - 5) * percentage / 100) self.draw.rectangle((2, top + 2, bar_width, top + 3), fill=255, outline=255, width=1) # Percentage and ETA self.draw.text((0, top + 5), "%s%%" % (percentage), font=self.font, fill=255) eta = time.strftime(self._eta_strftime, time.localtime(time.time() + time_left)) eta_width = self.draw.textsize(eta, font=self.font)[0] self.draw.text((self.width - eta_width, top + 5), eta, font=self.font, fill=255) except Exception as ex: self.log_error(ex) # Taken from tpmullan/OctoPrint-DetailedProgress def _get_time_from_seconds(self, seconds): hours = 0 minutes = 0 if seconds >= 3600: hours = int(seconds / 3600) seconds = seconds % 3600 if seconds >= 60: minutes = int(seconds / 60) seconds = seconds % 60 return self._etl_format.format(**locals()) def float_count_formatter(self, number, max_chars): """ Show decimals up to a max number of characters, then flips over and rounds to integer """ int_part = "%i" % round(number) if len(int_part) >= max_chars - 1: return int_part elif len("%f" % number) <= max_chars: return "%f" % number else: return "{num:0.{width}f}".format(num=number, width=len(int_part) - 1) def log_error(self, ex): """ Helper function for more complete logging on exceptions """ template = "An exception of type {0} occurred on {1}. Arguments: {2!r}" message = template.format(type(ex).__name__, inspect.currentframe().f_code.co_name, ex.args) self._logger.warn(message)
class NavBarPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SettingsPlugin): def __init__(self): # Array of raspberry pi SoC's to check against, saves having a large if/then statement later self.piSocTypes = (["BCM2708", "BCM2709", "BCM2835", "BCM2711"]) self.debugMode = False # to simulate temp on Win/Mac self.displayRaspiTemp = None self._checkTempTimer = None self._checkCmdTimer = None self.sbc = SBC() self.cmd = None self.cmd_name = None def on_after_startup(self): self.displayRaspiTemp = self._settings.get(["displayRaspiTemp"]) self.piSocTypes = self._settings.get(["piSocTypes"]) self.cmd = self._settings.get(["cmd"]) self.cmd_name = self._settings.get(["cmd_name"]) self._logger.debug("Custom cmd name %r" % self.cmd_name) self._logger.debug("Custom cmd %r" % self.cmd) if sys.platform.startswith("linux"): self.sbc = SBCFactory().factory(self._logger) if self.debugMode: self.sbc.is_supported = True self.sbc.debugMode = True if self.sbc.is_supported and self.displayRaspiTemp: self._logger.debug("Let's start RepeatedTimer!") interval = 5.0 if self.debugMode else 30.0 self.start_soc_timer(interval) if self.cmd_name: interval = 5.0 if self.debugMode else 30.0 self.start_custom_timer(interval) try: self._logger.debug("is supported? - %s" % self.sbc.is_supported) except Exception: self._logger.debug("Embeded platform is not detected") def start_soc_timer(self, interval): self._checkTempTimer = RepeatedTimer(interval, self.update_soc_temp, run_first=True) self._checkTempTimer.start() def start_custom_timer(self, interval): self._checkCmdTimer = RepeatedTimer(interval, self.update_custom, run_first=True) self._checkCmdTimer.start() def update_soc_temp(self): temp = self.sbc.check_soc_temp() self._logger.debug("match: %s" % temp) self._plugin_manager.send_plugin_message(self._identifier, dict(isSupported=self.sbc.is_supported, soctemp=temp)) def update_custom(self): cmd_rtv = self.get_custom_result() self._plugin_manager.send_plugin_message(self._identifier, dict(cmd_result=cmd_rtv, cmd_name=self.cmd_name)) def get_custom_result(self): if self.cmd: try: cmd_rtv = str(os.popen(self.cmd).read()) self._logger.debug("cmd_rtv: %s" % cmd_rtv) return cmd_rtv except Exception: self._logger.debug("cmd error") return "" # ~~ SettingsPlugin def get_settings_defaults(self): return dict(displayRaspiTemp=True, piSocTypes=self.piSocTypes, cmd="", cmd_name="", useShortNames=False, makeMoreRoom=False, soc_name="SoC", ) def on_settings_save(self, data): diff = super(NavBarPlugin, self).on_settings_save(data) self._logger.debug("data: " + str(data)) if "displayRaspiTemp" in data: self.displayRaspiTemp = data["displayRaspiTemp"] if self.displayRaspiTemp: interval = 5.0 if self.debugMode else 30.0 self.start_soc_timer(interval) else: if self._checkTempTimer is not None: try: self._checkTempTimer.cancel() except Exceptionx: pass if "cmd" in data: self.cmd = data["cmd"] self.cmd_name = data["cmd_name"] if self.cmd: interval = 5.0 if self.debugMode else 30.0 self.start_custom_timer(interval) else: if self._checkCmdTimer is not None: try: self._checkCmdTimer.cancel() except Exceptionx: pass self._plugin_manager.send_plugin_message(self._identifier, dict()) return diff # ~~ TemplatePlugin API def get_template_configs(self): try: # Todo: settings have to be fixed # if self.sbc.is_supported: # return [ # dict(type="settings", template="navbartemp_settings_sbc.jinja2") # ] # else: return [dict(type="settings", template="navbartemp_settings.jinja2")] except Exception: return [] # ~~ AssetPlugin API def get_assets(self): return { "js": ["js/navbartemp.js"], "css": ["css/navbartemp.css"], "less": ["less/navbartemp.less"] } # ~~ Softwareupdate hook def get_update_information(self): return dict( navbartemp=dict( displayName="Navbar Temperature Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-NavbarTemp", current=self._plugin_version, # update method: pip w/ dependency links pip="https://github.com/imrahil/OctoPrint-NavbarTemp/archive/{target_version}.zip" ) )
class SnapPlugin(octoprint.plugin.EventHandlerPlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin): ##~~ SettingsPlugin mixin def get_settings_defaults(self): return dict( interval=0, iam_access_key_id="", iam_secret_access_key="", s3_bucket_name="", webhook_url="", ) def on_after_startup(self): self._logger.info("Oh Snap! (Current Interval: %s)", self._settings.get(["interval"])) # TODO Refactor if possible these event calls into meta invocations similar to `.send` or `.call` in node. # https://stackoverflow.com/questions/3951840/how-to-invoke-a-function-on-an-object-dynamically-by-name # Octopi events to handle TIMER_START_EVENTS = [ "PrintStarted", "PrintResumed", ] # Update the interval loop TIMER_UPDATE_EVENTS = [ "SettingsUpdated" ] # End the loop events TIMER_STOP_EVENTS = [ "PrintFailed", "PrintDone", "PrintCancelling", "PrintCancelled", "PrintPaused", ] def execute_timer_event(self, event): if event in self.TIMER_START_EVENTS: self.start_printing_timer() elif event in self.TIMER_STOP_EVENTS: self.stop_printing_timer() elif event in self.TIMER_UPDATE_EVENTS: self.restart_printing_timer() else: return def on_event(self, event, payload): self.execute_timer_event(event) # Timer for interval printing_timer = None # Event for timer def printing_timer_tick(self): self._logger.debug("timer tick at interval %s", self._settings.get(["interval"])) snapshot_url = self.snapshot_to_s3() self.send_ifttt(snapshot_url) # Passed to timer as interval function to dynamically change interval. def printing_timer_interval(self): return int(self._settings.get(["interval"])) # Start def start_printing_timer(self, run_first = False): self._logger.debug("start timer") # Create and start the timer. self.printing_timer = RepeatedTimer( self.printing_timer_interval, self.printing_timer_tick, run_first = run_first ) self.printing_timer.start() # Stop def stop_printing_timer(self): self._logger.debug("stop timer") if self.printing_timer != None: self.printing_timer.cancel() self.printing_timer = None return # Restart def restart_printing_timer(self): self._logger.debug("restart timer") if self.printing_timer == None: return self.stop_printing_timer() self.start_printing_timer(True) def snapshot_to_s3(self): s3_bucket = self._settings.get(["s3_bucket_name"]) # Get the content-type for extension and a timestamp for key snapshot_url = self._settings.global_get(["webcam","snapshot"]) extension = guess_extension(head(snapshot_url).headers['content-type']) object_key = datetime.utcnow().strftime("%m-%d-%Y_%H:%M:%S") + extension # Create object s3_object = resource( 's3', aws_access_key_id=self._settings.get(["iam_access_key_id"]), aws_secret_access_key=self._settings.get(["iam_secret_access_key"]) ).Object(s3_bucket, object_key) # Stream to s3 with get(snapshot_url, stream=True) as r: s3_object.put( Body=r.content, ACL='public-read' ) return "https://%s.s3.amazonaws.com/%s" % (s3_bucket, object_key) def send_ifttt(self, snapshot_url): json = {'value1':'this is value_1','value2':'this is value_2','value3':snapshot_url} post(self._settings.get(["webhook_url"]), data=json) def get_template_configs(self): return [ dict(type="settings", custom_bindings=False) ] ##~~ AssetPlugin mixin def get_assets(self): # Define your plugin's asset files to automatically include in the # core UI here. return dict( js=["js/snap.js"], css=["css/snap.css"], less=["less/snap.less"] ) ##~~ Softwareupdate hook def get_update_information(self): # Define the configuration for your plugin to use with the Software Update # Plugin here. See https://github.com/foosel/OctoPrint/wiki/Plugin:-Software-Update # for details. return dict( snap=dict( displayName="Snap Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-Snap", current=self._plugin_version, # update method: pip pip="https://github.com/codyolsen/OctoPrint-Snap/archive/{target_version}.zip" ) )
class OctoPiBoxPlugin(octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.StartupPlugin): def get_settings_defaults(self): return dict( enabled=False, timer_seconds=600, printer_pin=17, spare_pin=4, printer_button_pin=6, printer_button_enable_pin=18, spare_button_pin=5, spare_button_enable_pin=27, button_debounce=200, status_red_pin=22, status_green_pin=23, status_blue_pin=24 ) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self._load_settings() self._octopibox.restart( self._printer_pin, self._spare_pin, self._printer_button_pin, self._spare_button_pin, self._button_debounce) self._octopibox.restart_status_LED(self._status_red_pin, self._status_green_pin, self._status_blue_pin) def on_after_startup(self): self._timeout_value = None self._timer = None self._load_settings() self._octopibox = OctoPiBoxControl(self, self._printer_pin, self._spare_pin, self._printer_button_pin, self._spare_button_pin, self._button_debounce, self._powercallbackfunction) self._update_power_status() self._octopibox.pin_on(self._printer_button_enable_pin) self._octopibox.pin_on(self._spare_button_enable_pin) self._octopibox.init_status_LED(self._status_red_pin, self._status_green_pin, self._status_blue_pin) self._set_status_LED("DISCONNECTED") def on_shutdown(self): self._octopibox.pin_off(self._printer_button_enable_pin) self._octopibox.pin_off(self._spare_button_enable_pin) self._octopibox.clear_status_LED() self._octopibox.cancel() def get_assets(self): return dict(js=["js/octopibox.js"]) def get_template_configs(self): return [ dict(type="settings", name="OctoPiBox Configuration", custom_bindings=False) ] def get_api_commands(self): return dict(autopoweroff=[], abort=[]) def on_api_command(self, command, data): import flask if command == "abort": self._timer.cancel() self._timer = None self._set_status_LED("CONNECTED") self._logger.info("Automatic Power-Off aborted.") elif command == "autopoweroff": self._logger.debug("Automatic Printer Power-off called via API. Starting timer.") self._start_auto_power_off_timer() def on_event(self, event, payload): #self._logger.info("Event triggered: {}".format(event)) if event == Events.PRINT_DONE: self._octopibox.pin_on(self._printer_button_enable_pin) if not self._enabled: self._logger.debug("Print complete. Automatic Printer Power-off is currently DISABLED.") self._set_status_LED("CONNECTED") return if self._timer is not None: return self._logger.debug("Print complete. Automatic Printer Power-off is ENABLED. Starting timer.") self._start_auto_power_off_timer() elif event == Events.CONNECTED: self._set_status_LED("CONNECTED") elif event == Events.DISCONNECTED: self._set_status_LED("DISCONNECTED") elif event == Events.PRINT_STARTED: self._octopibox.pin_off(self._printer_button_enable_pin) self._set_status_LED("PRINTING") elif event == Events.PRINT_FAILED: self._octopibox.pin_on(self._printer_button_enable_pin) self._set_status_LED("ERROR") elif event == Events.PRINT_CANCELLED: self._octopibox.pin_on(self._printer_button_enable_pin) self._set_status_LED("ERROR") elif event == Events.CLIENT_OPENED: self._update_power_status() def _start_auto_power_off_timer(self): self._timeout_value = self._settings.get_int(['timer_seconds']) if (self._timeout_value < 30) | (self._timeout_value > 1800): self._timeout_value = 600 self._logger.debug("Automatic Printer Power-off started: {} seconds.".format(self._timeout_value)) self._set_status_LED("POWERINGOFF") self._timer = RepeatedTimer(1, self._timer_task) self._timer.start() self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeout", timeout_value=self._timeout_value)) def _load_settings(self): self._enabled = self._settings.get_boolean(['enabled']) self._printer_pin = self._settings.get_int(['printer_pin']) self._spare_pin = self._settings.get_int(['spare_pin']) self._printer_button_pin = self._settings.get_int(['printer_button_pin']) self._printer_button_enable_pin = self._settings.get_int(['printer_button_enable_pin']) self._spare_button_pin = self._settings.get_int(['spare_button_pin']) self._spare_button_enable_pin = self._settings.get_int(['spare_button_enable_pin']) self._button_debounce = self._settings.get_int(['button_debounce']) self._status_red_pin = self._settings.get_int(['status_red_pin']) self._status_green_pin = self._settings.get_int(['status_green_pin']) self._status_blue_pin = self._settings.get_int(['status_blue_pin']) def _timer_task(self): self._timeout_value -= 1 self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeout", timeout_value=self._timeout_value)) if self._timeout_value <= 0: self._timer.cancel() self._timer = None self._printeroff() def _powercallbackfunction(self, pin, level, tick): if pin == self._printer_pin: self._logger.debug("Printer pin {} level changed to {}".format(pin, level)) self._update_power_status() if level == 0: current_connection = self._printer.get_current_connection() if current_connection[0] != "Closed": self._logger.debug("Printer connection found: {}".format(current_connection[0:3])) self._printer.disconnect() self._logger.debug("Printer disconnected after power-off.") if self._timeout_value > 0: self._timer.cancel() self._timer = None self._timeout_value = 0 self._plugin_manager.send_plugin_message(self._identifier, dict(type="close_popup")) elif level == 1: self._logger.debug("Printer power-on detected.") self._set_status_LED("CONNECTING") self._printer.connect() self._logger.debug("Printer auto-connect after power-on attempted.") def _printeroff(self): self._logger.debug("Printer disconnect before power-off.") self._printer.disconnect() self._logger.info("Powering off printer on pin {}.".format( self._printer_pin)) self._octopibox.pin_off(self._printer_pin) self._logger.debug("Powering off spare outlet on pin {}.".format( self._spare_pin)) self._octopibox.pin_off(self._spare_pin) def _update_power_status(self): printer_power_status = ["Off", "On"] printer_power_status_text = printer_power_status[ self._octopibox.pin_value(self._printer_pin)] self._plugin_manager.send_plugin_message(self._identifier, dict(type="updatePowerStatus", power_status_value=printer_power_status_text)) self._logger.debug("Data message sent from {} for power update to {}.".format(self._identifier, printer_power_status_text)) def _set_status_LED(self, status="DISCONNECTED"): self._octopibox.clear_status_LED() if status=="DISCONNECTED": self._octopibox.set_status_LED_color("YELLOW", "OFF", "OFF") elif status == "CONNECTED": self._octopibox.set_status_LED_color("GREEN", "OFF", "OFF") elif status == "PRINTING": self._octopibox.set_status_LED_color("RED", "OFF", "OFF") elif status == "CONNECTING": self._octopibox.set_status_LED_color("GREEN", "YELLOW", "SLOW") elif status == "POWERINGOFF": self._octopibox.set_status_LED_color("RED", "YELLOW", "SLOW") elif status == "ERROR": self._octopibox.set_status_LED_color("RED", "OFF", "FAST") elif status == "OFF": self._octopibox.set_status_LED_color("OFF", "OFF", "OFF") else: self._octopibox.set_status_LED_color("OFF", "OFF", "OFF") ##~~ Softwareupdate hook def get_update_information(self): # Define the configuration for your plugin to use with the Software Update # Plugin here. See https://github.com/foosel/OctoPrint/wiki/Plugin:-Software-Update # for details. return dict( octopibox=dict( displayName="OctoPiBox Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-OctoPiBox", current=self._plugin_version, # update method: pip pip="https://github.com/hcomet/OctoPrint-OctoPiBox/archive/{target_version}.zip" ) )
class Timer(): def __init__(self, timeout_minutes): self._logEnabled = False self._timerEnabled = False self._logger = None self._timer = None self._plugin_manager = None self._identifier = None self._timeout_seconds = None self._cb = None self._default_timeout_seconds = self._min2sec(timeout_minutes) def initialize(self, plugin_manager, identifier, cb): if self._logEnabled is False: self._logEnabled = True self._logger = logging.getLogger("octoprint.plugins.{}".format(__name__)) self._plugin_manager = plugin_manager self._identifier = identifier self._cb = cb def initializeTimer(self): self._timer = RepeatedTimer(1, self._timer_task) def start(self): if self._timerEnabled is False: if self._timer is None: self.initializeTimer() self._timeout_seconds = self._default_timeout_seconds self._timerEnabled = True self._timer.start() self._logger.info("Powersave timer started with: {} mintues".format(self._sec2min(self._default_timeout_seconds))) def cancel(self): if self._timerEnabled is True: self._timerEnabled = False if self._timer is not None: self._plugin_manager.send_plugin_message(self._identifier, dict(type="cancel")) self._timer.cancel() self._timer = None self._logger.info("Powersave timer canceled") def setNewTimeoutMinutes(self, minutes): if not self._min2sec(minutes) == self._default_timeout_seconds: self._default_timeout_seconds = self._min2sec(minutes) self.cancel() self.start() self._logger.info("Powersave timeout value updated to: {}".format(minutes)) def reset(self): self._timeout_value = self.default_timeout_seconds self._logger.info("Powersave timer reset") def _timer_task(self): self._timeout_seconds -= 1 if self._timeout_seconds < 60 * 5: self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeout", timeout_value=self._timeout_seconds)) if self._timeout_seconds <= 0: self._timer.cancel() self._timer = None self._cb() def _sec2min(self, seconds): return seconds / 60 def _min2sec(self, minutes): return 60 * minutes
class DisplayETAPlugin(octoprint.plugin.ProgressPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.EventHandlerPlugin): def __init__(self): self.eta_string = "-" self.timer = RepeatedTimer(15.0, DisplayETAPlugin.fromTimer, args=[self], run_first=True,) def fromTimer(self): self.eta_string = self.calculate_ETA() self._plugin_manager.send_plugin_message(self._identifier, dict(eta_string=self.eta_string)) def calculate_ETA(self): currentData = self._printer.get_current_data() if not currentData["progress"]["printTimeLeft"]: return "-" current_time = datetime.datetime.today() finish_time = current_time + datetime.timedelta(0,currentData["progress"]["printTimeLeft"]) strtime = format_time(finish_time) strdate = "" if finish_time.day > current_time.day: if finish_time.day == current_time.day + 1: strdate = " Tomorrow" else: strtime = " " + format_date(finish_time,"EEE d") return strtime + strdate def on_print_progress(self,storage, path, progress): self.eta_string = self.calculate_ETA() self._printer.commands("M117 ETA is {}".format(self.eta_string)) self._plugin_manager.send_plugin_message(self._identifier, dict(eta_string=self.eta_string)) def on_event(self,event, payload): if event.startswith('Print'): if event not in {"PrintStarted","PrintResumed"}: self.eta_string="-" self.timer.cancel() else: self.eta_string = self.calculate_ETA() self.timer.cancel() self.timer = RepeatedTimer(10.0, DisplayETAPlugin.fromTimer, args=[self], run_first=True,) self.timer.start() self._plugin_manager.send_plugin_message(self._identifier, dict(eta_string=self.eta_string)) def get_assets(self): return { "js": ["js/display_eta.js"] } def get_update_information(self): return dict{ "display_eta"=dict( displayName=self._plugin_name, displayVersion=self._plugin_version, type="github_release", current=self._plugin_version, user="******", repo="Octoprint-Display-ETA", pip="https://github.com/AlexVerrico/Octoprint-Display-ETA/archive/{target}.zip" ) }
class UltimakerFormatPackagePlugin(octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.SimpleApiPlugin): def __init__(self): self._fileRemovalTimer = None self._fileRemovalLastDeleted = {} self._fileRemovalLastAdded = {} self._waitForAnalysis = False self._analysis_active = False ##~~ SettingsPlugin mixin def get_settings_defaults(self): return dict(installed=True, installed_version=self._plugin_version, inline_thumbnail=False, scale_inline_thumbnail=False, inline_thumbnail_scale_value="50", align_inline_thumbnail=False, inline_thumbnail_align_value="left", state_panel_thumbnail=True) ##~~ AssetPlugin mixin def get_assets(self): return dict(js=["js/UltimakerFormatPackage.js"], css=["css/UltimakerFormatPackage.css"]) ##~~ TemplatePlugin mixin def get_template_configs(self): return [ dict(type="settings", custom_bindings=False, template="UltimakerFormatPackage_settings.jinja2"), ] ##~~ EventHandlerPlugin mixin def on_event(self, event, payload): if event not in [ "FileAdded", "FileRemoved", "MetadataAnalysisStarted", "MetadataAnalysisFinished", "FolderRemoved" ]: return if event == "FolderRemoved" and payload["storage"] == "local": import shutil shutil.rmtree(self.get_plugin_data_folder() + "/" + payload["path"], ignore_errors=True) # Hack that deletes uploaded ufp file from upload path if event == "FileAdded" and "ufp" in payload["type"]: old_name = payload[ "path"] # self._settings.global_get_basefolder("uploads") + "/" + ufp_file = self.get_plugin_data_folder() + "/" + payload["path"] if os.path.exists(ufp_file): os.remove(ufp_file) self._file_manager.remove_file("local", old_name) return if event == "MetadataAnalysisStarted" and payload["path"].endswith( ".gcode"): self._waitForAnalysis = True return if event == "MetadataAnalysisFinished" and payload["path"].endswith( ".gcode"): self._waitForAnalysis = False return if event == "FileAdded" and payload["path"].endswith(".gcode"): self._logger.debug("File added %s" % payload["name"]) self._fileRemovalLastAdded[payload["name"]] = payload if event == "FileRemoved" and payload["name"].endswith(".gcode"): self._logger.debug("File removed %s" % payload["name"]) self._fileRemovalLastDeleted[payload["name"]] = payload self._file_removal_timer_start() return ##~~ File Removal Timer def _file_removal_timer_start(self): if self._fileRemovalTimer is None: self._logger.debug("Starting removal timer.") self._fileRemovalTimer = RepeatedTimer( 5, self._file_removal_timer_task) self._fileRemovalTimer.start() def _file_removal_timer_stop(self): self._logger.debug("Cancelling timer and setting everything None.") if self._fileRemovalTimer is not None: self._fileRemovalTimer.cancel() self._fileRemovalTimer = None def _file_removal_timer_task(self): if self._waitForAnalysis: return for key in list(self._fileRemovalLastDeleted): thumbnail = "%s/%s" % (self.get_plugin_data_folder(), self._fileRemovalLastDeleted[key] ["path"].replace(".gcode", ".png")) ufp_file = "%s/%s" % (self.get_plugin_data_folder(), self._fileRemovalLastDeleted[key] ["path"].replace(".gcode", ".ufp")) gcode_file = "%s/%s" % (self.get_plugin_data_folder(), self._fileRemovalLastDeleted[key]["path"]) # clean up double slashes thumbnail = thumbnail.replace("//", "/") ufp_file = ufp_file.replace("//", "/") gcode_file = gcode_file.replace("//", "/") if self._fileRemovalLastAdded.get(key, False): # copy thumbnail to new path and update metadata thumbnail_new = "%s/%s" % (self.get_plugin_data_folder(), self._fileRemovalLastAdded[key] ["path"].replace(".gcode", ".png")) thumbnail_new = thumbnail_new.replace("//", "/") thumbnail_new_path = os.path.dirname(thumbnail_new) self._logger.debug(thumbnail) self._logger.debug(thumbnail_new) self._logger.debug(thumbnail_new_path) if not os.path.exists(thumbnail_new_path): os.makedirs(thumbnail_new_path) if os.path.exists(thumbnail_new): os.remove(thumbnail_new) if thumbnail != thumbnail_new: os.rename(thumbnail, thumbnail_new) if os.path.exists(thumbnail_new): self._logger.debug("Updating thumbnail url.") thumbnail_url = "plugin/UltimakerFormatPackage/thumbnail/" + self._fileRemovalLastAdded[ key]["path"].replace( ".gcode", ".png") + "?" + "{:%Y%m%d%H%M%S}".format( datetime.datetime.now()) self._file_manager.set_additional_metadata( "local", self._fileRemovalLastAdded[key]["path"], "thumbnail", thumbnail_url, overwrite=True) self._file_manager.set_additional_metadata( "local", self._fileRemovalLastAdded[key]["path"], "thumbnail_src", self._identifier, overwrite=True) self._fileRemovalLastAdded.pop(key) # remove files just in case they are left behind. if os.path.exists(thumbnail): os.remove(thumbnail) if os.path.exists(ufp_file): os.remove(ufp_file) if os.path.exists(gcode_file): os.remove(gcode_file) self._fileRemovalLastDeleted.pop(key) self._file_removal_timer_stop() ##~~ Utility Functions def deep_get(self, d, keys, default=None): """ Example: d = {'meta': {'status': 'OK', 'status_code': 200}} deep_get(d, ['meta', 'status_code']) # => 200 deep_get(d, ['garbage', 'status_code']) # => None deep_get(d, ['meta', 'garbage'], default='-') # => '-' """ assert type(keys) is list if d is None: return default if not keys: return d return self.deep_get(d.get(keys[0]), keys[1:], default) ##~~ SimpleApiPlugin mixin def _process_gcode(self, gcode_file, results=[]): self._logger.debug(gcode_file["path"]) if gcode_file.get("type") == "machinecode": self._logger.debug(gcode_file.get("thumbnail")) if gcode_file.get("thumbnail_src", False): return results if gcode_file.get("refs", False) and gcode_file["refs"].get( "thumbnail", False): results["no_thumbnail_src"].append(gcode_file["path"]) self._logger.debug("Setting metadata for %s" % gcode_file["path"]) self._file_manager.set_additional_metadata( "local", gcode_file["path"], "thumbnail", gcode_file["refs"].get("thumbnail").replace( "/plugin/UltimakerFormatPackage", "plugin/UltimakerFormatPackage"), overwrite=True) self._file_manager.remove_additional_metadata( "local", gcode_file["path"], "refs") self._file_manager.set_additional_metadata("local", gcode_file["path"], "thumbnail_src", self._identifier, overwrite=True) elif gcode_file.get( "type") == "folder" and not gcode_file.get("children") == None: children = gcode_file["children"] for key, file in children.items(): self._process_gcode(children[key], results) return results def get_api_commands(self): return dict(crawl_files=[]) def on_api_command(self, command, data): import flask import json from octoprint.server import user_permission if not user_permission.can(): return flask.make_response("Insufficient rights", 403) if command == "crawl_files": self._logger.debug("Crawling Files") file_list = self._file_manager.list_files() local_files = file_list["local"] results = dict(no_thumbnail=[], no_thumbnail_src=[]) for key, file in local_files.items(): results = self._process_gcode(local_files[key], results) return flask.jsonify(results) ##-- UFP upload extenstion tree hook def get_extension_tree(self, *args, **kwargs): return dict(machinecode=dict(ufp=["ufp"])) ##~~ UFP upload preprocessor hook def ufp_upload(self, path, file_object, links=None, printer_profile=None, allow_overwrite=True, *args, **kwargs): ufp_extensions = [".ufp"] name, extension = os.path.splitext(file_object.filename) if extension in ufp_extensions: ufp_filename = self.get_plugin_data_folder() + "/" + path png_filename = ufp_filename.replace(".ufp", ".png") gco_filename = ufp_filename.replace(".ufp", ".gcode") ufp_filepath = os.path.dirname(ufp_filename) if not os.path.exists(ufp_filepath): os.makedirs(ufp_filepath) file_object.save(ufp_filename) with ZipFile(ufp_filename, 'r') as zipObj: with open(png_filename, 'wb') as thumbnail: thumbnail.write(zipObj.read("/Metadata/thumbnail.png")) with open(gco_filename, 'wb') as f: f.write(zipObj.read("/3D/model.gcode")) file_wrapper = octoprint.filemanager.util.DiskFileWrapper( path.replace(".ufp", ".gcode"), gco_filename, move=True) uploaded_file = self._file_manager.add_file("local", file_wrapper.filename, file_wrapper, allow_overwrite=True) self._logger.debug('Adding thumbnail url to metadata') thumbnail_url = "plugin/UltimakerFormatPackage/thumbnail/" + uploaded_file.replace( ".gcode", ".png") + "?" + "{:%Y%m%d%H%M%S}".format( datetime.datetime.now()) self._file_manager.set_additional_metadata("local", uploaded_file, "thumbnail", thumbnail_url, overwrite=True) self._file_manager.set_additional_metadata("local", uploaded_file, "thumbnail_src", self._identifier, overwrite=True) return octoprint.filemanager.util.DiskFileWrapper( path, ufp_filename) return file_object ##~~ Routes hook def route_hook(self, server_routes, *args, **kwargs): from octoprint.server.util.tornado import LargeResponseHandler, UrlProxyHandler, path_validation_factory from octoprint.util import is_hidden_path return [(r"thumbnail/(.*)", LargeResponseHandler, dict(path=self.get_plugin_data_folder(), as_attachment=False, path_validation=path_validation_factory( lambda path: not is_hidden_path(path), status_code=404)))] def additional_excludes_hook(self, excludes, *args, **kwargs): if "uploads" in excludes: return ["."] return [] ##~~ Softwareupdate hook def get_update_information(self): return dict(UltimakerFormatPackage=dict( displayName="Cura Thumbnails", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-UltimakerFormatPackage", current=self._plugin_version, stable_branch=dict( name="Stable", branch="master", comittish=["master"]), prerelease_branches=[ dict( name="Release Candidate", branch="rc", comittish=["rc", "master"], ) ], # update method: pip pip= "https://github.com/jneilliii/OctoPrint-UltimakerFormatPackage/archive/{target_version}.zip" ))
class DHTSensorPlugin(octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.ReloadNeedingPlugin): def __init__(self): self.enable = False self.sensorType = "" self.dataPin = 0 self.refreshInterval = 0 self.decimals = 0 self.maxHumidity = 0 self.maxTemperature = 0 self.humidity = -1 self.temperature = -1 self._updateTimer = None def initialize(self): self._load_settings() self._updateTimer = RepeatedTimer(self.refreshInterval, self._update_temperature, run_first=True) self._updateTimer.start() def _load_settings(self): self.enable = self._settings.get_boolean(["enable"]) self.sensorType = self._settings.get(["sensorType"]) self.dataPin = self._settings.get_int(["dataPin"]) self.refreshInterval = self._settings.get_int(["refreshInterval"]) self.decimals = self._settings.get_int(["decimals"]) self.maxHumidity = self._settings.get_int(["maxHumidity"]) self.maxTemperature = self._settings.get_int(["maxTemperature"]) if self.sensorType not in ["dht11", "dht22"]: self._logger.warning("Invalid sensorType: %s", self.sensorType) self.sensorType = DEFAULT_SENSOR_TYPE if self.dataPin < 0 or self.dataPin > 40: self._logger.warning("Invalid dataPin: %s", self.dataPin) self.dataPin = DEFAULT_DATA_PIN if self.refreshInterval < 10 or self.refreshInterval > 86400: self._logger.warning("Invalid refreshInterval: %s", self.refreshInterval) self.refreshInterval = DEFAULT_REFRESH_INTERVAL if self.decimals < 0 or self.decimals > 3: self._logger.warning("Invalid decimals: %s", self.decimals) self.decimals = DEFAULT_DECIMALS if self.maxHumidity < 0 or self.maxHumidity > 100: self._logger.warning("Invalid maxHumidity: %s", self.maxHumidity) self.maxHumidity = DEFAULT_MAX_HUMIDITY if self.maxTemperature < 0 or self.maxTemperature > 100: self._logger.warning("Invalid maxTemperature: %s", self.maxTemperature) self.maxTemperature = DEFAULT_MAX_TEMPERATURE self._logger.debug("enable: %s", self.enable) self._logger.debug("sensorType: %s", self.sensorType) self._logger.debug("dataPin: %s", self.dataPin) self._logger.debug("refreshInterval: %s", self.refreshInterval) self._logger.debug("decimals: %s", self.decimals) self._logger.debug("maxHumidity: %s", self.maxHumidity) self._logger.debug("maxTemperature: %s", self.maxTemperature) def _update_temperature(self): if not self.enable: return try: if self.sensorType == "dht11": humidity, temperature = Adafruit_DHT.read_retry( Adafruit_DHT.DHT11, self.dataPin, 5) elif self.sensorType == "dht22": humidity, temperature = Adafruit_DHT.read_retry( Adafruit_DHT.DHT22, self.dataPin, 5) else: return except Exception as err: self._logger.warning("Failed to read sensor: %s", err) humidity = -1 temperature = -1 self._logger.debug("Retrieved sensor values: %s | %s", humidity, temperature) if (humidity is None or temperature is None or humidity < 0 or temperature < 0 or humidity > 100 or temperature > 100): self._logger.warning("Invalid sensor data: %s | %s", humidity, temperature) humidity = -1 temperature = -1 self.humidity = int(round(humidity * pow(10, self.decimals))) self.temperature = int(round(temperature * pow(10, self.decimals))) self._plugin_manager.send_plugin_message( self._identifier, dict(humidity=self.humidity, temperature=self.temperature, decimals=self.decimals)) #~~ TemplatePlugin def get_template_configs(self): return [ dict(type="sidebar", name="DHT Sensor", custom_bindings=False, icon="archive", template_header="dhtsensor_sidebar_header.jinja2"), dict(type="settings", custom_bindings=False) ] #~~ AssetPlugin def get_assets(self): return dict(js=["js/decimal.min.js", "js/dhtsensor.js"], css=["css/dhtsensor.css"]) #~~ SettingsPlugin def get_settings_defaults(self): return dict(enable=DEFAULT_ENABLE, sensorType=DEFAULT_SENSOR_TYPE, dataPin=DEFAULT_DATA_PIN, refreshInterval=DEFAULT_REFRESH_INTERVAL, decimals=DEFAULT_DECIMALS, maxHumidity=DEFAULT_MAX_HUMIDITY, maxTemperature=DEFAULT_MAX_TEMPERATURE) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self._load_settings() self._plugin_manager.send_plugin_message( self._identifier, dict(humidity=-1, temperature=-1, enable=self.enable, decimals=0, maxHumidity=self.maxHumidity, maxTemperature=self.maxTemperature)) if self._updateTimer is not None: self._updateTimer.cancel() self._updateTimer = RepeatedTimer(self.refreshInterval, self._update_temperature, run_first=True) self._updateTimer.start() #~~ SimpleApiPlugin def get_api_commands(self): return dict(refresh=[]) def on_api_command(self, command, data): if command == "refresh": self._plugin_manager.send_plugin_message( self._identifier, dict(enable=self.enable, maxHumidity=self.maxHumidity, maxTemperature=self.maxTemperature)) self._update_temperature() else: return make_response("Not Found", 404) def on_api_get(self, request): return make_response("Not Found", 404) def get_update_information(self): return dict(dhtsensor=dict( displayName=self._plugin_name, displayVersion=self._plugin_version, type="github_release", user="******", repo="OctoPrint-DHTSensor", current=self._plugin_version, pip= "https://github.com/Desuuuu/OctoPrint-DHTSensor/archive/{target_version}.zip" ))
class BeeCom(MachineCom): STATE_WAITING_FOR_BTF = 21 STATE_PREPARING_PRINT = 22 STATE_HEATING = 23 STATE_SHUTDOWN = 24 _beeConn = None _beeCommands = None _responseQueue = queue.Queue() _statusQueue = queue.Queue() _monitor_print_progress = True _connection_monitor_active = True _prepare_print_thread = None def __init__(self, callbackObject=None, printerProfileManager=None): super(BeeCom, self).__init__(None, None, callbackObject, printerProfileManager) self._openConnection() # monitoring thread self._monitoring_active = True self.monitoring_thread = threading.Thread(target=self._monitor, name="comm._monitor") self.monitoring_thread.daemon = True self.monitoring_thread.start() def _openConnection(self): """ Opens a new connection using the BEEcom driver :return: True if the connection was successful """ if self._beeConn is None: self._beeConn = BeePrinterConn(self._connShutdownHook) self._changeState(self.STATE_CONNECTING) self._beeConn.connectToFirstPrinter() if self._beeConn.isConnected(): self._beeCommands = self._beeConn.getCommandIntf() # change to firmware if self._beeCommands.getPrinterMode() == 'Bootloader': # checks for firmware updates self.update_firmware() self._beeCommands.goToFirmware() # restart connection self._beeConn.reconnect() # post connection callback self._onConnected() return True else: return False def current_firmware(self): """ Gets the current firmware version :return: """ firmware_v = self.getCommandsInterface().getFirmwareVersion() if firmware_v is not None: return firmware_v else: return 'Not available' def update_firmware(self): """ Updates the printer firmware if the value in the firmware.properties file is different from the current printer firmware :return: if no printer is connected just returns void """ _logger = logging.getLogger() # get the latest firmware file for the connected printer conn_printer = self.getConnectedPrinterName() if conn_printer is None: return printer_id = conn_printer.replace(' ', '').lower() if printer_id: from os.path import isfile, join _logger.info("Checking for firmware updates...") firmware_path = settings().getBaseFolder('firmware') firmware_properties = parsePropertiesFile(join(firmware_path, 'firmware.properties')) firmware_file_name = firmware_properties['firmware.'+printer_id] if firmware_file_name is not None and isfile(join(firmware_path, firmware_file_name)): fname_parts = firmware_file_name.split('-') # gets the current firmware version curr_firmware = self.current_firmware() curr_firmware_parts = curr_firmware.split('-') if len(curr_firmware_parts) == 3 and curr_firmware is not "Not available": curr_version_parts = curr_firmware_parts[2].split('.') file_version_parts = fname_parts[2].split('.') if len(curr_version_parts) == 4 and len(file_version_parts) == 4: for i in xrange(3): if int(file_version_parts[i]) != int(curr_version_parts[i]): # version update found _logger.info("Updating printer firmware...") self.getCommandsInterface().flashFirmware(join(firmware_path, firmware_file_name), firmware_file_name) _logger.info("Firmware updated to %s" % fname_parts[2]) return elif curr_firmware == '0.0.0': # If curr_firmware is 0.0.0 it means something went wrong with a previous firmware update _logger.info("Updating printer firmware...") self.getCommandsInterface().flashFirmware(join(firmware_path, firmware_file_name), firmware_file_name) _logger.info("Firmware updated to %s" % fname_parts[2]) return else: _logger.error("No firmware file matching the configuration for printer %s found" % conn_printer) _logger.info("No firmware updates found") def sendCommand(self, cmd, cmd_type=None, processed=False, force=False): """ Sends a custom command through the open connection :param cmd: :param cmd_type: :param processed: :param force: :return: """ cmd = cmd.encode('ascii', 'replace') if not processed: cmd = comm.process_gcode_line(cmd) if not cmd: return #if self.isPrinting() and not self.isSdFileSelected(): # self._commandQueue.put((cmd, cmd_type)) if self.isOperational(): wait = None if "g" in cmd.lower(): wait = "3" resp = self._beeCommands.sendCmd(cmd, wait) if resp: # puts the response in the monitor queue self._responseQueue.put(resp) # logs the command reply with errors splits = resp.rstrip().split("\n") for r in splits: if "Error" in r: self._logger.warning(r) return True else: return False def close(self, is_error=False, wait=True, timeout=10.0, *args, **kwargs): """ Closes the connection to the printer if it's active :param is_error: :param wait: unused parameter (kept for interface compatibility) :param timeout: :param args: :param kwargs: :return: """ if self._beeCommands is not None: self._beeCommands.stopStatusMonitor() if self._beeConn is not None: self._beeConn.close() self._changeState(self.STATE_CLOSED) def _changeState(self, newState): if self._state == newState: return oldState = self.getStateString() self._state = newState self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString())) self._callback.on_comm_state_change(newState) def confirmConnection(self): """ Confirms the connection changing the internal state of the printer :return: """ if self._beeConn.isConnected(): if self._beeCommands.isPrinting(): self._changeState(self.STATE_PRINTING) elif self._beeCommands.isShutdown(): self._changeState(self.STATE_SHUTDOWN) else: self._changeState(self.STATE_OPERATIONAL) else: self._changeState(self.STATE_WAITING_FOR_BTF) def getConnectedPrinterName(self): """ Returns the current connected printer name :return: """ if self._beeConn is not None: return self._beeConn.getConnectedPrinterName() else: return "" def getConnectedPrinterSN(self): """ Returns the current connected printer serial number :return: """ if self._beeConn is not None: return self._beeConn.getConnectedPrinterSN() else: return None def isOperational(self): return self._state == self.STATE_OPERATIONAL \ or self._state == self.STATE_PRINTING \ or self._state == self.STATE_PAUSED \ or self._state == self.STATE_SHUTDOWN \ or self._state == self.STATE_TRANSFERING_FILE \ or self._state == self.STATE_PREPARING_PRINT \ or self._state == self.STATE_HEATING def isClosedOrError(self): return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR \ or self._state == self.STATE_CLOSED or self._state == self.STATE_WAITING_FOR_BTF def isBusy(self): return self.isPrinting() or self.isPaused() or self.isPreparingPrint() def isPreparingPrint(self): return self._state == self.STATE_PREPARING_PRINT or self._state == self.STATE_HEATING def isPrinting(self): return self._state == self.STATE_PRINTING def isHeating(self): return self._state == self.STATE_HEATING def isShutdown(self): return self._state == self.STATE_SHUTDOWN def getStateString(self): """ Returns the current printer state :return: """ if self._state == self.STATE_WAITING_FOR_BTF or self._state == self.STATE_CLOSED: return "No printer detected. Please connect your printer" elif self._state == self.STATE_PREPARING_PRINT: return "Preparing to print, please wait" elif self._state == self.STATE_HEATING: return "Heating" elif self._state == self.STATE_SHUTDOWN: return "Shutdown" elif self._state == self.STATE_OPERATIONAL: return "Ready" else: return super(BeeCom, self).getStateString() def startPrint(self, pos=None): """ Starts the printing operation :param pos: unused parameter, just to keep the interface compatible with octoprint """ if not self.isOperational() or self.isPrinting(): return if self._currentFile is None: raise ValueError("No file selected for printing") try: self._changeState(self.STATE_PREPARING_PRINT) if self.isSdFileSelected(): print_resp = self._beeCommands.startSDPrint(self._currentFile.getFilename()) if print_resp: self._sd_status_timer = RepeatedTimer(self._timeout_intervals.get("sdStatus", 1.0), self._poll_sd_status, run_first=True) self._sd_status_timer.start() else: print_resp = self._beeCommands.printFile(self._currentFile.getFilename()) if print_resp is True: self._heatupWaitStartTime = time.time() self._heatupWaitTimeLost = 0.0 self._pauseWaitStartTime = 0 self._pauseWaitTimeLost = 0.0 self._heating = True self._prepare_print_thread = threading.Thread(target=self._preparePrintThread, name="comm._preparePrint") self._prepare_print_thread.daemon = True self._prepare_print_thread.start() else: self._errorValue = "Error while preparing the printing operation." self._logger.exception(self._errorValue) self._changeState(self.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) return except: self._errorValue = get_exception_string() self._logger.exception("Error while trying to start printing: " + self.getErrorString()) self._changeState(self.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) def cancelPrint(self, firmware_error=None): """ Cancels the print operation :type firmware_error: unused parameter, just to keep the interface compatible with octoprint """ if not self.isOperational() or self.isStreaming(): return if self._beeCommands.cancelPrint(): self._changeState(self.STATE_OPERATIONAL) if self.isSdFileSelected(): if self._sd_status_timer is not None: try: self._sd_status_timer.cancel() except: pass # protects against any unexpected null selectedFile if self._currentFile is not None: payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation(), "firmwareError": firmware_error } else: payload = { "file": None, "filename": '', "origin": '', "firmwareError": firmware_error } eventManager().fire(Events.PRINT_CANCELLED, payload) # sends usage statistics self._sendUsageStatistics('cancel') else: self._logger.exception("Error while canceling the print operation.") eventManager().fire(Events.ERROR, {"error": "Error canceling print"}) return def setPause(self, pause): """ Toggle Pause method :param pause: True to pause or False to unpause :return: """ if self.isStreaming(): return if not self._currentFile: return payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() } if (not pause and self.isPaused()) or self.isShutdown(): if self._pauseWaitStartTime: self._pauseWaitTimeLost = self._pauseWaitTimeLost + (time.time() - self._pauseWaitStartTime) self._pauseWaitStartTime = None # resumes printing self._beeCommands.resumePrint() # restarts the progress monitor thread self.startPrintStatusProgressMonitor() self._changeState(self.STATE_PRINTING) eventManager().fire(Events.PRINT_RESUMED, payload) elif pause and self.isPrinting(): if not self._pauseWaitStartTime: self._pauseWaitStartTime = time.time() # pause print self._beeCommands.pausePrint() self._changeState(self.STATE_PAUSED) eventManager().fire(Events.PRINT_PAUSED, payload) def enterShutdownMode(self): """ Enters the printer shutdown mode :return: """ if self.isStreaming(): return if not self._currentFile: return payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() } # enter shutdown mode self._beeCommands.enterShutdown() self._changeState(self.STATE_SHUTDOWN) eventManager().fire(Events.POWER_OFF, payload) def initSdCard(self): """ Initializes the SD Card in the printer :return: """ if not self.isOperational(): return self._beeCommands.initSD() if settings().getBoolean(["feature", "sdAlwaysAvailable"]): self._sdAvailable = True self.refreshSdFiles() self._callback.on_comm_sd_state_change(self._sdAvailable) def refreshSdFiles(self): """ Refreshes the list of available SD card files :return: """ if not self.isOperational() or self.isBusy(): return fList = self._beeCommands.getFileList() ##~~ SD file list if len(fList) > 0 and 'FileNames' in fList: for sdFile in fList['FileNames']: if comm.valid_file_type(sdFile, "machinecode"): if comm.filter_non_ascii(sdFile): self._logger.warn("Got a file from printer's SD that has a non-ascii filename (%s), that shouldn't happen according to the protocol" % filename) else: if not filename.startswith("/"): # file from the root of the sd -- we'll prepend a / filename = "/" + filename self._sdFiles.append((sdFile, 0)) continue def startFileTransfer(self, filename, localFilename, remoteFilename): """ Transfers a file to the printer's SD Card """ if not self.isOperational() or self.isBusy(): self._log("Printer is not operation or busy") return self._currentFile = comm.StreamingGcodeFileInformation(filename, localFilename, remoteFilename) self._currentFile.start() # starts the transfer self._beeCommands.transferSDFile(filename, localFilename) eventManager().fire(Events.TRANSFER_STARTED, {"local": localFilename, "remote": remoteFilename}) self._callback.on_comm_file_transfer_started(remoteFilename, self._currentFile.getFilesize()) # waits for transfer to end while self._beeCommands.getTransferCompletionState() > 0: time.sleep(2) remote = self._currentFile.getRemoteFilename() payload = { "local": self._currentFile.getLocalFilename(), "remote": remote, "time": self.getPrintTime() } self._currentFile = None self._changeState(self.STATE_OPERATIONAL) self._callback.on_comm_file_transfer_done(remote) eventManager().fire(Events.TRANSFER_DONE, payload) self.refreshSdFiles() def startPrintStatusProgressMonitor(self): """ Starts the monitor thread that keeps track of the print progress :return: """ if self._beeCommands is not None: # starts the progress status thread self._beeCommands.startStatusMonitor(self._statusProgressQueueCallback) def selectFile(self, filename, sd): """ Overrides the original selectFile method to allow to select files when printer is busy. For example when reconnecting after connection was lost and the printer is still printing :param filename: :param sd: :return: """ if sd: if not self.isOperational(): # printer is not connected, can't use SD return self._sdFileToSelect = filename self.sendCommand("M23 %s" % filename) else: self._currentFile = comm.PrintingGcodeFileInformation(filename, offsets_callback=self.getOffsets, current_tool_callback=self.getCurrentTool) eventManager().fire(Events.FILE_SELECTED, { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() }) self._callback.on_comm_file_selected(filename, self._currentFile.getFilesize(), False) def getPrintProgress(self): """ Gets the current print progress :return: """ if self._currentFile is None: return None return self._currentFile.getProgress() def _getResponse(self): """ Auxiliar method to read the command response queue :return: """ if self._beeConn is None: return None try: ret = self._responseQueue.get() except: self._log("Exception raised while reading from command response queue: %s" % (get_exception_string())) self._errorValue = get_exception_string() return None if ret == '': #self._log("Recv: TIMEOUT") return '' try: self._log("Recv: %s" % sanitize_ascii(ret)) except ValueError as e: self._log("WARN: While reading last line: %s" % e) self._log("Recv: %r" % ret) return ret def triggerPrintFinished(self): """ This method runs the post-print job code :return: """ self._sdFilePos = 0 self._callback.on_comm_print_job_done() self._changeState(self.STATE_OPERATIONAL) eventManager().fire(Events.PRINT_DONE, { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation(), "time": self.getPrintTime() }) if self._sd_status_timer is not None: try: self._sd_status_timer.cancel() except: pass # sends usage statistics self._sendUsageStatistics('stop') def _monitor(self): """ Monitor thread of responses from the commands sent to the printer :return: """ feedback_controls, feedback_matcher = comm.convert_feedback_controls(settings().get(["controls"])) feedback_errors = [] pause_triggers = comm.convert_pause_triggers(settings().get(["printerParameters", "pauseTriggers"])) #exits if no connection is active if not self._beeConn.isConnected(): return startSeen = False supportWait = settings().getBoolean(["feature", "supportWait"]) while self._monitoring_active: try: line = self._getResponse() if line is None: continue ##~~ debugging output handling if line.startswith("//"): debugging_output = line[2:].strip() if debugging_output.startswith("action:"): action_command = debugging_output[len("action:"):].strip() if action_command == "pause": self._log("Pausing on request of the printer...") self.setPause(True) elif action_command == "resume": self._log("Resuming on request of the printer...") self.setPause(False) elif action_command == "disconnect": self._log("Disconnecting on request of the printer...") self._callback.on_comm_force_disconnect() else: for hook in self._printer_action_hooks: try: self._printer_action_hooks[hook](self, line, action_command) except: self._logger.exception("Error while calling hook {} with action command {}".format(self._printer_action_hooks[hook], action_command)) continue else: continue ##~~ Error handling line = self._handleErrors(line) ##~~ process oks if line.strip().startswith("ok") or (self.isPrinting() and supportWait and line.strip().startswith("wait")): self._clear_to_send.set() self._long_running_command = False ##~~ Temperature processing if ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:') \ or ' B:' in line or line.startswith('B:'): self._processTemperatures(line) self._callback.on_comm_temperature_update(self._temp, self._bedTemp) ##~~ SD Card handling elif 'SD init fail' in line or 'volume.init failed' in line or 'openRoot failed' in line: self._sdAvailable = False self._sdFiles = [] self._callback.on_comm_sd_state_change(self._sdAvailable) elif 'Not SD printing' in line: if self.isSdFileSelected() and self.isPrinting(): # something went wrong, printer is reporting that we actually are not printing right now... self._sdFilePos = 0 self._changeState(self.STATE_OPERATIONAL) elif 'SD card ok' in line and not self._sdAvailable: self._sdAvailable = True self.refreshSdFiles() self._callback.on_comm_sd_state_change(self._sdAvailable) elif 'Begin file list' in line: self._sdFiles = [] self._sdFileList = True elif 'End file list' in line: self._sdFileList = False self._callback.on_comm_sd_files(self._sdFiles) elif 'SD printing byte' in line and self.isSdPrinting(): # answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d" match = regex_sdPrintingByte.search(line) self._currentFile.setFilepos(int(match.group(1))) self._callback.on_comm_progress() elif 'File opened' in line and not self._ignore_select: # answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d" match = regex_sdFileOpened.search(line) if self._sdFileToSelect: name = self._sdFileToSelect self._sdFileToSelect = None else: name = match.group(1) self._currentFile = comm.PrintingSdFileInformation(name, int(match.group(2))) elif 'File selected' in line: if self._ignore_select: self._ignore_select = False elif self._currentFile is not None: # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" self._callback.on_comm_file_selected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True) eventManager().fire(Events.FILE_SELECTED, { "file": self._currentFile.getFilename(), "origin": self._currentFile.getFileLocation() }) elif 'Writing to file' in line: # answer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s" self._changeState(self.STATE_PRINTING) self._clear_to_send.set() line = "ok" elif 'Done saving file' in line: self.refreshSdFiles() elif 'File deleted' in line and line.strip().endswith("ok"): # buggy Marlin version that doesn't send a proper \r after the "File deleted" statement, fixed in # current versions self._clear_to_send.set() ##~~ Message handling elif line.strip() != '' \ and line.strip() != 'ok' and not line.startswith("wait") \ and not line.startswith('Resend:') \ and line != 'echo:Unknown command:""\n' \ and self.isOperational(): self._callback.on_comm_message(line) ##~~ Parsing for feedback commands if feedback_controls and feedback_matcher and not "_all" in feedback_errors: try: self._process_registered_message(line, feedback_matcher, feedback_controls, feedback_errors) except: # something went wrong while feedback matching self._logger.exception("Error while trying to apply feedback control matching, disabling it") feedback_errors.append("_all") ##~~ Parsing for pause triggers if pause_triggers and not self.isStreaming(): if "enable" in pause_triggers.keys() and pause_triggers["enable"].search(line) is not None: self.setPause(True) elif "disable" in pause_triggers.keys() and pause_triggers["disable"].search(line) is not None: self.setPause(False) elif "toggle" in pause_triggers.keys() and pause_triggers["toggle"].search(line) is not None: self.setPause(not self.isPaused()) self.setPause(not self.isPaused()) ### Connection attempt elif self._state == self.STATE_CONNECTING: if "start" in line and not startSeen: startSeen = True self._sendCommand("M110") self._clear_to_send.set() elif "ok" in line: self._onConnected() elif time.time() > self._timeout: self.close() ### Operational elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED: if "ok" in line: # if we still have commands to process, process them if self._resendDelta is not None: self._resendNextCommand() elif self._sendFromQueue(): pass # resend -> start resend procedure from requested line elif line.lower().startswith("resend") or line.lower().startswith("rs"): self._handleResendRequest(line) except Exception as ex: self._logger.exception("Something crashed inside the USB connection.") errorMsg = "See octoprint.log for details" self._log(ex.message) self._errorValue = errorMsg self._changeState(self.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) self._log("Connection closed, closing down monitor") def _statusProgressQueueCallback(self, status_obj): """ Auxiliar callback method to push the status object that comes from the printer into the queue :param status_obj: :return: """ # calls the Printer object to update the progress values self._callback.updateProgress(status_obj) self._callback.on_comm_progress() def _onConnected(self): """ Post connection callback """ # starts the connection monitor thread self._beeConn.startConnectionMonitor() self._temperature_timer = RepeatedTimer(self._timeout_intervals.get("temperature", 4.0), self._poll_temperature, run_first=True) self._temperature_timer.start() if self._sdAvailable: self.refreshSdFiles() else: self.initSdCard() payload = dict(port=self._port, baudrate=self._baudrate) eventManager().fire(Events.CONNECTED, payload) def _poll_temperature(self): """ Polls the temperature after the temperature timeout, re-enqueues itself. If the printer is not operational, not printing from sd, busy with a long running command or heating, no poll will be done. """ try: if self.isOperational() and not self.isStreaming() and not self._long_running_command and not self._heating: self.sendCommand("M105", cmd_type="temperature_poll") except Exception as e: self._log("Error polling temperature %s" % str(e)) def getCommandsInterface(self): """ Returns the commands interface for BVC printers :return: """ return self._beeCommands def _connShutdownHook(self): """ Function to be called by the BVC driver to shutdown the connection :return: """ self._callback.on_comm_force_disconnect() def _preparePrintThread(self): """ Thread code that runs while the print job is being prepared :return: """ # waits for heating/file transfer while self._beeCommands.isTransferring(): time.sleep(1) self._changeState(self.STATE_HEATING) while self._beeCommands.isHeating(): time.sleep(1) self._changeState(self.STATE_PRINTING) # Starts the real printing operation self._currentFile.start() payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() } eventManager().fire(Events.PRINT_STARTED, payload) # sends usage statistics self._sendUsageStatistics('start') # starts the progress status thread self.startPrintStatusProgressMonitor() if self._heatupWaitStartTime is not None: self._heatupWaitTimeLost = self._heatupWaitTimeLost + (time.time() - self._heatupWaitStartTime) self._heatupWaitStartTime = None self._heating = False def _sendUsageStatistics(self, operation): """ Calls and external executable to send usage statistics to a remote cloud server :param operation: Supports 'start' (Start Print), 'cancel' (Cancel Print), 'stop' (Print finished) operations :return: true in case the operation was successfull or false if not """ _logger = logging.getLogger() biExePath = settings().getBaseFolder('bi') + '/bi_azure' if operation != 'start' and operation != 'cancel' and operation != 'stop': return False if os.path.exists(biExePath) and os.path.isfile(biExePath): printerSN = self.getConnectedPrinterSN() if printerSN is None: _logger.error("Could not get Printer Serial Number for statistics communication.") return False else: cmd = '%s %s %s' % (biExePath,str(printerSN), str(operation)) _logger.info(u"Running %s" % cmd) import subprocess p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) (output, err) = p.communicate() p_status = p.wait() if p_status == 0 and 'IOTHUB_CLIENT_CONFIRMATION_OK' in output: _logger.info(u"Statistics sent to remote server. (Operation: %s)" % operation) return True else: _logger.info(u"Failed sending statistics to remote server. (Operation: %s)" % operation) return False
class PowercontrolPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.OctoPrintPlugin): ##~~ Initialization def __init__(self): self._cooldownOneTimeoutValue = None self._cooldownOneTimer = None self._cooldownTwoTimeoutValue = None self._cooldownTwoTimer = None self._helperWaitTimeoutValue = 1 self._helperWaitTimer = None self._pwrOneTimeoutValue = None self._pwrOneTimer = None self._pwrTwoTimeoutValue = None self._pwrTwoTimer = None self.cooldownDelay = "" self.inOnePin = "" self.inTwoPin = "" self.isRaspi = False self.onOneMessage = "" self.offOneMessage = "" self.onTwoMessage = "" self.offTwoMessage = "" self.powerinfoActive = False self.pwrMessage = "" self.relayOneName = "" self.relayOneCooldownEnabled = False self.relayTwoName = "" self.relayTwoCooldownEnabled = False self.showPwrOneRelay = False self.showPwrTwoRelay = False self.warnOnPwrOffRelayOne = False self.warnOnPwrOffRelayTwo = False ##~~ StartupPlugin mixin def on_after_startup(self): if sys.platform == "linux2": with open('/proc/cpuinfo', 'r') as infile: cpuinfo = infile.read() # Search for the cpu info match = re.search('Hardware\s+:\s+(\w+)$', cpuinfo, flags=re.MULTILINE | re.IGNORECASE) if match is None: # The hardware is not a pi self.isRaspi = False elif match.group(1) == 'BCM2708': self._logger.debug("Pi 1") self.isRaspi = True elif match.group(1) == 'BCM2709': self._logger.debug("Pi 2") self.isRaspi = True elif match.group(1) == 'BCM2710': self._logger.debug("Pi 3") self.isRaspi = True self.cooldownDelay = int(self._settings.get(["cooldownDelay"])) self.inOnePin = int(self._settings.get(["inOnePin"])) self.inTwoPin = int(self._settings.get(["inTwoPin"])) self.onOneMessage = self._settings.get(["onOneMessage"]) self.offOneMessage = self._settings.get(["offOneMessage"]) self.onTwoMessage = self._settings.get(["onTwoMessage"]) self.offTwoMessage = self._settings.get(["offTwoMessage"]) self._settings.set(["powerinfoActive"], self.powerinfoActive) self.relayOneName = self._settings.get(["relayOneName"]) self.relayOneCooldownEnabled = self._settings.get(["relayOneCooldownEnabled"]) self.relayTwoName = self._settings.get(["relayTwoName"]) self.relayTwoCooldownEnabled = self._settings.get(["relayTwoCooldownEnabled"]) self.showPwrOneRelay = self._settings.get(["showPwrOneRelay"]) self.showPwrTwoRelay = self._settings.get(["showPwrTwoRelay"]) self.warnOnPwrOffRelayOne = self._settings.get(["warnOnPwrOffRelayOne"]) self.warnOnPwrOffRelayTwo = self._settings.get(["warnOnPwrOffRelayTwo"]) self.updatePlugin() self._helperWaitTimer = RepeatedTimer(1, self._helper_wait_task) self._helperWaitTimer.start() def _helper_wait_task(self): self._helperWaitTimeoutValue -= 1 if self._helperWaitTimeoutValue <= 0: self._helperWaitTimer.cancel() self._helperWaitTimer = None self.get_helpers() def get_helpers(self): # Try to find powerinfo plugin helpers = self._plugin_manager.get_helpers("powerinfo") self._logger.info("helpers %s" % helpers) if helpers and "inOnePin" in helpers: self.powerinfoActive = True self._settings.set(["powerinfoActive"], self.powerinfoActive) self._logger.debug("Using powerinfo as reference") self.inOnePin = helpers["inOnePin"] self.inTwoPin = helpers["inTwoPin"] self.relayOneName = helpers["relayOneName"] self.relayTwoName = helpers["relayTwoName"] self.showPwrOneRelay = helpers["showPwrOneRelay"] self.showPwrTwoRelay = helpers["showPwrTwoRelay"] if self.isRaspi and not self.powerinfoActive: self._logger.debug("Powercontrol: Initialize GPIO") # Set GPIO layout like pin-number GPIO.setmode(GPIO.BOARD) # Configure our GPIO outputs GPIO.setup(self.inOnePin, GPIO.OUT) GPIO.setup(self.inTwoPin, GPIO.OUT) # Setup the initial state to high(off) GPIO.output(self.inOnePin, GPIO.HIGH) GPIO.output(self.inTwoPin, GPIO.HIGH) self.updatePlugin() ##~~ OctoPrintPlugin hook def hook_m117(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): if gcode and gcode == "M117": self._logger.info("Got message: {0}".format(cmd)) self.pwrMessage = cmd[5:] if self.pwrMessage == self.onOneMessage: GPIO.output(self.inOnePin, GPIO.LOW) elif self.pwrMessage == self.offOneMessage: if self.relayOneCooldownEnabled: self._cooldownOneTimeoutValue = self.cooldownDelay self._cooldownOneTimer = RepeatedTimer(1, self._cooldownOne_task) self._cooldownOneTimer.start() self._plugin_manager.send_plugin_message(self._identifier, dict(type="cooldownOne", timeout_value=self._cooldownOneTimeoutValue)) else: GPIO.output(self.inOnePin, GPIO.HIGH) elif self.pwrMessage == self.onTwoMessage: GPIO.output(self.inTwoPin, GPIO.LOW) elif self.pwrMessage == self.offTwoMessage: if self.relayTwoCooldownEnabled: self._cooldownTwoTimeoutValue = self.cooldownDelay self._cooldownTwoTimer = RepeatedTimer(1, self._cooldownTwo_task) self._cooldownTwoTimer.start() self._plugin_manager.send_plugin_message(self._identifier, dict(type="cooldownTwo", timeout_value=self._cooldownTwoTimeoutValue)) else: GPIO.output(self.inTwoPin, GPIO.HIGH) def _cooldownOne_task(self): self._cooldownOneTimeoutValue -= 1 self._plugin_manager.send_plugin_message(self._identifier, dict(type="cooldownOne", timeout_value=self._cooldownOneTimeoutValue)) if self._cooldownOneTimeoutValue <= 0: self._cooldownOneTimer.cancel() self._cooldownOneTimer = None GPIO.output(self.inOnePin, GPIO.HIGH) self.updatePlugin() def _cooldownTwo_task(self): self._cooldownTwoTimeoutValue -= 1 self._plugin_manager.send_plugin_message(self._identifier, dict(type="cooldownTwo", timeout_value=self._cooldownTwoTimeoutValue)) if self._cooldownTwoTimeoutValue <= 0: self._cooldownTwoTimer.cancel() self._cooldownTwoTimer = None GPIO.output(self.inTwoPin, GPIO.HIGH) self.updatePlugin() ##~~ SettingsPlugin mixin def get_settings_defaults(self): return dict( cooldownDelay="120", inOnePin="11", inTwoPin="12", onOneMessage="Printer on", offOneMessage="Printer off", onTwoMessage="Light on", offTwoMessage="Light off", powerinfoActive=False, relayOneName="Printer", relayOneCooldownEnabled=True, relayTwoName="Light", relayTwoCooldownEnabled=False, showPwrOneRelay=True, showPwrTwoRelay=False, warnOnPwrOffRelayOne=True, warnOnPwrOffRelayTwo=True ) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.cooldownDelay = int(self._settings.get(["cooldownDelay"])) self.inOnePin = int(self._settings.get(["inOnePin"])) self.inTwoPin = int(self._settings.get(["inTwoPin"])) self.onOneMessage = self._settings.get(["onOneMessage"]) self.offOneMessage = self._settings.get(["offOneMessage"]) self.onTwoMessage = self._settings.get(["onTwoMessage"]) self.offTwoMessage = self._settings.get(["offTwoMessage"]) self.relayOneName = self._settings.get(["relayOneName"]) self.relayOneCooldownEnabled = self._settings.get(["relayOneCooldownEnabled"]) self.relayTwoName = self._settings.get(["relayTwoName"]) self.relayTwoCooldownEnabled = self._settings.get(["relayTwoCooldownEnabled"]) self.showPwrOneRelay = self._settings.get(["showPwrOneRelay"]) self.showPwrTwoRelay = self._settings.get(["showPwrTwoRelay"]) self.warnOnPwrOffRelayOne = self._settings.get(["warnOnPwrOffRelayOne"]) self.warnOnPwrOffRelayTwo = self._settings.get(["warnOnPwrOffRelayTwo"]) self.updatePlugin() self._helperWaitTimeoutValue = 1 self._helperWaitTimer = RepeatedTimer(1, self._helper_wait_task) self._helperWaitTimer.start() ##~~ AssetPlugin mixin def get_assets(self): return dict( js=["js/powercontrol.js"] ) ##~~ TemplatePlugin mixin def get_template_configs(self): if self.isRaspi: return [ dict(type="sidebar", name="Powercontrol"), dict(type="settings", name="Powercontrol", custom_bindings=False) ] else: return [ ] ##~~ SimpleApiPlugin mixin def on_api_get(self, request): return flask.jsonify(dict( rOneName=self.relayOneName, rOneShow=self.showPwrOneRelay, rTwoName=self.relayTwoName, rTwoShow=self.showPwrTwoRelay )) def get_api_commands(self): return dict(pwrOnRelayOne=[], pwrOffRelayOne=[], pwrOnRelayTwo=[], pwrOffRelayTwo=[], cancelOne=[], cancelCooldownOne=[], cancelTwo=[], cancelCooldownTwo=[]) def on_api_command(self, command, data): if command == "pwrOnRelayOne": GPIO.output(self.inOnePin, GPIO.LOW) self.updatePlugin() elif command == "pwrOffRelayOne": if self.warnOnPwrOffRelayOne: self._pwrOneTimeoutValue = 10 self._pwrOneTimer = RepeatedTimer(1, self._timerOne_task) self._pwrOneTimer.start() self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeoutOne", timeout_value=self._pwrOneTimeoutValue)) else: GPIO.output(self.inOnePin, GPIO.HIGH) self.updatePlugin() elif command == "pwrOnRelayTwo": GPIO.output(self.inTwoPin, GPIO.LOW) self.updatePlugin() elif command == "pwrOffRelayTwo": if self.warnOnPwrOffRelayTwo: self._pwrTwoTimeoutValue = 10 self._pwrTwoTimer = RepeatedTimer(1, self._timerTwo_task) self._pwrTwoTimer.start() self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeoutTwo", timeout_value=self._pwrTwoTimeoutValue)) else: GPIO.output(self.inTwoPin, GPIO.HIGH) self.updatePlugin() elif command == "cancelOne": self._pwrOneTimer.cancel() self._logger.info("Cancelled power off relay 1.") elif command == "cancelCooldownOne": self._cooldownOneTimer.cancel() self._logger.info("Cancelled cooldown power off relay 1.") elif command == "cancelTwo": self._pwrTwoTimer.cancel() self._logger.info("Cancelled power off relay 2.") elif command == "cancelCooldownTwo": self._cooldownTwoTimer.cancel() self._logger.info("Cancelled cooldown power off relay 2.") def _timerOne_task(self): self._pwrOneTimeoutValue -= 1 self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeoutOne", timeout_value=self._pwrOneTimeoutValue)) if self._pwrOneTimeoutValue <= 0: self._pwrOneTimer.cancel() self._pwrOneTimer = None GPIO.output(self.inOnePin, GPIO.HIGH) self.updatePlugin() def _timerTwo_task(self): self._pwrTwoTimeoutValue -= 1 self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeoutTwo", timeout_value=self._pwrTwoTimeoutValue)) if self._pwrTwoTimeoutValue <= 0: self._pwrTwoTimer.cancel() self._pwrTwoTimer = None GPIO.output(self.inTwoPin, GPIO.HIGH) self.updatePlugin() def updatePlugin(self): # Send our status update self._plugin_manager.send_plugin_message(self._identifier, dict(rOneName=self.relayOneName, rOneShow=self.showPwrOneRelay, rTwoName=self.relayTwoName, rTwoShow=self.showPwrTwoRelay)) ##~~ Softwareupdate hook def get_update_information(self): return dict( powercontrol=dict( displayName="Powercontrol Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-Powercontrol", current=self._plugin_version, # update method: pip pip="https://github.com/konvader/OctoPrint-Relaycontrol/archive/{target_version}.zip" ) )
class AutomaticshutdownPlugin(octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.StartupPlugin): def __init__(self): self._automatic_shutdown_enabled = False self._timeout_value = None self._timer = None def get_assets(self): return dict(js=["js/automaticshutdown.js"]) def get_template_configs(self): return [dict(type="sidebar", name="Automatic Shutdown", custom_bindings=False, icon="power-off")] def get_api_commands(self): return dict(enable=[], disable=[], abort=[]) def on_api_command(self, command, data): import flask if command == "enable": self._automatic_shutdown_enabled = True elif command == "disable": self._automatic_shutdown_enabled = False elif command == "abort": self._timer.cancel() self._logger.info("Shutdown aborted.") def on_event(self, event, payload): if event != "SlicingDone": return if not self._automatic_shutdown_enabled or not self._settings.global_get(["server", "commands", "systemShutdownCommand"]): return if self._timer is not None: return self._timeout_value = 10 self._timer = RepeatedTimer(1, self._timer_task) self._timer.start() self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeout", timeout_value=self._timeout_value)) def _timer_task(self): self._timeout_value -= 1 self._plugin_manager.send_plugin_message(self._identifier, dict(type="timeout", timeout_value=self._timeout_value)) if self._timeout_value <= 0: self._timer.cancel() self._timer = None self._shutdown_system() def _shutdown_system(self): shutdown_command = self._settings.global_get(["server", "commands", "systemShutdownCommand"]) self._logger.info("Shutting down system with command: {command}".format(command=shutdown_command)) try: import sarge p = sarge.run(shutdown_command, async=True) except Exception as e: self._logger.exception("Error when shutting down: {error}".format(error=e)) return
class SimpleControlPanelPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.BlueprintPlugin): current_brightness = 50 pi = None rotary_decoder = None callbacks = [] lastGpio = 0 lastTick = 0 frontEndUpdateTimer = None temps = {} temp_sensors = [] def on_after_startup(self): self.initialize() self.set_brightness() def initialize(self): self.pi = pigpio.pi() self.temp_sensors = [] self.current_brightness = int(self._settings.get(["default_brightness"])) if self._settings.get(["enc_enabled"]): self.rotary_decoder = Decoder(self.pi, int(self._settings.get(["enc_a_pin"])), int(self._settings.get(["enc_b_pin"])), int(self._settings.get(["enc_sw_pin"])), self.hw_brightness_control, self.rotary_button_pressed) if self._settings.get(["home_enabled"]): self.enable_button(int(self._settings.get(["home_x_pin"]))) self.enable_button(int(self._settings.get(["home_y_pin"]))) self.enable_button(int(self._settings.get(["home_z_pin"]))) if self._settings.get(["xy_enabled"]): self.enable_button(int(self._settings.get(["x_plus_pin"]))) self.enable_button(int(self._settings.get(["x_minus_pin"]))) self.enable_button(int(self._settings.get(["y_plus_pin"]))) self.enable_button(int(self._settings.get(["y_minus_pin"]))) if self._settings.get(["z_enabled"]): self.enable_button(int(self._settings.get(["z_plus_pin"]))) self.enable_button(int(self._settings.get(["z_minus_pin"]))) if self._settings.get(["stop_enabled"]): self.enable_button(int(self._settings.get(["stop_pin"]))) try: if self._settings.get(["temp_1_enabled"]): self.temp_sensors.append(self.pi.i2c_open(1, 0x44)) if self._settings.get(["temp_2_enabled"]): self.temp_sensors.append(self.pi.i2c_open(1, 0x45)) except pigpio.error as e: self._logger.error("%s. Try restarting the pi", e) self.update_temps() self.frontEndUpdateTimer = RepeatedTimer(30.0, self.frontend_update) self.frontEndUpdateTimer.start() def clear_gpio(self): self.frontEndUpdateTimer.cancel() self.temp_sensors = [] for sensor in self.temp_sensors: self.pi.i2c_close(sensor) self.pi.stop() if self._settings.get(["enc_enabled"]): self.rotary_decoder.cancel() for cb in self.callbacks: cb.cancel() self.callbacks = [] def enable_button(self, pin): self.pi.set_mode(pin, pigpio.INPUT) self.pi.set_pull_up_down(pin, pigpio.PUD_DOWN) self.pi.set_glitch_filter(pin, 2000) self.callbacks.append(self.pi.callback(pin, pigpio.RISING_EDGE, self.button_pressed)) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.clear_gpio() self.initialize() def get_settings_defaults(self): return dict(mosfet_enabled=True, mosfet_pin="19", enc_enabled=True, enc_a_pin="26", enc_b_pin="13", enc_sw_pin="6", stop_enabled=True, stop_pin="5", home_enabled=True, home_x_pin="22", home_y_pin="27", home_z_pin="17", xy_enabled=True, x_plus_pin="20", x_minus_pin="24", y_plus_pin="21", y_minus_pin="23", z_enabled=True, z_plus_pin="16", z_minus_pin="18", default_xy_move="10", default_z_move="1", default_brightness="50", temp_1_enabled=True, temp_1_name="E", temp_2_enabled=True, temp_2_name="FB") def get_template_configs(self): return [ dict(type="navbar", custom_bindings=True), dict(type="settings", custom_bindings=False) ] def get_assets(self): return dict( js=["js/SimpleControlPanel.js"], css=["css/SimpleControlPanel.css"] ) def hw_brightness_control(self, level): self.current_brightness = self.current_brightness + level*5 self.set_brightness() def rotary_button_pressed(self): if self.pi.get_PWM_dutycycle(int(self._settings.get(["mosfet_pin"]))) > 0: self.set_pwm(0) else: self.set_brightness() def button_pressed(self, gpio, level, tick): self._logger.info('button pressed') if tick - self.lastTick > 50000 or gpio != self.lastGpio: self.lastGpio = gpio self.lastTick = tick if gpio == int(self._settings.get(["stop_pin"])): self._printer.cancel_print() elif gpio == int(self._settings.get(["home_x_pin"])): self._printer.home("x") elif gpio == int(self._settings.get(["home_y_pin"])): self._printer.home("y") elif gpio == int(self._settings.get(["home_z_pin"])): self._printer.home("z") elif gpio == int(self._settings.get(["x_plus_pin"])): self.move_tool("X", 1) elif gpio == int(self._settings.get(["x_minus_pin"])): self.move_tool("X", -1) elif gpio == int(self._settings.get(["y_plus_pin"])): self.move_tool("Y", 1) elif gpio == int(self._settings.get(["y_minus_pin"])): self.move_tool("Y", -1) elif gpio == int(self._settings.get(["z_plus_pin"])): self.move_tool("Z", 1) elif gpio == int(self._settings.get(["z_minus_pin"])): self.move_tool("Z", -1) def move_tool(self, axis, multiplier): if axis == "Z": move_value = multiplier * int(self._settings.get(["default_z_move"])) else: move_value = multiplier * int(self._settings.get(["default_xy_move"])) self._printer.commands('G91') self._printer.commands('G1 %s%s' % (axis, move_value)) def get_temps(self, h, index): try: self.pi.i2c_write_byte_data(h, 0x2C, 0x06) (b, d) = self.pi.i2c_read_i2c_block_data(h, 0x00, 6) temp = -45 + (175 * (d[0] * 256 + d[1]) / 65535.0) humidity = 100 * (d[3] * 256 + d[4]) / 65535.0 return temp, humidity except pigpio.error as e: self._logger.error("%s failed for sensor %s", e, index) return 0, 0 def update_temps(self): for i, sensor in enumerate(self.temp_sensors, start=1): temp, hum = self.get_temps(sensor, i) if temp != 0 and hum != 0: sensor_name = 'temp_{0}'.format(i) self.temps[sensor_name] = {'temp': round(temp, 1), 'hum': round(hum, 1)} @octoprint.plugin.BlueprintPlugin.route("/update", methods=["GET"]) def update(self): self.update_temps() return make_response(jsonify(dict(brightness=self.current_brightness, temps=self.temps)), 200) @octoprint.plugin.BlueprintPlugin.route("/values", methods=["GET"]) def get_values(self): return make_response(jsonify(dict(brightness=self.current_brightness, temps=self.temps)), 200) @octoprint.plugin.BlueprintPlugin.route("/brightness", methods=["GET"]) def get_brightness(self): return make_response(jsonify({"current_brightness": self.current_brightness}), 200) def frontend_update(self): self.update_temps() self._plugin_manager.send_plugin_message(self._identifier, dict(brightness=self.current_brightness, temps=self.temps)) @octoprint.plugin.BlueprintPlugin.route("/brightness", methods=["PATCH"]) def sw_brightness_control(self): if "application/json" not in request.headers["Content-Type"]: return make_response("expected json", 400) try: data = request.json except BadRequest: return make_response("malformed request", 400) if 'brightness' not in data: return make_response("missing duty_cycle attribute", 406) if self.current_brightness != int(data['brightness']): self.current_brightness = int(data['brightness']) self.set_brightness() return make_response('', 204) def set_brightness(self): if self.current_brightness > 100: self.current_brightness = 100 if self.current_brightness < 0: self.current_brightness = 0 self.set_pwm(self.current_brightness) def set_pwm(self, value): self._logger.info("Setting Mosfet") if self._settings.get(["mosfet_enabled"]): self.pi.hardware_PWM(int(self._settings.get(["mosfet_pin"])), 800, value * 10000)
class PowerinfoPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SimpleApiPlugin): ##~~ Intialization def __init__(self): self._checkPwrStatusTimer = None self.inOnePin = "" self.inTwoPin = "" self.isRaspi = False self.pwrOnName = "" self.pwrOffName = "" self.pwrOneStatus = "" self.pwrTwoStatus = "" self.relayOneName = "" self.relayTwoName = "" self.rOneMessage = "" self.rRate = "" self.rTwoMessage = "" self.showPwrOneStatus = False self.showPwrTwoStatus = False ##~~ StartupPlugin mixin def on_after_startup(self): # Get our settings self.inOnePin = int(self._settings.get(["inOnePin"])) self.inTwoPin = int(self._settings.get(["inTwoPin"])) self.pwrOnName = self._settings.get(["pwrOnName"]) self.pwrOffName = self._settings.get(["pwrOffName"]) self.relayOneName = self._settings.get(["relayOneName"]) self.relayTwoName = self._settings.get(["relayTwoName"]) self.rRate = int(self._settings.get(["rRate"])) self.showPwrOneStatus = self._settings.get(["showPwrOneStatus"]) self.showPwrTwoStatus = self._settings.get(["showPwrTwoStatus"]) # Update the plugin helpers __plugin_helpers__.update(dict( inOnePin=self.inOnePin, inTwoPin=self.inTwoPin, relayOneName=self.relayOneName, relayTwoName=self.relayTwoName, showPwrOneRelay=self.showPwrOneStatus, showPwrTwoRelay=self.showPwrTwoStatus )) self._logger.info("Powerinfo plugin started") if sys.platform == "linux2": with open('/proc/cpuinfo', 'r') as infile: cpuinfo = infile.read() # Search for the cpu info match = re.search('^Hardware\s+:\s+(\w+)$', cpuinfo, flags=re.MULTILINE | re.IGNORECASE) if match is None: # The hardware is not a pi. self.isRaspi = False elif match.group(1) == 'BCM2708': self._logger.debug("Pi 1") self.isRaspi = True elif match.group(1) == 'BCM2709': self._logger.debug("Pi 2") self.isRaspi = True elif match.group(1) == 'BCM2710': self._logger.debug("Pi 3") self.isRaspi = True if self.showPwrOneStatus or self.showPwrTwoStatus and self.isRaspi: self._logger.debug("Initialize GPOI") # Set GPIO layout like pin-number GPIO.setmode(GPIO.BOARD) # Configure our GPIO outputs GPIO.setup(self.inOnePin, GPIO.OUT) GPIO.setup(self.inTwoPin, GPIO.OUT) # Setup the initial state to high(off) GPIO.output(self.inOnePin, GPIO.HIGH) GPIO.output(self.inTwoPin, GPIO.HIGH) self._logger.debug("Start the timer now") self.startTimer(self.rRate) self._logger.debug("Running on Pi? - %s" % self.isRaspi) def startTimer(self, interval): self._checkPwrStatusTimer = RepeatedTimer(interval, self.checkPwrStatus, None, None, True) self._checkPwrStatusTimer.start() def checkPwrStatus(self): # Check the GPIO status of our relays and set our message self.pwrOneStatus = GPIO.input(self.inOnePin) if self.pwrOneStatus: self.rOneMessage = "%s: %s" % (self.relayOneName, self.pwrOffName) else: self.rOneMessage = "%s: %s" % (self.relayOneName, self.pwrOnName) self.pwrTwoStatus = GPIO.input(self.inTwoPin) if self.pwrTwoStatus: self.rTwoMessage = "%s: %s" % (self.relayTwoName, self.pwrOffName) else: self.rTwoMessage = "%s: %s" % (self.relayTwoName, self.pwrOnName) # Send our status message self._plugin_manager.send_plugin_message(self._identifier, dict(messageOne=self.rOneMessage, messageOneShow=self.showPwrOneStatus, messageTwo=self.rTwoMessage, messageTwoShow=self.showPwrTwoStatus)) ##~~ SettingsPlugin mixin def get_settings_defaults(self): return dict( inOnePin="11", inTwoPin="12", pwrOnName="On", pwrOffName="Off", relayOneName="Printer", relayTwoName="Light", rRate="10", showPwrOneStatus=True, showPwrTwoStatus=False ) def on_settings_save(self,data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.inOnePin = int(self._settings.get(["inOnePin"])) self.inTwoPin = int(self._settings.get(["inTwoPin"])) self.pwrOnName = self._settings.get(["pwrOnName"]) self.pwrOffName = self._settings.get(["pwrOffName"]) self.relayOneName = self._settings.get(["relayOneName"]) self.relayTwoName = self._settings.get(["relayTwoName"]) self.rRate = int(self._settings.get(["rRate"])) self.showPwrOneStatus = self._settings.get(["showPwrOneStatus"]) self.showPwrTwoStatus = self._settings.get(["showPwrTwoStatus"]) # Update the plugin helpers __plugin_helpers__.update(dict( inOnePin=self.inOnePin, inTwoPin=self.inTwoPin, relayOneName=self.relayOneName, relayTwoName=self.relayTwoName, showPwrOneRelay=self.showPwrOneStatus, showPwrTwoRelay=self.showPwrTwoStatus )) if self.showPwrOneStatus or self.showPwrTwoStatus and self.isRaspi: # Initialize the GPIO after a setting change GPIO.cleanup() # Set GPIO layout like pin-number GPIO.setmode(GPIO.BOARD) # Configure our GPIO outputs GPIO.setup(self.inOnePin, GPIO.OUT) GPIO.setup(self.inTwoPin, GPIO.OUT) # Setup the initial state to high(off) GPIO.output(self.inOnePin, GPIO.HIGH) GPIO.output(self.inTwoPin, GPIO.HIGH) # Start the timer self.startTimer(self.rRate) else: if self._checkPwrStatusTimer is not None: try: self._checkPwrStatusTimer.cancel() except: pass self._plugin_manager.send_plugin_message(self._identifier, dict()) ##~~ AssetPlugin mixin def get_assets(self): return dict( js=["js/powerinfo.js"] ) ##~~ TemplatePlugin mixin def get_template_configs(self): if self.isRaspi: return [ dict(type="sidebar", name="Powerinfo", icon="info"), dict(type="settings", name="Powerinfo", custom_bindings=False) ] else: return [ ] ##~~ SimpleApiPlugin mixin def on_api_get(self, request): return flask.jsonify(dict( messageOne=self.rOneMessage, messageOneShow=self.showPwrOneStatus, messageTwo=self.rTwoMessage, messageTwoShow=self.showPwrTwoStatus )) ##~~ Softwareupdate hook def get_update_information(self): return dict( powerinfo=dict( displayName="Powerinfo Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-Powerinfo", current=self._plugin_version, # update method: pip pip="https://github.com/konvader/OctoPrint-Powerinfo/archive/{target_version}.zip" ) )
class Mi_temperaturePlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.ShutdownPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.EventHandlerPlugin): readings = {} def ble_scan_timer(self): try: sensors = self._settings.get(["sensors"]) mac_set = set((s["mac"] for s in sensors)) result = {} t_start = time.time() def packet_handler(mac, adv_type, data, rssi): if time.time() - t_start > scan_timeout: return True # timeout, stop scan if mac not in mac_set: return data_str = raw_packet_to_str(data) atcIdentifier = data_str[6:10].upper() if atcIdentifier != "1A18": return temp = struct.unpack("H", data[11:13])[0] / 100 humid = struct.unpack("H", data[13:15])[0] / 100 battery = struct.unpack("B", data[17:18])[0] result[mac] = (temp, humid, battery) self._logger.info("[{}] T: {:.2f}, H: {:.2f}, B: {}".format( mac, temp, humid, battery)) if len(mac_set) == len(result.keys()): return True # found all entries, stop scan toggle_device(dev_id, True, logger=self._logger) sock = bluez.hci_open_dev(dev_id) enable_le_scan(sock, filter_duplicates=False, logger=self._logger) parse_le_advertising_events(sock, handler=packet_handler, debug=False, logger=self._logger) disable_le_scan(sock, logger=self._logger) if result: for mac in result: self.readings[mac] = result[mac] self._plugin_manager.send_plugin_message( self._identifier, dict(readings=self.readings)) except Exception as ex: self._logger.error(ex) def update_ui(self): sensors = self._settings.get(["sensors"]) for sensor in sensors: mac = sensor["mac"] if mac in self.readings: sensor["readings"] = self.readings[mac] self._plugin_manager.send_plugin_message(self._identifier, dict(sensors=sensors)) # ~~ StartupPlugin mixin def on_after_startup(self): # emulated check self.ble_timer = RepeatedTimer(60, self.ble_scan_timer, None, None, True) self.ble_timer.start() self._logger.info("MI temp: start timer") # ~~ ShutdownPlugin mixin def on_shutdown(self): self.ble_timer.cancel() self._logger.info("MI temp: stop timer") ##~~ SettingsPlugin mixin def get_settings_defaults(self): return dict( # put your plugin's default settings here sensors=[]) ##~~ AssetPlugin mixin def get_assets(self): # Define your plugin's asset files to automatically include in the # core UI here. return dict(js=["js/mi_temperature.js"], css=["css/mi_temperature.css"], less=["less/mi_temperature.less"]) ##~~ TemplatePlugin mixin def get_template_vars(self): return dict(sensors=self._settings.get(["sensors"])) def get_template_configs(self): return [ dict(type="navbar", custom_bindings=True, classes=["dropdown"]), dict(type="settings", custom_bindings=False) ] ##~~ EventHandlerPlugin mixin def on_event(self, event, payload): if event == Events.CONNECTED: self.update_ui() if event == Events.CLIENT_OPENED: self.update_ui() ##~~ Softwareupdate hook def get_update_information(self): # Define the configuration for your plugin to use with the Software Update # Plugin here. See https://docs.octoprint.org/en/master/bundledplugins/softwareupdate.html # for details. return dict(mi_temperature=dict( displayName="Mi_temperature Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-Mi-temperature", current=self._plugin_version, # update method: pip pip= "https://github.com/smallptsai/OctoPrint-Mi-temperature/archive/{target_version}.zip" ))
class RoomTempPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SettingsPlugin): def __init__(self): self.isRaspi = False self.displayRoomTemp = True self._checkTempTimer = None def on_after_startup(self): self.displayRoomTemp = self._settings.get(["displayRoomTemp"]) if sys.platform == "linux2": with open('/proc/cpuinfo', 'r') as infile: cpuinfo = infile.read() # Match a line like 'Hardware : BCM2709' match = re.search('^Hardware\s+:\s+(\w+)$', cpuinfo, flags=re.MULTILINE | re.IGNORECASE) if match.group(1) == 'BCM2708' or match.group( 1) == 'BCM2709' or match.group(1) == 'BCM2835': self.isRaspi = True else: self.isRaspi = False if self.isRaspi and self.displayRoomTemp: self.startTimer(30.0) if self.isRaspi == False: self._logger.info("This is not a Raspberry Pi - Plugin halted") sys.exit(1) def startTimer(self, interval): self._checkTempTimer = RepeatedTimer(interval, self.checkRoomTemp, None, None, True) self._checkTempTimer.start() def checkRoomTemp(self): os.system('modprobe w1-gpio') os.system('modprobe w1-therm') base_dir = '/sys/bus/w1/devices/' device_folder = glob.glob(base_dir + '[0-9][0-9]*')[0] device_file = device_folder + '/w1_slave' if os.path.isfile(device_file): lines = read_temp_raw(device_file) while lines[0].strip()[-3:] != 'YES': time.sleep(0.2) lines = read_temp_raw(device_file) equals_pos = lines[1].find('t=') if equals_pos != -1: temp_string = lines[1][equals_pos + 2:] temp_c = float(temp_string) / 1000.0 p = '{0:0.1f}'.format(temp_c) self._plugin_manager.send_plugin_message( self._identifier, dict(israspi=self.isRaspi, roomtemp=p)) else: self._logger.info("No file temperature found !!") ##~~ SettingsPlugin def get_settings_defaults(self): return dict(displayRoomTemp=self.displayRoomTemp) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.displayRoomTemp = self._settings.get(["displayRoomTemp"]) if self.displayRoomTemp: interval = 30.0 self.startTimer(interval) else: if self._checkTempTimer is not None: try: self._checkTempTimer.cancel() except: pass self._plugin_manager.send_plugin_message(self._identifier, dict()) ##~~ TemplatePlugin API def get_template_configs(self): if self.isRaspi: return [ dict(type="settings", template="roomtemp_settings_raspi.jinja2") ] else: return [] ##~~ AssetPlugin API def get_assets(self): return { "js": ["js/roomtemp.js"], "css": ["css/roomtemp.css"], "less": ["less/roomtemp.less"] } ##~~ Softwareupdate hook def get_update_information(self): return dict(roomtemp=dict( displayName="Room Temperature Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-roomTemp", current=self._plugin_version, # update method: pip w/ dependency links pip= "https://github.com/looma/OctoPrint-roomTemp/archive/{target_version}.zip" ))
class ExportDataPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin): folder = "" temperature_file = "" temperature_data = None status_file = "" status_data = None timer = None ##~~ SettingsPlugin mixin def get_settings_defaults(self): return dict(folder="/home/pi/exportdata/", temperature_file="temperature.txt", status_file="status.txt") def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.check_files(self._settings.get(["folder"]), self._settings.get(["temperature_file"]), self._settings.get(["status_file"])) ##~~ StartupPlugin mixin def on_after_startup(self): self.check_files(self._settings.get(["folder"]), self._settings.get(["temperature_file"]), self._settings.get(["status_file"])) ##~~ TemplatePlugin mixin def get_template_configs(self): return [{"type": "settings", "custom_bindings": False}] ##~~ Class specific def check_files(self, new_folder, new_temperature, new_status): self._logger.info("new settings:") self._logger.info("-- folder={}".format(new_folder)) self._logger.info("-- temperature={}".format(new_temperature)) self._logger.info("-- status={}".format(new_status)) changed = False if self.folder != new_folder: self.remove_file(self.folder, self.temperature_file) self.remove_file(self.folder, self.status_file) self.remove_path(self.folder) self.touch_path(new_folder) changed = True if self.temperature_file != new_temperature: self.remove_file(self.folder, self.temperature_file) changed = True if self.status_file != new_status: self.remove_file(self.folder, self.status_file) changed = True if changed: self.folder = new_folder self.temperature_file = new_temperature self.status_file = new_status self.start_timer() def start_timer(self): self.stop_timer() self.timer = RepeatedTimer(2.0, self.update_values, run_first=True) self.timer.start() def stop_timer(self): if self.timer: self.timer.cancel() self.timer = None def update_values(self): self.status_data = self._printer.get_current_data() self.temperature_data = self._printer.get_current_temperatures() self.update_temperature() self.update_status() def update_temperature(self): data = "" if self.temperature_data: if "tool0" in self.temperature_data: data += "nozzle: " data += str("{:.1f}".format( self.temperature_data["tool0"]["actual"])).rjust(5) data += "°C of " data += str("{:.1f}".format( self.temperature_data["tool0"]["target"])).rjust(5) data += "°C" if data: data += "\n" if "bed" in self.temperature_data: data += "bed: " data += str("{:.1f}".format( self.temperature_data["bed"]["actual"])).rjust(5) data += "°C of " data += str("{:.1f}".format( self.temperature_data["bed"]["target"])).rjust(5) data += "°C" self.touch_file(self.folder, self.temperature_file, data) def update_status(self): data = "" if self.status_data: printing = False if "state" in self.status_data: state_dict = self.status_data["state"] flags_dict = state_dict["flags"] if flags_dict["cancelling"] or \ flags_dict["finishing"] or \ flags_dict["paused"] or \ flags_dict["pausing"] or \ flags_dict["printing"]: printing = True data += "state: " data += state_dict["text"].lower() if printing: data += "\n" if "job" in self.status_data: job_dict = self.status_data["job"] if "file" in job_dict: if job_dict["file"]["name"] is None: data += "file: -" else: data += "file: " data += job_dict["file"]["name"] else: data += "file: -" data += "\n" if "progress" in self.status_data: progress_dict = self.status_data["progress"] print_time = progress_dict["printTime"] print_time_left = progress_dict["printTimeLeft"] if print_time: data += "elapsed: " data += self.seconds_to_text(print_time) else: data += "elapsed: 0s" data += "\n" if print_time_left: data += "left: " data += self.seconds_to_text(print_time_left) else: data += "left: 0s" data += "\n" if print_time and print_time_left: float_percent = 100.0 * float( print_time / (print_time_left + print_time)) data += "percent: {:.1f}%".format(float_percent) else: data += "percent: 0.0%" else: data = "\n\n\n\n" + data self.touch_file(self.folder, self.status_file, data) def get_update_information(self): return dict(test=dict( displayName="OctoPrint ExportData", displayVersion=self._plugin_version, type="github_release", user="******", repo="OctoPrint-ExportData", current=self._plugin_version, pip= "https://github.com/andili00/OctoPrint-ExportData/archive/{target_version}.zip" )) def touch_file(self, path, file, data): joined = os.path.join(path, file) if path: if file: self.touch_path(path) try: file_tmp = open(joined, 'w+') file_tmp.write(data) file_tmp.close() except OSError as error: self._logger.error( "file '{}' couldn't be created - errno:{}".format( joined, error.errno)) else: self._logger.error("file not valid") else: self._logger.error("path not valid") def remove_file(self, path, file): joined = os.path.join(path, file) if path: if file: if os.path.exists(joined): try: os.remove(joined) except OSError as error: self._logger.error( "file '{}' couldn't be removed - errno:{}".format( joined, error.errno)) else: self._logger.error("file does not exist") else: self._logger.error("file not valid") else: self._logger.error("path not valid") def touch_path(self, path): if path: if os.path.exists(path): pass else: try: os.makedirs(path, exist_ok=True) except OSError as error: self._logger.error( "path '{}' couldn't be created - errno:{}".format( path, error.errno)) else: self._logger.error("path not valid") def remove_path(self, path): if path: if os.path.exists(path): try: os.rmdir(path) except OSError as error: self._logger.error( "path '{}' couldn't be removed - errno:{}".format( path, error.errno)) else: self._logger.error("path does not exist") else: self._logger.error("path not valid") @staticmethod def seconds_to_text(seconds): result = "" days = seconds // 86400 hours = (seconds - days * 86400) // 3600 minutes = (seconds - days * 86400 - hours * 3600) // 60 seconds = seconds - days * 86400 - hours * 3600 - minutes * 60 if days > 0: result = "{}d{}h{}m{}s".format(days, hours, minutes, seconds) elif hours > 0: result = "{}h{}m{}s".format(hours, minutes, seconds) elif minutes > 0: result = "{}m{}s".format(minutes, seconds) elif seconds >= 0: result = "{}s".format(seconds) return result
class HomeassistantPlugin( octoprint.plugin.SettingsPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.ProgressPlugin, octoprint.plugin.WizardPlugin, ): def __init__(self): self._logger = logging.getLogger(__name__) self.mqtt_publish = None self.mqtt_publish_with_timestamp = None self.mqtt_subcribe = None self.update_timer = None self.constant_timer = None self.psucontrol_enabled = False def handle_timer(self): self._generate_printer_status() def handle_constant_timer(self): self._generate_status() ##~~ SettingsPlugin def get_settings_defaults(self): return SETTINGS_DEFAULTS def get_settings_version(self): return 2 def on_settings_migrate(self, target, current): if target >= 1: # This is the first version _node_uuid = self._settings.get(["unique_id"]) if _node_uuid: _node_id = (_node_uuid[:6]).upper() self._settings.set(["node_id"], _node_id) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self._generate_device_registration() self._generate_device_controls(subscribe=True) self._generate_connection_status() ##~~ TemplatePlugin mixin def get_template_configs(self): return [dict(type="settings", custom_bindings=False)] ##~~ StartupPlugin mixin def on_after_startup(self): if self._settings.get(["unique_id"]) is None: import uuid _uuid = uuid.uuid4() _uid = str(_uuid) self._settings.set(["unique_id"], _uid) self._settings.set(["node_id"], _uuid.hex) settings().save() helpers = self._plugin_manager.get_helpers( "mqtt", "mqtt_publish", "mqtt_publish_with_timestamp", "mqtt_subscribe" ) if helpers: if "mqtt_publish_with_timestamp" in helpers: self._logger.debug("Setup publish with timestamp helper") self.mqtt_publish_with_timestamp = helpers[ "mqtt_publish_with_timestamp" ] if "mqtt_publish" in helpers: self._logger.debug("Setup publish helper") self.mqtt_publish = helpers["mqtt_publish"] if "mqtt_subscribe" in helpers: self._logger.debug("Setup subscribe helper") self.mqtt_subscribe = helpers["mqtt_subscribe"] self.mqtt_subscribe( self._generate_topic("lwTopic", "", full=True), self._on_mqtt_message, ) # PSUControl helpers psu_helpers = self._plugin_manager.get_helpers( "psucontrol", "turn_psu_on", "turn_psu_off", "get_psu_state" ) self.psucontrol_enabled = True if psu_helpers: self._logger.info("PSUControl helpers found") if "get_psu_state" in psu_helpers: self.get_psu_state = psu_helpers["get_psu_state"] self._logger.debug("Setup get_psu_state helper") else: self._logger.error( "Helper get_psu_state not found, disabling PSUControl integration" ) self.psucontrol_enabled = False if "turn_psu_on" in psu_helpers: self.turn_psu_on = psu_helpers["turn_psu_on"] self._logger.debug("Setup turn_psu_on helper") else: self._logger.error( "Helper turn_psu_on not found, disabling PSUControl integration" ) self.psucontrol_enabled = False if "turn_psu_off" in psu_helpers: self.turn_psu_off = psu_helpers["turn_psu_off"] self._logger.debug("Setup turn_psu_off helper") else: self._logger.error( "Helper turn_psu_on not found, disabling PSUControl integration" ) self.psucontrol_enabled = False else: self._logger.info("PSUControl helpers not found") self.psucontrol_enabled = False self.snapshot_enabled = self._settings.global_get( ["webcam", "timelapseEnabled"] ) if self.snapshot_enabled: self.snapshot_path = self._settings.global_get(["webcam", "snapshot"]) if not self.snapshot_path: self.snapshot_enabled = False if not self.update_timer: self.update_timer = RepeatedTimer(60, self.handle_timer, None, None, False) if not self.constant_timer: self.constant_timer = RepeatedTimer( 30, self.handle_constant_timer, None, None, False ) self.constant_timer.start() # Since retain may not be used it's not always possible to simply tie this to the connected state self._generate_device_registration() self._generate_device_controls(subscribe=True) # For people who do not have retain setup, need to do this again to make sensors available _connected_topic = self._generate_topic("lwTopic", "", full=True) self.mqtt_publish(_connected_topic, "connected", allow_queueing=True) # Setup the default printer states self.mqtt_publish( self._generate_topic("hassTopic", "is_printing", full=True), "False", allow_queueing=True, ) self.mqtt_publish( self._generate_topic("hassTopic", "is_paused", full=True), "False", allow_queueing=True, ) self.on_print_progress("", "", 0) self._generate_connection_status() if self.psucontrol_enabled: self._generate_psu_state() def _get_mac_address(self): import uuid return ":".join(re.findall("..", "%012x" % uuid.getnode())) def _on_mqtt_message( self, topic, message, retained=None, qos=None, *args, **kwargs ): self._logger.info("Received MQTT message from " + topic) self._logger.info(message) # Don't rely on this, the message may be disabled. if message == "connected": self._generate_device_registration() self._generate_device_controls(subscribe=False) def _generate_topic(self, topic_type, topic, full=False): self._logger.debug("Generating topic for " + topic_type + ", " + topic) mqtt_defaults = dict(plugins=dict(mqtt=MQTT_DEFAULTS)) _topic = "" if topic_type != "baseTopic": _topic = settings().get( ["plugins", "mqtt", "publish", topic_type], defaults=mqtt_defaults ) _topic = re.sub(r"{.+}", "", _topic) if full or topic_type == "baseTopic": _topic = ( settings().get( ["plugins", "mqtt", "publish", "baseTopic"], defaults=mqtt_defaults ) + _topic ) _topic += topic self._logger.debug("Generated topic: " + _topic) return _topic def _generate_device_registration(self): _discovery_topic = self._settings.get(["discovery_topic"]) _node_name = self._settings.get(["node_name"]) _node_id = self._settings.get(["node_id"]) _device_manufacturer = self._settings.get(["device_manufacturer"]) _device_model = self._settings.get(["device_model"]) _config_device = self._generate_device_config( _node_id, _node_name, _device_manufacturer, _device_model ) ##~~ Configure Connected Sensor self._generate_sensor( topic=_discovery_topic + "/binary_sensor/" + _node_id + "_CONNECTED/config", values={ "name": _node_name + " Connected", "uniq_id": _node_id + "_CONNECTED", "stat_t": "~" + self._generate_topic("hassTopic", "Connected"), "pl_on": "Connected", "pl_off": "Disconnected", "dev_cla": "connectivity", "device": _config_device, }, ) ##~~ Configure Printing Sensor self._generate_sensor( topic=_discovery_topic + "/binary_sensor/" + _node_id + "_PRINTING/config", values={ "name": _node_name + " Printing", "uniq_id": _node_id + "_PRINTING", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "pl_on": "True", "pl_off": "False", "val_tpl": "{{value_json.state.flags.printing}}", "device": _config_device, }, ) ##~~ Configure Last Event Sensor self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_EVENT/config", values={ "name": _node_name + " Last Event", "uniq_id": _node_id + "_EVENT", "stat_t": "~" + self._generate_topic("eventTopic", "+"), "val_tpl": "{{value_json._event}}", "device": _config_device, }, ) ##~~ Configure Print Status self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_PRINTING_S/config", values={ "name": _node_name + " Print Status", "uniq_id": _node_id + "_PRINTING_S", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_tpl": "{{value_json.state|tojson}}", "val_tpl": "{{value_json.state.text}}", "device": _config_device, }, ) ##~~ Configure Print Progress self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_PRINTING_P/config", values={ "name": _node_name + " Print Progress", "uniq_id": _node_id + "_PRINTING_P", "json_attr_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_tpl": "{{value_json.progress|tojson}}", "stat_t": "~" + self._generate_topic("progressTopic", "printing"), "unit_of_meas": "%", "val_tpl": "{{value_json.progress|float(0)}}", "device": _config_device, }, ) ##~~ Configure Print File self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_PRINTING_F/config", values={ "name": _node_name + " Print File", "uniq_id": _node_id + "_PRINTING_F", "stat_t": "~" + self._generate_topic("progressTopic", "printing"), "val_tpl": "{{value_json.path}}", "device": _config_device, "ic": "mdi:file", }, ) ##~~ Configure Print Time self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_PRINTING_T/config", values={ "name": _node_name + " Print Time", "uniq_id": _node_id + "_PRINTING_T", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "val_tpl": "{{value_json.progress.printTimeFormatted}}", "device": _config_device, "ic": "mdi:clock-start", }, ) ##~~ Configure Print Time Left self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_PRINTING_E/config", values={ "name": _node_name + " Print Time Left", "uniq_id": _node_id + "_PRINTING_E", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "val_tpl": "{{value_json.progress.printTimeLeftFormatted}}", "device": _config_device, "ic": "mdi:clock-end", }, ) ##~~ Configure Print ETA self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_PRINTING_ETA/config", values={ "name": _node_name + " Print Estimated Time", "uniq_id": _node_id + "_PRINTING_ETA", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_tpl": "{{value_json.job|tojson}}", "val_tpl": "{{value_json.job.estimatedPrintTimeFormatted}}", "device": _config_device, }, ) ##~~ Configure Print Current Z self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_PRINTING_Z/config", values={ "name": _node_name + " Current Z", "uniq_id": _node_id + "_PRINTING_Z", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "unit_of_meas": "mm", "val_tpl": "{{value_json.currentZ|float(0)}}", "device": _config_device, "ic": "mdi:axis-z-arrow", }, ) ##~~ Configure Slicing Status self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_SLICING_P/config", values={ "name": _node_name + " Slicing Progress", "uniq_id": _node_id + "_SLICING_P", "stat_t": "~" + self._generate_topic("progressTopic", "slicing"), "unit_of_meas": "%", "val_tpl": "{{value_json.progress|float(0)}}", "device": _config_device, }, ) ##~~ Configure Slicing File self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_SLICING_F/config", values={ "name": _node_name + " Slicing File", "uniq_id": _node_id + "_SLICING_F", "stat_t": "~" + self._generate_topic("progressTopic", "slicing"), "val_tpl": "{{value_json.source_path}}", "device": _config_device, "ic": "mdi:file", }, ) ##~~ Tool Temperature _e = self._printer_profile_manager.get_current_or_default()["extruder"]["count"] for x in range(_e): self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_TOOL" + str(x) + "/config", values={ "name": _node_name + " Tool " + str(x) + " Temperature", "uniq_id": _node_id + "_TOOL" + str(x), "stat_t": "~" + self._generate_topic("temperatureTopic", "tool" + str(x)), "unit_of_meas": "°C", "val_tpl": "{{value_json.actual|float(0)}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:printer-3d-nozzle", }, ) self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_TOOL" + str(x) + "_TARGET" + "/config", values={ "name": _node_name + " Tool " + str(x) + " Target", "uniq_id": _node_id + "_TOOL" + str(x) + "_TARGET", "stat_t": "~" + self._generate_topic("temperatureTopic", "tool" + str(x)), "unit_of_meas": "°C", "val_tpl": "{{value_json.target|float(0)}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:printer-3d-nozzle", }, ) ##~~ Bed Temperature self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_BED/config", values={ "name": _node_name + " Bed Temperature", "uniq_id": _node_id + "_BED", "stat_t": "~" + self._generate_topic("temperatureTopic", "bed"), "unit_of_meas": "°C", "val_tpl": "{{value_json.actual|float(0)}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:radiator", }, ) self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_BED_TARGET/config", values={ "name": _node_name + " Bed Target", "uniq_id": _node_id + "_BED_TARGET", "stat_t": "~" + self._generate_topic("temperatureTopic", "bed"), "unit_of_meas": "°C", "val_tpl": "{{value_json.target|float(0)}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:radiator", }, ) ##~~ Chamber Temperature _h = self._printer_profile_manager.get_current_or_default()["heatedChamber"] if _h: self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_CHAMBER/config", values={ "name": _node_name + " Chamber Temperature", "uniq_id": _node_id + "_CHAMBER", "stat_t": "~" + self._generate_topic("temperatureTopic", "chamber"), "unit_of_meas": "°C", "val_tpl": "{{value_json.actual|float(0)}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:radiator", }, ) self._generate_sensor( topic=_discovery_topic + "/sensor/" + _node_id + "_CHAMBER_TARGET/config", values={ "name": _node_name + " Chamber Target", "uniq_id": _node_id + "_CHAMBER_TARGET", "stat_t": "~" + self._generate_topic("temperatureTopic", "chamber"), "unit_of_meas": "°C", "val_tpl": "{{value_json.target|float(0)}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:radiator", }, ) ##~~ SoC Temperature (if supported) self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_SOC/config", values={ "name": _node_name + " SoC Temperature", "uniq_id": _node_id + "_SOC", "stat_t": "~" + self._generate_topic("temperatureTopic", "soc"), "unit_of_meas": "°C", "val_tpl": "{{value_json.temperature|float(0)|round(1)}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:radiator", }, ) def _generate_sensor(self, topic, values): payload = { "avty_t": "~" + self._generate_topic("lwTopic", ""), "pl_avail": "connected", "pl_not_avail": "disconnected", "~": self._generate_topic("baseTopic", "", full=True), } payload.update(values) self.mqtt_publish(topic, payload, allow_queueing=True) def _generate_device_config( self, _node_id, _node_name, _device_manufacturer, _device_model ): _config_device = { "ids": _node_id, "name": _node_name, "mf": _device_manufacturer, "mdl": _device_model, "sw": "HomeAssistant Discovery for OctoPrint " + self._plugin_version, } return _config_device def _get_cpu_temp(self): if hasattr(psutil, "sensors_temperatures"): temps = psutil.sensors_temperatures() if temps: if "coretemp" in temps: return temps["coretemp"][0].current if "cpu-thermal" in temps: return temps["cpu-thermal"][0].current if "cpu_thermal" in temps: return temps["cpu_thermal"][0].current return None def _generate_status(self): data = {"temperature": self._get_cpu_temp()} if self.mqtt_publish_with_timestamp: self.mqtt_publish_with_timestamp( self._generate_topic("temperatureTopic", "soc", full=True), data, allow_queueing=True, ) def _generate_printer_status(self): data = self._printer.get_current_data() try: data["progress"]["printTimeLeftFormatted"] = str( datetime.timedelta(seconds=int(data["progress"]["printTimeLeft"])) ).split(".")[0] except: data["progress"]["printTimeLeftFormatted"] = None try: data["progress"]["printTimeFormatted"] = str( datetime.timedelta(seconds=data["progress"]["printTime"]) ).split(".")[0] except: data["progress"]["printTimeFormatted"] = None try: data["job"]["estimatedPrintTimeFormatted"] = str( datetime.timedelta(seconds=data["job"]["estimatedPrintTime"]) ).split(".")[0] except: data["job"]["estimatedPrintTimeFormatted"] = None if self.mqtt_publish_with_timestamp: self.mqtt_publish_with_timestamp( self._generate_topic("hassTopic", "printing", full=True), data, allow_queueing=True, ) def _generate_connection_status(self): state, _, _, _ = self._printer.get_current_connection() state_connected = "Disconnected" if state == "Closed" else "Connected" # Function can be called by on_event before on_after_startup has run. # This will throw a TypeError since self.mqtt_publish is still null. if self.mqtt_publish: self.mqtt_publish( self._generate_topic("hassTopic", "Connected", full=True), state_connected, allow_queueing=True, ) def _generate_psu_state(self, psu_state=None): if self.psucontrol_enabled: if psu_state is None: psu_state = self.get_psu_state() self._logger.debug( "No psu_state specified, state retrieved from helper: " + str(psu_state) ) self.mqtt_publish( self._generate_topic("hassTopic", "psu_on", full=True), str(psu_state), allow_queueing=True, ) def _on_emergency_stop( self, topic, message, retained=None, qos=None, *args, **kwargs ): self._logger.debug("Emergency stop message received: " + str(message)) if message: self._printer.commands("M112") def _on_cancel_print( self, topic, message, retained=None, qos=None, *args, **kwargs ): self._logger.debug("Cancel print message received: " + str(message)) if message: self._printer.cancel_print() def _on_pause_print(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Pause print message received: " + str(message)) if message: self._printer.pause_print() else: self._printer.resume_print() def _on_shutdown_system(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Shutdown print message received: " + str(message)) if message: shutdown_command = self._settings.global_get( ["server", "commands", "systemShutdownCommand"] ) try: import sarge sarge.run(shutdown_command, async_=True) except Exception as e: self._logger.info("Unable to run shutdown command: " + str(e)) def _on_psu(self, topic, message, retained=None, qos=None, *args, **kwargs): message = message.decode() self._logger.debug("PSUControl message received: " + message) if message == "True": self._logger.info("Turning on PSU") self.turn_psu_on() else: self._logger.info("Turning off PSU") self.turn_psu_off() def _on_camera(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Camera snapshot message received: " + str(message)) if self.snapshot_enabled: import urllib.request as urlreq url_handle = urlreq.urlopen(self.snapshot_path) file_content = url_handle.read() url_handle.close() self.mqtt_publish( self._generate_topic("baseTopic", "camera", full=True), file_content, allow_queueing=False, raw_data=True, ) def _on_connect_printer(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("(Dis)Connecting to printer" + str(message)) try: if message == b'on': self._printer.connect() else: self._printer.disconnect() except Exception as e: self._logger.error("Unable to run connect command: " + str(e)) def _on_home(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Homing printer: " + str(message)) if message: try: home_payload = json.loads(message) axes = set(home_payload) & set(["x", "y", "z", "e"]) self._printer.home(list(axes)) except Exception as e: self._logger.error("Unable to run home command: " + str(e)) def _on_jog(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Jogging printer: " + str(message)) if message: try: jog_payload = json.loads(message) axes_keys = set(jog_payload.keys()) & set(["x", "y", "z"]) axes = {k: v for (k, v) in jog_payload.items() if k in axes_keys} self._printer.jog(axes, jog_payload.get("speed")) except Exception as e: self._logger.error("Unable to run jog command: " + str(e)) def _on_command(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Jogging received gcode commands") try: self._printer.commands(message) except Exception as e: self._logger.error("Unable to run printer commands: " + str(e)) def _generate_device_controls(self, subscribe=False): _discovery_topic = self._settings.get(["discovery_topic"]) _node_name = self._settings.get(["node_name"]) _node_id = self._settings.get(["node_id"]) _device_manufacturer = self._settings.get(["device_manufacturer"]) _device_model = self._settings.get(["device_model"]) _config_device = self._generate_device_config( _node_id, _node_name, _device_manufacturer, _device_model ) # Connect printer if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "connect", full=True), self._on_connect_printer, ) self._generate_sensor( topic=_discovery_topic + "/switch/" + _node_id + "_CONNECT/config", values={ "name": _node_name + " Connect to printer", "uniq_id": _node_id + "_CONNECT", "cmd_t": "~" + self._generate_topic("controlTopic", "connect"), "stat_t": self._generate_topic("hassTopic", "Connected", full=True), "pl_off": "off", "pl_on": "on", "stat_on": "Connected", "stat_off": "Disconnected", "device": _config_device, "ic": "mdi:lan-connect", }, ) # Emergency stop if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "stop", full=True), self._on_emergency_stop, ) self._generate_sensor( topic=_discovery_topic + "/switch/" + _node_id + "_STOP/config", values={ "name": _node_name + " Emergency Stop", "uniq_id": _node_id + "_STOP", "cmd_t": "~" + self._generate_topic("controlTopic", "stop"), "stat_t": "~" + self._generate_topic("controlTopic", "stop"), "pl_off": "False", "pl_on": "True", "val_tpl": "{{False}}", "device": _config_device, "ic": "mdi:alert-octagon", }, ) # Cancel print if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "cancel", full=True), self._on_cancel_print, ) self._generate_sensor( topic=_discovery_topic + "/switch/" + _node_id + "_CANCEL/config", values={ "name": _node_name + " Cancel Print", "uniq_id": _node_id + "_CANCEL", "cmd_t": "~" + self._generate_topic("controlTopic", "cancel"), "stat_t": "~" + self._generate_topic("controlTopic", "cancel"), "avty_t": "~" + self._generate_topic("hassTopic", "is_printing"), "pl_avail": "True", "pl_not_avail": "False", "pl_off": "False", "pl_on": "True", "val_tpl": "{{False}}", "device": _config_device, "ic": "mdi:cancel", }, ) # Pause / resume print if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "pause", full=True), self._on_pause_print, ) self._generate_sensor( topic=_discovery_topic + "/switch/" + _node_id + "_PAUSE/config", values={ "name": _node_name + " Pause Print", "uniq_id": _node_id + "_PAUSE", "cmd_t": "~" + self._generate_topic("controlTopic", "pause"), "stat_t": "~" + self._generate_topic("hassTopic", "is_paused"), "avty_t": "~" + self._generate_topic("hassTopic", "is_printing"), "pl_avail": "True", "pl_not_avail": "False", "pl_off": "False", "pl_on": "True", "device": _config_device, "ic": "mdi:pause", }, ) # Shutdown OctoPrint if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "shutdown", full=True), self._on_shutdown_system, ) self._generate_sensor( topic=_discovery_topic + "/switch/" + _node_id + "_SHUTDOWN/config", values={ "name": _node_name + " Shutdown System", "uniq_id": _node_id + "_SHUTDOWN", "cmd_t": "~" + self._generate_topic("controlTopic", "shutdown"), "stat_t": "~" + self._generate_topic("controlTopic", "shutdown"), "pl_off": "False", "pl_on": "True", "val_tpl": "{{False}}", "device": _config_device, "ic": "mdi:power", }, ) # PSUControl if self.psucontrol_enabled: if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "psu", full=True), self._on_psu, ) self._generate_sensor( topic=_discovery_topic + "/switch/" + _node_id + "_PSU/config", values={ "name": _node_name + " PSU", "uniq_id": _node_id + "_PSU", "cmd_t": "~" + self._generate_topic("controlTopic", "psu"), "stat_t": "~" + self._generate_topic("hassTopic", "psu_on"), "pl_on": "True", "pl_off": "False", "device": _config_device, "ic": "mdi:flash", }, ) # Camera output if self.snapshot_enabled: if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "camera_snapshot", full=True), self._on_camera, ) self._generate_sensor( topic=_discovery_topic + "/switch/" + _node_id + "_CAMERA_SNAPSHOT/config", values={ "name": _node_name + " Camera snapshot", "uniq_id": _node_id + "_CAMERA_SNAPSHOT", "cmd_t": "~" + self._generate_topic("controlTopic", "camera_snapshot"), "stat_t": "~" + self._generate_topic("controlTopic", "camera_snapshot"), "pl_off": "False", "pl_on": "True", "val_tpl": "{{False}}", "device": _config_device, "ic": "mdi:camera-iris", }, ) self._generate_sensor( topic=_discovery_topic + "/camera/" + _node_id + "_CAMERA/config", values={ "name": _node_name + " Camera", "uniq_id": _node_id + "_CAMERA", "device": _config_device, "topic": self._generate_topic("baseTopic", "camera"), }, ) # Command topics that don't have a suitable sensor configuration. These can be used # through the MQTT.publish service call though. if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "jog", full=True), self._on_jog ) self.mqtt_subscribe( self._generate_topic("controlTopic", "connect", full=True), self._on_connect_printer ) self.mqtt_subscribe( self._generate_topic("controlTopic", "home", full=True), self._on_home ) self.mqtt_subscribe( self._generate_topic("controlTopic", "commands", full=True), self._on_command, ) ##~~ EventHandlerPlugin API def on_event(self, event, payload): events = dict( comm=( Events.CONNECTING, Events.CONNECTED, Events.DISCONNECTING, Events.DISCONNECTED, Events.ERROR, Events.PRINTER_STATE_CHANGED, ), files=( Events.FILE_SELECTED, Events.FILE_DESELECTED, Events.CAPTURE_DONE, ), status=( Events.PRINT_STARTED, Events.PRINT_FAILED, Events.PRINT_DONE, Events.PRINT_CANCELLED, Events.PRINT_PAUSED, Events.PRINT_RESUMED, Events.Z_CHANGE, ), ) # Printer connectivity status events if event in events["comm"]: self._generate_connection_status() # Print job status events if ( event in events["comm"] or event in events["files"] or event in events["status"] ): self._logger.debug("Received event " + event + ", updating status") self._generate_printer_status() if event == Events.PRINT_STARTED: if self.update_timer: self.mqtt_publish( self._generate_topic("hassTopic", "is_printing", full=True), "True", allow_queueing=True, ) try: self.update_timer.start() except RuntimeError: # May already be running, it's ok pass elif event in (Events.PRINT_DONE, Events.PRINT_FAILED, Events.PRINT_CANCELLED): if self.update_timer: self.mqtt_publish( self._generate_topic("hassTopic", "is_printing", full=True), "False", allow_queueing=True, ) try: self.update_timer.cancel() except RuntimeError: # May already be stopped, it's ok pass if event == Events.PRINT_PAUSED: self.mqtt_publish( self._generate_topic("hassTopic", "is_paused", full=True), "True", allow_queueing=True, ) elif event in (Events.PRINT_RESUMED, Events.PRINT_STARTED): self.mqtt_publish( self._generate_topic("hassTopic", "is_paused", full=True), "False", allow_queueing=True, ) if ( self.psucontrol_enabled and event == Events.PLUGIN_PSUCONTROL_PSU_STATE_CHANGED ): self._generate_psu_state(payload["isPSUOn"]) if event == Events.CAPTURE_DONE: file_handle = open(payload["file"], "rb") file_content = file_handle.read() file_handle.close() self.mqtt_publish( self._generate_topic("baseTopic", "camera", full=True), file_content, allow_queueing=False, raw_data=True, ) ##~~ ProgressPlugin API def on_print_progress(self, storage, path, progress): self._generate_printer_status() def on_slicing_progress( self, slicer, source_location, source_path, destination_location, destination_path, progress, ): pass ##~~ WizardPlugin mixin def is_wizard_required(self): helpers = self._plugin_manager.get_helpers("mqtt") if helpers: return False mqtt_defaults = dict(plugins=dict(mqtt=MQTT_DEFAULTS)) _retain = settings().get_boolean( ["plugins", "mqtt", "broker", "retain"], defaults=mqtt_defaults ) if not _retain: return False return True ##~~ Softwareupdate hook def get_update_information(self): # Define the configuration for your plugin to use with the Software Update # Plugin here. See https://docs.octoprint.org/en/master/bundledplugins/softwareupdate.html # for details. return dict( homeassistant=dict( displayName="HomeAssistant Discovery Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-HomeAssistant", current=self._plugin_version, # update method: pip pip="https://github.com/cmroche/OctoPrint-HomeAssistant/archive/{target_version}.zip", ) )
class NavBarPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SettingsPlugin): def __init__(self): self.isRaspi = False self.debugMode = False # to simulate temp on Win/Mac self.displayRaspiTemp = True self._checkTempTimer = None def on_after_startup(self): self.displayRaspiTemp = self._settings.get(["displayRaspiTemp"]) self._logger.debug("displayRaspiTemp: %s" % self.displayRaspiTemp) if sys.platform == "linux2": with open('/proc/cpuinfo', 'r') as infile: cpuinfo = infile.read() # Match a line like 'Hardware : BCM2709' match = re.search('^Hardware\s+:\s+(\w+)$', cpuinfo, flags=re.MULTILINE | re.IGNORECASE) if match is None: # Couldn't find the hardware, assume it isn't a pi. self.isRaspi = False elif match.group(1) == 'BCM2708': self._logger.debug("Pi 1") self.isRaspi = True elif match.group(1) == 'BCM2709': self._logger.debug("Pi 2") self.isRaspi = True if self.isRaspi and self.displayRaspiTemp: self._logger.debug("Let's start RepeatedTimer!") self.startTimer(30.0) elif self.debugMode: self.isRaspi = True if self.displayRaspiTemp: self.startTimer(5.0) self._logger.debug("is Raspberry Pi? - %s" % self.isRaspi) def startTimer(self, interval): self._checkTempTimer = RepeatedTimer(interval, self.checkRaspiTemp, None, None, True) self._checkTempTimer.start() def checkRaspiTemp(self): from sarge import run, Capture self._logger.debug("Checking Raspberry Pi internal temperature") if sys.platform == "linux2": p = run("/opt/vc/bin/vcgencmd measure_temp", stdout=Capture()) p = p.stdout.text elif self.debugMode: import random def randrange_float(start, stop, step): return random.randint(0, int((stop - start) / step)) * step + start p = "temp=%s'C" % randrange_float(5, 60, 0.1) self._logger.debug("response from sarge: %s" % p) match = re.search('=(.*)\'', p) if not match: self.isRaspi = False else: temp = match.group(1) self._logger.debug("match: %s" % temp) self._plugin_manager.send_plugin_message(self._identifier, dict(israspi=self.isRaspi, raspitemp=temp)) ##~~ SettingsPlugin def get_settings_defaults(self): return dict(displayRaspiTemp = self.displayRaspiTemp) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.displayRaspiTemp = self._settings.get(["displayRaspiTemp"]) if self.displayRaspiTemp: interval = 5.0 if self.debugMode else 30.0 self.startTimer(interval) else: if self._checkTempTimer is not None: try: self._checkTempTimer.cancel() except: pass self._plugin_manager.send_plugin_message(self._identifier, dict()) ##~~ TemplatePlugin API def get_template_configs(self): if self.isRaspi: return [ dict(type="settings", template="navbartemp_settings_raspi.jinja2") ] else: return [] ##~~ AssetPlugin API def get_assets(self): return { "js": ["js/navbartemp.js"], "css": ["css/navbartemp.css"], "less": ["less/navbartemp.less"] } ##~~ Softwareupdate hook def get_update_information(self): return dict( navbartemp=dict( displayName="Navbar Temperature Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-NavbarTemp", current=self._plugin_version, # update method: pip w/ dependency links pip="https://github.com/imrahil/OctoPrint-NavbarTemp/archive/{target_version}.zip" ) )
class AiPrintBoxPlugin( octoprint.plugin.SettingsPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.ShutdownPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SimpleApiPlugin, octoprint.printer.PrinterCallback): def __init__(self): self._mqtt = None self._mqtt_connected = False self._mqtt_tls_set = False self._current_task_id = None self.server_host = "http://139.159.151.243:8080/fileoperation/" self.mmf_status_updater = None self._current_action_code = "999" self._current_temp_hotend = 0 self._current_temp_bed = 0 self._mmf_print = False self._printer_status = { "000": "free", "100": "prepare", "101": "printing", "102": "paused", "103": "resumed", "104": "printing", "201": "downloading", "301": "SlicingStarted", "302": "SlicingDone", "303": "SlicingFailed", "304": "SlicingCancelled", "305": "SlicingProfileAdded", "306": "SlicingProfileModified", "307": "SlicingProfileDeleted", "999": "offline" } def initialize(self): self._printer.register_callback(self) # ~~ SettingsPlugin mixin def get_settings_defaults(self): return dict( supported_printers=[], printer_manufacturer="CARS", printer_model="CARS-C8", printer_serial_number="", printer_firmware_version="", registration_complete=False, active_complete=False, printer_token="", client_name="octoprint_AiPrintBox", client_key= "acGxgLJmvgTZU2RDZ3vQaiitxc5Bf6DDeHL1", #"b4943605-52b5-4d13-94ee-34eb983a813f" auto_start_print=True, mmf_print_complete=False, mmf_print_cancelled=False, bypass_bed_clear=False) def get_settings_version(self): return 1 def on_settings_migrate(self, target, current=None): self._logger.debug("Settings migrate complete.") # ~~ EventHandlerPlugin API def on_event(self, event, payload): try: if event == Events.PRINT_STARTED: self._current_action_code = "101" self._settings.set_boolean(["mmf_print_complete"], False) self._settings.set_boolean(["mmf_print_cancelled"], False) self._settings.save() if not self._mmf_print: self._current_task_id = None elif event == Events.PRINT_DONE: if self._mmf_print and not self._settings.get_boolean([ "bypass_bed_clear" ]): # Send message back to UI to confirm clearing of bed. self._settings.set_boolean(["mmf_print_complete"], True) self._settings.save() self._plugin_manager.send_plugin_message( self._identifier, dict(mmf_print_complete=True)) else: # self._current_action_code = "000" self._mmf_print = False self._current_action_code = "000" elif event == Events.PRINT_CANCELLED: if self._mmf_print and not self._settings.get_boolean([ "bypass_bed_clear" ]): # Send message back to UI to confirm clearing of bed. self._settings.set_boolean(["mmf_print_cancelled"], True) self._settings.save() self._plugin_manager.send_plugin_message( self._identifier, dict(mmf_print_cancelled=True)) else: # self._current_action_code = "000" self._mmf_print = False self._current_action_code = "000" if event == Events.PRINT_PAUSED: self._current_action_code = "102" # self._current_action_code = "101" if event == Events.PRINT_RESUMED: self._current_action_code = "103" if event == Events.SLICING_STARTED: self._current_action_code = "301" if event == Events.SLICING_DONE: self._current_action_code = "302" if event == Events.SLICING_FAILED: self._current_action_code = "303" self._logger.info("receive info:" + str(event)) except Exception as e: self._logger.info("on event error:" + str(e)) self._plugin_manager.send_plugin_message(self._identifier, dict(error=str(e))) # ~~ StartupPlugin mixin def on_startup(self, host, port): self._port = port if self._settings.get_boolean( ["mmf_print_complete"]) == False and self._settings.get_boolean( ["mmf_print_cancelled"]) == False: self._current_action_code = "000" if not self._settings.get_boolean(["registration_complete"]): printInfo = dict(manufacturer="CARS", model="CARS-C8") self._on_regist_printer(printInfo) if self._settings.get_boolean(["registration_complete"]): self._on_active_printer() if self._settings.get_boolean(["active_complete"]): self.mqtt_connect() self.on_after_startup() else: self._settings.set(["supported_printers"], self.get_supported_printers()) def on_after_startup(self): if self._mqtt is None: return if self._settings.get_boolean(["active_complete" ]) and self.mmf_status_updater is None: # start repeated timer publishing current status_code self.mmf_status_updater = RepeatedTimer(5, self.send_status) # self.send_status() self.mmf_status_updater.start() return # ~~ ShutdownPlugin mixin def on_shutdown(self): self.mqtt_disconnect(force=True) # ~~ AssetPlugin mixin def get_assets(self): return dict(js=["js/AiPrintBox.js"], css=["css/AiPrintBox.css"]) # ~~ SimpleApiPlugin mixin def get_api_commands(self): return dict(register_printer=["manufacturer", "model"], forget_printer=[], mmf_print_complete=[]) def _on_regist_printer(self, data): try: # Generate serial number if it doesn't already exist. if self._settings.get(["printer_serial_number"]) == "": import uuid CM_UUID = str(uuid.uuid4()) # CM_UUID = "2576c5ea-78c9-44b0-ab56-3ebd88cc4ac0" self._settings.set(["printer_serial_number"], CM_UUID) import qrcode import image qrcodeStr = "{\"manufactor\":\"%s\",\"type\":\"%s\",\"name\":\"C8\",\"code\":\"%s\"}" % ( data["manufacturer"], data["model"], CM_UUID) img = qrcode.make(qrcodeStr) current_work_dir = os.path.dirname(__file__) qrcode_path = os.path.join(current_work_dir, "static/images/cmid_qrcode.jpg") img.save(qrcode_path) url = "%sprinterInfo/registerPrinterInfo?printerCode=%s&manufactor=%s&type=%s" % ( self.server_host, self._settings.get([ "printer_serial_number" ]), data["manufacturer"], data["model"]) mac_address = ':'.join( ("%012X" % get_mac())[i:i + 2] for i in range(0, 12, 2)) payload = "{\"manufacturer\": \"%s\",\"model\": \"%s\",\"firmware_version\": \"%s\",\"serial_number\": \"%s\",\"mac_address\": \"%s\"}" % ( data["manufacturer"], data["model"], "1.0.0", self._settings.get(["printer_serial_number"]), mac_address) headers = { 'X-Api-Key': self._settings.get(["client_key"]), 'Content-Type': "application/json" } self._logger.debug("Sending data: %s with header: %s" % (payload, json.dumps(headers))) response = requests.post(url, data=payload, headers=headers) if response.status_code == 200: serialized_response = json.loads(response.text) self._logger.debug(json.dumps(serialized_response)) self._settings.set(["printer_manufacturer"], data["manufacturer"]) self._settings.set(["printer_model"], data["model"]) self._settings.set(["printer_identifier"], self._identifier) self._settings.set_boolean(["registration_complete"], True) self._settings.save() else: self._logger.info("API Error: %s" % response) self._plugin_manager.send_plugin_message( self._identifier, dict(error=response.status_code)) except Exception as e: self._logger.info("regist printer error:" + str(e)) self._plugin_manager.send_plugin_message(self._identifier, dict(error=str(e))) def _on_active_printer(self): try: serial_number = self._settings.get(["printer_serial_number"]) if serial_number == "": self._plugin_manager.send_plugin_message( self._identifier, dict(error="The machine is not registered")) return url = "%sprinterInfo/findPrinterInfoTokenIdByPrinterCode?printerCode=%s" % ( self.server_host, serial_number) payload = {} headers = { 'X-Api-Key': self._settings.get(["client_key"]), 'Content-Type': "application/json" } self._logger.debug("Sending data: %s with header: %s" % (payload, json.dumps(headers))) response = requests.get(url, data=payload, headers=headers) if response.status_code == 200: serialized_response = json.loads(response.text) self._logger.debug(json.dumps(serialized_response)) data = serialized_response["data"] if data != None and len(data) > 0: self.mqtt_disconnect(force=True) self._settings.set(["printer_token"], serialized_response["data"]) self._settings.set_boolean(["active_complete"], True) self._settings.save() ''' count = 10 while count > 0: try: address = "localhost" port = 8025 url = "http://%s:%s/%s" % (address,port,'updateResolv') requests.get(url) time.sleep(3) print(url) break except: count = count - 1 continue ''' else: self._settings.set_boolean(["active_complete"], False) else: self._settings.set_boolean(["active_complete"], False) # self._plugin_manager.send_plugin_message(self._identifier, dict(qr_image_url=serialized_response["qr_image_url"],printer_serial_number=self._settings.get(["printer_serial_number"]))) self._plugin_manager.send_plugin_message( self._identifier, dict(printer_serial_number=self._settings.get( ["printer_serial_number"]))) except Exception as e: self._logger.info("active printer error: " + str(e)) self._plugin_manager.send_plugin_message(self._identifier, dict(error=str(e))) def on_api_command(self, command, data): if not user_permission.can(): return flask.make_response("Insufficient rights", 403) if command == "register_printer": """ if "manufacturer" not in data: data["manufacturer"] = "cars" if "model" not in data: data["model"] = "fdm-printer" """ self._on_regist_printer(data) if command == "forget_printer": # new_supported_printers = self.get_supported_printers() self.mqtt_disconnect(force=True) self._settings.set(["printer_serial_number"], "") self._settings.set(["printer_token"], "") self._settings.set_boolean(["registration_complete"], False) # self._settings.set(["supported_printers"],new_supported_printers) self._settings.save() # self._plugin_manager.send_plugin_message(self._identifier, dict(printer_removed=True)) return flask.jsonify( {"printer_removed": True}) #,"supported_printers":new_supported_printers}) if command == "mmf_print_complete": self._mmf_print = False self._current_action_code = "000" self._settings.set_boolean(["mmf_print_complete"], False) self._settings.set_boolean(["mmf_print_cancelled"], False) self._settings.save() return flask.jsonify(bed_cleared=True) # ~~ PrinterCallback def on_printer_add_temperature(self, data): if self._settings.get_boolean(["active_complete"]): # self._logger.info("add temperature %s" % data) if data.get("tool0"): self._current_temp_hotend = data["tool0"]["actual"] if data.get("bed"): self._current_temp_bed = data["bed"]["actual"] # ~~ AiPrintBox Functions def get_supported_printers(self): url = "%sprinterInfo/supportedPrinters" % (self.server_host) headers = {'X-Api-Key': self._settings.get(["client_key"])} response = requests.get(url, headers=headers) if response.status_code == 200: self._logger.debug("Received printers: %s" % response.text) filtered_printers = json.loads(response.text)["items"] return filtered_printers else: self._logger.debug("Error getting printers: %s" % response) def send_status(self): # self.mmf_status_updater.cancel() printer_disconnected = self._printer.is_closed_or_error() if not printer_disconnected: printer_token = self._settings.get(["printer_token"]), topic = "/printers/%s/client/status" % printer_token printer_data = self._printer.get_current_data() # self._logger.info(printer_data) message = dict( actionCode=300, status=self._get_current_status(), printer_token=printer_token, manufacturer=self._settings.get(["printer_manufacturer"]), model=self._settings.get(["printer_model"]), firmware_version=self._settings.get( ["printer_firmware_version"]), serial_number=self._settings.get(["printer_serial_number"]), current_task_id=self._current_task_id, temperature="%s" % self._current_temp_hotend, bed_temperature="%s" % self._current_temp_bed, print_progress=printer_data["progress"], #print_time = int(printer_data["progress"]["printTime"] or 0), #print_progress = int(printer_data["progress"]["completion"] or 0), #remaining_time = int(printer_data["progress"]["printTimeLeft"] or 0), #total_time = int(printer_data["job"]["estimatedPrintTime"] or 0), date=self._get_timestamp()) self._logger.debug(message) self.mqtt_publish(topic, message) self._logger.info('send status: ' + self._get_current_status()) # if not self.mmf_status_updater._timer_active(): # self.mmf_status_updater.start() def _get_current_status(self): return self._printer_status[self._current_action_code] def _get_timestamp(self): timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") return timestamp # ~~ Printer Action Functions def _download_file(self, data, pub_topic, restapi, message): try: # Make API call to AiPrintBox to download gcode file. # payload = dict(file_id = action["file_id"],printer_token = self._settings.get(["printer_token"])) action = json.loads(data) url = action["filePath"] headers = {'X-Api-Key': self._settings.get(["client_key"])} fileName = action["fileName"] self._current_action_code = "201" payload = "key=%s" % action["key"] response = requests.get(url, params=payload, headers=headers) self._logger.debug("Sending parameters: %s with header: %s" % (payload, headers)) if response.status_code == 200: # Save file to uploads folder sanitize_file_name = self._file_manager.sanitize_name( "local", fileName) download_file = "%s/%s" % ( self._settings.global_get_basefolder("uploads"), sanitize_file_name) self._logger.debug("Saving file: %s" % download_file) with open(download_file, 'wb') as f: f.write(response.content) f.close() if download_file.endswith(".obj"): mesh = trimesh.load_mesh(download_file) stl_download_file = download_file.replace(".obj", ".stl") mesh.export(stl_download_file, "stl_ascii") os.remove(download_file) state = dict(status_code=response.status_code, text="successful") else: self._logger.debug("API Error: %s" % response) self._plugin_manager.send_plugin_message( self._identifier, dict(error=response.status_code)) state = dict(status_code=response.status_code, text=response.text) except Exception as e: self._logger.info("download file error :" + str(e)) self._plugin_manager.send_plugin_message(self._identifier, dict(error=str(e))) state = dict(status_code=400, text=str(e)) self._current_action_code = "000" self.mqtt_publish("%s/%s/status" % (pub_topic, restapi), state["status_code"]) self.mqtt_publish("%s/%s/response" % (pub_topic, restapi), state["text"]) self._plugin_manager.send_plugin_message( self._identifier, dict(topic=restapi, message=message, subscribecommand="Status code: %s" % state["status_code"])) # return dict(status_code = 400,text = str(e)) # ~~ MQTT Functions def mqtt_connect(self): # broker_url = "mqtt.AiPrintBox.com" # broker_username = self._settings.get(["client_name"]) # broker_password = self._settings.get(["client_key"]) broker_url = "139.159.151.243" broker_username = "******" broker_password = "******" # broker_insecure_port = 1883 broker_insecure_port = 5059 broker_tls_port = 8883 broker_port = broker_insecure_port broker_keepalive = 60 use_tls = False broker_tls_insecure = False # may need to set this to true import paho.mqtt.client as mqtt broker_protocol = mqtt.MQTTv31 if self._mqtt is None: self._mqtt = mqtt.Client(protocol=broker_protocol) if broker_username is not None: self._mqtt.username_pw_set(broker_username, password=broker_password) if use_tls and not self._mqtt_tls_set: self._mqtt.tls_set( ) # Uses the default certification authority of the system https://pypi.org/project/paho-mqtt/#tls-set self._mqtt_tls_set = True if broker_tls_insecure and not self._mqtt_tls_set: self._mqtt.tls_insecure_set(broker_tls_insecure) broker_port = broker_insecure_port # Fallbacks to the non-secure port 1883 self._mqtt.on_connect = self._on_mqtt_connect self._mqtt.on_disconnect = self._on_mqtt_disconnect self._mqtt.on_message = self._on_mqtt_message self._mqtt.connect_async(broker_url, broker_port, keepalive=broker_keepalive) if self._mqtt.loop_start() == mqtt.MQTT_ERR_INVAL: self._logger.error( "Could not start MQTT connection, loop_start returned MQTT_ERR_INVAL" ) def mqtt_disconnect(self, force=False): if self._mqtt is None: return self._mqtt.loop_stop() if force: time.sleep(1) self._mqtt.loop_stop(force=True) if self.mmf_status_updater: self._logger.debug("Stopping MQTT status updates.") self.mmf_status_updater.cancel() self._logger.debug("Disconnected from AiPrintBox.") def mqtt_publish(self, topic, payload, retained=False, qos=0): if not isinstance(payload, basestring): payload = json.dumps(payload) if self._mqtt_connected: self._mqtt.publish(topic, payload=payload, retain=retained, qos=qos) # self._logger.debug("Sent message: {topic} - {payload}".format(**locals())) return True else: return False def _on_mqtt_subscription(self, topic, message, retained=None, qos=None, *args, **kwargs): action = json.loads(message) try: settings = octoprint.settings.Settings() api_key = settings.get(["api", "key"]) address = "localhost" port = self._port restapi = action["act_restapi"] url = "http://%s:%s/api/%s" % (address, port, restapi) self._logger.debug("Received from " + topic + "|" + str(message)) # content_type = base64.b64decode(action["act_content-type"]).encode("utf-8") content_type = action["act_content-type"] headers = {'Content-type': content_type, 'X-Api-Key': api_key} # headers = {'Content-type': 'application/json','X-Api-Key': api_key} pub_topic = "/printers/%s/client" % self._settings.get( ["printer_token"]) # OctoPrint RestAPI if action["act_type"] == "post": data = base64.b64decode(action["act_cmd"]) r = requests.post(url, data=data, headers=headers) self.mqtt_publish("%s/%s/status" % (pub_topic, restapi), r.status_code) self.mqtt_publish("%s/%s/response" % (pub_topic, restapi), r.text) self._plugin_manager.send_plugin_message( self._identifier, dict(topic=restapi, message=message, subscribecommand="Status code: %s" % r.status_code)) if action["act_type"] == "get": r = requests.get(url, headers=headers) self.mqtt_publish("%s/%s/status" % (pub_topic, restapi), r.status_code) self.mqtt_publish("%s/%s/response" % (pub_topic, restapi), r.text) self._plugin_manager.send_plugin_message( self._identifier, dict(topic=restapi, message=message, subscribecommand="Response: %s" % r.text)) # download file from BIM System if action["act_type"] == "download": data = base64.b64decode(action["act_cmd"]) _thread.start_new_thread(self._download_file, ( data, pub_topic, restapi, message, )) # reset access point command if action["act_type"] == "resetap": self.mqtt_publish( "%s/%s/response" % (pub_topic, restapi), 'reset access point. please wait a moment......') url = "http://%s:%s/%s" % (address, 8025, 'setHotPoint') r = requests.get(url, headers=headers) if action["act_type"] == "update_resolv": self.mqtt_publish("%s/%s/response" % (pub_topic, restapi), 'update resolv configuration......') url = "http://%s:%s/%s" % (address, 8025, 'updateResolv') r = requests.get(url, headers=headers) if action["act_type"] == "delete": r = requests.delete(url, headers=headers) self.mqtt_publish("%s/%s/status" % (pub_topic, restapi), r.status_code) self.mqtt_publish("%s/%s/response" % (pub_topic, restapi), r.text) self._plugin_manager.send_plugin_message( self._identifier, dict(topic=restapi, message=message, subscribecommand="Response: %s" % r.text)) except Exception as e: self.mqtt_publish("%s/%s/response" % (pub_topic, restapi), str(e)) self._logger.info("subscription message error:" + str(e)) self._plugin_manager.send_plugin_message(self._identifier, dict(message=str(e))) def _on_mqtt_connect(self, client, userdata, flags, rc): if not client == self._mqtt: return if not rc == 0: reasons = [ None, "Connection to AiPrintBox refused, wrong protocol version", "Connection to AiPrintBox refused, incorrect client identifier", "Connection to AiPrintBox refused, server unavailable", "Connection to AiPrintBox refused, bad username or password", "Connection to AiPrintBox refused, not authorised" ] if rc < len(reasons): reason = reasons[rc] else: reason = None self._logger.error( reason if str(reason) else "Connection to AiPrintBox broker refused, unknown error") return # self._logger.info("Connected to AiPrintBox") printer_actived = self._settings.get_boolean(["active_complete"]) if printer_actived: self._mqtt.subscribe("/printers/%s/controller" % self._settings.get(["printer_token"])) self._logger.info("Subscribed to AiPrintBox printer topic:%s" % self._settings.get(["printer_token"])) self._mqtt_connected = True def _on_mqtt_disconnect(self, client, userdata, rc): if not client == self._mqtt: return self._logger.info("Disconnected from AiPrintBox.") def _on_mqtt_message(self, client, userdata, msg): if not client == self._mqtt: return from paho.mqtt.client import topic_matches_sub if topic_matches_sub( "/printers/%s/controller" % self._settings.get(["printer_token"]), msg.topic): args = [msg.topic, msg.payload] kwargs = dict(retained=msg.retain, qos=msg.qos) try: self._on_mqtt_subscription(*args, **kwargs) except: self._logger.exception( "Error while calling AiPrintBox callback") # ~~ Softwareupdate hook def get_update_information(self): return dict(AiPrintBox=dict( displayName="AiPrintBox", displayVersion=self._plugin_version, type="github_release", user="******", repo="OctoPrint-AiPrintBox", current=self._plugin_version, pip= "https://github.com/springtiger/aiprintbox/archive/{target_version}.zip" ))
class DisplayETAPlugin(octoprint.plugin.AssetPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.ProgressPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.RestartNeedingPlugin,): def __init__(self): # This allows us to store and display our logs with the rest of the OctoPrint logs self.logger = logging.getLogger('octoprint.plugins.display_eta') return ####################### # StartupPlugin Mixin # ####################### # Function to run after Octoprint starts, used to initialise variables def on_after_startup(self): # Create a string to store the latest ETA self.eta_string = "-" # Check if the user has chosen to display the ETA on the printer LCD self.doM117 = self._settings.get(['displayOnPrinter']) # Check if the user has chosen to use 24hr time if self._settings.get(['time24hr']) is True: # See http://babel.pocoo.org/en/latest/dates.html#time-fields self.CustomTimeFormat = "HH:mm:ss" else: # See http://babel.pocoo.org/en/latest/dates.html#time-fields self.CustomTimeFormat = "hh:mm:ss a" # Check if the user has chosen to remove colons from the ETA displayed on the printer LCD self.replaceColons = self._settings.get(['removeColons']) # Load the set interval in seconds between updating the ETA value self.update_interval = self._settings.get(['updateInterval']) # Create a RepeatedTimer object to run calculate_eta every update_interval seconds, eg every 10 seconds self.timer = RepeatedTimer(self.update_interval, DisplayETAPlugin.calculate_eta, args=[self], run_first=True, ) ######################## # SettingsPlugin Mixin # ######################## # Function to return the default values for all settings for this plugin def get_settings_defaults(self): return dict( time24hr=False, displayOnPrinter=True, removeColons=False, updateInterval=10.0 ) # Function to run when the settings are saved def on_settings_save(self, data): # Store the new settings values for easy access octoprint.plugin.SettingsPlugin.on_settings_save(self, data) # Check if the user has chosen to use 24hr time if self._settings.get(["time24hr"]) is True: self.logger.debug('24HR = True') # See http://babel.pocoo.org/en/latest/dates.html#time-fields for details on the time format self.CustomTimeFormat = "HH:mm:ss" pass else: self.logger.debug('24HR = False') # See http://babel.pocoo.org/en/latest/dates.html#time-fields for details on the time format self.CustomTimeFormat = "hh:mm:ss a" # Check if the user has chosen to display the ETA on the printer LCD if self._settings.get(["displayOnPrinter"]) is True: self.logger.debug('M117 = True') self.doM117 = True else: self.doM117 = False self.logger.debug('M117 = False') # Check if the user has chosen to remove colons from the ETA displayed on the printer LCD if self._settings.get(["removeColons"]) is True: self.replaceColons = True self.logger.debug('self.replaceColons = True') else: self.replaceColons = False # Store what the new updateInterval is self.update_interval = self._settings.get(['updateInterval']) ######################## # TemplatePlugin Mixin # ######################## # Function to inform OctoPrint what parts of the UI we will be binding to def get_template_configs(self): return [ # We bind to the navbar to allow us to manipulate the main Web UI dict(type="navbar", custom_bindings=False), # We bind to the settings to allow us to show a settings page to the user dict(type="settings", custom_bindings=False) ] ######################## # ProgressPlugin Mixin # ######################## # Function to be called by Octoprint on print progress def on_print_progress(self, storage, path, progress): self.logger.debug('on_print_progress called') # Calculate the ETA and push it to the Web UI and printer LCD (if enabled) self.calculate_eta() self.logger.debug('reached end of on_print_progress') ############################ # EventHandlerPlugin Mixin # ############################ # Fucntion to be called by Octoprint when an event occurs def on_event(self, event, payload): self.logger.debug('on_event called') self.logger.debug('event is {}'.format(event)) # Check if the event is PrintStarted or PrintResumed if event in ('PrintStarted', 'PrintResumed'): self.logger.debug('event is PrintStarted or PrintResumed. Calling calculate_eta') # Calculate the ETA and push it to the Web UI and printer LCD (if enabled) self.calculate_eta() # Stop the timer self.timer.cancel() # Get the updateInterval from the settings self.update_interval = self._settings.get(['updateInterval']) # Redefine the timer with the new updateInterval self.timer = RepeatedTimer(self.update_interval, DisplayETAPlugin.calculate_eta, args=[self], run_first=True,) # Start the timer self.timer.start() self.logger.debug('reached end of on_event') return # If the even starts with Print but isn't PrintStarted or PrintResumed elif event.startswith("Print"): # Set the current ETA to "-" self.eta_string = "-" # Stop the timer self.timer.cancel() self.logger.debug('event starts with Print but is not PrintStarted or PrintResumed') return else: return ######################### # SimpleApiPlugin Mixin # ######################### # Function to return a list of commands that we accept through the POST endpoint of the API for this plugin def get_api_commands(self): return dict( current_eta=[], update_eta=[] ) # Function for Octoprint to call when a request is sent to the POST endpoint of the API for this plugin def on_api_command(self, command, data): # If the command sent to the API is current_eta if command == 'current_eta': # Return the current eta_string return flask.jsonify({'eta': self.eta_string}), 200 # If the command sent to the API is update_eta if command == 'update_eta': # Store the current eta_string old_eta = self.eta_string # Calculate the ETA and push it to the Web UI and printer LCD (if enabled) self.calculate_eta() # Return the old eta_string and the newly calculated eta_string return flask.jsonify({'eta': self.eta_string, 'old_eta': old_eta}), 200 # Function for Octoprint to call when a request is sent to the GET endpoint of the API for this plugin def on_api_get(self, request): # Check if the request includes the command parameter if 'command' not in request.values: # If the command parameter is not present, return an error return "Error, please include the command parameter", 400 # Create a list of command that we accept recognised_commands = ['current_eta', 'update_eta'] # Store the value of the command parameter command = request.values['command'] # Check if the command is a supported one if command not in recognised_commands: # If the command isn't supported, return an error return "Error, unrecognised command", 400 # If the command sent ot the API is current_eta if command == 'current_eta': # Return the current eta_string return flask.jsonify({'eta': self.eta_string}), 200 # If the command sent to the API is update_eta if command == 'update_eta': # Store the current eta_string old_eta = self.eta_string # Calculate the ETA and push it to the Web UI and printer LCD (if enabled) self.calculate_eta() # Return the old eta_string and the newly calculated eta_string return flask.jsonify({'eta': self.eta_string, 'old_eta': old_eta}), 200 ##################### # AssetPlugin Mixin # ##################### # Function to tell Octoprint what assets we need loaded in the Web UI def get_assets(self): return { "js": ["js/display_eta.js"] } #################### # Custom functions # #################### # Function to calculate and update the ETA def calculate_eta(self, do_update_eta=True): self.logger.debug('calculate_eta called') # Get the current printer data, which should include the time left for the current print current_data = self._printer.get_current_data() # Check if the time left for the current print is included in the data returned if not current_data["progress"]["printTimeLeft"]: self.logger.debug("calculate_eta() returning blank string") return "-" # Get the current time and date current_time = datetime.datetime.today() # Add the time left for the current print to the current time and date finish_time = current_time + datetime.timedelta(0, current_data["progress"]["printTimeLeft"]) # Format the time according to the users choice (either 12hr or 24hr time) strtime = format_time(finish_time, self.CustomTimeFormat) self.logger.debug('strtime = ' + strtime) # Create an empty string to store the finish date for the print strdate = "" # Check if the print will finish today if finish_time.day > current_time.day: # If the print will finish tomorrow if finish_time.day == current_time.day + 1: # Set strdate to Tomorrow strdate = " Tomorrow" # If the print will finish after tomorrow else: # Set strdate to the finish date for the print strtime = " " + format_date(finish_time, "EEE d") self.logger.debug('reached end of calculate_eta') # Join strtime and strdate and store it as eta_string self.eta_string = strtime + strdate if do_update_eta is True: # Push the ETA to the Web UI and printer LCD(If enabled) self.update_eta() return # Function to push the current ETA to the Web UI and printer LCD (If enabled) def update_eta(self): # Send the current ETA to the Web UI self._plugin_manager.send_plugin_message(self._identifier, dict(eta_string=self.eta_string)) # Check if the user has chosen to display the ETA on the printer LCD if self.doM117 is True: # Check if the user has chosen to remove colons from the output to the printer LCD if self.replaceColons is True: # Send the ETA to the printer LCD, minus the colons self._printer.commands("M117 ETA is {}".format(self.eta_string.replace(":", " "))) else: # Send the ETA to the printer LCD self._printer.commands("M117 ETA is {}".format(self.eta_string)) return # Function to tell Octoprint how to update the plugin def get_update_information(self): return dict( display_eta=dict( displayName=self._plugin_name, displayVersion=self._plugin_version, type="github_release", current=self._plugin_version, user="******", repo="Octoprint-Display-ETA", pip="https://github.com/AlexVerrico/Octoprint-Display-ETA/archive/{target}.zip" ) )
class NavBarPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SettingsPlugin): def __init__(self): self.piSocTypes = ( ["BCM2708", "BCM2709", "BCM2835"] ) # Array of raspberry pi SoC's to check against, saves having a large if/then statement later self.debugMode = False # to simulate temp on Win/Mac self.displayTempSoC = True self.displayTempGPIO = True self._checkTempTimer = None self.sbc = None def on_after_startup(self): self.displayTempSoC = self._settings.get(["displayTempSoC"]) self.displayTempGPIO = self._settings.get(["displayTempGPIO"]) self.piSocTypes = self._settings.get(["piSocTypes"]) self._logger.debug("displayTempSoC: %s" % self.displayTempSoC) if sys.platform == "linux2": self.sbc = SBCFactory().factory(self._logger) if self.sbc.is_supported and (self.displayTempSoC or self.displayTempGPIO): self._logger.debug("Let's start RepeatedTimer!") self.startTimer(10.0) # debug mode doesn't work if the OS is linux on a regular pc try: self._logger.debug("is supported? - %s" % self.sbc.is_supported) except: self._logger.debug("Embeded platform is not detected") def startTimer(self, interval): self._checkTempTimer = RepeatedTimer(interval, self.updateTemps, None, None, True) self._checkTempTimer.start() def updateTemps(self): gpio = 0 soc = 0 if self.displayTempGPIO: gpio = self.getTempGPIO() if self.displayTempSoC: soc = self.sbc.checkSoCTemp() self._logger.debug("soc: %s" % soc) self._plugin_manager.send_plugin_message( self._identifier, dict(isSupported=self.sbc.is_supported, showsoc=self.displayTempSoC, soctemp=soc, showgpio=self.displayTempGPIO, gpiotemp=gpio)) def getTempGPIO(self): os.system('modprobe w1-gpio') os.system('modprobe w1-therm') base_dir = '/sys/bus/w1/devices/' device_folder = glob.glob(base_dir + '28*')[0] device_file = device_folder + '/w1_slave' if os.path.isfile(device_file): lines = self.readTempGPIO(device_file) count = 5 while lines[0].strip()[-3:] != 'YES' and count <= 0: count -= 1 time.sleep(0.1) lines = self.readTempGPIO(device_file) equals_pos = lines[1].find('t=') if equals_pos != -1: temp_string = lines[1][equals_pos + 2:] temp_c = float(temp_string) / 1000.0 p = '{0:0.1f}'.format(temp_c) return p return 'err' def readTempGPIO(self, device_file): f = open(device_file, 'r') lines = f.readlines() f.close() return lines ##~~ SettingsPlugin def get_settings_defaults(self): return dict(displayTempSoC=self.displayTempSoC, piSocTypes=self.piSocTypes) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.displayTempSoC = self._settings.get(["displayTempSoC"]) if self.displayTempSoC: interval = 5.0 if self.debugMode else 30.0 self.startTimer(interval) else: if self._checkTempTimer is not None: try: self._checkTempTimer.cancel() except: pass self._plugin_manager.send_plugin_message(self._identifier, dict()) ##~~ TemplatePlugin API def get_template_configs(self): try: if self.sbc.is_supported: return [ dict(type="settings", template="navbartemp_settings_raspi.jinja2") ] else: return [] except: return [] ##~~ AssetPlugin API def get_assets(self): return { "js": ["js/navbartemp.js"], "css": ["css/navbartemp.css"], "less": ["less/navbartemp.less"] } ##~~ Softwareupdate hook def get_update_information(self): return dict(navbartemp=dict( displayName="Navbar Temperature Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-NavbarTemp", current=self._plugin_version, # update method: pip w/ dependency links pip= "https://github.com/imrahil/OctoPrint-NavbarTemp/archive/{target_version}.zip" ))
class shutdownprinterPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.StartupPlugin): def __init__(self): self.url = "" self._mode_shutdown_gcode = True self._mode_shutdown_api = False self._mode_shutdown_api_custom = False self.api_custom_GET = False self.api_custom_POST = False self.api_custom_url = "" self.api_custom_json_header = "" self.api_custom_body = "" self.api_key_plugin = "" self.api_json_command = "" self.api_plugin_name = "" self.api_plugin_port = 5000 self.abortTimeout = 0 self.temperatureValue = 0 self.temperatureTarget = False self.printFailed = False self.printCancelled = False self.rememberCheckBox = False self.lastCheckBoxValue = False self._shutdown_printer_enabled = True self._timeout_value = None self._abort_timer = None self._abort_timer_temp = None def initialize(self): self.url = self._settings.get(["url"]) self._logger.debug("url: %s" % self.url) self.api_key_plugin = self._settings.get(["api_key_plugin"]) self._logger.debug("api_key_plugin: %s" % self.api_key_plugin) self._mode_shutdown_gcode = self._settings.get_boolean( ["_mode_shutdown_gcode"]) self._logger.debug("_mode_shutdown_gcode: %s" % self._mode_shutdown_gcode) self._mode_shutdown_api = self._settings.get_boolean( ["_mode_shutdown_api"]) self._logger.debug("_mode_shutdown_api: %s" % self._mode_shutdown_api) self._mode_shutdown_api_custom = self._settings.get_boolean( ["_mode_shutdown_api_custom"]) self._logger.debug("_mode_shutdown_api_custom: %s" % self._mode_shutdown_api_custom) self.api_custom_POST = self._settings.get_boolean(["api_custom_POST"]) self._logger.debug("api_custom_POST: %s" % self.api_custom_POST) self.api_custom_GET = self._settings.get_boolean(["api_custom_GET"]) self._logger.debug("api_custom_GET: %s" % self.api_custom_GET) self.api_custom_url = self._settings.get(["api_custom_url"]) self._logger.debug("api_custom_url: %s" % self.api_custom_url) self.api_custom_json_header = self._settings.get( ["api_custom_json_header"]) self._logger.debug("api_custom_json_header: %s" % self.api_custom_json_header) self.api_custom_body = self._settings.get(["api_custom_body"]) self._logger.debug("api_custom_body: %s" % self.api_custom_body) self.api_json_command = self._settings.get(["api_json_command"]) self._logger.debug("api_json_command: %s" % self.api_json_command) self.api_plugin_name = self._settings.get(["api_plugin_name"]) self._logger.debug("api_plugin_name: %s" % self.api_plugin_name) self.api_plugin_port = self._settings.get_int(["api_plugin_port"]) self._logger.debug("api_plugin_port: %s" % self.api_plugin_port) self.temperatureValue = self._settings.get_int(["temperatureValue"]) self._logger.debug("temperatureValue: %s" % self.temperatureValue) self.temperatureTarget = self._settings.get_boolean( ["temperatureTarget"]) self._logger.debug("temperatureTarget: %s" % self.temperatureTarget) self.abortTimeout = self._settings.get_int(["abortTimeout"]) self._logger.debug("abortTimeout: %s" % self.abortTimeout) self.printFailed = self._settings.get_boolean(["printFailed"]) self._logger.debug("printFailed: %s" % self.printFailed) self.printCancelled = self._settings.get_boolean(["printCancelled"]) self._logger.debug("printCancelled: %s" % self.printCancelled) self.rememberCheckBox = self._settings.get_boolean( ["rememberCheckBox"]) self._logger.debug("rememberCheckBox: %s" % self.rememberCheckBox) self.lastCheckBoxValue = self._settings.get_boolean( ["lastCheckBoxValue"]) self._logger.debug("lastCheckBoxValue: %s" % self.lastCheckBoxValue) if self.rememberCheckBox: self._shutdown_printer_enabled = self.lastCheckBoxValue def get_assets(self): return dict(js=["js/shutdownprinter.js"], css=["css/shutdownprinter.css"]) def get_template_configs(self): return [ dict(type="sidebar", name="Shutdown Printer", custom_bindings=False, icon="power-off"), dict(type="settings", custom_bindings=False) ] def get_api_commands(self): return dict(enable=[], disable=[], shutdown=["mode"], abort=[]) def on_api_command(self, command, data): if not user_permission.can(): return make_response("Insufficient rights", 403) if command == "enable": self._shutdown_printer_enabled = True elif command == "disable": self._shutdown_printer_enabled = False elif command == "shutdown": self._shutdown_printer_API_CMD( data["mode"] ) #mode 1 = gcode, mode 2 = api, mode 3 = custom api elif command == "abort": if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._abort_timer_temp.cancel() self._abort_timer_temp = None self._timeout_value = None self._logger.info("Shutdown aborted.") if command == "enable" or command == "disable": self.lastCheckBoxValue = self._shutdown_printer_enabled if self.rememberCheckBox: self._settings.set_boolean(["lastCheckBoxValue"], self.lastCheckBoxValue) self._settings.save() eventManager().fire(Events.SETTINGS_UPDATED) self._plugin_manager.send_plugin_message( self._identifier, dict(shutdownprinterEnabled=self._shutdown_printer_enabled, type="timeout", timeout_value=self._timeout_value)) def on_event(self, event, payload): if event == Events.CLIENT_OPENED: self._plugin_manager.send_plugin_message( self._identifier, dict(shutdownprinterEnabled=self._shutdown_printer_enabled, type="timeout", timeout_value=self._timeout_value)) return if not self._shutdown_printer_enabled: return if event not in [ Events.PRINT_DONE, Events.PRINT_FAILED, Events.PRINT_CANCELLED ]: return if event == Events.PRINT_DONE: self._temperature_target() return elif event == Events.PRINT_CANCELLED and self.printCancelled: self._temperature_target() return elif event == Events.PRINT_FAILED and self.printFailed: self._temperature_target() return else: return def _temperature_target(self): if self._abort_timer_temp is not None: return if self.temperatureTarget: self._abort_timer_temp = RepeatedTimer(2, self._temperature_task) self._abort_timer_temp.start() else: self._timer_start() def _temperature_task(self): if self._printer.is_printing(): self._abort_timer_temp.cancel() self._abort_timer_temp = None return self._temp = self._printer.get_current_temperatures() tester = 0 number = 0 for tool in self._temp.keys(): if not tool == "bed": if self._temp[tool]["actual"] <= self.temperatureValue: tester += 1 number += 1 if tester == number: self._abort_timer_temp.cancel() self._abort_timer_temp = None self._timer_start() def _timer_start(self): if self._abort_timer is not None: return self._logger.info("Starting abort shutdown printer timer.") self._timeout_value = self.abortTimeout self._abort_timer = RepeatedTimer(1, self._timer_task) self._abort_timer.start() def _timer_task(self): if self._timeout_value is None: return self._timeout_value -= 1 self._plugin_manager.send_plugin_message( self._identifier, dict(shutdownprinterEnabled=self._shutdown_printer_enabled, type="timeout", timeout_value=self._timeout_value)) if self._printer.is_printing(): self._timeout_value = 0 self._plugin_manager.send_plugin_message( self._identifier, dict(shutdownprinterEnabled=self._shutdown_printer_enabled, type="timeout", timeout_value=self._timeout_value)) self._abort_timer.cancel() self._abort_timer = None return if self._timeout_value <= 0: if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._shutdown_printer() def _shutdown_printer(self): if self._mode_shutdown_gcode == True: self._shutdown_printer_by_gcode() elif self._mode_shutdown_api == True: self._shutdown_printer_by_API() else: self._shutdown_printer_by_API_custom() def _shutdown_printer_API_CMD(self, mode): if mode == 1: self._shutdown_printer_by_gcode() elif mode == 2: self._shutdown_printer_by_API() elif mode == 3: self._shutdown_printer_by_API_custom() def _shutdown_printer_by_API(self): url = "http://127.0.0.1:" + str( self.api_plugin_port) + "/api/plugin/" + self.api_plugin_name headers = { 'Content-Type': 'application/json', 'X-Api-Key': self.api_key_plugin } data = self.api_json_command self._logger.info("Shutting down printer with API") try: response = requests.post(url, headers=headers, data=data, verify=False, timeout=0.001) except requests.exceptions.Timeout: return except Exception as e: self._logger.error("Failed to connect to call api: %s" % e.message) return def _shutdown_printer_by_API_custom(self): headers = {} if self.api_custom_json_header != "": headers = json.loads(self.api_custom_json_header) if self.api_custom_POST == True: data = self.api_custom_body self._logger.info("Shutting down printer with API custom (POST)") try: response = requests.post(self.api_custom_url, headers=headers, data=data, verify=False, timeout=0.001) except requests.exceptions.Timeout: return except Exception as e: self._logger.error("Failed to connect to call api: %s" % e.message) return elif self.api_custom_GET == True: self._logger.info("Shutting down printer with API custom (GET)") try: response = requests.post(self.api_custom_url, headers=headers, verify=False, timeout=0.001) except requests.exceptions.Timeout: return except Exception as e: self._logger.error("Failed to connect to call api: %s" % e.message) return def _shutdown_printer_by_gcode(self): self._printer.commands("M81 " + self.url) self._logger.info("Shutting down printer with command: M81 " + self.url) def get_settings_defaults(self): return dict(url="", api_key_plugin="", abortTimeout=30, _mode_shutdown_gcode=True, _mode_shutdown_api=False, _mode_shutdown_api_custom=False, api_custom_POST=False, api_custom_GET=False, api_custom_url="", api_custom_json_header="", api_custom_body="", api_plugin_port=5000, temperatureValue=110, _shutdown_printer_enabled=True, printFailed=False, printCancelled=False, rememberCheckBox=False, lastCheckBoxValue=False) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.url = self._settings.get(["url"]) self.api_key_plugin = self._settings.get(["api_key_plugin"]) self._mode_shutdown_gcode = self._settings.get_boolean( ["_mode_shutdown_gcode"]) self._mode_shutdown_api = self._settings.get_boolean( ["_mode_shutdown_api"]) self._mode_shutdown_api_custom = self._settings.get_boolean( ["_mode_shutdown_api_custom"]) self.api_custom_POST = self._settings.get_boolean(["api_custom_POST"]) self.api_custom_GET = self._settings.get_boolean(["api_custom_GET"]) self.api_custom_url = self._settings.get(["api_custom_url"]) self.api_custom_json_header = self._settings.get( ["api_custom_json_header"]) self.api_custom_body = self._settings.get(["api_custom_body"]) self.api_json_command = self._settings.get(["api_json_command"]) self.api_plugin_name = self._settings.get(["api_plugin_name"]) self.api_plugin_port = self._settings.get_int(["api_plugin_port"]) self.temperatureValue = self._settings.get_int(["temperatureValue"]) self.temperatureTarget = self._settings.get_int(["temperatureTarget"]) self.printFailed = self._settings.get_boolean(["printFailed"]) self.printCancelled = self._settings.get_boolean(["printCancelled"]) self.abortTimeout = self._settings.get_int(["abortTimeout"]) self.rememberCheckBox = self._settings.get_boolean( ["rememberCheckBox"]) self.lastCheckBoxValue = self._settings.get_boolean( ["lastCheckBoxValue"]) def get_update_information(self): return dict(shutdownprinter=dict( displayName="Shutdown Printer", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-ShutdownPrinter", current=self._plugin_version, # update method: pip w/ dependency links pip= "https://github.com/devildant/OctoPrint-ShutdownPrinter/archive/{target_version}.zip" ))
class NavBarPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SettingsPlugin): def __init__(self): self.piSocTypes = (["BCM2708", "BCM2709", "BCM2835"]) # Array of raspberry pi SoC's to check against, saves having a large if/then statement later self.debugMode = False # to simulate temp on Win/Mac self.displayRaspiTemp = True self._checkTempTimer = None self.sbc = None def on_after_startup(self): self.displayRaspiTemp = self._settings.get(["displayRaspiTemp"]) self.piSocTypes = self._settings.get(["piSocTypes"]) self._logger.debug("displayRaspiTemp: %s" % self.displayRaspiTemp) if sys.platform == "linux2": self.sbc = SBCFactory().factory(self._logger) if self.sbc.is_supported and self.displayRaspiTemp: self._logger.debug("Let's start RepeatedTimer!") self.startTimer(30.0) # debug mode doesn't work if the OS is linux on a regular pc try: self._logger.debug("is supported? - %s" % self.sbc.is_supported) except: self._logger.debug("Embeded platform is not detected") def startTimer(self, interval): self._checkTempTimer = RepeatedTimer(interval, self.updateSoCTemp, None, None, True) self._checkTempTimer.start() # To try and catch our min temp error, we are adding logging for bed and extruder temps def updateSoCTemp(self): temp = self.sbc.checkSoCTemp() printer_temps = self._printer.get_current_temperatures() self._logger.debug("Found temps: %r" % printer_temps) log_line = "SoC: " + temp if printer_temps is None: self._logger.info("No Extruder and Bed Temperature Data") else: for key in printer_temps.keys(): log_line += " " + key + ": " + key.actual self._logger.info(log_line) self._plugin_manager.send_plugin_message(self._identifier, dict(isSupported=self.sbc.is_supported, soctemp=temp)) ##~~ SettingsPlugin def get_settings_defaults(self): return dict(displayRaspiTemp=self.displayRaspiTemp, piSocTypes=self.piSocTypes) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.displayRaspiTemp = self._settings.get(["displayRaspiTemp"]) if self.displayRaspiTemp: interval = 5.0 if self.debugMode else 30.0 self.startTimer(interval) else: if self._checkTempTimer is not None: try: self._checkTempTimer.cancel() except: pass self._plugin_manager.send_plugin_message(self._identifier, dict()) ##~~ TemplatePlugin API def get_template_configs(self): try: if self.sbc.is_supported: return [ dict(type="settings", template="navbartemp_settings_raspi.jinja2") ] else: return [] except: return [] ##~~ AssetPlugin API def get_assets(self): return { "js": ["js/navbartemp.js"], "css": ["css/navbartemp.css"], "less": ["less/navbartemp.less"] } ##~~ Softwareupdate hook def get_update_information(self): return dict( navbartemp=dict( displayName="Navbar Temperature Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-NavbarTemp", current=self._plugin_version, # update method: pip w/ dependency links pip="https://github.com/imrahil/OctoPrint-NavbarTemp/archive/{target_version}.zip" ) )
class tplinksmartplugPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.ProgressPlugin, octoprint.plugin.EventHandlerPlugin): def __init__(self): self._logger = logging.getLogger("octoprint.plugins.tplinksmartplug") self._tplinksmartplug_logger = logging.getLogger("octoprint.plugins.tplinksmartplug.debug") self.abortTimeout = 0 self._timeout_value = None self._abort_timer = None self._countdown_active = False self.print_job_power = 0.0 self.print_job_started = False self._waitForHeaters = False self._waitForTimelapse = False self._timelapse_active = False self._skipIdleTimer = False self.powerOffWhenIdle = False self._idleTimer = None ##~~ StartupPlugin mixin def on_startup(self, host, port): # setup customized logger from octoprint.logging.handlers import CleaningTimedRotatingFileHandler tplinksmartplug_logging_handler = CleaningTimedRotatingFileHandler(self._settings.get_plugin_logfile_path(postfix="debug"), when="D", backupCount=3) tplinksmartplug_logging_handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s")) tplinksmartplug_logging_handler.setLevel(logging.DEBUG) self._tplinksmartplug_logger.addHandler(tplinksmartplug_logging_handler) self._tplinksmartplug_logger.setLevel(logging.DEBUG if self._settings.get_boolean(["debug_logging"]) else logging.INFO) self._tplinksmartplug_logger.propagate = False self.db_path = os.path.join(self.get_plugin_data_folder(),"energy_data.db") if not os.path.exists(self.db_path): db = sqlite3.connect(self.db_path) cursor = db.cursor() cursor.execute('''CREATE TABLE energy_data(id INTEGER PRIMARY KEY, ip TEXT, timestamp TEXT, current REAL, power REAL, total REAL, voltage REAL)''') db.commit() db.close() self.abortTimeout = self._settings.get_int(["abortTimeout"]) self._tplinksmartplug_logger.debug("abortTimeout: %s" % self.abortTimeout) self.powerOffWhenIdle = self._settings.get_boolean(["powerOffWhenIdle"]) self._tplinksmartplug_logger.debug("powerOffWhenIdle: %s" % self.powerOffWhenIdle) self.idleTimeout = self._settings.get_int(["idleTimeout"]) self._tplinksmartplug_logger.debug("idleTimeout: %s" % self.idleTimeout) self.idleIgnoreCommands = self._settings.get(["idleIgnoreCommands"]) self._idleIgnoreCommandsArray = self.idleIgnoreCommands.split(',') self._tplinksmartplug_logger.debug("idleIgnoreCommands: %s" % self.idleIgnoreCommands) self.idleTimeoutWaitTemp = self._settings.get_int(["idleTimeoutWaitTemp"]) self._tplinksmartplug_logger.debug("idleTimeoutWaitTemp: %s" % self.idleTimeoutWaitTemp) self._start_idle_timer() def on_after_startup(self): self._logger.info("TPLinkSmartplug loaded!") if self._settings.get(["pollingEnabled"]): self.poll_status = RepeatedTimer(int(self._settings.get(["pollingInterval"]))*60, self.check_statuses) self.poll_status.start() ##~~ SettingsPlugin mixin def get_settings_defaults(self): return dict( debug_logging = False, arrSmartplugs = [], pollingInterval = 15, pollingEnabled = False, thermal_runaway_monitoring = False, thermal_runaway_max_bed = 0, thermal_runaway_max_extruder = 0, event_on_error_monitoring = False, event_on_disconnect_monitoring = False, cost_rate = 0, abortTimeout = 30, powerOffWhenIdle = False, idleTimeout = 30, idleIgnoreCommands = 'M105', idleTimeoutWaitTemp = 50 ) def on_settings_save(self, data): old_debug_logging = self._settings.get_boolean(["debug_logging"]) old_polling_value = self._settings.get_boolean(["pollingEnabled"]) old_polling_timer = self._settings.get(["pollingInterval"]) old_powerOffWhenIdle = self._settings.get_boolean(["powerOffWhenIdle"]) old_idleTimeout = self._settings.get_int(["idleTimeout"]) old_idleIgnoreCommands = self._settings.get(["idleIgnoreCommands"]) old_idleTimeoutWaitTemp = self._settings.get_int(["idleTimeoutWaitTemp"]) octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.abortTimeout = self._settings.get_int(["abortTimeout"]) self.powerOffWhenIdle = self._settings.get_boolean(["powerOffWhenIdle"]) self.idleTimeout = self._settings.get_int(["idleTimeout"]) self.idleIgnoreCommands = self._settings.get(["idleIgnoreCommands"]) self._idleIgnoreCommandsArray = self.idleIgnoreCommands.split(',') self.idleTimeoutWaitTemp = self._settings.get_int(["idleTimeoutWaitTemp"]) if self.powerOffWhenIdle != old_powerOffWhenIdle: self._plugin_manager.send_plugin_message(self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) if self.powerOffWhenIdle == True: self._tplinksmartplug_logger.debug("Settings saved, Automatic Power Off Endabled, starting idle timer...") self._start_idle_timer() new_debug_logging = self._settings.get_boolean(["debug_logging"]) new_polling_value = self._settings.get_boolean(["pollingEnabled"]) new_polling_timer = self._settings.get(["pollingInterval"]) if old_debug_logging != new_debug_logging: if new_debug_logging: self._tplinksmartplug_logger.setLevel(logging.DEBUG) else: self._tplinksmartplug_logger.setLevel(logging.INFO) if old_polling_value != new_polling_value or old_polling_timer != new_polling_timer: if self.poll_status: self.poll_status.cancel() if new_polling_value: self.poll_status = RepeatedTimer(int(self._settings.get(["pollingInterval"]))*60, self.check_statuses) self.poll_status.start() def get_settings_version(self): return 11 def on_settings_migrate(self, target, current=None): if current is None or current < 5: # Reset plug settings to defaults. self._tplinksmartplug_logger.debug("Resetting arrSmartplugs for tplinksmartplug settings.") self._settings.set(['arrSmartplugs'], self.get_settings_defaults()["arrSmartplugs"]) elif current == 6: # Loop through plug array and set emeter to None arrSmartplugs_new = [] for plug in self._settings.get(['arrSmartplugs']): plug["emeter"] = None arrSmartplugs_new.append(plug) self._tplinksmartplug_logger.info("Updating plug array, converting") self._tplinksmartplug_logger.info(self._settings.get(['arrSmartplugs'])) self._tplinksmartplug_logger.info("to") self._tplinksmartplug_logger.info(arrSmartplugs_new) self._settings.set(["arrSmartplugs"],arrSmartplugs_new) elif current == 7: # Loop through plug array and set emeter to None arrSmartplugs_new = [] for plug in self._settings.get(['arrSmartplugs']): plug["emeter"] = dict(get_realtime = False) arrSmartplugs_new.append(plug) self._tplinksmartplug_logger.info("Updating plug array, converting") self._tplinksmartplug_logger.info(self._settings.get(['arrSmartplugs'])) self._tplinksmartplug_logger.info("to") self._tplinksmartplug_logger.info(arrSmartplugs_new) self._settings.set(["arrSmartplugs"],arrSmartplugs_new) if current is not None and current < 9: arrSmartplugs_new = [] for plug in self._settings.get(['arrSmartplugs']): plug["thermal_runaway"] = False arrSmartplugs_new.append(plug) self._settings.set(["arrSmartplugs"],arrSmartplugs_new) if current is not None and current < 10: arrSmartplugs_new = [] for plug in self._settings.get(['arrSmartplugs']): plug["event_on_error"] = False plug["event_on_disconnect"] = False arrSmartplugs_new.append(plug) self._settings.set(["arrSmartplugs"],arrSmartplugs_new) if current is not None and current < 11: arrSmartplugs_new = [] for plug in self._settings.get(['arrSmartplugs']): plug["automaticShutdownEnabled"] = False arrSmartplugs_new.append(plug) self._settings.set(["arrSmartplugs"],arrSmartplugs_new) ##~~ AssetPlugin mixin def get_assets(self): return dict( js=["js/jquery-ui.min.js","js/knockout-sortable.js","js/fontawesome-iconpicker.js","js/ko.iconpicker.js","js/tplinksmartplug.js","js/knockout-bootstrap.min.js","js/ko.observableDictionary.js","js/plotly-latest.min.js"], css=["css/font-awesome.min.css","css/font-awesome-v4-shims.min.css","css/fontawesome-iconpicker.css","css/tplinksmartplug.css"] ) ##~~ TemplatePlugin mixin def get_template_configs(self): #templates_to_load = [dict(type="navbar", custom_bindings=True),dict(type="settings", custom_bindings=True),dict(type="sidebar", icon="plug", custom_bindings=True, data_bind="visible: show_sidebar()", template="tplinksmartplug_sidebar.jinja2", template_header="tplinksmartplug_sidebar_header.jinja2"),dict(type="tab", custom_bindings=True)] templates_to_load = [dict(type="navbar", custom_bindings=True),dict(type="settings", custom_bindings=True),dict(type="sidebar", icon="plug", custom_bindings=True, data_bind="visible: arrSmartplugs().length > 0", template="tplinksmartplug_sidebar.jinja2", template_header="tplinksmartplug_sidebar_header.jinja2"),dict(type="tab", custom_bindings=True, data_bind="visible: show_sidebar()", template="tplinksmartplug_tab.jinja2")] return templates_to_load ##~~ ProgressPlugin mixin def on_print_progress(self, storage, path, progress): self._tplinksmartplug_logger.debug("Checking statuses during print progress (%s)." % progress) self.check_statuses() self._plugin_manager.send_plugin_message(self._identifier, dict(updatePlot=True)) if self.powerOffWhenIdle == True and not (self._skipIdleTimer == True): self._tplinksmartplug_logger.debug("Resetting idle timer during print progress (%s)..." % progress) self._waitForHeaters = False self._reset_idle_timer() ##~~ SimpleApiPlugin mixin def turn_on(self, plugip): self._tplinksmartplug_logger.debug("Turning on %s." % plugip) plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip) self._tplinksmartplug_logger.debug(plug) if "/" in plugip: plug_ip, plug_num = plugip.split("/") else: plug_ip = plugip plug_num = -1 if plug["useCountdownRules"] and int(plug["countdownOnDelay"]) > 0: self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug_ip, plug_num) chk = self.lookup(self.sendCommand(json.loads('{"count_down":{"add_rule":{"enable":1,"delay":%s,"act":1,"name":"turn on"}}}' % plug["countdownOnDelay"]),plug_ip,plug_num),*["count_down","add_rule","err_code"]) if chk == 0: self._countdown_active = True c = threading.Timer(int(plug["countdownOnDelay"])+3,self._plugin_manager.send_plugin_message,[self._identifier, dict(check_status=True,ip=plugip)]) c.start() else: turn_on_cmnd = dict(system=dict(set_relay_state=dict(state=1))) chk = self.lookup(self.sendCommand(turn_on_cmnd,plug_ip,plug_num),*["system","set_relay_state","err_code"]) self._tplinksmartplug_logger.debug(chk) if chk == 0: if plug["autoConnect"] and self._printer.is_closed_or_error(): c = threading.Timer(int(plug["autoConnectDelay"]),self._printer.connect) c.start() if plug["sysCmdOn"]: t = threading.Timer(int(plug["sysCmdOnDelay"]),os.system,args=[plug["sysRunCmdOn"]]) t.start() if self.powerOffWhenIdle == True and plug["automaticShutdownEnabled"] == True: self._tplinksmartplug_logger.debug("Resetting idle timer since plug %s was just turned on." % plugip) self._waitForHeaters = False self._reset_idle_timer() return self.check_status(plugip) def turn_off(self, plugip): self._tplinksmartplug_logger.debug("Turning off %s." % plugip) plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip) self._tplinksmartplug_logger.debug(plug) if "/" in plugip: plug_ip, plug_num = plugip.split("/") else: plug_ip = plugip plug_num = -1 if plug["useCountdownRules"] and int(plug["countdownOffDelay"]) > 0: self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug_ip,plug_num) chk = self.lookup(self.sendCommand(json.loads('{"count_down":{"add_rule":{"enable":1,"delay":%s,"act":0,"name":"turn off"}}}' % plug["countdownOffDelay"]),plug_ip,plug_num),*["count_down","add_rule","err_code"]) if chk == 0: self._countdown_active = True c = threading.Timer(int(plug["countdownOffDelay"])+3,self._plugin_manager.send_plugin_message,[self._identifier, dict(check_status=True,ip=plugip)]) c.start() if plug["sysCmdOff"]: t = threading.Timer(int(plug["sysCmdOffDelay"]),os.system,args=[plug["sysRunCmdOff"]]) t.start() if plug["autoDisconnect"]: self._printer.disconnect() time.sleep(int(plug["autoDisconnectDelay"])) if not plug["useCountdownRules"]: turn_off_cmnd = dict(system=dict(set_relay_state=dict(state=0))) chk = self.lookup(self.sendCommand(turn_off_cmnd,plug_ip,plug_num),*["system","set_relay_state","err_code"]) self._tplinksmartplug_logger.debug(chk) if chk == 0: return self.check_status(plugip) def check_statuses(self): for plug in self._settings.get(["arrSmartplugs"]): chk = self.check_status(plug["ip"]) self._plugin_manager.send_plugin_message(self._identifier, chk) def check_status(self, plugip): self._tplinksmartplug_logger.debug("Checking status of %s." % plugip) if plugip != "": emeter_data = None today = datetime.today() check_status_cmnd = dict(system = dict(get_sysinfo = dict())) plug_ip = plugip.split("/") self._tplinksmartplug_logger.debug(check_status_cmnd) if len(plug_ip) == 2: response = self.sendCommand(check_status_cmnd, plug_ip[0], plug_ip[1]) timer_chk = self.lookup(response, *["system","get_sysinfo","children"])[int(plug_ip[1])]["on_time"] else: response = self.sendCommand(check_status_cmnd, plug_ip[0]) timer_chk = self.deep_get(response,["system","get_sysinfo","on_time"], default=0) if timer_chk == 0 and self._countdown_active: self._tplinksmartplug_logger.debug("Clearing previously active countdown timer flag") self._countdown_active = False self._tplinksmartplug_logger.debug(self.deep_get(response,["system","get_sysinfo","feature"], default="")) if "ENE" in self.deep_get(response,["system","get_sysinfo","feature"], default=""): # if "ENE" in self.lookup(response, *["system","get_sysinfo","feature"]): emeter_data_cmnd = dict(emeter = dict(get_realtime = dict())) if len(plug_ip) == 2: check_emeter_data = self.sendCommand(emeter_data_cmnd, plug_ip[0], plug_ip[1]) else: check_emeter_data = self.sendCommand(emeter_data_cmnd, plug_ip[0]) if self.lookup(check_emeter_data, *["emeter","get_realtime"]): emeter_data = check_emeter_data["emeter"] if "voltage_mv" in emeter_data["get_realtime"]: v = emeter_data["get_realtime"]["voltage_mv"] / 1000.0 elif "voltage" in emeter_data["get_realtime"]: v = emeter_data["get_realtime"]["voltage"] else: v = "" if "current_ma" in emeter_data["get_realtime"]: c = emeter_data["get_realtime"]["current_ma"] / 1000.0 elif "current" in emeter_data["get_realtime"]: c = emeter_data["get_realtime"]["current"] else: c = "" if "power_mw" in emeter_data["get_realtime"]: p = emeter_data["get_realtime"]["power_mw"] / 1000.0 elif "power" in emeter_data["get_realtime"]: p = emeter_data["get_realtime"]["power"] else: p = "" if "total_wh" in emeter_data["get_realtime"]: t = emeter_data["get_realtime"]["total_wh"] / 1000.0 elif "total" in emeter_data["get_realtime"]: t = emeter_data["get_realtime"]["total"] else: t = "" db = sqlite3.connect(self.db_path) cursor = db.cursor() cursor.execute('''INSERT INTO energy_data(ip, timestamp, current, power, total, voltage) VALUES(?,?,?,?,?,?)''', [plugip,today.isoformat(' '),c,p,t,v]) db.commit() db.close() if len(plug_ip) == 2: chk = self.lookup(response,*["system","get_sysinfo","children"]) if chk: chk = chk[int(plug_ip[1])]["state"] else: chk = self.lookup(response,*["system","get_sysinfo","relay_state"]) if chk == 1: return dict(currentState="on",emeter=emeter_data,ip=plugip) elif chk == 0: return dict(currentState="off",emeter=emeter_data,ip=plugip) else: self._tplinksmartplug_logger.debug(response) return dict(currentState="unknown",emeter=emeter_data,ip=plugip) def get_api_commands(self): return dict( turnOn=["ip"], turnOff=["ip"], checkStatus=["ip"], getEnergyData=["ip"], enableAutomaticShutdown=[], disableAutomaticShutdown=[], abortAutomaticShutdown=[]) def on_api_get(self, request): self._tplinksmartplug_logger.debug(request.args) if request.args.get("checkStatus"): response = self.check_status(request.args.get("checkStatus")) return flask.jsonify(response) def on_api_command(self, command, data): if not user_permission.can(): return flask.make_response("Insufficient rights", 403) if command == 'turnOn': response = self.turn_on("{ip}".format(**data)) self._plugin_manager.send_plugin_message(self._identifier, response) elif command == 'turnOff': response = self.turn_off("{ip}".format(**data)) self._plugin_manager.send_plugin_message(self._identifier, response) elif command == 'checkStatus': response = self.check_status("{ip}".format(**data)) elif command == 'getEnergyData': db = sqlite3.connect(self.db_path) cursor = db.cursor() cursor.execute('''SELECT timestamp, current, power, total, voltage FROM energy_data WHERE ip=? ORDER BY timestamp DESC LIMIT ?,?''', (data["ip"],data["record_offset"],data["record_limit"])) response = {'energy_data' : cursor.fetchall()} db.close() self._tplinksmartplug_logger.debug(response) #SELECT * FROM energy_data WHERE ip = '192.168.0.102' LIMIT 0,30 elif command == 'enableAutomaticShutdown': self.powerOffWhenIdle = True elif command == 'disableAutomaticShutdown': self.powerOffWhenIdle = False elif command == 'abortAutomaticShutdown': if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._timeout_value = None for plug in self._settings.get(["arrSmartplugs"]): if plug["useCountdownRules"] and int(plug["countdownOffDelay"]) > 0: if "/" in plug["ip"]: plug_ip, plug_num = plug["ip"].split("/") else: plug_ip = plug["ip"] plug_num = -1 self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug_ip,plug_num) self._tplinksmartplug_logger.debug("Cleared countdown rules for %s" % plug["ip"]) self._tplinksmartplug_logger.debug("Power off aborted.") self._tplinksmartplug_logger.debug("Restarting idle timer.") self._reset_idle_timer() else: response = dict(ip = data.ip, currentState = "unknown") if command == "enableAutomaticShutdown" or command == "disableAutomaticShutdown": self._tplinksmartplug_logger.debug("Automatic power off setting changed: %s" % self.powerOffWhenIdle) self._settings.set_boolean(["powerOffWhenIdle"], self.powerOffWhenIdle) self._settings.save() #eventManager().fire(Events.SETTINGS_UPDATED) if command == "enableAutomaticShutdown" or command == "disableAutomaticShutdown" or command == "abortAutomaticShutdown": self._plugin_manager.send_plugin_message(self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) else: return flask.jsonify(response) ##~~ EventHandlerPlugin mixin def on_event(self, event, payload): # Error Event if event == Events.ERROR and self._settings.getBoolean(["event_on_error_monitoring"]) == True: self._tplinksmartplug_logger.debug("powering off due to %s event." % event) for plug in self._settings.get(['arrSmartplugs']): if plug["event_on_error"] == True: self._tplinksmartplug_logger.debug("powering off %s due to %s event." % (plug["ip"], event)) response = self.turn_off(plug["ip"]) if response["currentState"] == "off": self._plugin_manager.send_plugin_message(self._identifier, response) # Disconnected Event if event == Events.DISCONNECTED and self._settings.getBoolean(["event_on_disconnect_monitoring"]) == True: self._tplinksmartplug_logger.debug("powering off due to %s event." % event) for plug in self._settings.get(['arrSmartplugs']): if plug["event_on_disconnect"] == True: self._tplinksmartplug_logger.debug("powering off %s due to %s event." % (plug["ip"], event)) response = self.turn_off(plug["ip"]) if response["currentState"] == "off": self._plugin_manager.send_plugin_message(self._identifier, response) # Client Opened Event if event == Events.CLIENT_OPENED: self._plugin_manager.send_plugin_message(self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) return # Cancelled Print Interpreted Event if event == Events.PRINT_FAILED and not self._printer.is_closed_or_error(): self._tplinksmartplug_logger.debug("Print cancelled, reseting job_power to 0") self.print_job_power = 0.0 self.print_job_started = False return # Print Started Event if event == Events.PRINT_STARTED and self._settings.getFloat(["cost_rate"]) > 0: self.print_job_started = True self._tplinksmartplug_logger.debug(payload.get("path", None)) for plug in self._settings.get(["arrSmartplugs"]): status = self.check_status(plug["ip"]) self.print_job_power -= float(self.deep_get(status,["emeter","get_realtime","total_wh"], default=0)) / 1000 self.print_job_power -= float(self.deep_get(status,["emeter","get_realtime","total"], default=0)) self._tplinksmartplug_logger.debug(self.print_job_power) if event == Events.PRINT_STARTED and self.powerOffWhenIdle == True: if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._tplinksmartplug_logger.debug("Power off aborted because starting new print.") if self._idleTimer is not None: self._reset_idle_timer() self._timeout_value = None self._plugin_manager.send_plugin_message(self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) if event == Events.PRINT_STARTED and self._countdown_active: for plug in self._settings.get(["arrSmartplugs"]): if plug["useCountdownRules"] and int(plug["countdownOffDelay"]) > 0: if "/" in plug["ip"]: plug_ip, plug_num = plug["ip"].split("/") else: plug_ip = plug["ip"] plug_num = -1 self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug_ip,plug_num) self._tplinksmartplug_logger.debug("Cleared countdown rules for %s" % plug["ip"]) # Print Done Event if event == Events.PRINT_DONE and self.print_job_started: self._tplinksmartplug_logger.debug(payload) for plug in self._settings.get(["arrSmartplugs"]): status = self.check_status(plug["ip"]) self.print_job_power += float(self.deep_get(status,["emeter","get_realtime","total_wh"], default=0)) / 1000 self.print_job_power += float(self.deep_get(status,["emeter","get_realtime","total"], default=0)) self._tplinksmartplug_logger.debug(self.print_job_power) hours = (payload.get("time", 0)/60)/60 self._tplinksmartplug_logger.debug("hours: %s" % hours) power_used = self.print_job_power * hours self._tplinksmartplug_logger.debug("power used: %s" % power_used) power_cost = power_used * self._settings.getFloat(["cost_rate"]) self._tplinksmartplug_logger.debug("power total cost: %s" % power_cost) self._storage_interface = self._file_manager._storage(payload.get("origin", "local")) self._storage_interface.set_additional_metadata(payload.get("path"), "statistics", dict(lastPowerCost=dict(_default=float('{:.4f}'.format(power_cost)))), merge=True) self.print_job_power = 0.0 self.print_job_started = False if self.powerOffWhenIdle == True and event == Events.MOVIE_RENDERING: self._tplinksmartplug_logger.debug("Timelapse generation started: %s" % payload.get("movie_basename", "")) self._timelapse_active = True if self._timelapse_active and event == Events.MOVIE_DONE or event == Events.MOVIE_FAILED: self._tplinksmartplug_logger.debug("Timelapse generation finished: %s. Return Code: %s" % (payload.get("movie_basename", ""), payload.get("returncode", "completed"))) self._timelapse_active = False ##~~ Idle Timeout def _start_idle_timer(self): self._stop_idle_timer() if self.powerOffWhenIdle: self._idleTimer = ResettableTimer(self.idleTimeout * 60, self._idle_poweroff) self._idleTimer.start() def _stop_idle_timer(self): if self._idleTimer: self._idleTimer.cancel() self._idleTimer = None def _reset_idle_timer(self): try: if self._idleTimer.is_alive(): self._idleTimer.reset() else: raise Exception() except: self._start_idle_timer() def _idle_poweroff(self): if not self.powerOffWhenIdle: return if self._waitForHeaters: return if self._waitForTimelapse: return if self._printer.is_printing() or self._printer.is_paused(): return self._tplinksmartplug_logger.debug("Idle timeout reached after %s minute(s). Turning heaters off prior to powering off plugs." % self.idleTimeout) if self._wait_for_heaters(): self._tplinksmartplug_logger.debug("Heaters below temperature.") if self._wait_for_timelapse(): self._timer_start() else: self._tplinksmartplug_logger.debug("Aborted power off due to activity.") ##~~ Timelapse Monitoring def _wait_for_timelapse(self): self._waitForTimelapse = True self._tplinksmartplug_logger.debug("Checking timelapse status before shutting off power...") while True: if not self._waitForTimelapse: return False if not self._timelapse_active: self._waitForTimelapse = False return True self._tplinksmartplug_logger.debug("Waiting for timelapse before shutting off power...") time.sleep(5) ##~~ Temperature Cooldown def _wait_for_heaters(self): self._waitForHeaters = True heaters = self._printer.get_current_temperatures() for heater, entry in heaters.items(): target = entry.get("target") if target is None: # heater doesn't exist in fw continue try: temp = float(target) except ValueError: # not a float for some reason, skip it continue if temp != 0: self._tplinksmartplug_logger.debug("Turning off heater: %s" % heater) self._skipIdleTimer = True self._printer.set_temperature(heater, 0) self._skipIdleTimer = False else: self._tplinksmartplug_logger.debug("Heater %s already off." % heater) while True: if not self._waitForHeaters: return False heaters = self._printer.get_current_temperatures() highest_temp = 0 heaters_above_waittemp = [] for heater, entry in heaters.items(): if not heater.startswith("tool"): continue actual = entry.get("actual") if actual is None: # heater doesn't exist in fw continue try: temp = float(actual) except ValueError: # not a float for some reason, skip it continue self._tplinksmartplug_logger.debug("Heater %s = %sC" % (heater,temp)) if temp > self.idleTimeoutWaitTemp: heaters_above_waittemp.append(heater) if temp > highest_temp: highest_temp = temp if highest_temp <= self.idleTimeoutWaitTemp: self._waitForHeaters = False return True self._tplinksmartplug_logger.debug("Waiting for heaters(%s) before shutting power off..." % ', '.join(heaters_above_waittemp)) time.sleep(5) ##~~ Abort Power Off Timer def _timer_start(self): if self._abort_timer is not None: return self._tplinksmartplug_logger.debug("Starting abort power off timer.") self._timeout_value = self.abortTimeout self._abort_timer = RepeatedTimer(1, self._timer_task) self._abort_timer.start() def _timer_task(self): if self._timeout_value is None: return self._timeout_value -= 1 self._plugin_manager.send_plugin_message(self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) if self._timeout_value <= 0: if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._shutdown_system() def _shutdown_system(self): self._tplinksmartplug_logger.debug("Automatically powering off enabled plugs.") for plug in self._settings.get(['arrSmartplugs']): if plug.get("automaticShutdownEnabled", False): response = self.turn_off("{ip}".format(**plug)) self._plugin_manager.send_plugin_message(self._identifier, response) ##~~ Utilities def _get_device_id(self, plugip): response = self._settings.get([plugip]) if not response: check_status_cmnd = dict(system = dict(get_sysinfo = dict())) plug_ip = plugip.split("/") self._tplinksmartplug_logger.debug(check_status_cmnd) plug_data = self.sendCommand(check_status_cmnd, plug_ip[0]) if len(plug_ip) == 2: response = self.deep_get(plug_data,["system","get_sysinfo","children"], default=False) if response: response = response[int(plug_ip[1])]["id"] else: response = self.deep_get(response,["system","get_sysinfo","deviceId"]) if response: self._settings.set([plugip],response) self._settings.save() self._tplinksmartplug_logger.debug("get_device_id response: %s" % response) return response def deep_get(self, d, keys, default=None): """ Example: d = {'meta': {'status': 'OK', 'status_code': 200}} deep_get(d, ['meta', 'status_code']) # => 200 deep_get(d, ['garbage', 'status_code']) # => None deep_get(d, ['meta', 'garbage'], default='-') # => '-' """ assert type(keys) is list if d is None: return default if not keys: return d return self.deep_get(d.get(keys[0]), keys[1:], default) def lookup(self, dic, key, *keys): if keys: return self.lookup(dic.get(key, {}), *keys) return dic.get(key) def plug_search(self, list, key, value): for item in list: if item[key] == value.strip(): return item def encrypt(self, string): key = 171 result = b"\0\0\0" + bytes([len(string)]) for i in bytes(string.encode('latin-1')): a = key ^ i key = a result += bytes([a]) return result def decrypt(self, string): key = 171 result = b"" for i in bytes(string): a = key ^ i key = i result += bytes([a]) return result.decode('latin-1') def sendCommand(self, cmd, plugip, plug_num = -1): commands = {'info' : '{"system":{"get_sysinfo":{}}}', 'on' : '{"system":{"set_relay_state":{"state":1}}}', 'off' : '{"system":{"set_relay_state":{"state":0}}}', 'cloudinfo': '{"cnCloud":{"get_info":{}}}', 'wlanscan' : '{"netif":{"get_scaninfo":{"refresh":0}}}', 'time' : '{"time":{"get_time":{}}}', 'schedule' : '{"schedule":{"get_rules":{}}}', 'countdown': '{"count_down":{"get_rules":{}}}', 'antitheft': '{"anti_theft":{"get_rules":{}}}', 'reboot' : '{"system":{"reboot":{"delay":1}}}', 'reset' : '{"system":{"reset":{"delay":1}}}' } if re.search('/\d+$', plugip): self._tplinksmartplug_logger.exception("Internal error passing unsplit %s", plugip) # try to connect via ip address try: socket.inet_aton(plugip) ip = plugip self._tplinksmartplug_logger.debug("IP %s is valid." % plugip) except socket.error: # try to convert hostname to ip self._tplinksmartplug_logger.debug("Invalid ip %s trying hostname." % plugip) try: ip = socket.gethostbyname(plugip) self._tplinksmartplug_logger.debug("Hostname %s is valid." % plugip) except (socket.herror, socket.gaierror): self._tplinksmartplug_logger.debug("Invalid hostname %s." % plugip) return {"system":{"get_sysinfo":{"relay_state":3}},"emeter":{"err_code": True}} if int(plug_num) >= 0: plug_ip_num = plugip + "/" + plug_num cmd["context"] = dict(child_ids = [self._get_device_id(plug_ip_num)]) try: self._tplinksmartplug_logger.debug("Sending command %s to %s" % (cmd,plugip)) sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_tcp.connect((ip, 9999)) sock_tcp.send(self.encrypt(json.dumps(cmd))) data = sock_tcp.recv(1024) len_data = unpack('>I', data[0:4]) while (len(data) - 4) < len_data[0]: data = data + sock_tcp.recv(1024) sock_tcp.close() self._tplinksmartplug_logger.debug(self.decrypt(data)) return json.loads(self.decrypt(data[4:])) except socket.error: self._tplinksmartplug_logger.debug("Could not connect to %s." % plugip) return {"system":{"get_sysinfo":{"relay_state":3}},"emeter":{"err_code": True}} ##~~ Gcode processing hook def gcode_turn_off(self, plug): if plug["warnPrinting"] and self._printer.is_printing(): self._tplinksmartplug_logger.debug("Not powering off %s because printer is printing." % plug["label"]) else: chk = self.turn_off(plug["ip"]) self._plugin_manager.send_plugin_message(self._identifier, chk) def gcode_turn_on(self, plug): chk = self.turn_on(plug["ip"]) self._plugin_manager.send_plugin_message(self._identifier, chk) def processGCODE(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): if gcode: if cmd.startswith("M80"): plugip = re.sub(r'^M80\s?', '', cmd) self._tplinksmartplug_logger.debug("Received M80 command, attempting power on of %s." % plugip) plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip) self._tplinksmartplug_logger.debug(plug) if plug and plug["gcodeEnabled"]: t = threading.Timer(int(plug["gcodeOnDelay"]),self.gcode_turn_on,[plug]) t.start() return elif cmd.startswith("M81"): plugip = re.sub(r'^M81\s?', '', cmd) self._tplinksmartplug_logger.debug("Received M81 command, attempting power off of %s." % plugip) plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip) self._tplinksmartplug_logger.debug(plug) if plug and plug["gcodeEnabled"]: t = threading.Timer(int(plug["gcodeOffDelay"]),self.gcode_turn_off,[plug]) t.start() return elif self.powerOffWhenIdle and not (gcode in self._idleIgnoreCommandsArray): self._waitForHeaters = False self._reset_idle_timer() else: return def processAtCommand(self, comm_instance, phase, command, parameters, tags=None, *args, **kwargs): self._logger.info(command) self._logger.info(parameters) if command == "TPLINKON": plugip = parameters self._tplinksmartplug_logger.debug("Received @TPLINKON command, attempting power on of %s." % plugip) plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip) self._tplinksmartplug_logger.debug(plug) if plug and plug["gcodeEnabled"]: t = threading.Timer(int(plug["gcodeOnDelay"]),self.gcode_turn_on,[plug]) t.start() return None if command == "TPLINKOFF": plugip = parameters self._tplinksmartplug_logger.debug("Received TPLINKOFF command, attempting power off of %s." % plugip) plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip) self._tplinksmartplug_logger.debug(plug) if plug and plug["gcodeEnabled"]: t = threading.Timer(int(plug["gcodeOffDelay"]),self.gcode_turn_off,[plug]) t.start() return None ##~~ Temperatures received hook def check_temps(self, parsed_temps): thermal_runaway_triggered = False for k, v in parsed_temps.items(): if k == "B" and v[0] > int(self._settings.get(["thermal_runaway_max_bed"])): self._tplinksmartplug_logger.debug("Max bed temp reached, shutting off plugs.") thermal_runaway_triggered = True if k.startswith("T") and v[0] > int(self._settings.get(["thermal_runaway_max_extruder"])): self._tplinksmartplug_logger.debug("Extruder max temp reached, shutting off plugs.") thermal_runaway_triggered = True if thermal_runaway_triggered == True: for plug in self._settings.get(['arrSmartplugs']): if plug["thermal_runaway"] == True: response = self.turn_off(plug["ip"]) if response["currentState"] == "off": self._plugin_manager.send_plugin_message(self._identifier, response) def monitor_temperatures(self, comm, parsed_temps): if self._settings.get(["thermal_runaway_monitoring"]): # Run inside it's own thread to prevent communication blocking t = threading.Timer(0,self.check_temps,[parsed_temps]) t.start() return parsed_temps ##~~ Softwareupdate hook def get_update_information(self): return dict( tplinksmartplug=dict( displayName="TP-Link Smartplug", displayVersion=self._plugin_version, type="github_release", user="******", repo="OctoPrint-TPLinkSmartplug", current=self._plugin_version, pip="https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/archive/{target_version}.zip" ) )
class MachineCom(octoprint.plugin.MachineComPlugin): #pylint: disable=too-many-instance-attributes, too-many-public-methods _logger = None _serialLogger = None _state = None _port = None _baudrate = None _printer_uri = None _print_job_uri = None _authentise_process = None _authentise_model = None _authentise_url = None _session = None _command_uri_queue = None _printer_status_timer = None _tool_tempuratures = None _bed_tempurature = None _print_progress = None _callback = None _printer_profile_manager = None monitoring_thread = None _monitoring_active = False _errorValue = None def __init__(self): self._logger = logging.getLogger(__name__) self._serialLogger = logging.getLogger("SERIAL") self._command_uri_queue = comm_helpers.TypedQueue() self._state = self.STATE_NONE def startup(self, callbackObject=None, printerProfileManager=None): if callbackObject == None: callbackObject = comm_helpers.MachineComPrintCallback() self._callback = callbackObject self._printer_profile_manager = printerProfileManager self._authentise_url = self._settings.get(['authentise_url']) #pylint: disable=no-member def connect(self, port=None, baudrate=None): try: self._session = helpers.session(self._settings) #pylint: disable=no-member helpers.claim_node(self.node_uuid, self._settings, self._logger) #pylint: disable=no-member except (helpers.ClaimNodeException, helpers.SessionException) as e: self._errorValue = e.message self._change_state(PRINTER_STATE['ERROR']) return if port == None: port = settings().get(["serial", "port"]) if baudrate == None: settings_baudrate = settings().getInt(["serial", "baudrate"]) if settings_baudrate is None: baudrate = 0 else: baudrate = settings_baudrate self._port = port self._baudrate = baudrate self._printer_uri = self._get_or_create_printer(port, baudrate) self._authentise_process = helpers.run_client(self._settings) #pylint: disable=no-member # monitoring thread self._monitoring_active = True self.monitoring_thread = threading.Thread(target=self._monitor_loop, name="comm._monitor") self.monitoring_thread.daemon = True self.monitoring_thread.start() self._printer_status_timer = RepeatedTimer( lambda: comm_helpers.get_interval("temperature", default_value=10.0), self._update_printer_data, run_first=True ) self._printer_status_timer.start() self._change_state(PRINTER_STATE['CONNECTING']) def _get_or_create_printer(self, port, baud_rate): client_url = urlparse.urljoin(self._authentise_url, '/client/{}/'.format(self.node_uuid)) #pylint: disable=no-member url = urlparse.urljoin(self._authentise_url, '/printer/instance/?filter[client]={}'.format(quote_plus(client_url))) target_printer = None self._log('Getting printer list from: {}'.format(url)) printer_get_resp = self._session.get(url=url) for printer in printer_get_resp.json()["resources"]: if printer['port'] == port: target_printer = printer self._log('Printer {} matches selected port {}'.format(printer, port)) break if target_printer: if target_printer['baud_rate'] != baud_rate: self._session.put(target_printer["uri"], json={'baud_rate': baud_rate}) return target_printer['uri'] else: self._log('No printer found for port {}. Creating it.'.format(port)) if os.path.exists('/usr/lib/python2.7/dist-packages/octoprint/static/img/type_a_machines.svg'): model = 14 else: model = 9 payload = { 'baud_rate' : baud_rate, 'client' : client_url, 'name' : 'Octoprint Printer', 'port' : port, 'printer_model' : 'https://print.dev-auth.com/printer/model/{}/'.format(model), } create_printer_resp = self._session.post(urlparse.urljoin(self._authentise_url, '/printer/instance/'), json=payload) return create_printer_resp.headers["Location"] # #~~ internal state management def _change_state(self, new_state): # Change the printer state if self._state == new_state: return old_state = self._state old_state_string = self.getStateString() self._state = new_state self._log("Changed printer state from '{}' to '{}'".format(old_state_string, self.getStateString())) self._callback.on_comm_state_change(new_state) # Deal with firing nessesary events if new_state in [PRINTER_STATE['OPERATIONAL'], PRINTER_STATE['PRINTING'], PRINTER_STATE['PAUSED']]: # Send connected event if needed if old_state == PRINTER_STATE['CONNECTING']: payload = dict(port=self._port, baudrate=self._baudrate) eventManager().fire(Events.CONNECTED, payload) # Pausing and resuming printing if new_state == PRINTER_STATE['PAUSED']: eventManager().fire(Events.PRINT_PAUSED, None) elif new_state == PRINTER_STATE['PRINTING'] and old_state == PRINTER_STATE['PAUSED']: eventManager().fire(Events.PRINT_RESUMED, None) # New print elif new_state == PRINTER_STATE['PRINTING']: eventManager().fire(Events.PRINT_STARTED, None) self._callback.on_comm_set_job_data('Authentise Streaming Print', 10000, None) # It is not easy to tell the difference between an completed print and a cancled print at this point elif new_state == PRINTER_STATE['OPERATIONAL'] and old_state != PRINTER_STATE['CONNECTING']: eventManager().fire(Events.PRINT_DONE, None) self._callback.on_comm_set_job_data(None, None, None) elif new_state == PRINTER_STATE['CLOSED']: eventManager().fire(Events.DISCONNECTED) elif new_state in [PRINTER_STATE['ERROR'], PRINTER_STATE['CLOSED_WITH_ERROR']]: eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) def _log(self, message): self._callback.on_comm_log(message) self._serialLogger.debug(message) ##~~ getters def getState(self): return self._state def getStateId(self, state=None): if state is None: state = self._state possible_states = filter(lambda x: x.startswith("STATE_"), self.__class__.__dict__.keys()) #pylint: disable=bad-builtin, deprecated-lambda for possible_state in possible_states: if getattr(self, possible_state) == state: return possible_state[len("STATE_"):] return "UNKNOWN" def getStateString(self, state=None): #pylint: disable=arguments-differ if not state: state = self._state if state in [PRINTER_STATE['ERROR'], PRINTER_STATE['CLOSED_WITH_ERROR']]: return "Error: {}".format(self.getErrorString()) return PRINTER_STATE_REVERSE[state].title() def getErrorString(self): return self._errorValue def isClosedOrError(self): return self._state in [PRINTER_STATE['ERROR'], PRINTER_STATE['CLOSED_WITH_ERROR'], PRINTER_STATE['CLOSED']] def isError(self): return self._state in [PRINTER_STATE['ERROR'], PRINTER_STATE['CLOSED_WITH_ERROR']] def isOperational(self): return self._state in [ PRINTER_STATE['OPERATIONAL'], PRINTER_STATE['PRINTING'], PRINTER_STATE['PAUSED']] def isPrinting(self): return self._state == PRINTER_STATE['PRINTING'] def isStreaming(self): return False def isPaused(self): return self._state == PRINTER_STATE['PAUSED'] def isBusy(self): return self.isPrinting() or self.isPaused() def isSdReady(self): return False def isSdFileSelected(self): return False def isSdPrinting(self): return False def getSdFiles(self): return def getPrintProgress(self): return self._print_progress['percent_complete'] if self._print_progress else None def getPrintFilepos(self): return int(self._print_progress['percent_complete']*10000) if self._print_progress else None def getPrintTime(self): return self._print_progress['elapsed'] if self._print_progress else None def getCleanedPrintTime(self): return self._print_progress['elapsed'] if self._print_progress else None def getTemp(self): return self._tool_tempuratures def getBedTemp(self): return self._bed_tempurature def getOffsets(self): return {} def getCurrentTool(self): return 0 def getConnection(self): return self._port, self._baudrate def getTransport(self): return ##~~ external interface def close(self, is_error=False, wait=True, *args, **kwargs): #pylint: disable=unused-argument if self._printer_status_timer: self._printer_status_timer.cancel() self._monitoring_active = False printing = self.isPrinting() or self.isPaused() if printing: eventManager().fire(Events.PRINT_FAILED, None) # close the Authentise client if it is open if self._authentise_process: self._authentise_process.send_signal(2) #send the SIGINT signal self._print_job_uri = None self._change_state(PRINTER_STATE['CLOSED']) def setTemperatureOffset(self, offsets): pass def fakeOk(self): pass def sendCommand(self, cmd, cmd_type=None, processed=False, force=False): #pylint: disable=unused-argument, arguments-differ cmd = cmd.encode('ascii', 'replace') if not processed: cmd = comm_helpers.process_gcode_line(cmd) if not cmd: return if self.isOperational(): data = {'command': cmd} printer_command_url = urlparse.urljoin(self._printer_uri, 'command/') response = self._session.post(printer_command_url, json=data) if not response.ok: self._log( 'Warning: Got invalid response {}: {} for {}: {}'.format( response.status_code, response.content, response.request.url, response.request.body)) return self._log( 'Sent {} to {} with response {}: {}'.format( response.request.body, response.request.url, response.status_code, response.content)) command_uri = response.headers['Location'] self._command_uri_queue.put({ 'uri' : command_uri, 'start_time' : time.time(), 'previous_time' : None, }) def startPrint(self): pass def selectFile(self, model_uri, sd): pass def unselectFile(self): pass def _send_pause_cancel_request(self, status): try: response = self._session.put(self._print_job_uri, json={'status':status}) except (requests.exceptions.MissingSchema, requests.exceptions.ConnectionError) as e: self._log('Request to {} generated error: {}'.format(self._print_job_uri, e)) response = None if response and response.ok: status_map = { 'cancel': PRINTER_STATE['OPERATIONAL'], 'pause': PRINTER_STATE['PAUSED'], 'resume': PRINTER_STATE['PRINTING'], } self._change_state(status_map[status]) def cancelPrint(self): if not self.isPrinting() and not self.isPaused(): return self._send_pause_cancel_request('cancel') def setPause(self, pause): if not pause and self.isPaused(): self._send_pause_cancel_request('resume') elif pause and self.isPrinting(): self._send_pause_cancel_request('pause') def sendGcodeScript(self, scriptName, replacements=None): return def startFileTransfer(self, filename, localFilename, remoteFilename): return def startSdFileTransfer(self, filename): return def endSdFileTransfer(self, filename): return def deleteSdFile(self, filename): return def refreshSdFiles(self): return def initSdCard(self): return def releaseSdCard(self): return def sayHello(self): #pylint: disable=no-self-use return def resetLineNumbers(self, number=0): #pylint: disable=unused-argument, no-self-use return ##~~ Serial monitor processing received messages def _readline(self): def _put_command_on_queue(data, start_time_diff): if start_time_diff < 120: self._command_uri_queue.put(data) current_time = time.time() try: command = self._command_uri_queue.get_nowait() except Queue.Empty: return '' start_time = command['start_time'] previous_time = command['previous_time'] command_uri = command['uri'] start_time_diff = current_time - start_time previous_time_diff = (current_time - previous_time) if previous_time else start_time_diff if previous_time_diff < 2: _put_command_on_queue({ 'uri' : command_uri, 'start_time' : start_time, 'previous_time' : previous_time, }, start_time_diff) return '' response = self._session.get(command_uri) if response.ok and response.json()['status'] in ['error', 'printer_offline']: return '' elif not response.ok or response.json()['status'] != 'ok': _put_command_on_queue({ 'uri' : command_uri, 'start_time' : start_time, 'previous_time' : current_time, }, start_time_diff) return '' command_response = response.json() self._log('Got response: {}, for command: {}'.format(command_response['response'], command_response['command'])) return command_response['response'] def _monitor_loop(self): self._log("Connected, starting monitor") while self._monitoring_active: try: line = self._readline() if not line: continue temps = parse_temps(line) if temps: tool_temps = {i: [temp['actual'], temp['target']] for i, temp in enumerate(temps['tools'])} bed_temp = (temps['bed']['actual'], temps['bed']['target']) if temps['bed'] else None self._callback.on_comm_temperature_update(tool_temps, bed_temp) self._callback.on_comm_message(line) except: #pylint: disable=bare-except self._logger.exception("Something crashed inside the serial connection loop," " please report this in OctoPrint's bug tracker:") errorMsg = "See octoprint.log for details" self._log(errorMsg) self._errorValue = errorMsg self._change_state(PRINTER_STATE['ERROR']) time.sleep(0.1) self._log("Connection closed, closing down monitor") def _update_printer_data(self): if not self._printer_uri: return response = self._session.get(self._printer_uri) if not response.ok: self._log('Unable to get printer status: {}: {}'.format(response.status_code, response.content)) return response_data = response.json() if response_data['current_print'] and response_data['current_print']['status'].lower() != 'new': self._print_job_uri = response_data['current_print']['job_uri'] else: self._print_job_uri = None self._update_state(response_data) self._update_temps(response_data) self._update_progress(response_data) def _update_temps(self, response_data): temps = response_data['temperatures'] self._tool_tempuratures = {0: [ temps['extruder1'].get('current') if temps.get('extruder1') else None, temps['extruder1'].get('target') if temps.get('extruder1') else None, ]} self._bed_tempurature = [ temps['bed'].get('current') if temps.get('bed') else None, temps['bed'].get('target') if temps.get('bed') else None, ] if temps.get('bed') else None self._callback.on_comm_temperature_update(self._tool_tempuratures, self._bed_tempurature) def _update_progress(self, response_data): current_print = response_data['current_print'] if current_print and response_data['current_print']['status'].lower() != 'new': self._print_progress = { 'percent_complete' : current_print['percent_complete']/100, 'elapsed' : current_print['elapsed'], 'remaining' : current_print['remaining'], } self._callback.on_comm_set_progress_data( current_print['percent_complete'], current_print['percent_complete']*100 if current_print['percent_complete'] else None, current_print['elapsed'], current_print['remaining'], ) else: self._print_progress = None self._callback.on_comm_set_progress_data(None, None, None, None) def _update_state(self, response_data): if response_data['status'].lower() == 'online': if not response_data['current_print'] or response_data['current_print']['status'].lower() == 'new': self._change_state(PRINTER_STATE['OPERATIONAL']) elif response_data['current_print']: if response_data['current_print']['status'].lower() in ['printing', 'warming_up']: self._change_state(PRINTER_STATE['PRINTING']) elif response_data['current_print']['status'].lower() == 'paused': self._change_state(PRINTER_STATE['PAUSED']) else: self._log('Unknown print state: {}'.format(response_data['current_print']['status'])) else: self._change_state(PRINTER_STATE['CONNECTING'])
class AutomaticshutdownPlugin(octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.SettingsPlugin, octoprint.plugin.StartupPlugin): def __init__(self): self.abortTimeout = 0 self.rememberCheckBox = False self.lastCheckBoxValue = False self._automatic_shutdown_enabled = False self._timeout_value = None self._abort_timer = None self._wait_for_timelapse_timer = None def initialize(self): self.abortTimeout = self._settings.get_int(["abortTimeout"]) self._logger.debug("abortTimeout: %s" % self.abortTimeout) self.rememberCheckBox = self._settings.get_boolean(["rememberCheckBox"]) self._logger.debug("rememberCheckBox: %s" % self.rememberCheckBox) self.lastCheckBoxValue = self._settings.get_boolean(["lastCheckBoxValue"]) self._logger.debug("lastCheckBoxValue: %s" % self.lastCheckBoxValue) if self.rememberCheckBox: self._automatic_shutdown_enabled = self.lastCheckBoxValue def get_assets(self): return dict(js=["js/automaticshutdown.js"]) def get_template_configs(self): return [dict(type="sidebar", name="Automatic Shutdown", custom_bindings=False, icon="power-off"), dict(type="settings", custom_bindings=False)] def get_api_commands(self): return dict(enable=[], disable=[], abort=[]) def on_api_command(self, command, data): if not user_permission.can(): return make_response("Insufficient rights", 403) if command == "enable": self._automatic_shutdown_enabled = True elif command == "disable": self._automatic_shutdown_enabled = False elif command == "abort": if self._wait_for_timelapse_timer is not None: self._wait_for_timelapse_timer.cancel() self._wait_for_timelapse_timer = None if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._timeout_value = None self._logger.info("Shutdown aborted.") if command == "enable" or command == "disable": self.lastCheckBoxValue = self._automatic_shutdown_enabled if self.rememberCheckBox: self._settings.set_boolean(["lastCheckBoxValue"], self.lastCheckBoxValue) self._settings.save() eventManager().fire(Events.SETTINGS_UPDATED) self._plugin_manager.send_plugin_message(self._identifier, dict(automaticShutdownEnabled=self._automatic_shutdown_enabled, type="timeout", timeout_value=self._timeout_value)) def on_event(self, event, payload): if event == Events.CLIENT_OPENED: self._plugin_manager.send_plugin_message(self._identifier, dict(automaticShutdownEnabled=self._automatic_shutdown_enabled, type="timeout", timeout_value=self._timeout_value)) return if not self._automatic_shutdown_enabled: return if not self._settings.global_get(["server", "commands", "systemShutdownCommand"]): self._logger.warning("systemShutdownCommand is not defined. Aborting shutdown...") return if event not in [Events.PRINT_DONE, Events.PRINT_FAILED]: return if event == Events.PRINT_FAILED and not self._printer.is_closed_or_error(): #Cancelled job return if event in [Events.PRINT_DONE, Events.PRINT_FAILED]: webcam_config = self._settings.global_get(["webcam", "timelapse"], merged=True) timelapse_type = webcam_config["type"] if (timelapse_type is not None and timelapse_type != "off"): self._wait_for_timelapse_start() else: self._timer_start() return def _wait_for_timelapse_start(self): if self._wait_for_timelapse_timer is not None: return self._wait_for_timelapse_timer = RepeatedTimer(5, self._wait_for_timelapse) self._wait_for_timelapse_timer.start() def _wait_for_timelapse(self): c = len(octoprint.timelapse.get_unrendered_timelapses()) if c > 0: self._logger.info("Waiting for %s timelapse(s) to finish rendering before starting shutdown timer..." % c) else: self._timer_start() def _timer_start(self): if self._abort_timer is not None: return if self._wait_for_timelapse_timer is not None: self._wait_for_timelapse_timer.cancel() self._logger.info("Starting abort shutdown timer.") self._timeout_value = self.abortTimeout self._abort_timer = RepeatedTimer(1, self._timer_task) self._abort_timer.start() def _timer_task(self): if self._timeout_value is None: return self._timeout_value -= 1 self._plugin_manager.send_plugin_message(self._identifier, dict(automaticShutdownEnabled=self._automatic_shutdown_enabled, type="timeout", timeout_value=self._timeout_value)) if self._timeout_value <= 0: if self._wait_for_timelapse_timer is not None: self._wait_for_timelapse_timer.cancel() self._wait_for_timelapse_timer = None if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._shutdown_system() def _shutdown_system(self): shutdown_command = self._settings.global_get(["server", "commands", "printerShutdownCommand"]) self._logger.info("Shutting down system with command: {command}".format(command=shutdown_command)) try: import sarge p = sarge.run(shutdown_command, async=True) except Exception as e: self._logger.exception("Error when shutting down: {error}".format(error=e)) return def get_settings_defaults(self): return dict( abortTimeout = 30, rememberCheckBox = False, lastCheckBoxValue = False ) def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.abortTimeout = self._settings.get_int(["abortTimeout"]) self.rememberCheckBox = self._settings.get_boolean(["rememberCheckBox"]) self.lastCheckBoxValue = self._settings.get_boolean(["lastCheckBoxValue"]) def get_update_information(self): return dict( automaticshutdown=dict( displayName="Automatic Shutdown", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-AutomaticShutdown", current=self._plugin_version, # update method: pip w/ dependency links pip="https://github.com/OctoPrint/OctoPrint-AutomaticShutdown/archive/{target_version}.zip" ) )
class MachineCom(octoprint.plugin.MachineComPlugin): #pylint: disable=too-many-instance-attributes, too-many-public-methods _logger = None _serialLogger = None _state = None _port = None _baudrate = None _printer_uri = None _print_job_uri = None _authentise_process = None _authentise_model = None _authentise_url = None _session = None _command_uri_queue = None _printer_status_timer = None _tool_tempuratures = None _bed_tempurature = None _print_progress = None _callback = None _printer_profile_manager = None monitoring_thread = None _monitoring_active = False _errorValue = None def __init__(self): self._logger = logging.getLogger(__name__) self._serialLogger = logging.getLogger("SERIAL") self._command_uri_queue = comm_helpers.TypedQueue() self._state = self.STATE_NONE def startup(self, callbackObject=None, printerProfileManager=None): if callbackObject == None: callbackObject = comm_helpers.MachineComPrintCallback() self._callback = callbackObject self._printer_profile_manager = printerProfileManager self._authentise_url = self._settings.get(['authentise_url']) #pylint: disable=no-member def connect(self, port=None, baudrate=None): try: self._session = helpers.session(self._settings) #pylint: disable=no-member helpers.claim_node(self.node_uuid, self._settings, self._logger) #pylint: disable=no-member except (helpers.ClaimNodeException, helpers.SessionException) as e: self._errorValue = e.message self._change_state(PRINTER_STATE['ERROR']) return if port == None: port = settings().get(["serial", "port"]) if baudrate == None: settings_baudrate = settings().getInt(["serial", "baudrate"]) if settings_baudrate is None: baudrate = 0 else: baudrate = settings_baudrate self._port = port self._baudrate = baudrate self._printer_uri = self._get_or_create_printer(port, baudrate) self._authentise_process = helpers.run_client(self._settings) #pylint: disable=no-member # monitoring thread self._monitoring_active = True self.monitoring_thread = threading.Thread(target=self._monitor_loop, name="comm._monitor") self.monitoring_thread.daemon = True self.monitoring_thread.start() self._printer_status_timer = RepeatedTimer( lambda: comm_helpers.get_interval("temperature", default_value=10.0), self._update_printer_data, run_first=True) self._printer_status_timer.start() self._change_state(PRINTER_STATE['CONNECTING']) def _get_or_create_printer(self, port, baud_rate): client_url = urllib.parse.urljoin(self._authentise_url, '/client/{}/'.format(self.node_uuid)) #pylint: disable=no-member url = urllib.parse.urljoin( self._authentise_url, '/printer/instance/?filter[client]={}'.format( quote_plus(client_url))) target_printer = None self._log('Getting printer list from: {}'.format(url)) printer_get_resp = self._session.get(url=url) for printer in printer_get_resp.json()["resources"]: if printer['port'] == port: target_printer = printer self._log('Printer {} matches selected port {}'.format( printer, port)) break if target_printer: if target_printer['baud_rate'] != baud_rate: self._session.put(target_printer["uri"], json={'baud_rate': baud_rate}) return target_printer['uri'] else: self._log( 'No printer found for port {}. Creating it.'.format(port)) if os.path.exists( '/usr/lib/python2.7/dist-packages/octoprint/static/img/type_a_machines.svg' ): model = 14 else: model = 9 payload = { 'baud_rate': baud_rate, 'client': client_url, 'name': 'Octoprint Printer', 'port': port, 'printer_model': 'https://print.dev-auth.com/printer/model/{}/'.format(model), } create_printer_resp = self._session.post(urllib.parse.urljoin( self._authentise_url, '/printer/instance/'), json=payload) return create_printer_resp.headers["Location"] # #~~ internal state management def _change_state(self, new_state): # Change the printer state if self._state == new_state: return old_state = self._state old_state_string = self.getStateString() self._state = new_state self._log("Changed printer state from '{}' to '{}'".format( old_state_string, self.getStateString())) self._callback.on_comm_state_change(new_state) # Deal with firing nessesary events if new_state in [ PRINTER_STATE['OPERATIONAL'], PRINTER_STATE['PRINTING'], PRINTER_STATE['PAUSED'] ]: # Send connected event if needed if old_state == PRINTER_STATE['CONNECTING']: payload = dict(port=self._port, baudrate=self._baudrate) eventManager().fire(Events.CONNECTED, payload) # Pausing and resuming printing if new_state == PRINTER_STATE['PAUSED']: eventManager().fire(Events.PRINT_PAUSED, None) elif new_state == PRINTER_STATE[ 'PRINTING'] and old_state == PRINTER_STATE['PAUSED']: eventManager().fire(Events.PRINT_RESUMED, None) # New print elif new_state == PRINTER_STATE['PRINTING']: eventManager().fire(Events.PRINT_STARTED, None) self._callback.on_comm_set_job_data( 'Authentise Streaming Print', 10000, None) # It is not easy to tell the difference between an completed print and a cancled print at this point elif new_state == PRINTER_STATE[ 'OPERATIONAL'] and old_state != PRINTER_STATE['CONNECTING']: eventManager().fire(Events.PRINT_DONE, None) self._callback.on_comm_set_job_data(None, None, None) elif new_state == PRINTER_STATE['CLOSED']: eventManager().fire(Events.DISCONNECTED) elif new_state in [ PRINTER_STATE['ERROR'], PRINTER_STATE['CLOSED_WITH_ERROR'] ]: eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) def _log(self, message): self._callback.on_comm_log(message) self._serialLogger.debug(message) ##~~ getters def getState(self): return self._state def getStateId(self, state=None): if state is None: state = self._state possible_states = [ x for x in list(self.__class__.__dict__.keys()) if x.startswith("STATE_") ] #pylint: disable=bad-builtin, deprecated-lambda for possible_state in possible_states: if getattr(self, possible_state) == state: return possible_state[len("STATE_"):] return "UNKNOWN" def getStateString(self, state=None): #pylint: disable=arguments-differ if not state: state = self._state if state in [ PRINTER_STATE['ERROR'], PRINTER_STATE['CLOSED_WITH_ERROR'] ]: return "Error: {}".format(self.getErrorString()) return PRINTER_STATE_REVERSE[state].title() def getErrorString(self): return self._errorValue def isClosedOrError(self): return self._state in [ PRINTER_STATE['ERROR'], PRINTER_STATE['CLOSED_WITH_ERROR'], PRINTER_STATE['CLOSED'] ] def isError(self): return self._state in [ PRINTER_STATE['ERROR'], PRINTER_STATE['CLOSED_WITH_ERROR'] ] def isOperational(self): return self._state in [ PRINTER_STATE['OPERATIONAL'], PRINTER_STATE['PRINTING'], PRINTER_STATE['PAUSED'] ] def isPrinting(self): return self._state == PRINTER_STATE['PRINTING'] def isStreaming(self): return False def isPaused(self): return self._state == PRINTER_STATE['PAUSED'] def isBusy(self): return self.isPrinting() or self.isPaused() def isSdReady(self): return False def isSdFileSelected(self): return False def isSdPrinting(self): return False def getSdFiles(self): return def getPrintProgress(self): return self._print_progress[ 'percent_complete'] if self._print_progress else None def getPrintFilepos(self): return int(self._print_progress['percent_complete'] * 10000) if self._print_progress else None def getPrintTime(self): return self._print_progress['elapsed'] if self._print_progress else None def getCleanedPrintTime(self): return self._print_progress['elapsed'] if self._print_progress else None def getTemp(self): return self._tool_tempuratures def getBedTemp(self): return self._bed_tempurature def getOffsets(self): return {} def getCurrentTool(self): return 0 def getConnection(self): return self._port, self._baudrate def getTransport(self): return ##~~ external interface def close(self, is_error=False, wait=True, *args, **kwargs): #pylint: disable=unused-argument if self._printer_status_timer: self._printer_status_timer.cancel() self._monitoring_active = False printing = self.isPrinting() or self.isPaused() if printing: eventManager().fire(Events.PRINT_FAILED, None) # close the Authentise client if it is open if self._authentise_process: self._authentise_process.send_signal(2) #send the SIGINT signal self._print_job_uri = None self._change_state(PRINTER_STATE['CLOSED']) def setTemperatureOffset(self, offsets): pass def fakeOk(self): pass def sendCommand(self, cmd, cmd_type=None, processed=False, force=False): #pylint: disable=unused-argument, arguments-differ cmd = cmd.encode('ascii', 'replace') if not processed: cmd = comm_helpers.process_gcode_line(cmd) if not cmd: return if self.isOperational(): data = {'command': cmd} printer_command_url = urllib.parse.urljoin(self._printer_uri, 'command/') response = self._session.post(printer_command_url, json=data) if not response.ok: self._log( 'Warning: Got invalid response {}: {} for {}: {}'.format( response.status_code, response.content, response.request.url, response.request.body)) return self._log('Sent {} to {} with response {}: {}'.format( response.request.body, response.request.url, response.status_code, response.content)) command_uri = response.headers['Location'] self._command_uri_queue.put({ 'uri': command_uri, 'start_time': time.time(), 'previous_time': None, }) def startPrint(self): pass def selectFile(self, model_uri, sd): pass def unselectFile(self): pass def _send_pause_cancel_request(self, status): try: response = self._session.put(self._print_job_uri, json={'status': status}) except (requests.exceptions.MissingSchema, requests.exceptions.ConnectionError) as e: self._log('Request to {} generated error: {}'.format( self._print_job_uri, e)) response = None if response and response.ok: status_map = { 'cancel': PRINTER_STATE['OPERATIONAL'], 'pause': PRINTER_STATE['PAUSED'], 'resume': PRINTER_STATE['PRINTING'], } self._change_state(status_map[status]) def cancelPrint(self): if not self.isPrinting() and not self.isPaused(): return self._send_pause_cancel_request('cancel') def setPause(self, pause): if not pause and self.isPaused(): self._send_pause_cancel_request('resume') elif pause and self.isPrinting(): self._send_pause_cancel_request('pause') def sendGcodeScript(self, scriptName, replacements=None): return def startFileTransfer(self, filename, localFilename, remoteFilename): return def startSdFileTransfer(self, filename): return def endSdFileTransfer(self, filename): return def deleteSdFile(self, filename): return def refreshSdFiles(self): return def initSdCard(self): return def releaseSdCard(self): return def sayHello(self): #pylint: disable=no-self-use return def resetLineNumbers(self, number=0): #pylint: disable=unused-argument, no-self-use return ##~~ Serial monitor processing received messages def _readline(self): def _put_command_on_queue(data, start_time_diff): if start_time_diff < 120: self._command_uri_queue.put(data) current_time = time.time() try: command = self._command_uri_queue.get_nowait() except queue.Empty: return '' start_time = command['start_time'] previous_time = command['previous_time'] command_uri = command['uri'] start_time_diff = current_time - start_time previous_time_diff = ( current_time - previous_time) if previous_time else start_time_diff if previous_time_diff < 2: _put_command_on_queue( { 'uri': command_uri, 'start_time': start_time, 'previous_time': previous_time, }, start_time_diff) return '' response = self._session.get(command_uri) if response.ok and response.json()['status'] in [ 'error', 'printer_offline' ]: return '' elif not response.ok or response.json()['status'] != 'ok': _put_command_on_queue( { 'uri': command_uri, 'start_time': start_time, 'previous_time': current_time, }, start_time_diff) return '' command_response = response.json() self._log('Got response: {}, for command: {}'.format( command_response['response'], command_response['command'])) return command_response['response'] def _monitor_loop(self): self._log("Connected, starting monitor") while self._monitoring_active: try: line = self._readline() if not line: continue temps = parse_temps(line) if temps: tool_temps = { i: [temp['actual'], temp['target']] for i, temp in enumerate(temps['tools']) } bed_temp = ( temps['bed']['actual'], temps['bed']['target']) if temps['bed'] else None self._callback.on_comm_temperature_update( tool_temps, bed_temp) self._callback.on_comm_message(line) except: #pylint: disable=bare-except self._logger.exception( "Something crashed inside the serial connection loop," " please report this in OctoPrint's bug tracker:") errorMsg = "See octoprint.log for details" self._log(errorMsg) self._errorValue = errorMsg self._change_state(PRINTER_STATE['ERROR']) time.sleep(0.1) self._log("Connection closed, closing down monitor") def _update_printer_data(self): if not self._printer_uri: return response = self._session.get(self._printer_uri) if not response.ok: self._log('Unable to get printer status: {}: {}'.format( response.status_code, response.content)) return response_data = response.json() if response_data['current_print'] and response_data['current_print'][ 'status'].lower() != 'new': self._print_job_uri = response_data['current_print']['job_uri'] else: self._print_job_uri = None self._update_state(response_data) self._update_temps(response_data) self._update_progress(response_data) def _update_temps(self, response_data): temps = response_data['temperatures'] self._tool_tempuratures = { 0: [ temps['extruder1'].get('current') if temps.get('extruder1') else None, temps['extruder1'].get('target') if temps.get('extruder1') else None, ] } self._bed_tempurature = [ temps['bed'].get('current') if temps.get('bed') else None, temps['bed'].get('target') if temps.get('bed') else None, ] if temps.get('bed') else None self._callback.on_comm_temperature_update(self._tool_tempuratures, self._bed_tempurature) def _update_progress(self, response_data): current_print = response_data['current_print'] if current_print and response_data['current_print']['status'].lower( ) != 'new': self._print_progress = { 'percent_complete': current_print['percent_complete'] / 100, 'elapsed': current_print['elapsed'], 'remaining': current_print['remaining'], } self._callback.on_comm_set_progress_data( current_print['percent_complete'], current_print['percent_complete'] * 100 if current_print['percent_complete'] else None, current_print['elapsed'], current_print['remaining'], ) else: self._print_progress = None self._callback.on_comm_set_progress_data(None, None, None, None) def _update_state(self, response_data): if response_data['status'].lower() == 'online': if not response_data['current_print'] or response_data[ 'current_print']['status'].lower() == 'new': self._change_state(PRINTER_STATE['OPERATIONAL']) elif response_data['current_print']: if response_data['current_print']['status'].lower() in [ 'printing', 'warming_up' ]: self._change_state(PRINTER_STATE['PRINTING']) elif response_data['current_print']['status'].lower( ) == 'paused': self._change_state(PRINTER_STATE['PAUSED']) else: self._log('Unknown print state: {}'.format( response_data['current_print']['status'])) else: self._change_state(PRINTER_STATE['CONNECTING'])
class PowerFailurePlugin(octoprint.plugin.TemplatePlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.SettingsPlugin): def __init__(self): super(PowerFailurePlugin, self).__init__() self.will_print = "" def get_settings_defaults(self): return dict( auto_continue=False, z_homing_height=0, gcode=("M80\n" "M140 S{bedT}\n" "M104 S{tool0T}\n" "M190 S{bedT}\n" "M109 S{tool0T}\n" "G21 ;metric values\n" "G90 ;absolute positioning\n" "G28 X0 Y0 ;move X/Y to min endstops\n" "G92 E0 Z{currentZ} ;zero the extruded length again\n" "M211 S0\n" "G91\n" "G1 Z-{z_homing_height} F200 ; correcting Z_HOMING_HEIGHT\n" "G90\n" "M211 S1\n" "G1 F9000\n"), recovery=False, filename="", filepos=0, currentZ=0.0, bedT=0.0, tool0T=0.0) def on_after_startup(self): if self._settings.getBoolean(["recovery"]): # hay que recuperar self._logger.info("Recovering from a power failure") filename = self._settings.get(["filename"]) filepos = self._settings.getInt(["filepos"]) currentZ = self._settings.getFloat(["currentZ"]) bedT = self._settings.getFloat(["bedT"]) tool0T = self._settings.getFloat(["tool0T"]) self._logger.info("Recovering printing of %s" % filename) recovery_fn = self.generateContinuation(filename, filepos, currentZ, bedT, tool0T) self.clean() if self._settings.getBoolean(["auto_continue"]): self.will_print = recovery_fn self._printer.select_file( recovery_fn, False, printAfterSelect=False) # selecciona directo self._logger.info("Recovered from a power failure") else: self._logger.info("There was no power failure.") def generateContinuation(self, filename, filepos, currentZ, bedT, tool0T): z_homing_height = self._settings.getFloat(["z_homing_height"]) currentZ += z_homing_height gcode = self._settings.get(["gcode"]).format(**locals()) original_fn = self._file_manager.path_on_disk("local", filename) path, filename = os.path.split(original_fn) recovery_fn = self._file_manager.path_on_disk( "local", os.path.join(path, "recovery_" + filename)) fan = False extruder = False for line in reverse_readlines(original_fn, filepos): # buscando las ultimas lineas importantes if not fan and (line.startswith("M106") or line.startswith("M107")): fan = True # encontre el fan gcode += line + "\n" if not extruder and (line.startswith("G1 ") or line.startswith("G92 ")) and ("E" in line): # G1 X135.248 Y122.666 E4.03755 extruder = True # encontre el extruder subcommands = line.split() # dividido por espacios ecommand = [sc for sc in subcommands if "E" in sc] assert len(ecommand) == 1 ecommand = ecommand[0] gcode += "G92 " + ecommand + "\n" if fan and extruder: break original = open(original_fn, 'r') original.seek(filepos) data = gcode + original.read() original.close() stream = octoprint.filemanager.util.StreamWrapper( recovery_fn, io.BytesIO(data)) self._file_manager.add_file( octoprint.filemanager.FileDestinations.LOCAL, recovery_fn, stream, allow_overwrite=True) return os.path.join(path, "recovery_" + filename) def get_template_configs(self): return [dict(type="settings", custom_bindings=False)] def backupState(self): currentData = self._printer.get_current_data() if currentData["job"]["file"]["origin"] != "local": self._logger.info( "SD printing does not support power failure recovery") self._settings.setBoolean(["recovery"], False) self.timer.cancel() return currentTemp = self._printer.get_current_temperatures() bedT = currentTemp["bed"]["target"] tool0T = currentTemp["tool0"]["target"] filepos = currentData["progress"]["filepos"] filename = currentData["job"]["file"]["path"] currentZ = currentData["currentZ"] self._logger.info("Backup printing: %s Offset:%s Z:%s Bed:%s Tool:%s" % (filename, filepos, currentZ, bedT, tool0T)) self._settings.setBoolean(["recovery"], True) self._settings.set(["filename"], str(filename)) self._settings.setInt(["filepos"], sanitize_number(filepos)) self._settings.setFloat(["currentZ"], sanitize_number(currentZ)) self._settings.setFloat(["bedT"], sanitize_number(bedT)) self._settings.setFloat(["tool0T"], sanitize_number(tool0T)) self._settings.save() def clean(self): self._settings.setBoolean(["recovery"], False) self._settings.save() def on_event(self, event, payload): if self.will_print and self._printer.is_ready(): will_print, self.will_print = self.will_print, "" # larga imprimiendo directamente self._printer.select_file(will_print, False, printAfterSelect=True) if event.startswith("Print"): if event in {"PrintStarted"}: # empiezo a revisar # empiezo a chequear self.timer = RepeatedTimer( 1.0, PowerFailurePlugin.backupState, args=[self], run_first=True, ) self.timer.start() # casos en que dejo de revisar y borro elif event in {"PrintDone", "PrintFailed", "PrintCancelled"}: # cancelo el chequeo self.timer.cancel() self.clean() else: # casos pause y resume pass def get_update_information(self): return dict(powerfailure=dict( displayName=self._plugin_name, displayVersion=self._plugin_version, type="github_release", current=self._plugin_version, user="******", repo="Octoprint-PowerFailure", pip= "https://github.com/pablogventura/OctoPrint-PowerFailure/archive/{target_version}.zip" ))
class CooldownnotificationPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin): def __init__(self): self.inProgress = False self._TempTimer = None ##~~ SettingsPlugin def get_settings_defaults(self): return dict(Enabled=False, Threshold='40', GCODE="") ##~~ AssetPlugin def get_assets(self): # Define your plugin's asset files to automatically include in the # core UI here. return dict(js=["js/CooldownNotification.js"]) ##~~ StartupPlugin def on_after_startup(self): self.inProgress = False ##~~ SimpleApiPlugin def get_api_commands(self): return dict(testGCODE=["GCODE"]) def on_api_command(self, command, data): if command == "testGCODE": if not self.inProgress: self._logger.debug("Testing GOCDE") thread = threading.Timer(0, self.doExecute, [data['GCODE']]) thread.start() ##~~EventHandlerPlugin def on_event(self, event, payload): if not self.inProgress and self._settings.get(["Enabled"]): notify_events = ['PrintFailed', 'PrintDone'] if event in notify_events: self._logger.debug("Received Event: " + event) self._logger.info("Print Ended, Watching Heatbed Temp") self.inProgress = True self._plugin_manager.send_plugin_message( self._identifier, dict(action="startTimer")) self._TempTimer = RepeatedTimer(5, self.checkTemp, run_first=True) self._TempTimer.start() ##~~Custom Functions def checkTemp(self): if 'bed' in self._printer.get_current_temperatures(): bedTemp = self._printer.get_current_temperatures()['bed']['actual'] threshold = int(self._settings.get(["Threshold"])) self._logger.debug("Heatbed Temp: " + str(bedTemp)) if bedTemp <= threshold: self._logger.debug("Heatbed Temp Reached Threshold") self._logger.debug("Heatbed Temp: " + str(bedTemp) + " Type: " + str(type(bedTemp).__name__)) self._logger.debug("Threshold: " + str(threshold) + " Type: " + str(type(threshold).__name__)) self.doExecute(self._settings.get(["GCODE"])) self._TempTimer.cancel() def doExecute(self, GCODE): for line in GCODE.splitlines(): self._logger.debug("Sending GCODE: " + line) self._printer.commands(line) self.inProgress = False self._plugin_manager.send_plugin_message(self._identifier, dict(action="doneExecute")) self._plugin_manager.send_plugin_message( self._identifier, dict(action="popup", type="info", text="Cooldown Notification Sent")) self._logger.info("Cooldown Notification Sent") ##~~ Softwareupdate hook def get_update_information(self): # Define the configuration for your plugin to use with the Software Update # Plugin here. See https://docs.octoprint.org/en/master/bundledplugins/softwareupdate.html # for details. return dict(CooldownNotification=dict( displayName="Cooldownnotification Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-CooldownNotification", current=self._plugin_version, # update method: pip pip= "https://github.com/gmccauley/OctoPrint-CooldownNotification/archive/{target_version}.zip" ))
class BeeCom(MachineCom): STATE_WAITING_FOR_BTF = 21 STATE_PREPARING_PRINT = 22 STATE_HEATING = 23 STATE_SHUTDOWN = 24 _beeConn = None _beeCommands = None _responseQueue = queue.Queue() _statusQueue = queue.Queue() _monitor_print_progress = True _connection_monitor_active = True _prepare_print_thread = None _preparing_print = False _resume_print_thread = None def __init__(self, callbackObject=None, printerProfileManager=None): super(BeeCom, self).__init__(None, None, callbackObject, printerProfileManager) self._openConnection() # monitoring thread self._monitoring_active = True self.monitoring_thread = threading.Thread(target=self._monitor, name="comm._monitor") self.monitoring_thread.daemon = True self.monitoring_thread.start() def _openConnection(self): """ Opens a new connection using the BEEcom driver :return: True if the connection was successful """ if self._beeConn is None: self._beeConn = BeePrinterConn(self._connShutdownHook) self._changeState(self.STATE_CONNECTING) self._beeConn.connectToFirstPrinter() if self._beeConn.isConnected(): self._beeCommands = self._beeConn.getCommandIntf() # change to firmware if self._beeCommands.getPrinterMode() == 'Bootloader': # checks for firmware updates self.update_firmware() self._beeCommands.goToFirmware() # restart connection self._beeConn.reconnect() # post connection callback self._onConnected() return True else: return False def current_firmware(self): """ Gets the current firmware version :return: """ firmware_v = self.getCommandsInterface().getFirmwareVersion() if firmware_v is not None: return firmware_v else: return 'Not available' def update_firmware(self): """ Updates the printer firmware if the value in the firmware.properties file is different from the current printer firmware :return: if no printer is connected just returns void """ _logger = logging.getLogger() # get the latest firmware file for the connected printer conn_printer = self.getConnectedPrinterName() if conn_printer is None: return printer_id = conn_printer.replace(' ', '').lower() if printer_id: from os.path import isfile, join _logger.info("Checking for firmware updates...") try: firmware_path = settings().getBaseFolder('firmware') firmware_properties = parsePropertiesFile(join(firmware_path, 'firmware.properties')) firmware_file_name = firmware_properties['firmware.' + printer_id] except KeyError as e: _logger.error("Problem with printer_id %s. Firmware properties not found for this printer model." % printer_id) return if firmware_file_name is not None and isfile(join(firmware_path, firmware_file_name)): fname_parts = firmware_file_name.split('-') # gets the current firmware version curr_firmware = self.current_firmware() curr_firmware_parts = curr_firmware.split('-') if len(curr_firmware_parts) == 3 and curr_firmware is not "Not available": curr_version_parts = curr_firmware_parts[2].split('.') file_version_parts = fname_parts[2].split('.') if len(curr_version_parts) >= 3 and len(file_version_parts) >=3: for i in xrange(3): if int(file_version_parts[i]) != int(curr_version_parts[i]): # version update found _logger.info("Updating printer firmware...") self.getCommandsInterface().flashFirmware(join(firmware_path, firmware_file_name), firmware_file_name) _logger.info("Firmware updated to %s" % fname_parts[2]) return elif curr_firmware == '0.0.0': # If curr_firmware is 0.0.0 it means something went wrong with a previous firmware update _logger.info("Updating printer firmware...") self.getCommandsInterface().flashFirmware(join(firmware_path, firmware_file_name), firmware_file_name) _logger.info("Firmware updated to %s" % fname_parts[2]) return else: _logger.error("No firmware file matching the configuration for printer %s found" % conn_printer) _logger.info("No firmware updates found") def sendCommand(self, cmd, cmd_type=None, processed=False, force=False): """ Sends a custom command through the open connection :param cmd: :param cmd_type: :param processed: :param force: :return: """ cmd = cmd.encode('ascii', 'replace') if not processed: cmd = comm.process_gcode_line(cmd) if not cmd: return #if self.isPrinting() and not self.isSdFileSelected(): # self._commandQueue.put((cmd, cmd_type)) if self.isOperational(): wait = None if "g" in cmd.lower(): wait = "3" resp = self._beeCommands.sendCmd(cmd, wait) if resp: # puts the response in the monitor queue self._responseQueue.put(resp) # logs the command reply with errors splits = resp.rstrip().split("\n") for r in splits: if "Error" in r: self._logger.warning(r) return True else: return False def close(self, is_error=False, wait=True, timeout=10.0, *args, **kwargs): """ Closes the connection to the printer if it's active :param is_error: :param wait: unused parameter (kept for interface compatibility) :param timeout: :param args: :param kwargs: :return: """ if self._beeCommands is not None: self._beeCommands.stopStatusMonitor() if self._beeConn is not None: self._beeConn.close() self._changeState(self.STATE_CLOSED) def _changeState(self, newState): if self._state == newState: return oldState = self.getStateString() self._state = newState self._log('Changing monitoring state from \'%s\' to \'%s\'' % (oldState, self.getStateString())) self._callback.on_comm_state_change(newState) def confirmConnection(self): """ Confirms the connection changing the internal state of the printer :return: """ if self._beeConn.isConnected(): if self._beeCommands.isPrinting(): self._changeState(self.STATE_PRINTING) elif self._beeCommands.isShutdown(): self._changeState(self.STATE_SHUTDOWN) elif self._beeCommands.isPaused(): self._changeState(self.STATE_PAUSED) else: self._changeState(self.STATE_OPERATIONAL) else: self._changeState(self.STATE_WAITING_FOR_BTF) def getConnectedPrinterName(self): """ Returns the current connected printer name :return: """ if self._beeConn is not None: return self._beeConn.getConnectedPrinterName() else: return "" def getConnectedPrinterSN(self): """ Returns the current connected printer serial number :return: """ if self._beeConn is not None: return self._beeConn.getConnectedPrinterSN() else: return None def isOperational(self): return self._state == self.STATE_OPERATIONAL \ or self._state == self.STATE_PRINTING \ or self._state == self.STATE_PAUSED \ or self._state == self.STATE_SHUTDOWN \ or self._state == self.STATE_TRANSFERING_FILE \ or self._state == self.STATE_PREPARING_PRINT \ or self._state == self.STATE_HEATING def isClosedOrError(self): return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR \ or self._state == self.STATE_CLOSED or self._state == self.STATE_WAITING_FOR_BTF def isBusy(self): return self.isPrinting() or self.isPaused() or self.isPreparingPrint() def isPreparingPrint(self): return self._state == self.STATE_PREPARING_PRINT or self._state == self.STATE_HEATING def isPrinting(self): return self._state == self.STATE_PRINTING def isHeating(self): return self._state == self.STATE_HEATING def isShutdown(self): return self._state == self.STATE_SHUTDOWN def getStateString(self): """ Returns the current printer state :return: """ if self._state == self.STATE_WAITING_FOR_BTF or self._state == self.STATE_CLOSED: return "Disconnected" elif self._state == self.STATE_PREPARING_PRINT: return "Preparing..." elif self._state == self.STATE_HEATING: return "Heating" elif self._state == self.STATE_SHUTDOWN: return "Shutdown" elif self._state == self.STATE_OPERATIONAL: return "Ready" else: return super(BeeCom, self).getStateString() def startPrint(self, pos=None): """ Starts the printing operation :param pos: if the string 'memory' is passed the printer will print the last file in the printer's memory """ if not self.isOperational() or self.isPrinting(): return if self._currentFile is None and pos is None: raise ValueError("No file selected for printing") try: self._changeState(self.STATE_PREPARING_PRINT) if self.isSdFileSelected(): print_resp = self._beeCommands.startSDPrint(self._currentFile.getFilename()) if print_resp: self._sd_status_timer = RepeatedTimer(self._timeout_intervals.get("sdStatus", 1.0), self._poll_sd_status, run_first=True) self._sd_status_timer.start() elif pos == 'from_memory': print_resp = self._beeCommands.repeatLastPrint() else: print_resp = self._beeCommands.printFile(self._currentFile.getFilename()) if print_resp is True: self._heatupWaitStartTime = time.time() self._heatupWaitTimeLost = 0.0 self._pauseWaitStartTime = 0 self._pauseWaitTimeLost = 0.0 self._heating = True self._preparing_print = True self._prepare_print_thread = threading.Thread(target=self._preparePrintThread, name="comm._preparePrint") self._prepare_print_thread.daemon = True self._prepare_print_thread.start() else: self._errorValue = "Error while preparing the printing operation." self._logger.exception(self._errorValue) self._changeState(self.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) return except: self._errorValue = get_exception_string() self._logger.exception("Error while trying to start printing: " + self.getErrorString()) self._changeState(self.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) def cancelPrint(self, firmware_error=None): """ Cancels the print operation :type firmware_error: unused parameter, just to keep the interface compatible with octoprint """ if not self.isOperational() or self.isStreaming(): return self._preparing_print = False if self._beeCommands.cancelPrint(): self._changeState(self.STATE_OPERATIONAL) if self.isSdFileSelected(): if self._sd_status_timer is not None: try: self._sd_status_timer.cancel() except: pass else: self._logger.exception("Error while canceling the print operation.") eventManager().fire(Events.ERROR, {"error": "Error canceling print"}) return def setPause(self, pause): """ Toggle Pause method :param pause: True to pause or False to unpause :return: """ if self.isStreaming(): return if not self._currentFile: return payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() } if (not pause and self.isPaused()) or (not pause and self.isShutdown()): if self._pauseWaitStartTime: self._pauseWaitTimeLost = self._pauseWaitTimeLost + (time.time() - self._pauseWaitStartTime) self._pauseWaitStartTime = None # resumes printing self._beeCommands.resumePrint() self._heating = True self._resume_print_thread = threading.Thread(target=self._resumePrintThread, name="comm._resumePrint") self._resume_print_thread.daemon = True self._resume_print_thread.start() elif pause and self.isPrinting(): if not self._pauseWaitStartTime: self._pauseWaitStartTime = time.time() # pause print self._beeCommands.pausePrint() self._changeState(self.STATE_PAUSED) eventManager().fire(Events.PRINT_PAUSED, payload) def enterShutdownMode(self): """ Enters the printer shutdown mode :return: """ if self.isStreaming(): return if not self._currentFile: return payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() } # enter shutdown mode self._beeCommands.enterShutdown() self._changeState(self.STATE_SHUTDOWN) eventManager().fire(Events.POWER_OFF, payload) def initSdCard(self): """ Initializes the SD Card in the printer :return: """ if not self.isOperational(): return self._beeCommands.initSD() if settings().getBoolean(["feature", "sdAlwaysAvailable"]): self._sdAvailable = True self.refreshSdFiles() self._callback.on_comm_sd_state_change(self._sdAvailable) def refreshSdFiles(self): """ Refreshes the list of available SD card files :return: """ if not self.isOperational() or self.isBusy(): return fList = self._beeCommands.getFileList() ##~~ SD file list if len(fList) > 0 and 'FileNames' in fList: for sdFile in fList['FileNames']: if comm.valid_file_type(sdFile, "machinecode"): if comm.filter_non_ascii(sdFile): self._logger.warn("Got a file from printer's SD that has a non-ascii filename (%s), that shouldn't happen according to the protocol" % filename) else: if not filename.startswith("/"): # file from the root of the sd -- we'll prepend a / filename = "/" + filename self._sdFiles.append((sdFile, 0)) continue def startFileTransfer(self, filename, localFilename, remoteFilename): """ Transfers a file to the printer's SD Card """ if not self.isOperational() or self.isBusy(): self._log("Printer is not operation or busy") return self._currentFile = comm.StreamingGcodeFileInformation(filename, localFilename, remoteFilename) self._currentFile.start() # starts the transfer self._beeCommands.transferSDFile(filename, localFilename) eventManager().fire(Events.TRANSFER_STARTED, {"local": localFilename, "remote": remoteFilename}) self._callback.on_comm_file_transfer_started(remoteFilename, self._currentFile.getFilesize()) # waits for transfer to end while self._beeCommands.getTransferCompletionState() > 0: time.sleep(2) remote = self._currentFile.getRemoteFilename() payload = { "local": self._currentFile.getLocalFilename(), "remote": remote, "time": self.getPrintTime() } self._currentFile = None self._changeState(self.STATE_OPERATIONAL) self._callback.on_comm_file_transfer_done(remote) eventManager().fire(Events.TRANSFER_DONE, payload) self.refreshSdFiles() def startPrintStatusProgressMonitor(self): """ Starts the monitor thread that keeps track of the print progress :return: """ if self._beeCommands is not None: # starts the progress status thread self._beeCommands.startStatusMonitor(self._statusProgressQueueCallback) def selectFile(self, filename, sd): """ Overrides the original selectFile method to allow to select files when printer is busy. For example when reconnecting after connection was lost and the printer is still printing :param filename: :param sd: :return: """ if sd: if not self.isOperational(): # printer is not connected, can't use SD return self._sdFileToSelect = filename self.sendCommand("M23 %s" % filename) else: # Special case treatment for in memory file printing if filename == 'Memory File': self._currentFile = InMemoryFileInformation(filename, offsets_callback=self.getOffsets, current_tool_callback=self.getCurrentTool) self._callback.on_comm_file_selected(filename, 0, False) else: self._currentFile = comm.PrintingGcodeFileInformation(filename, offsets_callback=self.getOffsets, current_tool_callback=self.getCurrentTool) eventManager().fire(Events.FILE_SELECTED, { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() }) self._callback.on_comm_file_selected(filename, self._currentFile.getFilesize(), False) def getPrintProgress(self): """ Gets the current print progress :return: """ if self._currentFile is None: return None return self._currentFile.getProgress() def getCurrentFile(self): """ Gets the current PrintFileInformation object :return: """ return self._currentFile def _getResponse(self): """ Auxiliar method to read the command response queue :return: """ if self._beeConn is None: return None try: ret = self._responseQueue.get() except: self._log("Exception raised while reading from command response queue: %s" % (get_exception_string())) self._errorValue = get_exception_string() return None if ret == '': #self._log("Recv: TIMEOUT") return '' try: self._log("Recv: %s" % sanitize_ascii(ret)) except ValueError as e: self._log("WARN: While reading last line: %s" % e) self._log("Recv: %r" % ret) return ret def triggerPrintFinished(self): """ This method runs the post-print job code :return: """ self._sdFilePos = 0 self._callback.on_comm_print_job_done() self._changeState(self.STATE_OPERATIONAL) eventManager().fire(Events.PRINT_DONE, { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation(), "time": self.getPrintTime() }) if self._sd_status_timer is not None: try: self._sd_status_timer.cancel() except: pass def _monitor(self): """ Monitor thread of responses from the commands sent to the printer :return: """ feedback_controls, feedback_matcher = comm.convert_feedback_controls(settings().get(["controls"])) feedback_errors = [] pause_triggers = comm.convert_pause_triggers(settings().get(["printerParameters", "pauseTriggers"])) #exits if no connection is active if not self._beeConn.isConnected(): return startSeen = False supportWait = settings().getBoolean(["feature", "supportWait"]) while self._monitoring_active: try: line = self._getResponse() if line is None: continue ##~~ debugging output handling if line.startswith("//"): debugging_output = line[2:].strip() if debugging_output.startswith("action:"): action_command = debugging_output[len("action:"):].strip() if action_command == "pause": self._log("Pausing on request of the printer...") self.setPause(True) elif action_command == "resume": self._log("Resuming on request of the printer...") self.setPause(False) elif action_command == "disconnect": self._log("Disconnecting on request of the printer...") self._callback.on_comm_force_disconnect() else: for hook in self._printer_action_hooks: try: self._printer_action_hooks[hook](self, line, action_command) except: self._logger.exception("Error while calling hook {} with action command {}".format(self._printer_action_hooks[hook], action_command)) continue else: continue ##~~ Error handling line = self._handleErrors(line) ##~~ process oks if line.strip().startswith("ok") or (self.isPrinting() and supportWait and line.strip().startswith("wait")): self._clear_to_send.set() self._long_running_command = False ##~~ Temperature processing if ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:') \ or ' B:' in line or line.startswith('B:'): self._processTemperatures(line) self._callback.on_comm_temperature_update(self._temp, self._bedTemp) ##~~ SD Card handling elif 'SD init fail' in line or 'volume.init failed' in line or 'openRoot failed' in line: self._sdAvailable = False self._sdFiles = [] self._callback.on_comm_sd_state_change(self._sdAvailable) elif 'Not SD printing' in line: if self.isSdFileSelected() and self.isPrinting(): # something went wrong, printer is reporting that we actually are not printing right now... self._sdFilePos = 0 self._changeState(self.STATE_OPERATIONAL) elif 'SD card ok' in line and not self._sdAvailable: self._sdAvailable = True self.refreshSdFiles() self._callback.on_comm_sd_state_change(self._sdAvailable) elif 'Begin file list' in line: self._sdFiles = [] self._sdFileList = True elif 'End file list' in line: self._sdFileList = False self._callback.on_comm_sd_files(self._sdFiles) elif 'SD printing byte' in line and self.isSdPrinting(): # answer to M27, at least on Marlin, Repetier and Sprinter: "SD printing byte %d/%d" match = regex_sdPrintingByte.search(line) self._currentFile.setFilepos(int(match.group(1))) self._callback.on_comm_progress() elif 'File opened' in line and not self._ignore_select: # answer to M23, at least on Marlin, Repetier and Sprinter: "File opened:%s Size:%d" match = regex_sdFileOpened.search(line) if self._sdFileToSelect: name = self._sdFileToSelect self._sdFileToSelect = None else: name = match.group(1) self._currentFile = comm.PrintingSdFileInformation(name, int(match.group(2))) elif 'File selected' in line: if self._ignore_select: self._ignore_select = False elif self._currentFile is not None: # final answer to M23, at least on Marlin, Repetier and Sprinter: "File selected" self._callback.on_comm_file_selected(self._currentFile.getFilename(), self._currentFile.getFilesize(), True) eventManager().fire(Events.FILE_SELECTED, { "file": self._currentFile.getFilename(), "origin": self._currentFile.getFileLocation() }) elif 'Writing to file' in line: # answer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s" self._changeState(self.STATE_PRINTING) self._clear_to_send.set() line = "ok" elif 'Done saving file' in line: self.refreshSdFiles() elif 'File deleted' in line and line.strip().endswith("ok"): # buggy Marlin version that doesn't send a proper \r after the "File deleted" statement, fixed in # current versions self._clear_to_send.set() ##~~ Message handling elif line.strip() != '' \ and line.strip() != 'ok' and not line.startswith("wait") \ and not line.startswith('Resend:') \ and line != 'echo:Unknown command:""\n' \ and self.isOperational(): self._callback.on_comm_message(line) ##~~ Parsing for feedback commands if feedback_controls and feedback_matcher and not "_all" in feedback_errors: try: self._process_registered_message(line, feedback_matcher, feedback_controls, feedback_errors) except: # something went wrong while feedback matching self._logger.exception("Error while trying to apply feedback control matching, disabling it") feedback_errors.append("_all") ##~~ Parsing for pause triggers if pause_triggers and not self.isStreaming(): if "enable" in pause_triggers.keys() and pause_triggers["enable"].search(line) is not None: self.setPause(True) elif "disable" in pause_triggers.keys() and pause_triggers["disable"].search(line) is not None: self.setPause(False) elif "toggle" in pause_triggers.keys() and pause_triggers["toggle"].search(line) is not None: self.setPause(not self.isPaused()) self.setPause(not self.isPaused()) ### Connection attempt elif self._state == self.STATE_CONNECTING: if "start" in line and not startSeen: startSeen = True self._sendCommand("M110") self._clear_to_send.set() elif "ok" in line: self._onConnected() elif time.time() > self._timeout: self.close() ### Operational elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED: if "ok" in line: # if we still have commands to process, process them if self._resendDelta is not None: self._resendNextCommand() elif self._sendFromQueue(): pass # resend -> start resend procedure from requested line elif line.lower().startswith("resend") or line.lower().startswith("rs"): self._handleResendRequest(line) except Exception as ex: self._logger.exception("Something crashed inside the USB connection.") errorMsg = "See octoprint.log for details" self._log(ex.message) self._errorValue = errorMsg self._changeState(self.STATE_ERROR) eventManager().fire(Events.ERROR, {"error": self.getErrorString()}) self._log("Connection closed, closing down monitor") def _statusProgressQueueCallback(self, status_obj): """ Auxiliar callback method to push the status object that comes from the printer into the queue :param status_obj: :return: """ # calls the Printer object to update the progress values self._callback.updateProgress(status_obj) self._callback.on_comm_progress() def _onConnected(self): """ Post connection callback """ # starts the connection monitor thread self._beeConn.startConnectionMonitor() self._temperature_timer = RepeatedTimer(self._timeout_intervals.get("temperature", 4.0), self._poll_temperature, run_first=True) self._temperature_timer.start() if self._sdAvailable: self.refreshSdFiles() else: self.initSdCard() payload = dict(port=self._port, baudrate=self._baudrate) eventManager().fire(Events.CONNECTED, payload) def _poll_temperature(self): """ Polls the temperature after the temperature timeout, re-enqueues itself. If the printer is not operational, not printing from sd, busy with a long running command or heating, no poll will be done. """ try: if self.isOperational() and not self.isStreaming() and not self._long_running_command and not self._heating: self.sendCommand("M105", cmd_type="temperature_poll") except Exception as e: self._log("Error polling temperature %s" % str(e)) def getCommandsInterface(self): """ Returns the commands interface for BVC printers :return: """ return self._beeCommands def _connShutdownHook(self): """ Function to be called by the BVC driver to shutdown the connection :return: """ self._callback.on_comm_force_disconnect() def _preparePrintThread(self): """ Thread code that runs while the print job is being prepared :return: """ # waits for heating/file transfer while self._beeCommands.isTransferring(): time.sleep(1) if not self._preparing_print: # the print (transfer) was cancelled return self._changeState(self.STATE_HEATING) while self._beeCommands.isHeating(): time.sleep(1) if not self._preparing_print: # the print (heating) was cancelled return if self._currentFile is not None: # Starts the real printing operation self._currentFile.start() self._changeState(self.STATE_PRINTING) payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() } eventManager().fire(Events.PRINT_STARTED, payload) # starts the progress status thread self.startPrintStatusProgressMonitor() if self._heatupWaitStartTime is not None: self._heatupWaitTimeLost = self._heatupWaitTimeLost + (time.time() - self._heatupWaitStartTime) self._heatupWaitStartTime = None self._heating = False self._preparing_print = False else: self._changeState(self.STATE_READY) self._logger.error('Error starting Print operation. No selected file found.') def _resumePrintThread(self): """ Thread code that runs while the print job is being resumed after pause/shutdown :return: """ self._changeState(self.STATE_HEATING) while self._beeCommands.isHeating(): time.sleep(1) if not self._preparing_print: # the print (heating) was cancelled return if self._currentFile is not None: # Starts the real printing operation self._changeState(self.STATE_PRINTING) payload = { "file": self._currentFile.getFilename(), "filename": os.path.basename(self._currentFile.getFilename()), "origin": self._currentFile.getFileLocation() } eventManager().fire(Events.PRINT_RESUMED, payload) # starts the progress status thread self.startPrintStatusProgressMonitor() if self._heatupWaitStartTime is not None: self._heatupWaitTimeLost = self._heatupWaitTimeLost + (time.time() - self._heatupWaitStartTime) self._heatupWaitStartTime = None self._heating = False self._preparing_print = False else: self._changeState(self.STATE_READY) self._logger.error('Error starting Print operation. No selected file found.')
class HomeassistantPlugin( octoprint.plugin.SettingsPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.ProgressPlugin, octoprint.plugin.WizardPlugin, ): def __init__(self): self._logger = logging.getLogger(__name__) self.mqtt_publish = None self.mqtt_publish_with_timestamp = None self.mqtt_subcribe = None self.update_timer = None def handle_timer(self): self._generate_printer_status() ##~~ SettingsPlugin def get_settings_defaults(self): return SETTINGS_DEFAULTS def get_settings_version(self): return 1 def on_settings_migrate(self, target, current): if target == 1: # This is the first version _node_uuid = self._settings.get(["unique_id"]) if _node_uuid: _node_id = (_node_uuid[:6]).upper() self._settings.set(["node_id"], _node_id) ##~~ StartupPlugin mixin def on_after_startup(self): if self._settings.get(["unique_id"]) is None: import uuid _uuid = uuid.uuid4() _uid = str(_uuid) self._settings.set(["unique_id"], _uid) self._settings.set(["node_id"], _uuid.hex) settings().save() helpers = self._plugin_manager.get_helpers( "mqtt", "mqtt_publish", "mqtt_publish_with_timestamp", "mqtt_subscribe") if helpers: if "mqtt_publish_with_timestamp" in helpers: self._logger.debug("Setup publish with timestamp helper") self.mqtt_publish_with_timestamp = helpers[ "mqtt_publish_with_timestamp"] if "mqtt_publish" in helpers: self._logger.debug("Setup publish helper") self.mqtt_publish = helpers["mqtt_publish"] if "mqtt_subscribe" in helpers: self._logger.debug("Setup subscribe helper") self.mqtt_subscribe = helpers["mqtt_subscribe"] self.mqtt_subscribe( self._generate_topic("lwTopic", "", full=True), self._on_mqtt_message, ) if not self.update_timer: self.update_timer = RepeatedTimer(60, self.handle_timer, None, None, False) # Since retain may not be used it's not always possible to simply tie this to the connected state self._generate_device_registration() self._generate_device_controls(subscribe=True) # For people who do not have retain setup, need to do this again to make sensors available _connected_topic = self._generate_topic("lwTopic", "", full=True) self.mqtt_publish(_connected_topic, "connected", allow_queueing=True) # Setup the default printer states self.mqtt_publish( self._generate_topic("hassTopic", "is_printing", full=True), "False", allow_queueing=True, ) self.mqtt_publish( self._generate_topic("hassTopic", "is_paused", full=True), "False", allow_queueing=True, ) self.on_print_progress("", "", 0) state, _, _, _ = self._printer.get_current_connection() state_connected = "Disconnected" if state == "Closed" else "Connected" self.mqtt_publish( self._generate_topic("eventTopic", "Connected", full=True), state_connected, allow_queueing=True, ) def _get_mac_address(self): import uuid return ":".join(re.findall("..", "%012x" % uuid.getnode())) def _on_mqtt_message(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.info("Received MQTT message from " + topic) self._logger.info(message) # Don't rely on this, the message may be disabled. if message == "connected": self._generate_device_registration() self._generate_device_controls(subscribe=False) def _generate_topic(self, topic_type, topic, full=False): self._logger.debug("Generating topic for " + topic_type + ", " + topic) mqtt_defaults = dict(plugins=dict(mqtt=MQTT_DEFAULTS)) _topic = "" if topic_type != "baseTopic": _topic = settings().get(["plugins", "mqtt", "publish", topic_type], defaults=mqtt_defaults) _topic = re.sub(r"{.+}", "", _topic) if full or topic_type == "baseTopic": _topic = ( settings().get(["plugins", "mqtt", "publish", "baseTopic"], defaults=mqtt_defaults) + _topic) _topic += topic self._logger.debug("Generated topic: " + _topic) return _topic def _generate_device_registration(self): s = settings() name_defaults = dict(appearance=dict(name="OctoPrint")) _node_name = s.get(["appearance", "name"], defaults=name_defaults) _node_id = self._settings.get(["node_id"]) _config_device = self._generate_device_config(_node_id, _node_name) ##~~ Configure Connected Sensor self._generate_sensor( topic="homeassistant/binary_sensor/" + _node_id + "_CONNECTED/config", values={ "name": _node_name + " Connected", "uniq_id": _node_id + "_CONNECTED", "stat_t": "~" + self._generate_topic("eventTopic", "Connected"), "json_attr_t": "~" + self._generate_topic("eventTopic", "Connected"), "pl_on": "Connected", "pl_off": "Disconnected", "val_tpl": "{{value_json._event}}", "dev_cla": "connectivity", "device": _config_device, }, ) ##~~ Configure Printing Sensor self._generate_sensor( topic="homeassistant/binary_sensor/" + _node_id + "_PRINTING/config", values={ "name": _node_name + " Printing", "uniq_id": _node_id + "_PRINTING", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "pl_on": "True", "pl_off": "False", "val_tpl": "{{value_json.state.flags.printing}}", "device": _config_device, }, ) ##~~ Configure Last Event Sensor self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_EVENT/config", values={ "name": _node_name + " Last Event", "uniq_id": _node_id + "_EVENT", "stat_t": "~" + self._generate_topic("eventTopic", "+"), "val_tpl": "{{value_json._event}}", "device": _config_device, }, ) ##~~ Configure Print Status self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_PRINTING_S/config", values={ "name": _node_name + " Print Status", "uniq_id": _node_id + "_PRINTING_S", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_tpl": "{{value_json.state|tojson}}", "val_tpl": "{{value_json.state.text}}", "device": _config_device, }, ) ##~~ Configure Print Progress self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_PRINTING_P/config", values={ "name": _node_name + " Print Progress", "uniq_id": _node_id + "_PRINTING_P", "stat_t": "~" + self._generate_topic("progressTopic", "printing"), "unit_of_meas": "%", "val_tpl": "{{value_json.progress|float|default(0,true)}}", "device": _config_device, }, ) ##~~ Configure Print File self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_PRINTING_F/config", values={ "name": _node_name + " Print File", "uniq_id": _node_id + "_PRINTING_F", "stat_t": "~" + self._generate_topic("progressTopic", "printing"), "val_tpl": "{{value_json.path}}", "device": _config_device, "ic": "mdi:file", }, ) ##~~ Configure Print Time self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_PRINTING_T/config", values={ "name": _node_name + " Print Time", "uniq_id": _node_id + "_PRINTING_T", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "val_tpl": "{{value_json.progress.printTimeFormatted}}", "device": _config_device, "ic": "mdi:clock-start", }, ) ##~~ Configure Print Time Left self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_PRINTING_E/config", values={ "name": _node_name + " Print Time Left", "uniq_id": _node_id + "_PRINTING_E", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "val_tpl": "{{value_json.progress.printTimeLeftFormatted}}", "device": _config_device, "ic": "mdi:clock-end", }, ) ##~~ Configure Print ETA self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_PRINTING_ETA/config", values={ "name": _node_name + " Print Estimated Time", "uniq_id": _node_id + "_PRINTING_ETA", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_t": "~" + self._generate_topic("hassTopic", "printing"), "json_attr_tpl": "{{value_json.job|tojson}}", "val_tpl": "{{value_json.job.estimatedPrintTimeFormatted}}", "device": _config_device, }, ) ##~~ Configure Print Current Z self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_PRINTING_Z/config", values={ "name": _node_name + " Current Z", "uniq_id": _node_id + "_PRINTING_Z", "stat_t": "~" + self._generate_topic("hassTopic", "printing"), "unit_of_meas": "mm", "val_tpl": "{{value_json.currentZ|float}}", "device": _config_device, "ic": "mdi:axis-z-arrow", }, ) ##~~ Configure Slicing Status self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_SLICING_P/config", values={ "name": _node_name + " Slicing Progress", "uniq_id": _node_id + "_SLICING_P", "stat_t": "~" + self._generate_topic("progressTopic", "slicing"), "unit_of_meas": "%", "val_tpl": "{{value_json.progress|float|default(0,true)}}", "device": _config_device, }, ) ##~~ Configure Slicing File self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_SLICING_F/config", values={ "name": _node_name + " Slicing File", "uniq_id": _node_id + "_SLICING_F", "stat_t": "~" + self._generate_topic("progressTopic", "slicing"), "val_tpl": "{{value_json.source_path}}", "device": _config_device, "ic": "mdi:file", }, ) ##~~ Tool Temperature _e = self._printer_profile_manager.get_current_or_default( )["extruder"]["count"] for x in range(_e): self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_TOOL" + str(x) + "/config", values={ "name": _node_name + " Tool " + str(x) + " Temperature", "uniq_id": _node_id + "_TOOL" + str(x), "stat_t": "~" + self._generate_topic("temperatureTopic", "tool" + str(x)), "unit_of_meas": "°C", "val_tpl": "{{value_json.actual|float}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:printer-3d-nozzle", }, ) self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_TOOL_TARGET" + str(x) + "/config", values={ "name": _node_name + " Tool " + str(x) + " Target", "uniq_id": _node_id + "_TOOL_TARGET" + str(x), "stat_t": "~" + self._generate_topic("temperatureTopic", "tool" + str(x)), "unit_of_meas": "°C", "val_tpl": "{{value_json.target|float}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:printer-3d-nozzle", }, ) ##~~ Bed Temperature self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_BED/config", values={ "name": _node_name + " Bed Temperature", "uniq_id": _node_id + "_BED", "stat_t": "~" + self._generate_topic("temperatureTopic", "bed"), "unit_of_meas": "°C", "val_tpl": "{{value_json.actual|float}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:radiator", }, ) self._generate_sensor( topic="homeassistant/sensor/" + _node_id + "_BED_TARGET/config", values={ "name": _node_name + " Bed Target", "uniq_id": _node_id + "_BED_TARGET", "stat_t": "~" + self._generate_topic("temperatureTopic", "bed"), "unit_of_meas": "°C", "val_tpl": "{{value_json.target|float}}", "device": _config_device, "dev_cla": "temperature", "ic": "mdi:radiator", }, ) def _generate_sensor(self, topic, values): payload = { "avty_t": "~" + self._generate_topic("lwTopic", ""), "pl_avail": "connected", "pl_not_avail": "disconnected", "~": self._generate_topic("baseTopic", "", full=True), } payload.update(values) self.mqtt_publish(topic, payload, allow_queueing=True) def _generate_device_config(self, _node_id, _node_name): _config_device = { "ids": [_node_id], "cns": [["mac", self._get_mac_address()]], "name": _node_name, "mf": "Clifford Roche", "mdl": "HomeAssistant Discovery for OctoPrint", "sw": self._plugin_version, } return _config_device def _generate_printer_status(self): data = self._printer.get_current_data() try: data["progress"]["printTimeLeftFormatted"] = str( datetime.timedelta( seconds=int(data["progress"]["printTimeLeft"]))) except: data["progress"]["printTimeLeftFormatted"] = None try: data["progress"]["printTimeFormatted"] = str( datetime.timedelta(seconds=data["progress"]["printTime"])) except: data["progress"]["printTimeFormatted"] = None try: data["job"]["estimatedPrintTimeFormatted"] = str( datetime.timedelta(seconds=data["job"]["estimatedPrintTime"])) except: data["job"]["estimatedPrintTimeFormatted"] = None if self.mqtt_publish_with_timestamp: self.mqtt_publish_with_timestamp( self._generate_topic("hassTopic", "printing", full=True), data, allow_queueing=True, ) def _on_emergency_stop(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Emergency stop message received: " + str(message)) if message: self._printer.commands("M112") def _on_cancel_print(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Cancel print message received: " + str(message)) if message: self._printer.cancel_print() def _on_pause_print(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Pause print message received: " + str(message)) if message: self._printer.pause_print() else: self._printer.resume_print() def _on_shutdown_system(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Shutdown print message received: " + str(message)) if message: shutdown_command = self._settings.global_get( ["server", "commands", "systemShutdownCommand"]) try: import sarge params = {"async": True} sarge.run(shutdown_command, **params) except Exception as e: self._logger.info("Unable to run shutdown command: " + str(e)) def _on_home(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Homing printer: " + str(message)) if message: try: home_payload = json.loads(message) axes = set(home_payload) & set(["x", "y", "z", "e"]) self._printer.home(list(axes)) except Exception as e: self._logger.error("Unable to run home command: " + str(e)) def _on_jog(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Jogging printer: " + str(message)) if message: try: jog_payload = json.loads(message) axes_keys = set(jog_payload.keys()) & set(["x", "y", "z"]) axes = { k: v for (k, v) in jog_payload.items() if k in axes_keys } self._printer.jog(axes, jog_payload.get("speed")) except Exception as e: self._logger.error("Unable to run jog command: " + str(e)) def _on_command(self, topic, message, retained=None, qos=None, *args, **kwargs): self._logger.debug("Jogging received gcode commands") try: self._printer.commands(message) except Exception as e: self._logger.error("Unable to run printer commands: " + str(e)) def _generate_device_controls(self, subscribe=False): s = settings() name_defaults = dict(appearance=dict(name="OctoPrint")) _node_name = s.get(["appearance", "name"], defaults=name_defaults) _node_id = self._settings.get(["node_id"]) _config_device = self._generate_device_config(_node_id, _node_name) # Emergency stop if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "stop", full=True), self._on_emergency_stop, ) self._generate_sensor( topic="homeassistant/switch/" + _node_id + "_STOP/config", values={ "name": _node_name + " Emergency Stop", "uniq_id": _node_id + "_STOP", "cmd_t": "~" + self._generate_topic("controlTopic", "stop"), "stat_t": "~" + self._generate_topic("controlTopic", "stop"), "pl_off": "False", "pl_on": "True", "val_tpl": "{{False}}", "device": _config_device, "ic": "mdi:alert-octagon", }, ) # Cancel print if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "cancel", full=True), self._on_cancel_print, ) self._generate_sensor( topic="homeassistant/switch/" + _node_id + "_CANCEL/config", values={ "name": _node_name + " Cancel Print", "uniq_id": _node_id + "_CANCEL", "cmd_t": "~" + self._generate_topic("controlTopic", "cancel"), "stat_t": "~" + self._generate_topic("controlTopic", "cancel"), "avty_t": "~" + self._generate_topic("hassTopic", "is_printing"), "pl_avail": "True", "pl_not_avail": "False", "pl_off": "False", "pl_on": "True", "val_tpl": "{{False}}", "device": _config_device, "ic": "mdi:cancel", }, ) # Pause / resume print if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "pause", full=True), self._on_pause_print, ) self._generate_sensor( topic="homeassistant/switch/" + _node_id + "_PAUSE/config", values={ "name": _node_name + " Pause Print", "uniq_id": _node_id + "_PAUSE", "cmd_t": "~" + self._generate_topic("controlTopic", "pause"), "stat_t": "~" + self._generate_topic("hassTopic", "is_paused"), "avty_t": "~" + self._generate_topic("hassTopic", "is_printing"), "pl_avail": "True", "pl_not_avail": "False", "pl_off": "False", "pl_on": "True", "device": _config_device, "ic": "mdi:pause", }, ) # Shutdown OctoPrint if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "shutdown", full=True), self._on_shutdown_system, ) self._generate_sensor( topic="homeassistant/switch/" + _node_id + "_SHUTDOWN/config", values={ "name": _node_name + " Shutdown System", "uniq_id": _node_id + "_SHUTDOWN", "cmd_t": "~" + self._generate_topic("controlTopic", "shutdown"), "stat_t": "~" + self._generate_topic("controlTopic", "shutdown"), "pl_off": "False", "pl_on": "True", "val_tpl": "{{False}}", "device": _config_device, "ic": "mdi:power", }, ) # Command topics that don't have a suitable sensor configuration. These can be used # through the MQTT.publish service call though. if subscribe: self.mqtt_subscribe( self._generate_topic("controlTopic", "jog", full=True), self._on_jog) self.mqtt_subscribe( self._generate_topic("controlTopic", "home", full=True), self._on_home) self.mqtt_subscribe( self._generate_topic("controlTopic", "commands", full=True), self._on_command, ) ##~~ EventHandlerPlugin API def on_event(self, event, payload): events = dict( comm=( Events.CONNECTING, Events.CONNECTED, Events.DISCONNECTING, Events.DISCONNECTED, Events.ERROR, Events.PRINTER_STATE_CHANGED, ), files=(Events.FILE_SELECTED, Events.FILE_DESELECTED), status=( Events.PRINT_STARTED, Events.PRINT_FAILED, Events.PRINT_DONE, Events.PRINT_CANCELLED, Events.PRINT_PAUSED, Events.PRINT_RESUMED, Events.Z_CHANGE, ), ) if (event in events["comm"] or event in events["files"] or event in events["status"]): self._logger.debug("Received event " + event + ", updating status") self._generate_printer_status() if event == Events.PRINT_STARTED: if self.update_timer: self.mqtt_publish( self._generate_topic("hassTopic", "is_printing", full=True), "True", allow_queueing=True, ) self.update_timer.start() elif event in (Events.PRINT_DONE, Events.PRINT_FAILED, Events.PRINT_CANCELLED): if self.update_timer: self.mqtt_publish( self._generate_topic("hassTopic", "is_printing", full=True), "False", allow_queueing=True, ) self.update_timer.cancel() if event == Events.PRINT_PAUSED: self.mqtt_publish( self._generate_topic("hassTopic", "is_paused", full=True), "True", allow_queueing=True, ) elif event in (Events.PRINT_RESUMED, Events.PRINT_STARTED): self.mqtt_publish( self._generate_topic("hassTopic", "is_paused", full=True), "False", allow_queueing=True, ) ##~~ ProgressPlugin API def on_print_progress(self, storage, path, progress): self._generate_printer_status() def on_slicing_progress( self, slicer, source_location, source_path, destination_location, destination_path, progress, ): pass ##~~ WizardPlugin mixin def is_wizard_required(self): helpers = self._plugin_manager.get_helpers("mqtt") if helpers: return False mqtt_defaults = dict(plugins=dict(mqtt=MQTT_DEFAULTS)) _retain = settings().get_boolean( ["plugins", "mqtt", "broker", "retain"], defaults=mqtt_defaults) if not _retain: return False return True ##~~ Softwareupdate hook def get_update_information(self): # Define the configuration for your plugin to use with the Software Update # Plugin here. See https://docs.octoprint.org/en/master/bundledplugins/softwareupdate.html # for details. return dict(homeassistant=dict( displayName="HomeAssistant Discovery Plugin", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-HomeAssistant", current=self._plugin_version, # update method: pip pip= "https://github.com/cmroche/OctoPrint-HomeAssistant/archive/{target_version}.zip", ))
class TasmotaMQTTPlugin( octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.WizardPlugin): def __init__(self): self._logger = logging.getLogger("octoprint.plugins.tasmota_mqtt") self._tasmota_mqtt_logger = logging.getLogger( "octoprint.plugins.tasmota_mqtt.debug") self.abortTimeout = 0 self._timeout_value = None self._abort_timer = None self._countdown_active = False self._waitForHeaters = False self._waitForTimelapse = False self._timelapse_active = False self._skipIdleTimer = False self.powerOffWhenIdle = False self._idleTimer = None self._autostart_file = None self.mqtt_publish = None self.mqtt_subscribe = None self.idleTimeout = None self.idleIgnoreCommands = None self._idleIgnoreCommandsArray = None self.idleTimeoutWaitTemp = None ##~~ SettingsPlugin mixin def get_settings_defaults(self): return dict(arrRelays=[], full_topic_pattern='%topic%/%prefix%/', abortTimeout=30, powerOffWhenIdle=False, idleTimeout=30, idleIgnoreCommands='M105', idleTimeoutWaitTemp=50, debug_logging=False) def get_settings_version(self): return 5 def on_settings_migrate(self, target, current=None): if current is None or current < 3: self._settings.set(['arrRelays'], self.get_settings_defaults()["arrRelays"]) if current == 2: # Add new fields arrRelays_new = [] for relay in self._settings.get(['arrRelays']): relay["automaticShutdownEnabled"] = False arrRelays_new.append(relay) self._settings.set(["arrRelays"], arrRelays_new) if current <= 3: # Add new fields arrRelays_new = [] for relay in self._settings.get(['arrRelays']): relay["errorEvent"] = False arrRelays_new.append(relay) self._settings.set(["arrRelays"], arrRelays_new) if current <= 4: # Add new fields arrRelays_new = [] for relay in self._settings.get(['arrRelays']): relay["event_on_upload"] = False relay["event_on_startup"] = False arrRelays_new.append(relay) self._settings.set(["arrRelays"], arrRelays_new) def on_settings_save(self, data): old_debug_logging = self._settings.get_boolean(["debug_logging"]) old_powerOffWhenIdle = self._settings.get_boolean(["powerOffWhenIdle"]) old_idleTimeout = self._settings.get_int(["idleTimeout"]) old_idleIgnoreCommands = self._settings.get(["idleIgnoreCommands"]) old_idleTimeoutWaitTemp = self._settings.get_int( ["idleTimeoutWaitTemp"]) octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self.abortTimeout = self._settings.get_int(["abortTimeout"]) self.powerOffWhenIdle = self._settings.get_boolean( ["powerOffWhenIdle"]) self.idleTimeout = self._settings.get_int(["idleTimeout"]) self.idleIgnoreCommands = self._settings.get(["idleIgnoreCommands"]) self._idleIgnoreCommandsArray = self.idleIgnoreCommands.split(',') self.idleTimeoutWaitTemp = self._settings.get_int( ["idleTimeoutWaitTemp"]) if self.powerOffWhenIdle != old_powerOffWhenIdle: self._plugin_manager.send_plugin_message( self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) if self.powerOffWhenIdle: self._tasmota_mqtt_logger.debug( "Settings saved, Automatic Power Off Enabled, starting idle timer..." ) self._reset_idle_timer() new_debug_logging = self._settings.get_boolean(["debug_logging"]) if old_debug_logging != new_debug_logging: if new_debug_logging: self._tasmota_mqtt_logger.setLevel(logging.DEBUG) else: self._tasmota_mqtt_logger.setLevel(logging.INFO) ##~~ StartupPlugin mixin def on_startup(self, host, port): # setup customized logger from octoprint.logging.handlers import CleaningTimedRotatingFileHandler tasmota_mqtt_logging_hnadler = CleaningTimedRotatingFileHandler( self._settings.get_plugin_logfile_path(postfix="debug"), when="D", backupCount=3) tasmota_mqtt_logging_hnadler.setFormatter( logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s")) tasmota_mqtt_logging_hnadler.setLevel(logging.DEBUG) self._tasmota_mqtt_logger.addHandler(tasmota_mqtt_logging_hnadler) self._tasmota_mqtt_logger.setLevel( logging.DEBUG if self._settings.get_boolean(["debug_logging"] ) else logging.INFO) self._tasmota_mqtt_logger.propagate = False def on_after_startup(self): helpers = self._plugin_manager.get_helpers("mqtt", "mqtt_publish", "mqtt_subscribe", "mqtt_unsubscribe") if helpers: if "mqtt_subscribe" in helpers: self.mqtt_subscribe = helpers["mqtt_subscribe"] for relay in self._settings.get(["arrRelays"]): self._tasmota_mqtt_logger.debug( self.generate_mqtt_full_topic(relay, "stat")) self.mqtt_subscribe(self.generate_mqtt_full_topic( relay, "stat"), self._on_mqtt_subscription, kwargs=dict(top=relay["topic"], relayN=relay["relayN"])) if "mqtt_publish" in helpers: self.mqtt_publish = helpers["mqtt_publish"] self.mqtt_publish("octoprint/plugin/tasmota", "OctoPrint-TasmotaMQTT publishing.") if any( map(lambda r: r["event_on_startup"] == True, self._settings.get(["arrRelays"]))): for relay in self._settings.get(["arrRelays"]): self._tasmota_mqtt_logger.debug( "powering on {} due to startup.".format( relay["topic"])) self.turn_on(relay) if "mqtt_unsubscribe" in helpers: self.mqtt_unsubscribe = helpers["mqtt_unsubscribe"] else: self._plugin_manager.send_plugin_message(self._identifier, dict(noMQTT=True)) self.abortTimeout = self._settings.get_int(["abortTimeout"]) self._tasmota_mqtt_logger.debug("abortTimeout: %s" % self.abortTimeout) self.powerOffWhenIdle = self._settings.get_boolean( ["powerOffWhenIdle"]) self._tasmota_mqtt_logger.debug("powerOffWhenIdle: %s" % self.powerOffWhenIdle) self.idleTimeout = self._settings.get_int(["idleTimeout"]) self._tasmota_mqtt_logger.debug("idleTimeout: %s" % self.idleTimeout) self.idleIgnoreCommands = self._settings.get(["idleIgnoreCommands"]) self._idleIgnoreCommandsArray = self.idleIgnoreCommands.split(',') self._tasmota_mqtt_logger.debug("idleIgnoreCommands: %s" % self.idleIgnoreCommands) self.idleTimeoutWaitTemp = self._settings.get_int( ["idleTimeoutWaitTemp"]) self._tasmota_mqtt_logger.debug("idleTimeoutWaitTemp: %s" % self.idleTimeoutWaitTemp) if self.powerOffWhenIdle: self._tasmota_mqtt_logger.debug( "Starting idle timer due to startup") self._reset_idle_timer() def _on_mqtt_subscription(self, topic, message, retained=None, qos=None, *args, **kwargs): self._tasmota_mqtt_logger.debug( "Received message for {topic}: {message}".format(**locals())) self.mqtt_publish("octoprint/plugin/tasmota", "echo: " + message.decode("utf-8")) newrelays = [] bolRelayStateChanged = False bolForceIdleTimer = False for relay in self._settings.get(["arrRelays"]): if relay["topic"] == "{top}".format( **kwargs) and relay["relayN"] == "{relayN}".format( **kwargs ) and relay["currentstate"] != message.decode("utf-8"): bolRelayStateChanged = True relay["currentstate"] = message.decode("utf-8") if relay[ "automaticShutdownEnabled"] == True and self._settings.get_boolean( ["powerOffWhenIdle" ]) and relay["currentstate"] == "ON": self._tasmota_mqtt_logger.debug( "Forcing reset of idle timer because {} was just turned on." .format(relay["topic"])) bolForceIdleTimer = True self._plugin_manager.send_plugin_message( self._identifier, dict(topic="{top}".format(**kwargs), relayN="{relayN}".format(**kwargs), currentstate=message.decode("utf-8"))) newrelays.append(relay) if bolRelayStateChanged: self._settings.set(["arrRelays"], newrelays) self._settings.save() if bolForceIdleTimer: self._reset_idle_timer() ##~~ EventHandlerPlugin mixin def on_event(self, event, payload): if event == "WHERE": try: self.mqtt_unsubscribe(self._on_mqtt_subscription) for relay in self._settings.get(["arrRelays"]): self.mqtt_subscribe(self.generate_mqtt_full_topic( relay, "stat"), self._on_mqtt_subscription, kwargs=dict(top=relay["topic"], relayN=relay["relayN"])) except: self._plugin_manager.send_plugin_message( self._identifier, dict(noMQTT=True)) # Client Opened Event if event == Events.CLIENT_OPENED: self._plugin_manager.send_plugin_message( self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) return # Print Started Event if event == Events.PRINT_STARTED and self.powerOffWhenIdle == True: if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._tasmota_mqtt_logger.debug( "Power off aborted because starting new print.") if self._idleTimer is not None: self._reset_idle_timer() self._timeout_value = None self._plugin_manager.send_plugin_message( self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) # Print Error Event if event == Events.ERROR: self._tasmota_mqtt_logger.debug( "Powering off enabled plugs because there was an error.") for relay in self._settings.get(['arrRelays']): if relay.get("errorEvent", False): self.turn_off(relay) # Timeplapse Events if self.powerOffWhenIdle == True and event == Events.MOVIE_RENDERING: self._tasmota_mqtt_logger.debug( "Timelapse generation started: %s" % payload.get("movie_basename", "")) self._timelapse_active = True if self._timelapse_active and event == Events.MOVIE_DONE or event == Events.MOVIE_FAILED: self._tasmota_mqtt_logger.debug( "Timelapse generation finished: %s. Return Code: %s" % (payload.get("movie_basename", ""), payload.get("returncode", "completed"))) self._timelapse_active = False # Printer Connected Event if event == Events.CONNECTED: if self._autostart_file: self._tasmota_mqtt_logger.debug( "printer connected starting print of %s" % self._autostart_file) self._printer.select_file(self._autostart_file, False, printAfterSelect=True) self._autostart_file = None # File Uploaded Event if event == Events.UPLOAD and any( map(lambda r: r["event_on_upload"] == True, self._settings.get(["arrRelays"]))): if payload.get("print", False): # implemented in OctoPrint version 1.4.1 self._tasmota_mqtt_logger.debug( "File uploaded: %s. Turning enabled relays on." % payload.get("name", "")) self._tasmota_mqtt_logger.debug(payload) for relay in self._settings.get(['arrRelays']): self._tasmota_mqtt_logger.debug(relay) if relay[ "event_on_upload"] is True and not self._printer.is_ready( ): self._tasmota_mqtt_logger.debug( "powering on %s due to %s event." % (relay["topic"], event)) if payload.get( "path", False) and payload.get("target") == "local": self._autostart_file = payload.get("path") self.turn_on(relay) ##~~ AssetPlugin mixin def get_assets(self): return dict(js=[ "js/jquery-ui.min.js", "js/knockout-sortable.1.2.0.js", "js/fontawesome-iconpicker.js", "js/ko.iconpicker.js", "js/tasmota_mqtt.js" ], css=[ "css/font-awesome.min.css", "css/font-awesome-v4-shims.min.css", "css/fontawesome-iconpicker.css", "css/tasmota_mqtt.css" ]) ##~~ TemplatePlugin mixin def get_template_configs(self): return [ dict(type="navbar", custom_bindings=True), dict(type="settings", custom_bindings=True), dict(type="sidebar", icon="plug", custom_bindings=True, data_bind="visible: filteredSmartplugs().length > 0", template="tasmota_mqtt_sidebar.jinja2", template_header="tasmota_mqtt_sidebar_header.jinja2") ] ##~~ SimpleApiPlugin mixin def get_api_commands(self): return dict(turnOn=["topic", "relayN"], turnOff=["topic", "relayN"], toggleRelay=["topic", "relayN"], checkRelay=["topic", "relayN"], checkStatus=[], removeRelay=["topic", "relayN"], enableAutomaticShutdown=[], disableAutomaticShutdown=[], abortAutomaticShutdown=[], getListPlug=[]) def on_api_command(self, command, data): if not Permissions.PLUGIN_TASMOTA_MQTT_CONTROL.can(): from flask import make_response return make_response("Insufficient rights", 403) if command == 'toggleRelay' or command == 'turnOn' or command == 'turnOff': for relay in self._settings.get(["arrRelays"]): if relay["topic"] == "{topic}".format( **data) and relay["relayN"] == "{relayN}".format( **data): if command == "turnOff" or (command == "toggleRelay" and relay["currentstate"] == "ON"): self._tasmota_mqtt_logger.debug( "turning off {topic} relay {relayN}".format( **data)) self.turn_off(relay) if command == "turnOn" or (command == "toggleRelay" and relay["currentstate"] == "OFF"): self._tasmota_mqtt_logger.debug( "turning on {topic} relay {relayN}".format(**data)) self.turn_on(relay) if command == 'checkStatus': for relay in self._settings.get(["arrRelays"]): self._tasmota_mqtt_logger.debug( "checking status of %s relay %s" % (relay["topic"], relay["relayN"])) try: self.mqtt_publish( self.generate_mqtt_full_topic(relay, "cmnd"), "") except: self._plugin_manager.send_plugin_message( self._identifier, dict(noMQTT=True)) if command == 'checkRelay': self._tasmota_mqtt_logger.debug( "subscribing to {topic} relay {relayN}".format(**data)) for relay in self._settings.get(["arrRelays"]): if relay["topic"] == "{topic}".format( **data) and relay["relayN"] == "{relayN}".format( **data): self.mqtt_subscribe( self.generate_mqtt_full_topic(relay, "stat"), self._on_mqtt_subscription, kwargs=dict(top="{topic}".format(**data), relayN="{relayN}".format(**data))) self._tasmota_mqtt_logger.debug( "checking {topic} relay {relayN}".format(**data)) self.mqtt_publish( self.generate_mqtt_full_topic(relay, "cmnd"), "") if command == 'removeRelay': for relay in self._settings.get(["arrRelays"]): if relay["topic"] == "{topic}".format( **data) and relay["relayN"] == "{relayN}".format( **data): self.mqtt_unsubscribe(self._on_mqtt_subscription, topic=self.generate_mqtt_full_topic( relay, "stat")) if command == 'enableAutomaticShutdown': self.powerOffWhenIdle = True self._tasmota_mqtt_logger.debug( "Automatic Power Off enabled, starting idle timer.") self._start_idle_timer() if command == 'disableAutomaticShutdown': self.powerOffWhenIdle = False if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._timeout_value = None self._tasmota_mqtt_logger.debug( "Automatic Power Off disabled, stopping idle and abort timers." ) self._stop_idle_timer() if command == 'abortAutomaticShutdown': if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._timeout_value = None self._tasmota_mqtt_logger.debug("Power off aborted.") self._tasmota_mqtt_logger.debug("Restarting idle timer.") self._reset_idle_timer() if command == "enableAutomaticShutdown" or command == "disableAutomaticShutdown": self._tasmota_mqtt_logger.debug( "Automatic power off setting changed: %s" % self.powerOffWhenIdle) self._settings.set_boolean(["powerOffWhenIdle"], self.powerOffWhenIdle) self._settings.save() if command == "enableAutomaticShutdown" or command == "disableAutomaticShutdown" or command == "abortAutomaticShutdown": self._plugin_manager.send_plugin_message( self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) if command == "getListPlug": return json.dumps(self._settings.get(["arrRelays"])) def turn_on(self, relay): self.mqtt_publish(self.generate_mqtt_full_topic(relay, "cmnd"), "ON") if relay["sysCmdOn"]: t = threading.Timer(int(relay["sysCmdOnDelay"]), os.system, args=[relay["sysCmdRunOn"]]) t.start() if relay["connect"] and self._printer.is_closed_or_error(): t = threading.Timer(int(relay["connectOnDelay"]), self._printer.connect) t.start() if self.powerOffWhenIdle == True and relay[ "automaticShutdownEnabled"] == True: self._tasmota_mqtt_logger.debug( "Resetting idle timer since relay %s | %s was just turned on." % (relay["topic"], relay["relayN"])) self._waitForHeaters = False self._reset_idle_timer() def turn_off(self, relay): if relay["sysCmdOff"]: t = threading.Timer(int(relay["sysCmdOffDelay"]), os.system, args=[relay["sysCmdRunOff"]]) t.start() if relay["disconnect"]: self._printer.disconnect() time.sleep(int(relay["disconnectOffDelay"])) self.mqtt_publish(self.generate_mqtt_full_topic(relay, "cmnd"), "OFF") ##~~ Gcode processing hook def gcode_turn_off(self, relay): if relay["warnPrinting"] and self._printer.is_printing(): self._tasmota_mqtt_logger.debug( "Not powering off %s | %s because printer is printing." % (relay["topic"], relay["relayN"])) else: self.turn_off(relay) def processGCODE(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs): if gcode: if cmd.startswith("M8") and cmd.count(" ") >= 1: topic = cmd.split()[1] if cmd.count(" ") == 2: relayN = cmd.split()[2] else: relayN = "" for relay in self._settings.get(["arrRelays"]): if relay["topic"].upper() == topic.upper( ) and relay["relayN"] == relayN and relay["gcode"]: if cmd.startswith("M80"): t = threading.Timer(int(relay["gcodeOnDelay"]), self.turn_on, [relay]) t.start() return "M80" elif cmd.startswith("M81"): ## t = threading.Timer(int(relay["gcodeOffDelay"]),self.mqtt_publish,[relay["topic"] + "/cmnd/Power" + relay["relayN"], "OFF"]) t = threading.Timer(int(relay["gcodeOffDelay"]), self.gcode_turn_off, [relay]) t.start() return "M81" elif self.powerOffWhenIdle and not ( gcode in self._idleIgnoreCommandsArray): self._waitForHeaters = False self._reset_idle_timer() return else: return else: return ##~~ Idle Timeout def _start_idle_timer(self): self._stop_idle_timer() if self.powerOffWhenIdle and any( map(lambda r: r["currentstate"] == "ON", self._settings.get(["arrRelays"]))): self._idleTimer = ResettableTimer(self.idleTimeout * 60, self._idle_poweroff) self._idleTimer.start() def _stop_idle_timer(self): if self._idleTimer: self._idleTimer.cancel() self._idleTimer = None def _reset_idle_timer(self): try: if self._idleTimer.is_alive(): self._idleTimer.reset() else: raise Exception() except: self._start_idle_timer() def _idle_poweroff(self): if not self.powerOffWhenIdle: return if self._waitForHeaters: return if self._waitForTimelapse: return if self._printer.is_printing() or self._printer.is_paused(): return if (uptime() / 60) <= (self._settings.get_int(["idleTimeout"])): self._tasmota_mqtt_logger.debug( "Just booted so wait for time sync.") self._tasmota_mqtt_logger.debug( "uptime: {}, comparison: {}".format( (uptime() / 60), (self._settings.get_int(["idleTimeout"])))) self._reset_idle_timer() return self._tasmota_mqtt_logger.debug( "Idle timeout reached after %s minute(s). Turning heaters off prior to powering off plugs." % self.idleTimeout) if self._wait_for_heaters(): self._tasmota_mqtt_logger.debug("Heaters below temperature.") if self._wait_for_timelapse(): self._timer_start() else: self._tasmota_mqtt_logger.debug( "Aborted power off due to activity.") ##~~ Timelapse Monitoring def _wait_for_timelapse(self): self._waitForTimelapse = True self._tasmota_mqtt_logger.debug( "Checking timelapse status before shutting off power...") while True: if not self._waitForTimelapse: return False if not self._timelapse_active: self._waitForTimelapse = False return True self._tasmota_mqtt_logger.debug( "Waiting for timelapse before shutting off power...") time.sleep(5) ##~~ Temperature Cooldown def _wait_for_heaters(self): self._waitForHeaters = True heaters = self._printer.get_current_temperatures() for heater, entry in heaters.items(): target = entry.get("target") if target is None: # heater doesn't exist in fw continue try: temp = float(target) except ValueError: # not a float for some reason, skip it continue if temp != 0: self._tasmota_mqtt_logger.debug("Turning off heater: %s" % heater) self._skipIdleTimer = True self._printer.set_temperature(heater, 0) self._skipIdleTimer = False else: self._tasmota_mqtt_logger.debug("Heater %s already off." % heater) while True: if not self._waitForHeaters: return False heaters = self._printer.get_current_temperatures() highest_temp = 0 heaters_above_waittemp = [] for heater, entry in heaters.items(): if not heater.startswith("tool"): continue actual = entry.get("actual") if actual is None: # heater doesn't exist in fw continue try: temp = float(actual) except ValueError: # not a float for some reason, skip it continue self._tasmota_mqtt_logger.debug("Heater %s = %sC" % (heater, temp)) if temp > self.idleTimeoutWaitTemp: heaters_above_waittemp.append(heater) if temp > highest_temp: highest_temp = temp if highest_temp <= self.idleTimeoutWaitTemp: self._waitForHeaters = False return True self._tasmota_mqtt_logger.debug( "Waiting for heaters(%s) before shutting power off..." % ', '.join(heaters_above_waittemp)) time.sleep(5) ##~~ Abort Power Off Timer def _timer_start(self): if self._abort_timer is not None: return self._tasmota_mqtt_logger.debug("Starting abort power off timer.") self._timeout_value = self.abortTimeout self._abort_timer = RepeatedTimer(1, self._timer_task) self._abort_timer.start() def _timer_task(self): if self._timeout_value is None: return self._timeout_value -= 1 self._plugin_manager.send_plugin_message( self._identifier, dict(powerOffWhenIdle=self.powerOffWhenIdle, type="timeout", timeout_value=self._timeout_value)) if self._timeout_value <= 0: if self._abort_timer is not None: self._abort_timer.cancel() self._abort_timer = None self._shutdown_system() def _shutdown_system(self): self._tasmota_mqtt_logger.debug( "Automatically powering off enabled plugs.") for relay in self._settings.get(['arrRelays']): if relay.get("automaticShutdownEnabled", False): self.turn_off(relay) ##~~ Utility functions def generate_mqtt_full_topic(self, relay, prefix): full_topic = re.sub(r'%topic%', relay["topic"], self._settings.get(["full_topic_pattern"])) full_topic = re.sub(r'%prefix%', prefix, full_topic) full_topic = full_topic + "POWER" + relay["relayN"] return full_topic ##~~ WizardPlugin mixin def is_wizard_required(self): helpers = self._plugin_manager.get_helpers("mqtt") if helpers: return False return True ##~~ Access Permissions Hook def get_additional_permissions(self, *args, **kwargs): return [ dict(key="CONTROL", name="Control Relays", description=gettext("Allows control of configured relays."), roles=["admin"], dangerous=True, default_groups=[ADMIN_GROUP]) ] ##~~ Softwareupdate hook def get_update_information(self): return dict(tasmota_mqtt=dict( displayName="Tasmota-MQTT", displayVersion=self._plugin_version, # version check: github repository type="github_release", user="******", repo="OctoPrint-TasmotaMQTT", current=self._plugin_version, stable_branch=dict( name="Stable", branch="master", comittish=["master"]), prerelease_branches=[ dict( name="Release Candidate", branch="rc", comittish=["rc", "master"], ) ], # update method: pip pip= "https://github.com/jneilliii/OctoPrint-TasmotaMQTT/archive/{target_version}.zip" ))
class SafetyTimeoutPlugin(octoprint.plugin.AssetPlugin, octoprint.plugin.EventHandlerPlugin, octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin): def condition(self): ''' This function is to check and see if the temperature and time are set above 0, and a timer does not already exist. If they are: Starts a timer If they are not: It does nothing''' if self.initialstart == True: temperatures = self._printer.get_current_temperatures() if int(self._settings.get(["Time"])) > 0 and temperatures != {}: if float(temperatures.get("bed").get("target")) != 0 or float(temperatures.get("tool0").get("target")) != 0: self.makeTimer() return def makeTimer (self): ''' This function creates a timer instance by grabbing the most recent user specified time''' self.countdowndefined = True self.initial = int(self._settings.get(["Time"])) self._logger.info("The Timer Has Been Initiated!") seconds = self.initial * 60 self.countdown = RepeatedTimer(seconds, self.shutdown, run_first=False) self.initialstart = False self.countdown.start() def on_after_startup(self): ''' Upon server startup, we define the class variables and create our initial timer that constantly checks to see if a safety timeout is needed ''' self.countdowndefined = False self.initialstart = True self.initial = int(self._settings.get(["Time"])) self.timer = RepeatedTimer(1.0, self.condition, run_first=True) self.timer.start() def on_settings_save(self, data): ''' We overload this function in case someone changes the time after a timer has already been set. It will cancel the old timer, and resume checking to see if another one needs to be created''' old_time = self._settings.get(["Time"]) octoprint.plugin.SettingsPlugin.on_settings_save(self, data) new_time = self._settings.get(["Time"]) if old_time != new_time: if self.countdowndefined == True: self.countdown.cancel() self._logger.info("The Timer Has Been cancelled!") seconds = int(new_time) * 60 self.initialstart = True def shutdown(self): ''' Checks to see if machine is printing. If it is: Resumes checking to see if a timer is needed. If it is not: Sets the bed to 0''' self.countdown.cancel() self.countdowndefined = False #check to see if the printer is printing if self._printer.is_printing(): # print("The printer is printing!") self.initialstart = True # print("self.countdown has been cancelled") #if the printer is printing we do not want to interupt it else: #else, we need to set the temperature of the bed and the tool to 0 self._printer.set_temperature("bed", 0) # print("Bed temperature set to 0") self._printer.set_temperature("tool0", 0) # print("Tool temperature set to 0") self.initialstart = True # print("self.countdown has been cancelled") def get_settings_defaults(self): #set the default time return dict(Time="10") def get_template_configs(self): return [ dict(type="settings", custom_bindings=False) ]