コード例 #1
0
    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)
コード例 #2
0
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"]}
コード例 #3
0
ファイル: timelapse.py プロジェクト: MaxOLydian/OctoPrint
class TimedTimelapse(Timelapse):
	def __init__(self, post_roll=0, interval=1, fps=25):
		Timelapse.__init__(self, post_roll=post_roll, fps=fps)
		self._interval = interval
		if self._interval < 1:
			self._interval = 1 # force minimum interval of 1s
		self._postroll_captures = 0
		self._timer = None
		self._logger.debug("TimedTimelapse initialized")

	@property
	def interval(self):
		return self._interval

	def config_data(self):
		return {
			"type": "timed",
			"options": {
				"interval": self._interval
			}
		}

	def on_print_started(self, event, payload):
		Timelapse.on_print_started(self, event, payload)
		if self._timer is not None:
			return

		self._logger.debug("Starting timer for interval based timelapse")
		from octoprint.util import RepeatedTimer
		self._timer = RepeatedTimer(self._interval, self._timer_task,
		                            run_first=True, condition=self._timer_active,
		                            on_finish=self._on_timer_finished)
		self._timer.start()

	def on_print_done(self, event, payload):
		self._postroll_captures = self.post_roll * self.fps
		Timelapse.on_print_done(self, event, payload)

	def calculate_post_roll(self):
		return self.post_roll * self.fps * self.interval

	def process_post_roll(self):
		pass

	def post_roll_finished(self):
		Timelapse.post_roll_finished(self)
		self._timer = None

	def _timer_active(self):
		return self._in_timelapse or self._postroll_captures > 0

	def _timer_task(self):
		self.captureImage()
		if self._postroll_captures > 0:
			self._postroll_captures -= 1

	def _on_timer_finished(self):
		self.post_roll_finished()
コード例 #4
0
class TimedTimelapse(Timelapse):
	def __init__(self, post_roll=0, interval=1, fps=25):
		Timelapse.__init__(self, post_roll=post_roll, fps=fps)
		self._interval = interval
		if self._interval < 1:
			self._interval = 1 # force minimum interval of 1s
		self._postroll_captures = 0
		self._timer = None
		self._logger.debug("TimedTimelapse initialized")

	@property
	def interval(self):
		return self._interval

	def config_data(self):
		return {
			"type": "timed",
			"options": {
				"interval": self._interval
			}
		}

	def on_print_started(self, event, payload):
		Timelapse.on_print_started(self, event, payload)
		if self._timer is not None:
			return

		self._logger.debug("Starting timer for interval based timelapse")
		from octoprint.util import RepeatedTimer
		self._timer = RepeatedTimer(self._interval, self._timer_task,
		                            run_first=True, condition=self._timer_active,
		                            on_finish=self._on_timer_finished)
		self._timer.start()

	def on_print_done(self, event, payload):
		self._postroll_captures = self.post_roll * self.fps
		Timelapse.on_print_done(self, event, payload)

	def calculate_post_roll(self):
		return self.post_roll * self.fps * self.interval

	def process_post_roll(self):
		pass

	def post_roll_finished(self):
		Timelapse.post_roll_finished(self)
		self._timer = None

	def _timer_active(self):
		return self._in_timelapse or self._postroll_captures > 0

	def _timer_task(self):
		self.capture_image()
		if self._postroll_captures > 0:
			self._postroll_captures -= 1

	def _on_timer_finished(self):
		self.post_roll_finished()
コード例 #5
0
ファイル: __init__.py プロジェクト: letgo04/BBB-Controller
    def _settimer(timervar, timeval, methodcall):
        worktimer = None

        if timervar is not None:
            timervar.cancel()

        worktimer = RepeatedTimer(timeval, methodcall, None, None, True)
        worktimer.start()

        return worktimer               
コード例 #6
0
    def test_condition(self):
        countdown = Countdown(5)
        timer_task = mock.MagicMock()
        timer_task.side_effect = countdown.step

        timer = RepeatedTimer(0.1, timer_task, condition=lambda: countdown.counter > 0)
        timer.start()

        # wait for it
        timer.join()

        self.assertEqual(5, timer_task.call_count)
コード例 #7
0
	def test_condition(self):
		countdown = Countdown(5)
		timer_task = mock.MagicMock()
		timer_task.side_effect = countdown.step

		timer = RepeatedTimer(0.1, timer_task, condition=lambda: countdown.counter > 0)
		timer.start()

		# wait for it
		timer.join()

		self.assertEqual(5, timer_task.call_count)
コード例 #8
0
class NetworkHealthPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.RestartNeedingPlugin):
    def __init__(self):
        self._timer = None

    def on_after_startup(self):
        self._logger.info("Beginning to monitor Network health...")
        self._timer = RepeatedTimer(180, self._check_network)
        self._timer.start()

    def _check_network(self):
        try:
              if not check_ping():
                  self._logger.error("No Network Connection - Resetting Adapter(s)...")
                  reset_wlan0 = 'sudo ip link set wlan0 down; sudo ip link set wlan0 up'
                  reset_eth0  = 'sudo ip link set eth0 down; sudo ip link set eth0 up'
                  os.system(reset_wlan0)
                  os.system(reset_eth0)
        except Exception:
            self._logger.exception("Could not run network health check")

    def get_update_information(self, *args, **kwargs):
        return dict(
            networkhealth=dict(
                displayName=self._plugin_name,
                displayVersion=self._plugin_version,

                type="github_release",
                current=self._plugin_version,
                user="******",
                repo="OctoPrint-NetworkHealth",

                pip="https://github.com/jonfairbanks/OctoPrint-NetworkHealth/archive/{target}.zip"
            )
        )

    def default_gateway():
        import socket
        import struct
        with open("/proc/net/route") as fh:
            for line in fh:
                fields = line.strip().split()
                if fields[1] != '00000000' or not int(fields[3], 16) & 2:
                    continue
                return socket.inet_ntoa(struct.pack("<L", int(fields[2], 16)))

    def check_ping():
        hostname = default_gateway()
        response = os.system("ping -c 4 " + hostname)
        if response == 0:
            return True
        else:
            return False
コード例 #9
0
	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)
コード例 #10
0
 def start_repeated_timer(self, timer=None, callback=None):
     try:
         if timer is None and callback is not None:
             self._logger.debug("creating repeated timer")
             timer = RepeatedTimer(
                 self.polling_interval,
                 callback,
                 run_first=True,
                 condition=self._continue_polling,
                 on_condition_false=self._polling_canceled)
             timer.start()
         return True, timer
     except Exception:
         return False, timer
コード例 #11
0
    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)
コード例 #12
0
	def test_finished_callback(self):
		countdown = Countdown(5)
		timer_task = mock.MagicMock()
		timer_task.side_effect = countdown.step

		on_finished = mock.MagicMock()

		timer = RepeatedTimer(0.1, timer_task, condition=lambda: countdown.counter > 0, on_finish=on_finished)
		timer.start()

		# wait for it
		timer.join()

		self.assertEqual(1, on_finished.call_count)
コード例 #13
0
	def test_finished_callback(self):
		countdown = Countdown(5)
		timer_task = mock.MagicMock()
		timer_task.side_effect = countdown.step

		on_finished = mock.MagicMock()

		timer = RepeatedTimer(0.1, timer_task, condition=lambda: countdown.counter > 0, on_finish=on_finished)
		timer.start()

		# wait for it
		timer.join()

		self.assertEqual(1, on_finished.call_count)
コード例 #14
0
    def test_condition_change_during_task(self):
        def sleep():
            time.sleep(2)

        timer_task = mock.MagicMock()
        timer_task.side_effect = sleep

        timer = RepeatedTimer(0.1, timer_task, run_first=True)
        timer.start()

        time.sleep(1)
        timer.condition = lambda: False
        timer.join()

        self.assertEqual(1, timer_task.call_count)
コード例 #15
0
	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)
コード例 #16
0
    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)
コード例 #17
0
	def test_condition_change_during_task(self):
		def sleep():
			time.sleep(2)

		timer_task = mock.MagicMock()
		timer_task.side_effect = sleep

		timer = RepeatedTimer(0.1, timer_task, run_first=True)
		timer.start()

		time.sleep(1)
		timer.condition = lambda: False
		timer.join()

		self.assertEqual(1, timer_task.call_count)
コード例 #18
0
 def on_event(self, event, payload):
     if event == octoprint.events.Events.CONNECTED:
         self._logger.info('Printer connected')
         self.connected = True
         t = RepeatedTimer(5.0, self.send_M407_command, run_first=True, condition=self.timer_condition)
         t.start()
     elif event == octoprint.events.Events.PRINT_STARTED:
         self._logger.info('print started')
     elif event == octoprint.events.Events.PRINT_DONE:
         self._logger.info('print done')
     elif event == octoprint.events.Events.PRINT_FAILED or event == octoprint.events.Events.PRINT_CANCELLED:
         #self.connected = False
         self._logger.info('print failed or canceled')
     elif event == octoprint.events.Events.DISCONNECTED:
         self.connected = False
         self._logger.info('Printer disconnected')
コード例 #19
0
ファイル: timelapse.py プロジェクト: ByReaL/OctoPrint
class TimedTimelapse(Timelapse):
	def __init__(self, interval=1, post_roll=0, fps=25):
		Timelapse.__init__(self, post_roll=post_roll, fps=fps)
		self._interval = interval
		if self._interval < 1:
			self._interval = 1 # force minimum interval of 1s
		self._timer = None
		self._logger.debug("TimedTimelapse initialized")

	@property
	def interval(self):
		return self._interval

	def config_data(self):
		return {
			"type": "timed",
			"options": {
				"interval": self._interval
			}
		}

	def on_print_started(self, event, payload):
		Timelapse.on_print_started(self, event, payload)
		if self._timer is not None:
			return

		self._logger.debug("Starting timer for interval based timelapse")
		from octoprint.util import RepeatedTimer
		self._timer = RepeatedTimer(self._interval, self._timer_task,
		                            run_first=True, condition=self._timer_active,
		                            on_finish=self._on_timer_finished)
		self._timer.start()

	def process_post_roll(self):
		# we only use the final image as post roll
		self._copying_postroll()
		self.post_roll_finished()

	def _timer_active(self):
		return self._in_timelapse

	def _timer_task(self):
		self.capture_image()

	def _on_timer_finished(self):
		# timer is done, delete it
		self._timer = None
コード例 #20
0
ファイル: timelapse.py プロジェクト: lciscon/OctoPrint
class TimedTimelapse(Timelapse):
    def __init__(self, interval=1, post_roll=0, fps=25):
        Timelapse.__init__(self, post_roll=post_roll, fps=fps)
        self._interval = interval
        if self._interval < 1:
            self._interval = 1  # force minimum interval of 1s
        self._timer = None
        self._logger.debug("TimedTimelapse initialized")

    @property
    def interval(self):
        return self._interval

    def config_data(self):
        return {"type": "timed", "options": {"interval": self._interval}}

    def on_print_started(self, event, payload):
        Timelapse.on_print_started(self, event, payload)
        if self._timer is not None:
            return

        self._logger.debug("Starting timer for interval based timelapse")
        from octoprint.util import RepeatedTimer

        self._timer = RepeatedTimer(
            self._interval,
            self._timer_task,
            run_first=True,
            condition=self._timer_active,
            on_finish=self._on_timer_finished,
        )
        self._timer.start()

    def process_post_roll(self):
        # we only use the final image as post roll
        self._copying_postroll()
        self.post_roll_finished()

    def _timer_active(self):
        return self._in_timelapse

    def _timer_task(self):
        self.capture_image()

    def _on_timer_finished(self):
        # timer is done, delete it
        self._timer = None
コード例 #21
0
    def updateCmds(self):
        for timer in self.cmd_timers:
            timer.cancel()

        del self.cmd_timers[:]

        index = 0
        for command in self.cmd_commands:
            if (command.get("enabled")):

                t = RepeatedTimer(float(command.get("interval")),
                                  self.runCmd, [index],
                                  run_first=True)
                t.start()
                self.cmd_timers.append(t)

            index += 1
コード例 #22
0
ファイル: __init__.py プロジェクト: j7126/OctoPrint-Dashboard
    def updateCmds(self):
        self.cmd_commands = self._settings.get(["commandWidgetArray"])

        for timer in self.cmd_timers:
            timer.cancel()

        del self.cmd_timers[:]

        if self._settings.get_boolean(['showCommandWidgets']):
            index = 0
            for command in self.cmd_commands:
                if command.get("enabled"):

                    t = RepeatedTimer(float(command.get("interval")), self.runCmd, [index], run_first=True)
                    t.start()
                    self.cmd_timers.append(t)

                index += 1
コード例 #23
0
	def test_adjusted_interval(self):
		increasing_interval = IncreasingInterval(3, 1)

		timer_task = mock.MagicMock()
		timer_task.side_effect = increasing_interval.step

		timer = RepeatedTimer(increasing_interval.interval,
		                      timer_task,
		                      condition=lambda: increasing_interval.counter > 0)

		# this should take 1 + 2 + 3 = 6s
		start_time = time.time()
		timer.start()
		timer.join()
		duration = time.time() - start_time

		self.assertEqual(3, timer_task.call_count)
		self.assertGreaterEqual(duration, 6)
		self.assertLess(duration, 7)
コード例 #24
0
	def test_adjusted_interval(self):
		increasing_interval = IncreasingInterval(3, 1)

		timer_task = mock.MagicMock()
		timer_task.side_effect = increasing_interval.step

		timer = RepeatedTimer(increasing_interval.interval,
		                      timer_task,
		                      condition=lambda: increasing_interval.counter > 0)

		# this should take 1 + 2 + 3 = 6s
		start_time = time.time()
		timer.start()
		timer.join()
		duration = time.time() - start_time

		self.assertEqual(3, timer_task.call_count)
		self.assertGreaterEqual(duration, 6)
		self.assertLess(duration, 7)
