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)
Beispiel #8
0
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"
            )
        )
Beispiel #9
0
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"
            )
        )
Beispiel #12
0
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"
			)
		)
Beispiel #13
0
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"
                )
            }
Beispiel #16
0
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"
        ))
Beispiel #17
0
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"
        ))
Beispiel #18
0
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"
			)
		)
Beispiel #20
0
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"
        ))
Beispiel #24
0
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"
        ))
Beispiel #25
0
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
Beispiel #26
0
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",
            )
        )
Beispiel #27
0
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"
            )
        )
Beispiel #28
0
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"
        ))
Beispiel #29
0
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"
        ))
Beispiel #31
0
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"
            )
        )
Beispiel #33
0
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"
			)
		)
Beispiel #34
0
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'])
Beispiel #35
0
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"
                )
        )
Beispiel #36
0
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'])
Beispiel #37
0
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"
        ))
Beispiel #39
0
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.')
Beispiel #40
0
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)
    ]