コード例 #25
0
    def _initialize_lcd(self):
        self._logger.info("Running RPi.GPIO version %s" % GPIO.VERSION)
        if GPIO.VERSION < "0.6":
            self._logger.error("RPi.GPIO version 0.6.0 or greater required.")

        GPIO.setwarnings(False)

        for pin in self._configuredGPIOPins:
            self._logger.debug("Cleaning up pin %s" % pin)
            try:
                GPIO.cleanup(self._gpio_get_pin(pin))
            except (RuntimeError, ValueError) as e:
                self._logger.error(e)
        self._configuredGPIOPins = []

        if GPIO.getmode() is None:
            GPIO.setmode(GPIO.BOARD)

        GPIO.setup(self._gpio_get_pin(self.pin_rs), GPIO.OUT)
        GPIO.setup(self._gpio_get_pin(self.pin_e), GPIO.OUT)
        GPIO.setup(self._gpio_get_pin(self.pin_d4), GPIO.OUT)
        GPIO.setup(self._gpio_get_pin(self.pin_d5), GPIO.OUT)
        GPIO.setup(self._gpio_get_pin(self.pin_d6), GPIO.OUT)
        GPIO.setup(self._gpio_get_pin(self.pin_d7), GPIO.OUT)

        self._configuredGPIOPins = [self._gpio_get_pin(self.pin_rs), self._gpio_get_pin(self.pin_e), self._gpio_get_pin(self.pin_d4), self._gpio_get_pin(self.pin_d5), self._gpio_get_pin(self.pin_d6), self._gpio_get_pin(self.pin_d7)]

        self._lcd_send_byte(0x33, self._lcd_cmd)
        self._lcd_send_byte(0x32, self._lcd_cmd)
        self._lcd_send_byte(0x28, self._lcd_cmd)
        self._lcd_send_byte(0x0C, self._lcd_cmd)
        self._lcd_send_byte(0x06, self._lcd_cmd)
        self._lcd_send_byte(0x01, self._lcd_cmd)

        printerstate = self._printer.get_state_string()
        self._line1 = printerstate.center(20)
        self.clear_lower_half()

        timer = RepeatedTimer(30, self._lcd_update())
        timer.start()
コード例 #26
0
class FinishedJobControlClass(octoprint.plugin.StartupPlugin):
    def on_after_startup(self):
       # self._logger.info("Started Timer...")
        self.startTimer(10.0)

    def startTimer(self, interval):
        self._checkTempTimer = RepeatedTimer(interval, self.checkHotEndTemp, None, None, True)
        self._checkTempTimer.start()

    def checkHotEndTemp(self):
        druckerDrucktGerade = self._printer.is_printing()
        if not druckerDrucktGerade:
            # self._logger.info("Drucker druckt nicht, Fan-Control soll Fan steuern")
            currentTemp = self._printer.get_current_temperatures()
            if float(currentTemp["tool0"]["actual"]) < 35:
                self._printer.commands("M106 S0")
                # self._logger.info("Ist unter 35 Grad, Setting Fan to 0")
            elif (float(currentTemp["tool0"]["actual"]) >= 35) and (float(currentTemp["tool0"]["actual"]) <= 100):
                self._printer.commands("M106 S60")
                # self._logger.info("Ist zwischen 30 Grad und 100, Setting Fan to 60")
            else:
                self._printer.commands("M106 S100")
コード例 #27
0
	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)
コード例 #28
0
ファイル: bee_comm.py プロジェクト: beeverycreative/BEEweb
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
コード例 #29
0
class EnclosurePlugin(octoprint.plugin.StartupPlugin,
            octoprint.plugin.TemplatePlugin,
			octoprint.plugin.SettingsPlugin,
			octoprint.plugin.AssetPlugin,
			octoprint.plugin.BlueprintPlugin,
            octoprint.plugin.EventHandlerPlugin):


    enclosureSetTemperature=0.0
    enclosureCurrentTemperature=0.0
    enclosureCurrentHumidity=0.0
    
    def startGPIO(self):
        self.configureGPIO(self._settings.get_int(["heaterPin"]))
        self.configureGPIO(self._settings.get_int(["io1"]))
        self.configureGPIO(self._settings.get_int(["io2"]))
        self.configureGPIO(self._settings.get_int(["io3"]))
        self.configureGPIO(self._settings.get_int(["io4"]))
    
    def configureGPIO(self, pin):
        os.system("gpio -g mode "+str(pin)+" out")
        os.system("gpio -g write "+str(pin)+" 1")
        
    def startTimer(self):
        self._checkTempTimer = RepeatedTimer(10, self.checkEnclosureTemp, None, None, True)
        self._checkTempTimer.start()

    def toFloat(self, value):
        try:
            val = float(value)
            return val
        except:
            self._logger.info("Failed to convert to float")
            return 0

    def checkEnclosureTemp(self):
        if self._settings.get(["dhtModel"]) == 1820 or self._settings.get(["dhtModel"]) == '1820':
            stdout = Popen("sudo "+self._settings.get(["getTempScript"])+" "+str(self._settings.get(["dhtModel"])), shell=True, stdout=PIPE).stdout    
        else:
            stdout = Popen("sudo "+self._settings.get(["getTempScript"])+" "+str(self._settings.get(["dhtModel"]))+" "+str(self._settings.get(["dhtPin"])), shell=True, stdout=PIPE).stdout
        sTemp = stdout.read()
        sTemp.replace(" ", "")
        if sTemp.find("Failed") != -1:
            self._logger.info("Failed to read Temperature")
        else:
            #self._logger.info(sTemp)
            self.enclosureCurrentTemperature = self.toFloat(sTemp)
            #self._logger.info("enclosureCurrentTemperature is: %s",self.enclosureCurrentTemperature)

        if self._settings.get(["dhtModel"]) != '1820':
            stdout = Popen("sudo "+self._settings.get(["getHumiScript"])+" "+str(self._settings.get(["dhtModel"]))+" "+str(self._settings.get(["dhtPin"])), shell=True, stdout=PIPE).stdout
            sHum = stdout.read()
            sHum.replace(" ", "")
            if sHum.find("Failed") != -1:
                self._logger.info("Failed to read Humidity")
            else:
                self._logger.info(sHum)
                self.enclosureCurrentHumidity = self.toFloat(sHum)
        self._plugin_manager.send_plugin_message(self._identifier, dict(enclosuretemp=self.enclosureCurrentTemperature,enclosureHumidity=self.enclosureCurrentHumidity))
        self.heaterHandler()

    def heaterHandler(self):
        command=""
        if self.enclosureCurrentTemperature<float(self.enclosureSetTemperature) and self._settings.get_boolean(["heaterEnable"]):
            os.system("gpio -g write "+str(self._settings.get_int(["heaterPin"]))+" 0")
        else:
            os.system("gpio -g write "+str(self._settings.get_int(["heaterPin"]))+" 1")
        os.system(command)

    #~~ StartupPlugin mixin
    def on_after_startup(self):
        self.startTimer()
        self.startGPIO()
            
    #~~ Blueprintplugin mixin
    @octoprint.plugin.BlueprintPlugin.route("/setEnclosureTemperature", methods=["GET"])
    def setEnclosureTemperature(self):
        self.enclosureSetTemperature = flask.request.values["enclosureSetTemp"]
        self.heaterHandler()
        return flask.jsonify(enclosureSetTemperature=self.enclosureSetTemperature,enclosureCurrentTemperature=self.enclosureCurrentTemperature)

    @octoprint.plugin.BlueprintPlugin.route("/getEnclosureSetTemperature", methods=["GET"])
    def getEnclosureSetTemperature(self):
        return str(self.enclosureSetTemperature)

    @octoprint.plugin.BlueprintPlugin.route("/getEnclosureTemperature", methods=["GET"])
    def getEnclosureTemperature(self):
        return str(self.enclosureCurrentTemperature)

    @octoprint.plugin.BlueprintPlugin.route("/setIO", methods=["GET"])
    def setIO(self):
        io = flask.request.values["pin"]
        value = flask.request.values["status"]
        if value == "on":
            os.system("gpio -g write "+str(self._settings.get_int([io]))+" 0")
        else:
            os.system("gpio -g write "+str(self._settings.get_int([io]))+" 1")
        return flask.jsonify(success=True)
        
    #~~ EventPlugin mixin
    def on_event(self, event, payload):
        
        if event != "PrintDone":
            return
        
        if  self._settings.get(['heaterEnable']):
            self.enclosureSetTemperature = 0
                     
    #~~ SettingsPlugin mixin
    def on_settings_save(self, data):
        old_heaterPin = self._settings.get_int(["heaterPin"])
        old_dhtPin = self._settings.get_int(["dhtPin"])
        old_io1 = self._settings.get_int(["io1"])
        old_io2 = self._settings.get_int(["io2"])
        old_io3 = self._settings.get_int(["io3"])
        old_io4 = self._settings.get_int(["io4"])
        octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
        new_heaterPin = self._settings.get_int(["heaterPin"])
        new_dhtPin = self._settings.get_int(["dhtPin"])
        new_io1 = self._settings.get_int(["io1"])
        new_io2 = self._settings.get_int(["io2"])
        new_io3 = self._settings.get_int(["io3"])
        new_io4 = self._settings.get_int(["io4"])
        if new_heaterPin != old_heaterPin:
            self.configureGPIO(new_heaterPin)
        if old_dhtPin != new_dhtPin:
            self.configureGPIO(new_dhtPin)
        if old_io1 != new_io1:
            self.configureGPIO(new_io1)
        if old_io2 != new_io2:
            self.configureGPIO(new_io2)
        if old_io3 != new_io3:
            self.configureGPIO(new_io3)
        if old_io4 != new_io4:
            self.configureGPIO(new_io4)

    def get_settings_defaults(self):
        return dict(
            heaterEnable=False,
            heaterPin=18,
            io1=17,
            io2=18,
            io3=21,
            io4=22,
            dhtPin=4,
            dhtModel=22,
            io1Enable=False,
            io2Enable=False,
            io3Enable=False,
            io4Enable=False,
            io1Label="IO1",
            io2Label="IO2",
            io3Label="IO3",
            io4Label="IO4",
            getTempScript="~/.octoprint/plugins/OctoPrint-Enclosure/extras/GetTemperature.py",
            getHumiScript="~/.octoprint/plugins/OctoPrint-Enclosure/extras/GetHumidity.py"
        )
        
    #~~ TemplatePlugin
    def get_template_configs(self):
        return [dict(type="settings", custom_bindings=False)]

    ##~~ AssetPlugin mixin
    def get_assets(self):
        return dict(
            js=["js/enclosure.js"]
        )

    ##~~ Softwareupdate hook
    def get_update_information(self):
        return dict(
            enclosure=dict(
                displayName="Enclosure Plugin",
                displayVersion=self._plugin_version,

                # version check: github repository
                type="github_release",
                user="******",
                repo="OctoPrint-Enclosure",
                current=self._plugin_version,

                # update method: pip
                pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip"
            )
        )
コード例 #30
0
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"
			)
		)
コード例 #31
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"
			)
		)
コード例 #32
0
class OctopodPlugin(
        octoprint.plugin.SettingsPlugin, octoprint.plugin.AssetPlugin,
        octoprint.plugin.TemplatePlugin, octoprint.plugin.StartupPlugin,
        octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin,
        octoprint.plugin.ProgressPlugin):
    def __init__(self):
        super(OctopodPlugin, self).__init__()
        self._logger = logging.getLogger("octoprint.plugins.octopod")
        self._checkTempTimer = None
        self._ifttt_alerts = IFTTTAlerts(self._logger)
        self._job_notifications = JobNotifications(self._logger,
                                                   self._ifttt_alerts)
        self._tool_notifications = ToolsNotifications(self._logger,
                                                      self._ifttt_alerts)
        self._bed_notifications = BedNotifications(self._logger,
                                                   self._ifttt_alerts)
        self._mmu_assitance = MMUAssistance(self._logger, self._ifttt_alerts)
        self._paused_for_user = PausedForUser(self._logger, self._ifttt_alerts)
        self._palette2 = Palette2Notifications(self._logger,
                                               self._ifttt_alerts)
        self._layerNotifications = LayerNotifications(self._logger,
                                                      self._ifttt_alerts)
        self._check_soc_temp_timer = None
        self._soc_timer_interval = 5.0 if debug_soc_temp else 30.0
        self._soc_temp_notifications = SocTempNotifications(
            self._logger, self._ifttt_alerts, self._soc_timer_interval,
            debug_soc_temp)

    # StartupPlugin mixin

    def on_after_startup(self):
        self._logger.info("OctoPod loaded!")
        # Set logging level to what we have in the settings
        if self._settings.get_boolean(["debug_logging"]):
            self._logger.setLevel(logging.DEBUG)
        else:
            self._logger.setLevel(logging.INFO)

        # Register to listen for messages from other plugins
        self._plugin_manager.register_message_receiver(self.on_plugin_message)

        # Start timer that will check bed temperature and send notifications if needed
        self._restart_timer()

        # if running on linux then check soc temperature
        if sys.platform.startswith("linux") or debug_soc_temp:
            sbc = RPi(
                self._logger) if debug_soc_temp else SBCFactory().factory(
                    self._logger)
            if sbc.is_supported:
                self._soc_temp_notifications.sbc = sbc
                sbc.debugMode = debug_soc_temp
                self._soc_temp_notifications.send_plugin_message = self.send_plugin_message
                self.start_soc_timer(self._soc_timer_interval)

    # SettingsPlugin mixin

    def get_settings_defaults(self):
        return dict(
            debug_logging=False,
            server_url='http://octopodprint.com/',
            camera_snapshot_url='http://localhost:8080/?action=snapshot',
            tokens=[],
            temp_interval=5,
            tool0_low=0,
            bed_low=30,
            bed_target_temp_hold=10,
            mmu_interval=5,
            pause_interval=5,
            palette2_printing_error_codes=[103, 104, 111, 121],
            progress_type=
            '50',  # 0=disabled, 25=every 25%, 50=every 50%, 100=only when finished
            ifttt_key='',
            ifttt_name='',
            soc_temp_high=75,
            webcam_flipH=False,
            webcam_flipV=False,
            webcam_rotate90=False)

    def on_settings_save(self, data):
        old_debug_logging = self._settings.get_boolean(["debug_logging"])

        octoprint.plugin.SettingsPlugin.on_settings_save(self, data)

        new_debug_logging = self._settings.get_boolean(["debug_logging"])
        if old_debug_logging != new_debug_logging:
            if new_debug_logging:
                self._logger.setLevel(logging.DEBUG)
            else:
                self._logger.setLevel(logging.INFO)

    def get_settings_version(self):
        return 10

    def on_settings_migrate(self, target, current):
        if current == 1:
            # add the 2 new values included
            self._settings.set(['temp_interval'],
                               self.get_settings_defaults()["temp_interval"])
            self._settings.set(['bed_low'],
                               self.get_settings_defaults()["bed_low"])

        if current <= 2:
            self._settings.set(
                ['bed_target_temp_hold'],
                self.get_settings_defaults()["bed_target_temp_hold"])

        if current <= 3:
            self._settings.set(['mmu_interval'],
                               self.get_settings_defaults()["mmu_interval"])

        if current <= 4:
            self._settings.set(['pause_interval'],
                               self.get_settings_defaults()["pause_interval"])

        if current <= 5:
            self._settings.set(['tool0_low'],
                               self.get_settings_defaults()["tool0_low"])

        if current <= 6:
            self._settings.set(
                ['palette2_printing_error_codes'],
                self.get_settings_defaults()["palette2_printing_error_codes"])

        if current <= 7:
            self._settings.set(['progress_type'],
                               self.get_settings_defaults()["progress_type"])

        if current <= 8:
            self._settings.set(['ifttt_key'],
                               self.get_settings_defaults()["ifttt_key"])
            self._settings.set(['ifttt_name'],
                               self.get_settings_defaults()["ifttt_name"])

        if current <= 9:
            self._settings.set(['soc_temp_high'],
                               self.get_settings_defaults()["soc_temp_high"])
            self._settings.set(['webcam_flipH'],
                               self._settings.global_get(["webcam", "flipH"]))
            self._settings.set(['webcam_flipV'],
                               self._settings.global_get(["webcam", "flipV"]))
            self._settings.set(['webcam_rotate90'],
                               self._settings.global_get(
                                   ["webcam", "rotate90"]))

    # AssetPlugin mixin

    def get_assets(self):
        # Define your plugin's asset files to automatically include in the
        # core UI here.
        return dict(
            js=["js/octopod.js"],
            css=["css/octopod.css"],
        )

    # ProgressPlugin

    # progress-hook
    def on_print_progress(self, storage, path, progress):
        # progress 0 - 100
        self._job_notifications.on_print_progress(self._settings, progress)

    # EventHandlerPlugin mixin

    def on_event(self, event, payload):
        if event == Events.PRINTER_STATE_CHANGED:
            self._job_notifications.send__print_job_notification(
                self._settings, self._printer, payload)
        elif event == "DisplayLayerProgress_layerChanged":
            # Event sent from DisplayLayerProgress plugin when there was a detected layer changed
            self._layerNotifications.layer_changed(self._settings,
                                                   payload["currentLayer"])
        elif event == Events.PRINT_STARTED or event == Events.PRINT_DONE or event == Events.PRINT_CANCELLED \
          or event == Events.PRINT_FAILED:
            # Reset layers for which we need to send a notification. Each new print job has its own
            self._layerNotifications.reset_layers()

    # SimpleApiPlugin mixin

    def update_token(self, old_token, new_token, device_name, printer_id,
                     printer_name, language_code):
        self._logger.debug("Received tokens for %s." % device_name)

        existing_tokens = self._settings.get(["tokens"])

        # Safety check in case a user manually modified config.yaml and left invalid JSON
        if existing_tokens is None:
            existing_tokens = []

        found = False
        updated = False
        for token in existing_tokens:
            # Check if existing token has been updated
            if token["apnsToken"] == old_token and token[
                    "printerID"] == printer_id:
                if old_token != new_token:
                    self._logger.debug("Updating token for %s." % device_name)
                    # Token that exists needs to be updated with new token
                    token["apnsToken"] = new_token
                    token["date"] = datetime.datetime.now().strftime("%x %X")
                    updated = True
                found = True
            elif token["apnsToken"] == new_token and token[
                    "printerID"] == printer_id:
                found = True

            if found:
                if printer_name is not None and (
                        "printerName" not in token
                        or token["printerName"] != printer_name):
                    # Printer name in OctoPod has been updated
                    token["printerName"] = printer_name
                    token["date"] = datetime.datetime.now().strftime("%x %X")
                    updated = True
                if language_code is not None and (
                        "languageCode" not in token
                        or token["languageCode"] != language_code):
                    # Language being used by OctoPod has been updated
                    token["languageCode"] = language_code
                    token["date"] = datetime.datetime.now().strftime("%x %X")
                    updated = True
                break

        if not found:
            self._logger.debug("Adding token for %s." % device_name)
            # Token was not found so we need to add it
            existing_tokens.append({
                'apnsToken':
                new_token,
                'deviceName':
                device_name,
                'date':
                datetime.datetime.now().strftime("%x %X"),
                'printerID':
                printer_id,
                'printerName':
                printer_name,
                'languageCode':
                language_code
            })
            updated = True
        if updated:
            # Save new settings
            self._settings.set(["tokens"], existing_tokens)
            self._settings.save()
            eventManager().fire(Events.SETTINGS_UPDATED)
            self._logger.debug("Tokens saved")

    def get_api_commands(self):
        return dict(
            updateToken=["oldToken", "newToken", "deviceName", "printerID"],
            test=[],
            snooze=["eventCode", "minutes"],
            addLayer=["layer"],
            removeLayer=["layer"],
            getLayers=[],
            getSoCTemps=[])

    def on_api_command(self, command, data):
        if not user_permission.can():
            return flask.make_response("Insufficient rights", 403)

        if command == 'updateToken':
            # Convert from ASCII to UTF-8 since some chars will fail otherwise (e.g. apostrophe) - Only for Python 2
            if sys.version_info[0] == 2:
                data["deviceName"] = data["deviceName"].encode("utf-8")
            printer_name = data[
                "printerName"] if 'printerName' in data else None
            language_code = data[
                "languageCode"] if 'languageCode' in data else None

            self.update_token("{oldToken}".format(**data),
                              "{newToken}".format(**data),
                              "{deviceName}".format(**data),
                              "{printerID}".format(**data), printer_name,
                              language_code)
        elif command == 'test':
            payload = dict(state_id="OPERATIONAL", state_string="Operational")
            code = self._job_notifications.send__print_job_notification(
                self._settings, self._printer, payload, data["server_url"],
                data["camera_snapshot_url"], data["camera_flip_h"],
                data["camera_flip_v"], data["camera_rotate90"], True)
            return flask.jsonify(dict(code=code))
        elif command == 'snooze':
            if data["eventCode"] == 'mmu-event':
                self._mmu_assitance.snooze(data["minutes"])
            else:
                return flask.make_response("Snooze for unknown event", 400)
        elif command == 'addLayer':
            self._layerNotifications.add_layer(data["layer"])
        elif command == 'removeLayer':
            self._layerNotifications.remove_layer(data["layer"])
        elif command == 'getLayers':
            return flask.jsonify(
                dict(layers=self._layerNotifications.get_layers()))
        elif command == 'getSoCTemps':
            return flask.jsonify(self._soc_temp_notifications.get_soc_temps())
        else:
            return flask.make_response("Unknown command", 400)

    # TemplatePlugin mixin

    def get_template_configs(self):
        return [
            dict(type="settings",
                 name="OctoPod Notifications",
                 custom_bindings=True)
        ]

    # 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(octopod=dict(
            displayName="OctoPod Plugin",
            displayVersion=self._plugin_version,

            # version check: github repository
            type="github_release",
            user="******",
            repo="OctoPrint-OctoPod",
            current=self._plugin_version,

            # update method: pip
            pip=
            "https://github.com/gdombiak/OctoPrint-OctoPod/archive/{target_version}.zip"
        ))

    # Plugin messages

    def on_plugin_message(self, plugin, data, permissions=None):
        self._palette2.check_plugin_message(self._settings, plugin, data)

    def send_plugin_message(self, data):
        self._plugin_manager.send_plugin_message(self._identifier, data)

    # Timer functions

    def _restart_timer(self):
        # stop the timer
        if self._checkTempTimer:
            self._logger.debug(u"Stopping Timer...")
            self._checkTempTimer.cancel()
            self._checkTempTimer = None

        # start a new timer
        interval = self._settings.get_int(['temp_interval'])
        if interval:
            self._logger.debug(u"Starting Timer...")
            self._checkTempTimer = RepeatedTimer(interval, self.run_timer_job,
                                                 None, None, True)
            self._checkTempTimer.start()

    def run_timer_job(self):
        self._bed_notifications.check_temps(self._settings, self._printer)
        self._tool_notifications.check_temps(self._settings, self._printer)

    def start_soc_timer(self, interval):
        self._logger.debug(u"Monitoring SoC temp with Timer")
        self._check_soc_temp_timer = RepeatedTimer(interval,
                                                   self.update_soc_temp,
                                                   run_first=True)
        self._check_soc_temp_timer.start()

    def update_soc_temp(self):
        self._soc_temp_notifications.check_soc_temp(self._settings)

    # GCODE hook

    def process_gcode(self, comm, line, *args, **kwargs):
        line = self._paused_for_user.process_gcode(self._settings,
                                                   self._printer, line)
        return self._mmu_assitance.process_gcode(self._settings, line)
コード例 #33
0
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
コード例 #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'])
コード例 #35
0
ファイル: __init__.py プロジェクト: CupricReki/radtaz
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"
            )
        )
コード例 #36
0
class FirmwareUpdatePlugin(octoprint.plugin.StartupPlugin,
                       octoprint.plugin.TemplatePlugin,
                       octoprint.plugin.AssetPlugin,
                       octoprint.plugin.SettingsPlugin,
                       octoprint.plugin.SimpleApiPlugin):

    def __init__(self):
        self.isUpdating = False
        self._checkTimer = None
        self.updatePID = None

    def get_assets(self):
        return {
            "js": ["js/firmwareupdate.js"]
        }

    def startTimer(self, interval):
        self._checkTimer = RepeatedTimer(interval, self.checkStatus, run_first=True, condition=self.checkStatus)
        self._checkTimer.start()

    def checkStatus(self):
        update_result = open('/home/pi/Marlin/.build_log').read()
        if 'No device matching following was found' in update_result:
    	    self._logger.info("Failed update...")
            self.isUpdating = False
    	    self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, status="failed", reason="A connected device was not found."))
    	    return False
    	elif 'FAILED' in update_result:
    	    self._logger.info("Failed update...")
            self.isUpdating = False
    	    self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, status="failed"))
    	    return False
    	elif 'bytes of flash verified' in update_result and 'successfully' in update_result :
            self._logger.info("Successful update!")
    	    self.isUpdating = False
    	    self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, status="completed"))
    	    return False
    	elif 'ReceiveMessage(): timeout' in update_result:
    	    self._logger.info("Update timed out. Check if port is already in use!")
    	    self.isUpdating = False
    	    self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, status="failed", reason="Device timed out. Please check that the port is not in use!"))
    	    p = psutil.Process(self.updatePID)
    	    for child in p.children(recursive=True):
        	    child.kill()
    	    p.kill()
    	    return False
    	elif 'error:' in update_result:
    	    error_list = []
            with open('/home/pi/Marlin/.build_log') as myFile:
    		for num, line in enumerate(myFile, 1):
    		    if 'error:' in line:
    			    error_list.append(line)
    	    compileError = '<pre>' + ''.join(error_list) + '</pre>'
    	    self._logger.info("Update failed. Compiling error.")
    	    self.isUpdating = False
    	    self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, status="failed", reason=compileError))
    	    return False
    	elif 'Make failed' in update_result:
    	    self._logger.info("Update failed. Compiling error.")
    	    self.isUpdating = False
    	    self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, status="failed", reason="Build failed."))
    	    return False
    	else:
    	    self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, status="continue"))
    	    return True

    def get_update_information(self):
        return dict(
            firmwareupdate_plugin=dict(
                displayName="FirmwareUpdate Plugin",
                displayVersion=self._plugin_version,
                type="github_commit",
                user="******",
                repo="OctoPrint-FirmwareUpdate",
                current=self._plugin_version,
                pip="https://github.com/Voxel8/OctoPrint-FirmwareUpdate/archive/{target_version}.zip"
            )
        )

    def get_api_commands(self):
        return dict(
            update_firmware=[],
	        check_is_updating=[]
        )

    def on_api_command(self, command, data):
    	if command == "update_firmware":
            if not os.path.isdir("/home/pi/Marlin/"):
                self._logger.info("Firmware repository does not exist. Update cancelled.")
                self.isUpdating = False
                self._logger.info("Setting isUpdating to " + str(self.isUpdating))
                self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, status="failed", reason="Firmware repository does not exist. Please clone before running update function."))
            else:
                try:
                    os.remove('/home/pi/Marlin/.build_log')
                except OSError:
                    pass
                f = open("/home/pi/Marlin/.build_log", "w")
                self._logger.info("Firmware update request has been made. Running...")
                pro = Popen("cd /home/pi/Marlin; git fetch; git reset --hard origin/master; ./build.sh", stdout=f, stderr=f, shell=True, preexec_fn=os.setsid)
                self.updatePID = pro.pid
                self.isUpdating = True
                self._logger.info("Setting isUpdating to " + str(self.isUpdating))
                self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, createPopup="yes"))
                self.startTimer(1.0)

    	elif command == "check_is_updating":
    	    if self.isUpdating == True:
    	        self._logger.info("Setting isUpdating to " + str(self.isUpdating))
    	        self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, createPopup="yes"))
    	    else:
    	        self._logger.info("Setting isUpdating to " + str(self.isUpdating))
    	        self._plugin_manager.send_plugin_message(self._identifier, dict(isupdating=self.isUpdating, deletePopup="yes"))
    	else:
    	    self._logger.info("Uknown command.")

    def on_api_get(self, request):
        return flask.make_response("Not found", 404)

    def get_template_configs(self):
        return [
            dict(type="settings", name="Firmware Update", data_bind="visible: loginState.isAdmin()"),
	    ]
コード例 #37
0
ファイル: __init__.py プロジェクト: Mikykly/OctoPrint
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
コード例 #38
0
 def on_after_startup(self):
     self._logger.info("started Filament Box Healthcheck Plugin: ")
     t = RepeatedTimer(60, self.check_resources, run_first=True)
     t.start()
コード例 #39
0
ファイル: __init__.py プロジェクト: MxFrs/MaxNavBarTemp
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"
            )
        )
コード例 #40
0
ファイル: __init__.py プロジェクト: codyolsen/octoprint_snap
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"
			)
		)
コード例 #41
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"
        ))
コード例 #42
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"
                )
        )
コード例 #43
0
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"
            )
        )
コード例 #44
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"
        ))
コード例 #45
0
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)
コード例 #46
0
class EnclosurePlugin(octoprint.plugin.StartupPlugin,
			octoprint.plugin.TemplatePlugin,
                      	octoprint.plugin.SettingsPlugin,
                      	octoprint.plugin.AssetPlugin,
                        octoprint.plugin.BlueprintPlugin,
			octoprint.plugin.EventHandlerPlugin):


	enclosureSetTemperature=0.0
	enclosureCurrentTemperature=0.0
	enclosureCurrentHumidity=0.0
	
	def startGPIO(self):
		self.configureGPIO(self._settings.get_int(["heaterPin"]))
		self.configureGPIO(self._settings.get_int(["fanPin"]))
		self.configureGPIO(self._settings.get_int(["lightPin"]))
	
	def configureGPIO(self, pin):
		os.system("gpio -g mode "+str(pin)+" out")
		os.system("gpio -g write "+str(pin)+" 1")
		
	def startTimer(self):
		self._checkTempTimer = RepeatedTimer(10, self.checkEnclosureTemp, None, None, True)
		self._checkTempTimer.start()

	def checkEnclosureTemp(self):
		stdout = Popen("sudo "+self._settings.get(["getTempScript"])+" "+str(self._settings.get(["dhtModel"]))+" "+str(self._settings.get(["dhtPin"])), shell=True, stdout=PIPE).stdout
		sTemp = stdout.read()
		if sTemp.find("Failed") != -1:
			self._logger.info("Failed to read Temperature")
		else:
			self.enclosureCurrentTemperature = float(sTemp)

		stdout = Popen("sudo "+self._settings.get(["getHumiScript"])+" "+str(self._settings.get(["dhtModel"]))+" "+str(self._settings.get(["dhtPin"])), shell=True, stdout=PIPE).stdout
		sTemp = stdout.read()
		if sTemp.find("Failed") != -1:
			self._logger.info("Failed to read Humidity")
		else:
			self.enclosureCurrentHumidity = float(sTemp)

		self._plugin_manager.send_plugin_message(self._identifier, dict(enclosuretemp=self.enclosureCurrentTemperature,enclosureHumidity=self.enclosureCurrentHumidity))
		self.heaterHandler()

	def heaterHandler(self):
		command=""
		if self.enclosureCurrentTemperature<float(self.enclosureSetTemperature) and self._settings.get_boolean(["heaterEnable"]):
			os.system("gpio -g write "+str(self._settings.get_int(["heaterPin"]))+" 0")
		else:
			os.system("gpio -g write "+str(self._settings.get_int(["heaterPin"]))+" 1")
		os.system(command)

	#~~ StartupPlugin mixin
	def on_after_startup(self):
		self.startTimer()
		self.startGPIO()
			
	#~~ Blueprintplugin mixin
	@octoprint.plugin.BlueprintPlugin.route("/setEnclosureTemperature", methods=["GET"])
	def setEnclosureTemperature(self):
		self.enclosureSetTemperature = flask.request.values["enclosureSetTemp"]
		self.heaterHandler()
		return flask.jsonify(enclosureSetTemperature=self.enclosureSetTemperature,enclosureCurrentTemperature=self.enclosureCurrentTemperature)

	@octoprint.plugin.BlueprintPlugin.route("/getEnclosureSetTemperature", methods=["GET"])
	def getEnclosureSetTemperature(self):
		return str(self.enclosureSetTemperature)

	@octoprint.plugin.BlueprintPlugin.route("/getEnclosureTemperature", methods=["GET"])
	def getEnclosureTemperature(self):
		return str(self.enclosureCurrentTemperature)
		
	@octoprint.plugin.BlueprintPlugin.route("/handleFan", methods=["GET"])
	def handleFan(self):
		if self._settings.get_boolean(["fanEnable"]):
			if flask.request.values["status"] == "on":
				os.system("gpio -g write "+str(self._settings.get_int(["fanPin"]))+" 0")
			else:
				os.system("gpio -g write "+str(self._settings.get_int(["fanPin"]))+" 1")
		return flask.jsonify(success=True)
		
	@octoprint.plugin.BlueprintPlugin.route("/handleLight", methods=["GET"])
	def handleLight(self):
		if self._settings.get_boolean(["lightEnable"]):
			if flask.request.values["status"] == "on":
				os.system("gpio -g write "+str(self._settings.get_int(["lightPin"]))+" 0")
			else:
				os.system("gpio -g write "+str(self._settings.get_int(["lightPin"]))+" 1")
		return flask.jsonify(success=True)
		
	#~~ EventPlugin mixin
	def on_event(self, event, payload):
		
		if event != "PrintDone":
			return
		
		if  self._settings.get(['heaterEnable']):
			self.enclosureSetTemperature = 0
			
		if  self._settings.get(['fanEnable']):
			os.system("gpio -g write "+str(self._settings.get_int(["lightPin"]))+" 1")
			
	#~~ SettingsPlugin mixin
	def on_settings_save(self, data):
		old_heaterPin = self._settings.get_int(["heaterPin"])
		old_dhtPin = self._settings.get_int(["dhtPin"])
		old_fanPin = self._settings.get_int(["fanPin"])
		octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
		new_heaterPin = self._settings.get_int(["heaterPin"])
		new_dhtPin = self._settings.get_int(["dhtPin"])
		new_fanPin = self._settings.get_int(["fanPin"])
		if new_heaterPin != old_heaterPin:
			self.configureGPIO(new_heaterPin)
		if old_dhtPin != new_dhtPin:
			self.configureGPIO(new_dhtPin)
		if old_fanPin != new_fanPin:
			self.configureGPIO(new_fanPin)

	def get_settings_defaults(self):
		return dict(
			heaterEnable=False,
			heaterPin=18,
			fanPin=23,
			lightPin=15,
			dhtPin=4,
			dhtModel=22,
			fanEnable=False,
			lightEnable=False,
			getTempScript="~/.octoprint/plugins/OctoPrint-Enclosure/extras/GetTemperature.py",
			getHumiScript="~/.octoprint/plugins/OctoPrint-Enclosure/extras/GetHumidity.py"
		)
		
	#~~ TemplatePlugin
	def get_template_configs(self):
		return [dict(type="settings", custom_bindings=False)]

	##~~ AssetPlugin mixin
	def get_assets(self):
		return dict(
			js=["js/enclosure.js"]
		)

	##~~ Softwareupdate hook
	def get_update_information(self):
		return dict(
			enclosure=dict(
				displayName="Enclosure Plugin",
				displayVersion=self._plugin_version,

				# version check: github repository
				type="github_release",
				user="******",
				repo="OctoPrint-Enclosure",
				current=self._plugin_version,

				# update method: pip
				pip="https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/{target_version}.zip"
			)
		)
コード例 #47
0
class MiTemperature2Plugin(octoprint.plugin.StartupPlugin,
                           octoprint.plugin.TemplatePlugin,
                           octoprint.plugin.SettingsPlugin,
                           octoprint.plugin.EventHandlerPlugin):

    ## Requires StartupPlugin
    def on_after_startup(self):
        self._logger.info("MiTemperature2 started! (MAC: %s)" %
                          self._settings.get(["mac_address"]))
        self._start_repeat_timer()

## Requires SettingsPlugin

    def get_settings_defaults(self):
        # default settings here
        return dict(
            # config
            command=str(pathlib.Path(__file__).parent.absolute()) +
            "/MiTemperature2/LYWSD03MMC.py",
            args="-r -b -c 1 --callback sendToFile.sh",
            mac_address="A4:C1:38:2D:86:49",
            seconds="60",
            # values from device
            temp="0",
            humidity="0",
            bat_voltage="0",
            bat_level="0")

    ## Requires SettingsPlugin
    # def on_settings_save(self, data):
    #     # default save function
    #     octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
    #     # refresh cached settings
    #     self._cachedSettings.updateSettings(self._settings)
    #     self._update_device_data("Saved values")

    ## Requires SettingsPlugin
    # def on_settings_load(self):
    #     return dict(octoprint.plugin.SettingsPlugin.on_settings_load(self))

    ## Requires TemplatePlugin
    def get_template_configs(self):
        return [
            dict(type="navbar",
                 template="mitemp2_navbar.jinja2",
                 custom_bindings=True),
            dict(type="settings",
                 template="mitemp2_settings.jinja2",
                 custom_bindings=True,
                 name="Xiaomi MiTemperature2")
        ]

    ## SoftwareUpdate hook
    def get_update_information(self, *args, **kwargs):
        return dict(mitemp2=dict(
            displayName=self._plugin_name,
            displayVersion=self._plugin_version,
            # version check: github repository
            type="github_release",
            current=self._plugin_version,
            user="******",
            repo="octoprint_mitemp2",
            # update method: pip
            pip=
            "https://github.com/shawe/octoprint-mitemp2/archive/{target}.zip"))

    def _get_command_reply(self):
        cmd = self._settings.get(["command"])
        args = " " + self._settings.get(
            ["args"]) + " -d " + self._settings.get(["mac_address"])

        proc = subprocess.Popen(
            cmd + " " + args,
            shell=True,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
        )
        msg = 'through stdin to stdout\n'.encode('utf-8')
        stdout_value, stderr_value = proc.communicate(msg)
        output = repr(stdout_value.decode('utf-8'))

        output = output.split("\n")
        output = output[0].split('\\')
        res = []

        for line in output:
            res.append(line)

        return res

    def _get_device_values(self):
        res = self._get_command_reply()
        results = []

        for i in range(1, len(res) - 1):
            line = res[i]
            line = line.split(' ')
            results.append(line)

        res = {
            "temperature": results[0][1],
            "humidity": results[1][1],
            "bat_voltage": results[2][2],
            "bat_level": results[3][2]
        }

        return res

    def _update_device_details(self):
        reply = self._get_device_values()
        self._logger.info("Nuevos datos: %s" % str(reply))
        return reply

    def _update_device_data(self, updateReason=""):
        update = self._update_device_details()

        # store new values
        self._settings.set(["temperature"], update["temperature"])
        self._settings.set(["humidity"], update["humidity"])
        self._settings.set(["bat_voltage"], update["bat_voltage"])
        self._settings.set(["bat_level"], update["bat_level"])

        # prepare clientMessage
        clientMessageDict = dict()
        currentValueDict = {
            "[mac_address]": self._settings.get(["mac_address"]),
            "[temperature]": self._settings.get(["temperature"]),
            "[humidity]": self._settings.get(["humidity"]),
            "[bat_voltage]": self._settings.get(["bat_voltage"]),
            "[bat_level]": self._settings.get(["bat_level"])
        }
        navBarMessagePattern = "MiTemp2 <span>[mac_address]</span> <span>[temperature] ºC</span> <span>[humidity] %</span> <span>[bat_voltage] V</span> <span>([bat_level] %)</span>"
        navBarMessage = stringUtils.multiple_replace(navBarMessagePattern,
                                                     currentValueDict)
        clientMessageDict.update({'navBarMessage': navBarMessage})
        self._plugin_manager.send_plugin_message(self._identifier,
                                                 clientMessageDict)

    def _interval(self):
        return int(self._settings.get(["seconds"]))

    def _start_repeat_timer(self):
        self._repeat_timer = RepeatedTimer(
            self._interval(), self._update_device_data("Repeat timer"))
        self._repeat_timer.start()
        self._logger.info("MiTemperature2 every %s seconds" %
                          str(self._interval()))
コード例 #48
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"
			)
		)
コード例 #49
0
class PersistConnectionPlugin(octoprint.plugin.StartupPlugin,
                              octoprint.plugin.SettingsPlugin,
                              octoprint.plugin.SimpleApiPlugin):

    def __init__(self):
        self._timer = None
        self._enabled = None
        self._firmware_plugin = None
        self._automation_plugin = None

    def get_settings_defaults(self):
        return dict(
            enabled=True
        )

    def get_api_commands(self):
        return dict(
            toggle_enabled=[]
        )

    def on_after_startup(self):
        self._firmware_plugin = self._plugin_manager.get_plugin_info(
            "firmwareupdate")
        self._automation_plugin = self._plugin_manager.get_plugin_info(
            "automation_scripts")

        self._enabled = self._settings.get_boolean(["enabled"])
        self._timer = RepeatedTimer(
            1.0, self._check_connection, run_first=True)
        self._timer.start()

    def on_api_command(self, command, data):
        if command == "toggle_enabled":
            if data['current']:
                self._enabled = False
            else:
                self._enabled = True
            self._settings.set_boolean(["enabled"], self._enabled)
            self._settings.save()
            eventManager().fire(Events.SETTINGS_UPDATED)

    def on_settings_save(self, data):
        octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
        self._enabled = self._settings.get_boolean(["enabled"])

    def _check_connection(self):
        state = self._printer.get_state_string()
        if self._firmware_plugin is None:
            firmware_updating = False
        else:
            firmware_updating = (
                self._firmware_plugin.implementation._is_updating())

        if self._automation_plugin is None:
            automation_running = False
        else:
            automation_running = (
                self._automation_plugin.implementation._is_running())

        if self._enabled and not firmware_updating and not automation_running:
            if state in ["Closed", "Offline"]:
                self._logger.info("Offline or closed; reconnecting")
                self._printer.connect()
            elif "Error" in state or state == "Unknown":
                self._logger.info("Error detected; reopening connection")
                self._printer.disconnect()
                self._printer.connect()
                sleep(15)
コード例 #50
0
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"
			)
		)
コード例 #51
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"
        ))
コード例 #52
0
ファイル: __init__.py プロジェクト: DeltaMaker/OctoPrint
class TrackingPlugin(octoprint.plugin.SettingsPlugin,
                     octoprint.plugin.EnvironmentDetectionPlugin,
                     octoprint.plugin.StartupPlugin,
                     octoprint.plugin.ShutdownPlugin,
                     octoprint.plugin.TemplatePlugin,
                     octoprint.plugin.AssetPlugin,
                     octoprint.plugin.WizardPlugin,
                     octoprint.plugin.EventHandlerPlugin):

	def __init__(self):
		self._environment = None
		self._throttle_state = None
		self._helpers_get_throttle_state = None
		self._printer_connection_parameters = None
		self._url = None
		self._ping_worker = None
		self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)

		self._record_next_firmware_info = False

	def initialize(self):
		self._init_tracking()

	##~~ SettingsPlugin

	def get_settings_defaults(self):
		return dict(enabled=None,
		            unique_id=None,
		            server=TRACKING_URL,
		            ping=15*60,
		            events=dict(startup=True,
		                        printjob=True,
		                        plugin=True,
		                        update=True,
		                        printer=True,
		                        throttled=True))

	def get_settings_restricted_paths(self):
		return dict(admin=[["enabled"], ["unique_id"], ["events"]],
		            never=[["server"], ["ping"]])

	def on_settings_save(self, data):
		enabled = self._settings.get([b"enabled"])

		octoprint.plugin.SettingsPlugin.on_settings_save(self, data)

		if enabled is None and self._settings.get([b"enabled"]):
			# tracking was just enabled, let's init it and send a startup event
			self._init_tracking()
			self._track_startup()

	##~~ EnvironmentDetectionPlugin

	def on_environment_detected(self, environment, *args, **kwargs):
		self._environment = environment

	##~~ StartupPlugin

	def on_after_startup(self):
		ping = self._settings.get_int(["ping"])
		if ping:
			self._ping_worker = RepeatedTimer(ping, self._track_ping)
			self._ping_worker.start()

		# cautiously look for the get_throttled helper from pi_support
		pi_helper = self._plugin_manager.get_helpers("pi_support", "get_throttled")
		if pi_helper and 'get_throttled' in pi_helper:
			self._helpers_get_throttle_state = pi_helper['get_throttled']

		# now that we have everything set up, phone home.
		self._track_startup()

	##~~ ShutdownPlugin

	def on_shutdown(self):
		self._track_shutdown()

	##~~ EventHandlerPlugin

	def on_event(self, event, payload):
		if event.startswith("plugin_pluginmanager_"):
			self._track_plugin_event(event, payload)
		elif event.startswith("plugin_softwareupdate_"):
			self._track_update_event(event, payload)
		elif event in ("plugin_pi_support_throttle_state",):
			self._throttle_state = payload
			self._track_throttle_event(event, payload)
		elif event in (Events.PRINT_STARTED, Events.PRINT_DONE, Events.PRINT_FAILED, Events.PRINT_CANCELLED):
			self._track_printjob_event(event, payload)
		elif event in (Events.CONNECTED,):
			self._printer_connection_parameters = dict(port=payload["port"],
			                                           baudrate=payload["baudrate"])
			self._record_next_firmware_info = True
		elif event in (Events.FIRMWARE_DATA,) and self._record_next_firmware_info:
			self._record_next_firmware_info = False
			self._track_printer_event(event, payload)

	##~~ TemplatePlugin

	def get_template_configs(self):
		return [
			dict(type="settings", name=gettext("Anonymous Usage Tracking"), template="tracking_settings.jinja2", custom_bindings=False),
			dict(type="wizard", name=gettext("Anonymous Usage Tracking"), template="tracking_wizard.jinja2", custom_bindings=True, mandatory=True)
		]

	##~~ AssetPlugin

	def get_assets(self):
		return dict(js=["js/usage.js"])

	##~~ WizardPlugin

	def is_wizard_required(self):
		return self._settings.get([b"enabled"]) is None

	##~~ helpers

	def _init_tracking(self):
		if not self._settings.get_boolean([b"enabled"]):
			return
		self._init_id()
		self._logger.info("Initialized anonymous tracking")

	def _init_id(self):
		if self._settings.get_boolean([b"enabled"]) and not self._settings.get([b"unique_id"]):
			import uuid
			self._settings.set([b"unique_id"], str(uuid.uuid4()))
			self._settings.save()

	def _track_ping(self):
		self._track("ping")

	def _track_startup(self):
		if not self._settings.get_boolean(["events", "startup"]):
			return

		payload = dict(version=get_octoprint_version_string(),
		               os=self._environment[b"os"][b"id"],
		               python=self._environment[b"python"][b"version"],
		               pip=self._environment[b"python"][b"pip"],
		               cores=self._environment[b"hardware"][b"cores"],
		               freq=self._environment[b"hardware"][b"freq"],
		               ram=self._environment[b"hardware"][b"ram"])

		if b"plugins" in self._environment and b"pi_support" in self._environment[b"plugins"]:
			payload[b"pi_model"] = self._environment[b"plugins"][b"pi_support"][b"model"]

			if b"octopi_version" in self._environment[b"plugins"][b"pi_support"]:
				payload[b"octopi_version"] = self._environment[b"plugins"][b"pi_support"][b"octopi_version"]

		self._track("startup", **payload)

	def _track_shutdown(self):
		if not self._settings.get_boolean(["events", "startup"]):
			return

		self._track("shutdown")

	def _track_plugin_event(self, event, payload):
		if not self._settings.get_boolean(["events", "plugin"]):
			return

		if event.endswith("_installplugin"):
			self._track("install_plugin", plugin=payload.get(b"id"), plugin_version=payload.get(b"version"))
		elif event.endswith("_uninstallplugin"):
			self._track("uninstall_plugin", plugin=payload.get(b"id"), plugin_version=payload.get(b"version"))
		elif event.endswith("_enableplugin"):
			self._track("enable_plugin", plugin=payload.get(b"id"), plugin_version=payload.get(b"version"))
		elif event.endswith("_disableplugin"):
			self._track("disable_plugin", plugin=payload.get(b"id"), plugin_version=payload.get(b"version"))

	def _track_update_event(self, event, payload):
		if not self._settings.get_boolean(["events", "update"]):
			return

		if event.endswith("_update_succeeded"):
			self._track("update_successful", target=payload.get("target"), from_version=payload.get("from_version"), to_version=payload.get("to_version"))
		elif event.endswith("_update_failed"):
			self._track("update_failed", target=payload.get("target"), from_version=payload.get("from_version"), to_version=payload.get("to_version"))

	def _track_throttle_event(self, event, payload):
		if not self._settings.get_boolean(["events", "throttled"]):
			return

		args = dict(throttled_now=payload[b"current_issue"],
		            throttled_past=payload[b"past_issue"],
		            throttled_mask=payload[b"raw_value"],
		            throttled_voltage_now=payload[b"current_undervoltage"],
		            throttled_voltage_past=payload[b"past_undervoltage"],
		            throttled_overheat_now=payload[b"current_overheat"],
		            throttled_overheat_past=payload[b"past_overheat"])

		if payload[b"current_issue"]:
			track_event = "system_throttled"
		else:
			track_event = "system_unthrottled"

		if track_event is not None:
			self._track(track_event, **args)

	def _track_printjob_event(self, event, payload):
		if not self._settings.get_boolean(["events", "printjob"]):
			return

		sha = hashlib.sha1()
		sha.update(payload.get("path"))
		sha.update(self._settings.get([b"unique_id"]))

		track_event = None
		args = dict(origin=payload.get(b"origin"), file=sha.hexdigest())

		if event == Events.PRINT_STARTED:
			track_event = "print_started"
		elif event == Events.PRINT_DONE:
			try:
				elapsed = int(payload.get(b"time"))
			except ValueError:
				elapsed = "unknown"
			args[b"elapsed"] = elapsed
			track_event = "print_done"
		elif event == Events.PRINT_FAILED:
			try:
				elapsed = int(payload.get(b"time"))
			except ValueError:
				elapsed = "unknown"
			args[b"elapsed"] = elapsed
			args[b"reason"] = payload.get(b"reason", "unknown")
			track_event = "print_failed"
		elif event == Events.PRINT_CANCELLED:
			track_event = "print_cancelled"

		if callable(self._helpers_get_throttle_state):
			try:
				throttle_state = self._helpers_get_throttle_state(run_now=True)
				if throttle_state and (throttle_state.get(b"current_issue", False) or throttle_state.get(b"past_issue", False)):
					args[b"throttled_now"] = throttle_state[b"current_issue"]
					args[b"throttled_past"] = throttle_state[b"past_issue"]
					args[b"throttled_mask"] = throttle_state[b"raw_value"]
			except:
				# ignored
				pass

		if track_event is not None:
			self._track(track_event, **args)

	def _track_printer_event(self, event, payload):
		if not self._settings.get_boolean(["events", "printer"]):
			return

		if event in (Events.FIRMWARE_DATA,):
			args = dict(firmware_name=payload["name"])
			if self._printer_connection_parameters:
				args["printer_port"] = self._printer_connection_parameters["port"]
				args["printer_baudrate"] = self._printer_connection_parameters["baudrate"]
			self._track("printer_connected", **args)

	def _track(self, event, **kwargs):
		if not self._settings.get_boolean([b"enabled"]):
			return

		self._executor.submit(self._do_track, event, **kwargs)

	def _do_track(self, event, **kwargs):
		if not self._connectivity_checker.online:
			return

		server = self._settings.get([b"server"])
		url = server.format(id=self._settings.get([b"unique_id"]), event=event)
		# Don't print the URL or UUID! That would expose the UUID in forums/tickets
		# if pasted. It's okay for the user to know their uuid, but it shouldn't be shared.

		headers = {"User-Agent": "OctoPrint/{}".format(get_octoprint_version_string())}
		try:
			params = urlencode(kwargs, doseq=True).replace("+", "%20")

			requests.get(url,
			             params=params,
			             timeout=3.1,
			             headers=headers)
			self._logger.info("Sent tracking event {}, payload: {!r}".format(event, kwargs))
		except:
			if self._logger.isEnabledFor(logging.DEBUG):
				self._logger.exception("Error while sending event to anonymous usage tracking".format(url))
			else:
				pass
コード例 #53
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.')
コード例 #54
0
ファイル: __init__.py プロジェクト: foosel/OctoPrint
class PiSupportPlugin(octoprint.plugin.EnvironmentDetectionPlugin,
                      octoprint.plugin.SimpleApiPlugin,
                      octoprint.plugin.AssetPlugin,
                      octoprint.plugin.TemplatePlugin,
                      octoprint.plugin.StartupPlugin,
                      octoprint.plugin.SettingsPlugin):

	# noinspection PyMissingConstructor
	def __init__(self):
		self._throttle_state = ThrottleState()
		self._throttle_check = None
		self._throttle_undervoltage = False
		self._throttle_overheat = False
		self._throttle_functional = True

	#~~ EnvironmentDetectionPlugin

	def get_additional_environment(self):
		result = dict(model=get_proc_dt_model())

		if is_octopi():
			result.update(dict(octopi_version=get_octopi_version()))

		return result

	#~~ SimpleApiPlugin

	def on_api_get(self, request):
		result = dict(throttle_state=self._throttle_state.as_dict())
		result.update(self.get_additional_environment())
		return flask.jsonify(**result)

	#~~ AssetPlugin

	def get_assets(self):
		return dict(
			js=["js/pi_support.js"],
			clientjs=["clientjs/pi_support.js"],
			css=["css/pi_support.css"]
		)

	#~~ TemplatePlugin

	def get_template_configs(self):
		configs = [dict(type="settings", name=gettext("Pi Support"), template="pi_support_settings.jinja2", custom_bindings=False)]

		if is_octopi():
			configs.append(dict(type="about", name="About OctoPi", template="pi_support_about_octopi.jinja2"))

		return configs

	def get_template_vars(self):
		return self.get_additional_environment()

	#~~ StartupPlugin

	def on_startup(self, *args, **kwargs):
		if self._settings.get_boolean(["vcgencmd_throttle_check_enabled"]):
			self._check_throttled_state()
			self._throttle_check = RepeatedTimer(self._check_throttled_state_interval,
			                                     self._check_throttled_state,
			                                     condition=self._check_throttled_state_condition)
			self._throttle_check.start()

	#~~ SettingsPlugin

	def get_settings_defaults(self):
		return dict(vcgencmd_throttle_check_enabled=True,
		            vcgencmd_throttle_check_command=_VCGENCMD_THROTTLE)

	def get_settings_restricted_paths(self):
		return dict(admin=[["vcgencmd_throttle_check_enabled"], ["vcgencmd_throttle_check_command"]])

	#~~ Helpers

	def _check_throttled_state_interval(self):
		if self._throttle_state.current_issue:
			# check state every 30s if something's currently amiss
			return 30
		else:
			# check state every 5min if nothing's currently amiss
			return 300

	def _check_throttled_state_condition(self):
		return self._throttle_functional

	def get_throttle_state(self, run_now=False):
		"""Exposed as public helper."""
		if run_now:
			self._check_throttled_state()

		if not self._throttle_functional:
			return False

		return self._throttle_state.as_dict()

	def _check_throttled_state(self):
		command = self._settings.get(["vcgencmd_throttle_check_command"])

		self._logger.debug("Retrieving throttle state via \"{}\"".format(command))
		try:
			state = get_vcgencmd_throttled_state(command)
		except:
			self._logger.exception("Got an error while trying to fetch the current throttle state via \"{}\"".format(command))
			self._throttle_functional = False
			return

		if self._throttle_state == state:
			# no change
			return

		self._throttle_state = state

		if (not self._throttle_undervoltage and self._throttle_state.undervoltage) \
				or (not self._throttle_overheat and self._throttle_state.overheat):
			message = "This Raspberry Pi is reporting problems that might lead to bad performance or errors caused " \
			          "by overheating or insufficient power."

			if self._throttle_state.undervoltage:
				self._throttle_undervoltage = True
				message += "\n!!! UNDERVOLTAGE REPORTED !!! Make sure that the power supply and power cable are " \
				           "capable of supplying enough voltage and current to your Pi."

			if self._throttle_state.overheat:
				self._throttle_overheat = True
				message += "\n!!! FREQUENCY CAPPING DUE TO OVERHEATING REPORTED !!! Improve cooling on the Pi's " \
				           "CPU and GPU."

			self._logger.warn(message)

		self._plugin_manager.send_plugin_message(self._identifier, dict(type="throttle_state",
		                                                                state=self._throttle_state.as_dict()))

		# noinspection PyUnresolvedReferences
		self._event_bus.fire(octoprint.events.Events.PLUGIN_PI_SUPPORT_THROTTLE_STATE, self._throttle_state.as_dict())
コード例 #55
0
ファイル: __init__.py プロジェクト: ByReaL/OctoPrint
class ConnectivityChecker(object):
	"""
	Regularly checks for online connectivity.

	Tries to open a connection to the provided ``host`` and ``port`` every ``interval``
	seconds and sets the ``online`` status accordingly.
	"""

	def __init__(self, interval, host, port, enabled=True, on_change=None):
		self._interval = interval
		self._host = host
		self._port = port
		self._enabled = enabled
		self._on_change = on_change

		self._logger = logging.getLogger(__name__ + ".connectivity_checker")

		# we initialize the online flag to True if we are not enabled (we don't know any better
		# but these days it's probably a sane default)
		self._online = not self._enabled

		self._check_worker = None
		self._check_mutex = threading.RLock()

		self._run()

	@property
	def online(self):
		"""Current online status, True if online, False if offline."""
		with self._check_mutex:
			return self._online

	@property
	def host(self):
		"""DNS host to query."""
		with self._check_mutex:
			return self._host

	@host.setter
	def host(self, value):
		with self._check_mutex:
			self._host = value

	@property
	def port(self):
		"""DNS port to query."""
		with self._check_mutex:
			return self._port

	@port.setter
	def port(self, value):
		with self._check_mutex:
			self._port = value

	@property
	def interval(self):
		"""Interval between consecutive automatic checks."""
		return self._interval

	@interval.setter
	def interval(self, value):
		self._interval = value

	@property
	def enabled(self):
		"""Whether the check is enabled or not."""
		return self._enabled

	@enabled.setter
	def enabled(self, value):
		with self._check_mutex:
			old_enabled = self._enabled
			self._enabled = value

			if not self._enabled:
				if self._check_worker is not None:
					self._check_worker.cancel()

				old_value = self._online
				self._online = True

				if old_value != self._online:
					self._trigger_change(old_value, self._online)

			elif self._enabled and not old_enabled:
				self._run()

	def check_immediately(self):
		"""Check immediately and return result."""
		with self._check_mutex:
			self._perform_check()
			return self.online

	def _run(self):
		from octoprint.util import RepeatedTimer

		if not self._enabled:
			return

		if self._check_worker is not None:
			self._check_worker.cancel()

		self._check_worker = RepeatedTimer(self._interval, self._perform_check,
		                                   run_first=True)
		self._check_worker.start()

	def _perform_check(self):
		if not self._enabled:
			return

		with self._check_mutex:
			self._logger.debug("Checking against {}:{} if we are online...".format(self._host, self._port))

			old_value = self._online
			self._online = server_reachable(self._host, port=self._port)

			if old_value != self._online:
				self._trigger_change(old_value, self._online)

	def _trigger_change(self, old_value, new_value):
		self._logger.info("Connectivity changed from {} to {}".format("online" if old_value else "offline",
		                                                              "online" if new_value else "offline"))
		if callable(self._on_change):
			self._on_change(old_value, new_value)
コード例 #56
0
ファイル: virtual.py プロジェクト: ByReaL/OctoPrint
class VirtualPrinter(object):
	command_regex = re.compile("^([GMTF])(\d+)")
	sleep_regex = re.compile("sleep (\d+)")
	sleep_after_regex = re.compile("sleep_after ([GMTF]\d+) (\d+)")
	sleep_after_next_regex = re.compile("sleep_after_next ([GMTF]\d+) (\d+)")
	custom_action_regex = re.compile("action_custom ([a-zA-Z0-9_]+)(\s+.*)?")
	prepare_ok_regex = re.compile("prepare_ok (.*)")
	send_regex = re.compile("send (.*)")
	set_ambient_regex = re.compile("set_ambient ([-+]?[0-9]*\.?[0-9]+)")
	start_sd_regex = re.compile("start_sd (.*)")
	select_sd_regex = re.compile("select_sd (.*)")

	def __init__(self, seriallog_handler=None, read_timeout=5.0, write_timeout=10.0):
		import logging
		self._logger = logging.getLogger("octoprint.plugins.virtual_printer.VirtualPrinter")

		self._seriallog = logging.getLogger("octoprint.plugin.virtual_printer.VirtualPrinter.serial")
		self._seriallog.setLevel(logging.CRITICAL)
		self._seriallog.propagate = False

		if seriallog_handler is not None:
			import logging.handlers
			self._seriallog.addHandler(seriallog_handler)
			self._seriallog.setLevel(logging.INFO)

		self._seriallog.info("-"*78)

		self._read_timeout = read_timeout
		self._write_timeout = write_timeout

		self._rx_buffer_size = settings().getInt(["devel", "virtualPrinter", "rxBuffer"])

		self.incoming = CharCountingQueue(self._rx_buffer_size, name="RxBuffer")
		self.outgoing = queue.Queue()
		self.buffered = queue.Queue(maxsize=settings().getInt(["devel", "virtualPrinter", "commandBuffer"]))

		if settings().getBoolean(["devel", "virtualPrinter", "simulateReset"]):
			for item in settings().get(["devel", "virtualPrinter", "resetLines"]):
				self._send(item + "\n")

		self._prepared_oks = []
		prepared = settings().get(["devel", "virtualPrinter", "preparedOks"])
		if prepared and isinstance(prepared, list):
			for prep in prepared:
				self._prepared_oks.append(prep)

		self._prepared_errors = []

		self._errors = settings().get(["devel", "virtualPrinter", "errors"], merged=True)

		self.currentExtruder = 0
		self.extruderCount = settings().getInt(["devel", "virtualPrinter", "numExtruders"])
		self.pinnedExtruders = settings().get(["devel", "virtualPrinter", "pinnedExtruders"])
		if self.pinnedExtruders is None:
			self.pinnedExtruders = dict()
		self.sharedNozzle = settings().getBoolean(["devel", "virtualPrinter", "sharedNozzle"])
		self.temperatureCount = (1 if self.sharedNozzle else self.extruderCount)

		self._ambient_temperature = settings().getFloat(["devel", "virtualPrinter", "ambientTemperature"])

		self.temp = [self._ambient_temperature] * self.temperatureCount
		self.targetTemp = [0.0] * self.temperatureCount
		self.bedTemp = self._ambient_temperature
		self.bedTargetTemp = 0.0
		self.lastTempAt = time.time()

		self._relative = True
		self._lastX = 0.0
		self._lastY = 0.0
		self._lastZ = 0.0
		self._lastE = [0.0] * self.extruderCount
		self._lastF = 200

		self._unitModifier = 1
		self._feedrate_multiplier = 100
		self._flowrate_multiplier = 100

		self._virtualSd = settings().getBaseFolder("virtualSd")
		self._sdCardReady = True
		self._sdPrinter = None
		self._sdPrintingSemaphore = threading.Event()
		self._selectedSdFile = None
		self._selectedSdFileSize = None
		self._selectedSdFilePos = None

		self._writingToSd = False
		self._writingToSdHandle = None
		self._newSdFilePos = None

		self._heatingUp = False

		self._okBeforeCommandOutput = settings().getBoolean(["devel", "virtualPrinter", "okBeforeCommandOutput"])
		self._supportM112 = settings().getBoolean(["devel", "virtualPrinter", "supportM112"])
		self._supportF = settings().getBoolean(["devel", "virtualPrinter", "supportF"])

		self._sendWait = settings().getBoolean(["devel", "virtualPrinter", "sendWait"])
		self._sendBusy = settings().getBoolean(["devel", "virtualPrinter", "sendBusy"])
		self._waitInterval = settings().getFloat(["devel", "virtualPrinter", "waitInterval"])
		self._busyInterval = settings().getFloat(["devel", "virtualPrinter", "busyInterval"])

		self._echoOnM117 = settings().getBoolean(["devel", "virtualPrinter", "echoOnM117"])

		self._brokenM29 = settings().getBoolean(["devel", "virtualPrinter", "brokenM29"])
		self._brokenResend = settings().getBoolean(["devel", "virtualPrinter", "brokenResend"])

		self._m115FormatString = settings().get(["devel", "virtualPrinter", "m115FormatString"])
		self._firmwareName = settings().get(["devel", "virtualPrinter", "firmwareName"])

		self._okFormatString = settings().get(["devel", "virtualPrinter", "okFormatString"])

		self._capabilities = settings().get(["devel", "virtualPrinter", "capabilities"], merged=True)

		self._temperature_reporter = None
		self._sdstatus_reporter = None

		self.currentLine = 0
		self.lastN = 0

		self._incoming_lock = threading.RLock()

		self._debug_awol = False
		self._debug_sleep = None
		self._sleepAfterNext = dict()
		self._sleepAfter = dict()

		self._dont_answer = False

		self._debug_drop_connection = False

		self._action_hooks = plugin_manager().get_hooks("octoprint.plugin.virtual_printer.custom_action")

		self._killed = False

		self._triggerResendAt100 = True
		self._triggerResendWithTimeoutAt105 = True
		self._triggerResendWithMissingLinenoAt110 = True
		self._triggerResendWithChecksumMismatchAt115 = True

		readThread = threading.Thread(target=self._processIncoming, name="octoprint.plugins.virtual_printer.wait_thread")
		readThread.start()

		bufferThread = threading.Thread(target=self._processBuffer, name="octoprint.plugins.virtual_printer.buffer_thread")
		bufferThread.start()

	def __str__(self):
		return "VIRTUAL(read_timeout={read_timeout},write_timeout={write_timeout},options={options})"\
			.format(read_timeout=self._read_timeout, write_timeout=self._write_timeout, options=settings().get(["devel", "virtualPrinter"]))

	def _reset(self):
		with self._incoming_lock:
			self._relative = True
			self._lastX = 0.0
			self._lastY = 0.0
			self._lastZ = 0.0
			self._lastE = [0.0] * self.extruderCount
			self._lastF = 200

			self._unitModifier = 1
			self._feedrate_multiplier = 100
			self._flowrate_multiplier = 100

			self._sdCardReady = True
			self._sdPrinting = False
			if self._sdPrinter:
				self._sdPrinting = False
				self._sdPrintingSemaphore.set()
			self._sdPrinter = None
			self._selectedSdFile = None
			self._selectedSdFileSize = None
			self._selectedSdFilePos = None

			if self._writingToSdHandle:
				try:
					self._writingToSdHandle.close()
				except:
					pass
			self._writingToSd = False
			self._writingToSdHandle = None
			self._newSdFilePos = None

			self._heatingUp = False

			self.currentLine = 0
			self.lastN = 0

			self._debug_awol = False
			self._debug_sleep = None
			self._sleepAfterNext.clear()
			self._sleepAfter.clear()

			self._dont_answer = False

			self._debug_drop_connection = False

			self._killed = False

			self._triggerResendAt100 = True
			self._triggerResendWithTimeoutAt105 = True
			self._triggerResendWithMissingLinenoAt110 = True
			self._triggerResendWithChecksumMismatchAt115 = True

			if self._temperature_reporter is not None:
				self._temperature_reporter.cancel()
				self._temperature_reporter = None

			if self._sdstatus_reporter is not None:
				self._sdstatus_reporter.cancel()
				self._sdstatus_reporter = None

			self._clearQueue(self.incoming)
			self._clearQueue(self.outgoing)
			self._clearQueue(self.buffered)

			if settings().getBoolean(["devel", "virtualPrinter", "simulateReset"]):
				for item in settings().get(["devel", "virtualPrinter", "resetLines"]):
					self._send(item + "\n")

	@property
	def timeout(self):
		return self._read_timeout

	@timeout.setter
	def timeout(self, value):
		self._logger.debug("Setting read timeout to {}s".format(value))
		self._read_timeout = value

	@property
	def write_timeout(self):
		return self._write_timeout

	@write_timeout.setter
	def write_timeout(self, value):
		self._logger.debug("Setting write timeout to {}s".format(value))
		self._write_timeout = value

	def _clearQueue(self, q):
		try:
			while q.get(block=False):
				q.task_done()
				continue
		except queue.Empty:
			pass

	def _processIncoming(self):
		next_wait_timeout = time.time() + self._waitInterval
		buf = ""
		while self.incoming is not None and not self._killed:
			self._simulateTemps()

			if self._heatingUp:
				time.sleep(1)
				continue

			try:
				data = self.incoming.get(timeout=0.01)
				self.incoming.task_done()
			except queue.Empty:
				if self._sendWait and time.time() > next_wait_timeout:
					self._send("wait")
					next_wait_timeout = time.time() + self._waitInterval
				continue

			buf += data
			if "\n" in buf:
				data = buf[:buf.find("\n") + 1]
				buf = buf[buf.find("\n") + 1:]
			else:
				continue

			next_wait_timeout = time.time() + self._waitInterval

			if data is None:
				continue

			if self._dont_answer:
				self._dont_answer = False
				continue

			data = data.strip()

			# strip checksum
			if "*" in data:
				checksum = int(data[data.rfind("*") + 1:])
				data = data[:data.rfind("*")]
				if not checksum == self._calculate_checksum(data):
					self._triggerResend(expected=self.currentLine + 1)
					continue

				self.currentLine += 1
			elif settings().getBoolean(["devel", "virtualPrinter", "forceChecksum"]):
				self._send(self._error("checksum_missing"))
				continue

			# track N = N + 1
			if data.startswith("N") and "M110" in data:
				linenumber = int(re.search("N([0-9]+)", data).group(1))
				self.lastN = linenumber
				self.currentLine = linenumber

				self._triggerResendAt100 = True
				self._triggerResendWithTimeoutAt105 = True

				self._sendOk()
				continue
			elif data.startswith("N"):
				linenumber = int(re.search("N([0-9]+)", data).group(1))
				expected = self.lastN + 1
				if linenumber != expected:
					self._triggerResend(actual=linenumber)
					continue
				elif linenumber == 100 and self._triggerResendAt100:
					# simulate a resend at line 100
					self._triggerResendAt100 = False
					self._triggerResend(expected=100)
					continue
				elif linenumber == 105 and self._triggerResendWithTimeoutAt105 and not self._writingToSd:
					# simulate a resend with timeout at line 105
					self._triggerResendWithTimeoutAt105 = False
					self._triggerResend(expected=105)
					self._dont_answer = True
					self.lastN = linenumber
					continue
				elif linenumber == 110 and self._triggerResendWithMissingLinenoAt110 and not self._writingToSd:
					self._triggerResendWithMissingLinenoAt110 = False
					self._send(self._error("lineno_missing", self.lastN))
					continue
				elif linenumber == 115 and self._triggerResendWithChecksumMismatchAt115 and not self._writingToSd:
					self._triggerResendWithChecksumMismatchAt115 = False
					self._triggerResend(checksum=True)
					continue
				elif len(self._prepared_errors):
					prepared = self._prepared_errors.pop(0)
					if callable(prepared):
						prepared(linenumber, self.lastN, data)
						continue
					elif isinstance(prepared, basestring):
						self._send(prepared)
						continue
				else:
					self.lastN = linenumber
				data = data.split(None, 1)[1].strip()

			data += "\n"

			if data.startswith("!!DEBUG:") or data.strip() == "!!DEBUG":
				debug_command = ""
				if data.startswith("!!DEBUG:"):
					debug_command = data[len("!!DEBUG:"):].strip()
				self._debugTrigger(debug_command)
				continue

			# shortcut for writing to SD
			if self._writingToSd and self._writingToSdHandle is not None and not "M29" in data:
				self._writingToSdHandle.write(data)
				self._sendOk()
				continue

			if data.strip() == "version":
				from octoprint import __version__
				self._send("OctoPrint VirtualPrinter v" + __version__)
				continue

			# if we are sending oks before command output, send it now
			if len(data.strip()) > 0 and self._okBeforeCommandOutput:
				self._sendOk()

			# actual command handling
			command_match = VirtualPrinter.command_regex.match(data)
			if command_match is not None:
				command = command_match.group(0)
				letter = command_match.group(1)

				try:
					# if we have a method _gcode_G, _gcode_M or _gcode_T, execute that first
					letter_handler = "_gcode_{}".format(letter)
					if hasattr(self, letter_handler):
						code = command_match.group(2)
						handled = getattr(self, letter_handler)(code, data)
						if handled:
							continue

					# then look for a method _gcode_<command> and execute that if it exists
					command_handler = "_gcode_{}".format(command)
					if hasattr(self, command_handler):
						handled = getattr(self, command_handler)(data)
						if handled:
							continue

				finally:
					# make sure that the debug sleepAfter and sleepAfterNext stuff works even
					# if we continued above
					if len(self._sleepAfter) or len(self._sleepAfterNext):
						interval = None
						if command in self._sleepAfter:
							interval = self._sleepAfter[command]
						elif command in self._sleepAfterNext:
							interval = self._sleepAfterNext[command]
							del self._sleepAfterNext[command]

						if interval is not None:
							self._send("// sleeping for {interval} seconds".format(interval=interval))
							time.sleep(interval)

			# if we are sending oks after command output, send it now
			if len(data.strip()) > 0 and not self._okBeforeCommandOutput:
				self._sendOk()

		self._logger.info("Closing down read loop")

	##~~ command implementations

	def _gcode_T(self, code, data):
		t = int(code)
		if 0 <= t < self.extruderCount:
			self.currentExtruder = t
			self._send("Active Extruder: %d" % self.currentExtruder)
		else:
			self._send("echo:T{} Invalid extruder ".format(t))

	def _gcode_F(self, code, data):
		if self._supportF:
			self._send("echo:changed F value")
			return False
		else:
			self._send(self._error("command_unknown", "F"))
			return True

	def _gcode_M104(self, data):
		self._parseHotendCommand(data)

	def _gcode_M109(self, data):
		self._parseHotendCommand(data, wait=True, support_r=True)

	def _gcode_M140(self, data):
		self._parseBedCommand(data)

	def _gcode_M190(self, data):
		self._parseBedCommand(data, wait=True, support_r=True)

	def _gcode_M105(self, data):
		self._processTemperatureQuery()
		return True

	def _gcode_M20(self, data):
		if self._sdCardReady:
			self._listSd()

	def _gcode_M21(self, data):
		self._sdCardReady = True
		self._send("SD card ok")

	def _gcode_M22(self, data):
		self._sdCardReady = False

	def _gcode_M23(self, data):
		if self._sdCardReady:
			filename = data.split(None, 1)[1].strip()
			self._selectSdFile(filename)

	def _gcode_M24(self, data):
		if self._sdCardReady:
			self._startSdPrint()

	def _gcode_M25(self, data):
		if self._sdCardReady:
			self._pauseSdPrint()

	def _gcode_M26(self, data):
		if self._sdCardReady:
			pos = int(re.search("S([0-9]+)", data).group(1))
			self._setSdPos(pos)

	def _gcode_M27(self, data):
		def report():
			if self._sdCardReady:
				self._reportSdStatus()

		match = re.search("S([0-9]+)", data)
		if match:
			interval = int(match.group(1))
			if self._sdstatus_reporter is not None:
				self._sdstatus_reporter.cancel()

			if interval > 0:
				self._sdstatus_reporter = RepeatedTimer(interval, report)
				self._sdstatus_reporter.start()
			else:
				self._sdstatus_reporter = None

		report()

	def _gcode_M28(self, data):
		if self._sdCardReady:
			filename = data.split(None, 1)[1].strip()
			self._writeSdFile(filename)

	def _gcode_M29(self, data):
		if self._sdCardReady:
			self._finishSdFile()

	def _gcode_M30(self, data):
		if self._sdCardReady:
			filename = data.split(None, 1)[1].strip()
			self._deleteSdFile(filename)

	def _gcode_M113(self, data):
		interval = int(re.search("S([0-9]+)", data).group(1))
		if 0 <= interval <= 60:
			self._busyInterval = interval

	def _gcode_M114(self, data):
		m114FormatString = settings().get(["devel", "virtualPrinter", "m114FormatString"])
		e = dict((index, value) for index, value in enumerate(self._lastE))
		e["current"] = self._lastE[self.currentExtruder]
		e["all"] = " ".join(["E{}:{}".format(num, self._lastE[self.currentExtruder]) for num in range(self.extruderCount)])
		output = m114FormatString.format(x=self._lastX,
										 y=self._lastY,
										 z=self._lastZ,
										 e=e,
										 f=self._lastF,
										 a=int(self._lastX*100),
										 b=int(self._lastY*100),
										 c=int(self._lastZ*100))

		if not self._okBeforeCommandOutput:
			ok = self._ok()
			if ok:
				output = "{} {}".format(self._ok(), output)
		self._send(output)
		return True

	def _gcode_M115(self, data):
		output = self._m115FormatString.format(firmware_name=self._firmwareName)
		self._send(output)

		if settings().getBoolean(["devel", "virtualPrinter", "m115ReportCapabilities"]):
			for cap, enabled in self._capabilities.items():
				self._send("Cap:{}:{}".format(cap.upper(), "1" if enabled else "0"))

	def _gcode_M117(self, data):
		# we'll just use this to echo a message, to allow playing around with pause triggers
		if self._echoOnM117:
			self._send("echo:%s" % re.search("M117\s+(.*)", data).group(1))

	def _gcode_M155(self, data):
		interval = int(re.search("S([0-9]+)", data).group(1))
		if self._temperature_reporter is not None:
			self._temperature_reporter.cancel()

		if interval > 0:
			self._temperature_reporter = RepeatedTimer(interval, lambda: self._send(self._generateTemperatureOutput()))
			self._temperature_reporter.start()
		else:
			self._temperature_reporter = None

	def _gcode_M220(self, data):
		self._feedrate_multiplier = float(re.search('S([0-9]+)', data).group(1))

	def _gcode_M221(self, data):
		self._flowrate_multiplier = float(re.search('S([0-9]+)', data).group(1))

	def _gcode_M400(self, data):
		self.buffered.join()

	def _gcode_M999(self, data):
		# mirror Marlin behaviour
		self._send("Resend: 1")

	def _gcode_G20(self, data):
		self._unitModifier = 1.0 / 2.54
		if self._lastX is not None:
			self._lastX *= 2.54
		if self._lastY is not None:
			self._lastY *= 2.54
		if self._lastZ is not None:
			self._lastZ *= 2.54
		if self._lastE is not None:
			self._lastE = [e * 2.54 if e is not None else None for e in self._lastE]

	def _gcode_G21(self, data):
		self._unitModifier = 1.0
		if self._lastX is not None:
			self._lastX /= 2.54
		if self._lastY is not None:
			self._lastY /= 2.54
		if self._lastZ is not None:
			self._lastZ /= 2.54
		if self._lastE is not None:
			self._lastE = [e / 2.54 if e is not None else None for e in self._lastE]

	def _gcode_G90(self, data):
		self._relative = False

	def _gcode_G91(self, data):
		self._relative = True

	def _gcode_G92(self, data):
		self._setPosition(data)

	def _gcode_G28(self, data):
		self._performMove(data)

	def _gcode_G0(self, data):
		# simulate reprap buffered commands via a Queue with maxsize which internally simulates the moves
		self.buffered.put(data)
	_gcode_G1 = _gcode_G0
	_gcode_G2 = _gcode_G0
	_gcode_G3 = _gcode_G0

	def _gcode_G4(self, data):
		matchS = re.search('S([0-9]+)', data)
		matchP = re.search('P([0-9]+)', data)

		_timeout = 0
		if matchP:
			_timeout = float(matchP.group(1)) / 1000.0
		elif matchS:
			_timeout = float(matchS.group(1))

		if self._sendBusy and self._busyInterval > 0:
			until = time.time() + _timeout
			while time.time() < until:
				time.sleep(self._busyInterval)
				self._send("busy:processing")
		else:
			time.sleep(_timeout)

	##~~ further helpers

	def _calculate_checksum(self, line):
		checksum = 0
		for c in line:
			checksum ^= ord(c)
		return checksum

	def _kill(self):
		if not self._supportM112:
			return
		self._killed = True
		self._send("echo:EMERGENCY SHUTDOWN DETECTED. KILLED.")

	def _triggerResend(self, expected=None, actual=None, checksum=None):
		with self._incoming_lock:
			if expected is None:
				expected = self.lastN + 1
			else:
				self.lastN = expected - 1

			if actual is None:
				if checksum:
					self._send(self._error("checksum_mismatch"))
				else:
					self._send(self._error("checksum_missing"))
			else:
				self._send(self._error("lineno_mismatch", expected, actual))

			def request_resend():
				self._send("Resend:%d" % expected)
				if not self._brokenResend:
					self._sendOk()

			request_resend()

	def _debugTrigger(self, data):
		if data == "" or data == "help" or data == "?":
			usage = """
			OctoPrint Virtual Printer debug commands

			help
			?
			| This help.

			# Action Triggers

			action_pause
			| Sends a "// action:pause" action trigger to the host.
			action_resume
			| Sends a "// action:resume" action trigger to the host.
			action_disconnect
			| Sends a "// action:disconnect" action trigger to the
			| host.
			action_custom <action>[ <parameters>]
			| Sends a custom "// action:<action> <parameters>"
			| action trigger to the host.

			# Communication Errors

			dont_answer
			| Will not acknowledge the next command.
			go_awol
			| Will completely stop replying
			trigger_resend_lineno
			| Triggers a resend error with a line number mismatch
			trigger_resend_checksum
			| Triggers a resend error with a checksum mismatch
			trigger_missing_checksum
			| Triggers a resend error with a missing checksum
			trigger_missing_lineno
			| Triggers a "no line number with checksum" error w/o resend request
			drop_connection
			| Drops the serial connection
			prepare_ok <broken ok>
			| Will cause <broken ok> to be enqueued for use,
			| will be used instead of actual "ok"

			# Reply Timing / Sleeping

			sleep <int:seconds>
			| Sleep <seconds> s
			sleep_after <str:command> <int:seconds>
			| Sleeps <seconds> s after each execution of <command>
			sleep_after_next <str:command> <int:seconds>
			| Sleeps <seconds> s after execution of next <command>

			# SD printing

			start_sd <str:file>
			| Select and start printing file <file> from SD
			select_sd <str:file>
			| Select file <file> from SD, don't start printing it yet. Use
			| start_sd to start the print
			cancel_sd
			| Cancels an ongoing SD print

			# Misc

			send <str:message>
			| Sends back <message>
			reset
			| Simulates a reset. Internal state will be lost.
			"""
			for line in usage.split("\n"):
				self._send("echo: {}".format(line.strip()))
		elif data == "action_pause":
			self._send("// action:pause")
		elif data == "action_resume":
			self._send("// action:resume")
		elif data == "action_disconnect":
			self._send("// action:disconnect")
		elif data == "dont_answer":
			self._dont_answer = True
		elif data == "trigger_resend_lineno":
			self._prepared_errors.append(lambda cur, last, line: self._triggerResend(expected=last, actual=last+1))
		elif data == "trigger_resend_checksum":
			self._prepared_errors.append(lambda cur, last, line: self._triggerResend(expected=last, checksum=True))
		elif data == "trigger_missing_checksum":
			self._prepared_errors.append(lambda cur, last, line: self._triggerResend(expected=last, checksum=False))
		elif data == "trigger_missing_lineno":
			self._prepared_errors.append(lambda cur, last, line: self._send(self._error("lineno_missing", last)))
		elif data == "drop_connection":
			self._debug_drop_connection = True
		elif data == "reset":
			self._reset()
		elif data == "mintemp_error":
			self._send(self._error("mintemp"))
		elif data == "maxtemp_error":
			self._send(self._error("maxtemp"))
		elif data == "go_awol":
			self._send("// Going AWOL")
			self._debug_awol = True
		elif data == "cancel_sd":
			if self._sdPrinting and self._sdPrinter:
				self._pauseSdPrint()
				self._sdPrinting = False
				self._sdPrintingSemaphore.set()
				self._sdPrinter.join()
				self._finishSdPrint()
		else:
			try:
				sleep_match = VirtualPrinter.sleep_regex.match(data)
				sleep_after_match = VirtualPrinter.sleep_after_regex.match(data)
				sleep_after_next_match = VirtualPrinter.sleep_after_next_regex.match(data)
				custom_action_match = VirtualPrinter.custom_action_regex.match(data)
				prepare_ok_match = VirtualPrinter.prepare_ok_regex.match(data)
				send_match = VirtualPrinter.send_regex.match(data)
				set_ambient_match = VirtualPrinter.set_ambient_regex.match(data)
				start_sd_match = VirtualPrinter.start_sd_regex.match(data)
				select_sd_match = VirtualPrinter.select_sd_regex.match(data)

				if sleep_match is not None:
					interval = int(sleep_match.group(1))
					self._send("// sleeping for {interval} seconds".format(interval=interval))
					self._debug_sleep = interval
				elif sleep_after_match is not None:
					command = sleep_after_match.group(1)
					interval = int(sleep_after_match.group(2))
					self._sleepAfter[command] = interval
					self._send("// going to sleep {interval} seconds after each {command}".format(**locals()))
				elif sleep_after_next_match is not None:
					command = sleep_after_next_match.group(1)
					interval = int(sleep_after_next_match.group(2))
					self._sleepAfterNext[command] = interval
					self._send("// going to sleep {interval} seconds after next {command}".format(**locals()))
				elif custom_action_match is not None:
					action = custom_action_match.group(1)
					params = custom_action_match.group(2)
					params = params.strip() if params is not None else ""
					self._send("// action:{action} {params}".format(**locals()).strip())
				elif prepare_ok_match is not None:
					ok = prepare_ok_match.group(1)
					self._prepared_oks.append(ok)
				elif send_match is not None:
					self._send(send_match.group(1))
				elif set_ambient_match is not None:
					self._ambient_temperature = float(set_ambient_match.group(1))
					self._send("// set ambient temperature to {}".format(self._ambient_temperature))
				elif start_sd_match is not None:
					self._selectSdFile(start_sd_match.group(1), check_already_open=True)
					self._startSdPrint()
				elif select_sd_match is not None:
					self._selectSdFile(select_sd_match.group(1))
			except:
				pass

	def _listSd(self):
		self._send("Begin file list")
		if settings().getBoolean(["devel", "virtualPrinter", "extendedSdFileList"]):
			items = map(
				lambda x: "%s %d" % (x.upper(), os.stat(os.path.join(self._virtualSd, x)).st_size),
				os.listdir(self._virtualSd)
			)
		else:
			items = map(
				lambda x: x.upper(),
				os.listdir(self._virtualSd)
			)
		for item in items:
			self._send(item)
		self._send("End file list")

	def _selectSdFile(self, filename, check_already_open=False):
		if filename.startswith("/"):
			filename = filename[1:]

		file = os.path.join(self._virtualSd, filename.lower())
		if self._selectedSdFile == file and check_already_open:
			return

		if not os.path.exists(file) or not os.path.isfile(file):
			self._send("open failed, File: %s." % filename)
		else:
			self._selectedSdFile = file
			self._selectedSdFileSize = os.stat(file).st_size
			if settings().getBoolean(["devel", "virtualPrinter", "includeFilenameInOpened"]):
				self._send("File opened: %s  Size: %d" % (filename, self._selectedSdFileSize))
			else:
				self._send("File opened")
			self._send("File selected")

	def _startSdPrint(self):
		if self._selectedSdFile is not None:
			if self._sdPrinter is None:
				self._sdPrinting = True
				self._sdPrinter = threading.Thread(target=self._sdPrintingWorker)
				self._sdPrinter.start()
		self._sdPrintingSemaphore.set()

	def _pauseSdPrint(self):
		self._sdPrintingSemaphore.clear()

	def _setSdPos(self, pos):
		self._newSdFilePos = pos

	def _reportSdStatus(self):
		if self._sdPrinter is not None and self._sdPrintingSemaphore.is_set:
			self._send("SD printing byte %d/%d" % (self._selectedSdFilePos, self._selectedSdFileSize))
		else:
			self._send("Not SD printing")

	def _generateTemperatureOutput(self):
		includeTarget = not settings().getBoolean(["devel", "virtualPrinter", "repetierStyleTargetTemperature"])

		# send simulated temperature data
		if self.temperatureCount > 1:
			allTemps = []
			for i in range(len(self.temp)):
				allTemps.append((i, self.temp[i], self.targetTemp[i]))
			allTempsString = " ".join(map(lambda x: "T%d:%.2f /%.2f" % x if includeTarget else "T%d:%.2f" % (x[0], x[1]), allTemps))

			if settings().getBoolean(["devel", "virtualPrinter", "smoothieTemperatureReporting"]):
				allTempsString = allTempsString.replace("T0:", "T:")

			if settings().getBoolean(["devel", "virtualPrinter", "hasBed"]):
				if includeTarget:
					allTempsString = "B:%.2f /%.2f %s" % (self.bedTemp, self.bedTargetTemp, allTempsString)
				else:
					allTempsString = "B:%.2f %s" % (self.bedTemp, allTempsString)

			if settings().getBoolean(["devel", "virtualPrinter", "includeCurrentToolInTemps"]):
				if includeTarget:
					output = "T:%.2f /%.2f %s" % (self.temp[self.currentExtruder], self.targetTemp[self.currentExtruder], allTempsString)
				else:
					output = "T:%.2f %s" % (self.temp[self.currentExtruder], allTempsString)
			else:
				output = allTempsString
		else:
			if includeTarget:
				output = "T:%.2f /%.2f B:%.2f /%.2f" % (self.temp[0], self.targetTemp[0], self.bedTemp, self.bedTargetTemp)
			else:
				output = "T:%.2f B:%.2f" % (self.temp[0], self.bedTemp)

		output += " @:64\n"
		return output

	def _processTemperatureQuery(self):
		includeOk = not self._okBeforeCommandOutput
		output = self._generateTemperatureOutput()

		if includeOk:
			ok = self._ok()
			if ok:
				output = "{} {}".format(ok, output)
		self._send(output)

	def _parseHotendCommand(self, line, wait=False, support_r=False):
		only_wait_if_higher = True
		tool = 0
		toolMatch = re.search('T([0-9]+)', line)
		if toolMatch:
			try:
				tool = int(toolMatch.group(1))
			except:
				pass

		if tool >= self.temperatureCount:
			return

		try:
			self.targetTemp[tool] = float(re.search('S([0-9]+)', line).group(1))
		except:
			if support_r:
				try:
					self.targetTemp[tool] = float(re.search('R([0-9]+)', line).group(1))
					only_wait_if_higher = False
				except:
					pass

		if wait:
			self._waitForHeatup("tool%d" % tool, only_wait_if_higher)
		if settings().getBoolean(["devel", "virtualPrinter", "repetierStyleTargetTemperature"]):
			self._send("TargetExtr%d:%d" % (tool, self.targetTemp[tool]))

	def _parseBedCommand(self, line, wait=False, support_r=False):
		only_wait_if_higher = True
		try:
			self.bedTargetTemp = float(re.search('S([0-9]+)', line).group(1))
		except:
			if support_r:
				try:
					self.bedTargetTemp = float(re.search('R([0-9]+)', line).group(1))
					only_wait_if_higher = False
				except:
					pass

		if wait:
			self._waitForHeatup("bed", only_wait_if_higher)
		if settings().getBoolean(["devel", "virtualPrinter", "repetierStyleTargetTemperature"]):
			self._send("TargetBed:%d" % self.bedTargetTemp)

	def _performMove(self, line):
		matchX = re.search("X([0-9.]+)", line)
		matchY = re.search("Y([0-9.]+)", line)
		matchZ = re.search("Z([0-9.]+)", line)
		matchE = re.search("E([0-9.]+)", line)
		matchF = re.search("F([0-9.]+)", line)

		duration = 0.0
		if matchF is not None:
			try:
				self._lastF = float(matchF.group(1))
			except:
				pass

		speedXYZ = self._lastF * (self._feedrate_multiplier / 100.0)
		speedE = self._lastF * (self._flowrate_multiplier / 100.0)

		if matchX is not None:
			try:
				x = float(matchX.group(1))
				if self._relative or self._lastX is None:
					duration = max(duration, x * self._unitModifier / speedXYZ * 60.0)
				else:
					duration = max(duration, (x - self._lastX) * self._unitModifier / speedXYZ * 60.0)

				if self._relative and self._lastX is not None:
					self._lastX += x
				else:
					self._lastX = x
			except:
				pass
		if matchY is not None:
			try:
				y = float(matchY.group(1))
				if self._relative or self._lastY is None:
					duration = max(duration, y * self._unitModifier / speedXYZ * 60.0)
				else:
					duration = max(duration, (y - self._lastY) * self._unitModifier / speedXYZ * 60.0)

				if self._relative and self._lastY is not None:
					self._lastY += y
				else:
					self._lastY = y
			except:
				pass
		if matchZ is not None:
			try:
				z = float(matchZ.group(1))
				if self._relative or self._lastZ is None:
					duration = max(duration, z * self._unitModifier / speedXYZ * 60.0)
				else:
					duration = max(duration, (z - self._lastZ) * self._unitModifier / speedXYZ * 60.0)

				if self._relative and self._lastZ is not None:
					self._lastZ += z
				else:
					self._lastZ = z
			except:
				pass
		if matchE is not None:
			try:
				e = float(matchE.group(1))
				lastE = self._lastE[self.currentExtruder]
				if self._relative or lastE is None:
					duration = max(duration, e * self._unitModifier / speedE * 60.0)
				else:
					duration = max(duration, (e - lastE) * self._unitModifier / speedE * 60.0)

				if self._relative and lastE is not None:
					self._lastE[self.currentExtruder] += e
				else:
					self._lastE[self.currentExtruder] = e
			except:
				pass

		if duration:
			duration *= 0.1
			if duration > self._read_timeout:
				slept = 0
				while duration - slept > self._read_timeout and not self._killed:
					time.sleep(self._read_timeout)
					slept += self._read_timeout
			else:
				time.sleep(duration)

	def _setPosition(self, line):
		matchX = re.search("X([0-9.]+)", line)
		matchY = re.search("Y([0-9.]+)", line)
		matchZ = re.search("Z([0-9.]+)", line)
		matchE = re.search("E([0-9.]+)", line)

		if matchX is None and matchY is None and matchZ is None and matchE is None:
			self._lastX = self._lastY = self._lastZ = self._lastE[self.currentExtruder] = 0
		else:
			if matchX is not None:
				try:
					self._lastX = float(matchX.group(1))
				except:
					pass
			if matchY is not None:
				try:
					self._lastY = float(matchY.group(1))
				except:
					pass
			if matchZ is not None:
				try:
					self._lastZ = float(matchZ.group(1))
				except:
					pass
			if matchE is not None:
				try:
					self._lastE[self.currentExtruder] = float(matchE.group(1))
				except:
					pass

	def _writeSdFile(self, filename):
		if filename.startswith("/"):
			filename = filename[1:]
		file = os.path.join(self._virtualSd, filename).lower()
		if os.path.exists(file):
			if os.path.isfile(file):
				os.remove(file)
			else:
				self._send("error writing to file")

		handle = None
		try:
			handle = open(file, "w")
		except:
			self._output("error writing to file")
			if handle is not None:
				try:
					handle.close()
				except:
					pass
		self._writingToSdHandle = handle
		self._writingToSd = True
		self._selectedSdFile = file
		self._send("Writing to file: %s" % filename)

	def _finishSdFile(self):
		try:
			self._writingToSdHandle.close()
		except:
			pass
		finally:
			self._writingToSdHandle = None
		self._writingToSd = False
		self._selectedSdFile = None
		self._output("Done saving file")

	def _sdPrintingWorker(self):
		self._selectedSdFilePos = 0
		try:
			with open(self._selectedSdFile, "r") as f:
				for line in iter(f.readline, ""):
					if self._killed or not self._sdPrinting:
						break

					# reset position if requested by client
					if self._newSdFilePos is not None:
						f.seek(self._newSdFilePos)
						self._newSdFilePos = None

					# read current file position
					self._selectedSdFilePos = f.tell()

					# if we are paused, wait for unpausing
					self._sdPrintingSemaphore.wait()
					if self._killed or not self._sdPrinting:
						break

					# set target temps
					if 'M104' in line or 'M109' in line:
						self._parseHotendCommand(line, wait='M109' in line)
					elif 'M140' in line or 'M190' in line:
						self._parseBedCommand(line, wait='M190' in line)
					elif line.startswith("G0") or line.startswith("G1") or line.startswith("G2") or line.startswith("G3"):
						# simulate reprap buffered commands via a Queue with maxsize which internally simulates the moves
						self.buffered.put(line)

		except AttributeError:
			if self.outgoing is not None:
				raise

		self._finishSdPrint()

	def _finishSdPrint(self):
		if not self._killed:
			self._sdPrintingSemaphore.clear()
			self._output("Done printing file")
			self._selectedSdFilePos = 0
			self._sdPrinting = False
			self._sdPrinter = None

	def _waitForHeatup(self, heater, only_wait_if_higher):
		delta = 1
		delay = 1
		last_busy = time.time()

		self._heatingUp = True
		try:
			if heater.startswith("tool"):
				toolNum = int(heater[len("tool"):])
				test = lambda: self.temp[toolNum] < self.targetTemp[toolNum] - delta or (not only_wait_if_higher and self.temp[toolNum] > self.targetTemp[toolNum] + delta)
				output = lambda: "T:%0.2f" % self.temp[toolNum]
			elif heater == "bed":
				test = lambda: self.bedTemp < self.bedTargetTemp - delta or (not only_wait_if_higher and self.bedTemp > self.bedTargetTemp + delta)
				output = lambda: "B:%0.2f" % self.bedTemp
			else:
				return

			while not self._killed and self._heatingUp and test():
				self._simulateTemps(delta=delta)
				self._output(output())
				if self._sendBusy and time.time() - last_busy >= self._busyInterval:
					self._output("echo:busy: processing")
					last_busy = time.time()
				time.sleep(delay)
		except AttributeError:
			if self.outgoing is not None:
				raise
		finally:
			self._heatingUp = False

	def _deleteSdFile(self, filename):
		if filename.startswith("/"):
			filename = filename[1:]
		f = os.path.join(self._virtualSd, filename)
		if os.path.exists(f) and os.path.isfile(f):
			os.remove(f)

	def _simulateTemps(self, delta=0.5):
		timeDiff = self.lastTempAt - time.time()
		self.lastTempAt = time.time()

		def simulate(actual, target, ambient):
			if target > 0 and abs(actual - target) > delta:
				goal = target
				factor = 10
			elif not target and abs(actual - ambient) > delta:
				goal = ambient
				factor = 2
			else:
				return actual

			old = actual
			actual += math.copysign(timeDiff * factor, goal - actual)

			if math.copysign(1, goal - old) != math.copysign(1, goal - actual):
				actual = goal

			return actual

		for i in range(len(self.temp)):
			if i in self.pinnedExtruders:
				self.temp[i] = self.pinnedExtruders[i]
				continue
			self.temp[i] = simulate(self.temp[i], self.targetTemp[i], self._ambient_temperature)
		self.bedTemp = simulate(self.bedTemp, self.bedTargetTemp, self._ambient_temperature)

	def _processBuffer(self):
		while self.buffered is not None:
			try:
				line = self.buffered.get(timeout=0.5)
			except queue.Empty:
				continue

			if line is None:
				continue

			self._performMove(line)
			self.buffered.task_done()

		self._logger.info("Closing down buffer loop")

	def _output(self, line):
		try:
			self.outgoing.put(line)
		except:
			if self.outgoing is None:
				pass

	def write(self, data):
		if self._debug_awol:
			return len(data)

		if self._debug_drop_connection:
			self._logger.info("Debug drop of connection requested, raising SerialTimeoutException")
			raise SerialTimeoutException()

		with self._incoming_lock:
			if self.incoming is None or self.outgoing is None:
				return 0

			if "M112" in data and self._supportM112:
				self._seriallog.info("<<< {}".format(data.strip()))
				self._kill()
				return len(data)

			try:
				written = self.incoming.put(data, timeout=self._write_timeout, partial=True)
				self._seriallog.info("<<< {}".format(data.strip()))
				return written
			except queue.Full:
				self._logger.info("Incoming queue is full, raising SerialTimeoutException")
				raise SerialTimeoutException()

	def readline(self):
		if self._debug_awol:
			time.sleep(self._read_timeout)
			return ""

		if self._debug_drop_connection:
			raise SerialTimeoutException()

		if self._debug_sleep > 0:
			# if we are supposed to sleep, we sleep not longer than the read timeout
			# (and then on the next call sleep again if there's time to sleep left)
			sleep_for = min(self._debug_sleep, self._read_timeout)
			self._debug_sleep -= sleep_for
			time.sleep(sleep_for)

			if self._debug_sleep > 0:
				# we slept the full read timeout, return an empty line
				return ""

			# otherwise our left over timeout is the read timeout minus what we already
			# slept for
			timeout = self._read_timeout - sleep_for

		else:
			# use the full read timeout as timeout
			timeout = self._read_timeout

		try:
			# fetch a line from the queue, wait no longer than timeout
			line = self.outgoing.get(timeout=timeout)
			self._seriallog.info(">>> {}".format(line.strip()))
			self.outgoing.task_done()
			return line
		except queue.Empty:
			# queue empty? return empty line
			return ""

	def close(self):
		self._killed = True
		self.incoming = None
		self.outgoing = None
		self.buffered = None

	def _sendOk(self):
		if self.outgoing is None:
			return
		ok = self._ok()
		if ok:
			self._send(ok)

	def _sendWaitAfterTimeout(self, timeout=5):
		time.sleep(timeout)
		if self.outgoing is not None:
			self._send("wait")

	def _send(self, line):
		if self.outgoing is not None:
			self.outgoing.put(line)

	def _ok(self):
		ok = self._okFormatString
		if self._prepared_oks:
			ok = self._prepared_oks.pop(0)
			if ok is None:
				return ok

		return ok.format(ok, lastN=self.lastN, buffer=self.buffered.maxsize - self.buffered.qsize())

	def _error(self, error, *args, **kwargs):
		return "Error: {}".format(self._errors.get(error).format(*args, **kwargs))
コード例 #57
0
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"
        ))
コード例 #58
0
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)
    ]