def _waitForHeatup(self, heater, only_wait_if_higher): delta = 1 delay = 1 last_busy = monotonic_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 elif heater == "chamber": test = lambda: self.chamberTemp < self.chamberTargetTemp - delta or (not only_wait_if_higher and self.chamberTemp > self.chamberTargetTemp + delta) output = lambda: "C:%0.2f" % self.chamberTemp else: return while not self._killed and self._heatingUp and test(): self._simulateTemps(delta=delta) self._output(output()) if self._sendBusy and monotonic_time() - last_busy >= self._busyInterval: self._output("echo:busy: processing") last_busy = monotonic_time() time.sleep(delay) except AttributeError: if self.outgoing is not None: raise finally: self._heatingUp = False
def _simulateTemps(self, delta=0.5): timeDiff = self.lastTempAt - monotonic_time() self.lastTempAt = monotonic_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) self.chamberTemp = simulate(self.chamberTemp, self.chamberTargetTemp, self._ambient_temperature)
def _analyze(self, entry, high_priority=False): path = entry.absolute_path if path is None or not os.path.exists(path): return self._current = entry self._current_highprio = high_priority self._current_progress = 0 try: start_time = monotonic_time() self._logger.info("Starting analysis of {}".format(entry)) eventManager().fire(Events.METADATA_ANALYSIS_STARTED, {"name": entry.name, "path": entry.path, "origin": entry.location, "type": entry.type, # TODO deprecated, remove in 1.4.0 "file": entry.path}) try: result = self._do_analysis(high_priority=high_priority) except TypeError: result = self._do_analysis() self._logger.info("Analysis of entry {} finished, needed {:.2f}s".format(entry, monotonic_time() - start_time)) self._finished_callback(self._current, result) finally: self._current = None self._current_progress = None
def _analyze(self, entry, high_priority=False): path = entry.absolute_path if path is None or not os.path.exists(path): return self._current = entry self._current_highprio = high_priority self._current_progress = 0 try: start_time = monotonic_time() self._logger.info("Starting analysis of {}".format(entry)) eventManager().fire( Events.METADATA_ANALYSIS_STARTED, { "name": entry.name, "path": entry.path, "origin": entry.location, "type": entry.type, }, ) try: result = self._do_analysis(high_priority=high_priority) except TypeError: result = self._do_analysis() self._logger.info( "Analysis of entry {} finished, needed {:.2f}s".format( entry, monotonic_time() - start_time ) ) self._finished_callback(self._current, result) finally: self._current = None self._current_progress = None
def __init__(self, user): wrapt.ObjectProxy.__init__(self, user) self._self_session = "".join('%02X' % z for z in bytes(uuid.uuid4().bytes)) self._self_created = monotonic_time() self._self_touched = monotonic_time()
def put(self, item, block=True, timeout=None, partial=False): self.not_full.acquire() try: if not self._will_it_fit(item) and partial: space_left = self.maxsize - self._qsize() if space_left: item = item[:space_left] if not block: if not self._will_it_fit(item): raise queue.Full elif timeout is None: while not self._will_it_fit(item): self.not_full.wait() elif timeout < 0: raise ValueError("'timeout' must be a positive number") else: endtime = monotonic_time() + timeout while not self._will_it_fit(item): remaining = endtime - monotonic_time() if remaining <= 0.0: raise queue.Full self.not_full.wait(remaining) self._put(item) self.unfinished_tasks += 1 self.not_empty.notify() return self._len(item) finally: self.not_full.release()
def perform_update(target, check, target_version, log_cb=None, online=True): duration = check.get("duration", 30) now = monotonic_time() end = now + duration while now < end: log_cb(["{}s left...".format(end - now)], prefix=">", stream="output") time.sleep(5) now = monotonic_time()
def evaluate_timeout(self): if self._start_time is None: self._start_time = monotonic_time() else: if monotonic_time() > self._start_time + self.ACTIVE_TIMEOUT: # timeout ran out, this is now inactive self._active = False logging.getLogger(__name__).info( "Deactivated {} due to timeout after {}s".format( __name__, self.ACTIVE_TIMEOUT))
def _gcode_G33(self, data): self._send("G33 Auto Calibrate") self._send("Will take ~60s") timeout = 60 if self._sendBusy and self._busyInterval > 0: until = monotonic_time() + timeout while monotonic_time() < until: time.sleep(self._busyInterval) self._send("busy:processing") else: time.sleep(timeout)
def gcode_command(path, speedx, speedy, speedz, offset, maxt, throttle, throttle_lines, g90_extruder, progress): """Runs a GCODE file analysis.""" import time import yaml from octoprint.util import monotonic_time from octoprint.util.gcodeInterpreter import gcode throttle_callback = None if throttle: def throttle_callback(filePos, readBytes): if filePos % throttle_lines == 0: # only apply throttle every $throttle_lines lines time.sleep(throttle) offsets = offset if offsets is None: offsets = [] elif isinstance(offset, tuple): offsets = list(offsets) offsets = [(0, 0)] + offsets if len(offsets) < maxt: offsets += [(0, 0)] * (maxt - len(offsets)) start_time = monotonic_time() progress_callback = None if progress: def progress_callback(percentage): click.echo("PROGRESS:{}".format(percentage)) interpreter = gcode(progress_callback=progress_callback) interpreter.load(path, speedx=speedx, speedy=speedy, offsets=offsets, throttle=throttle_callback, max_extruders=maxt, g90_extruder=g90_extruder) click.echo("DONE:{}s".format(monotonic_time() - start_time)) click.echo("RESULTS:") click.echo( yaml.safe_dump(interpreter.get_result(), default_flow_style=False, indent=4, allow_unicode=True))
def _cleanup_sessions(self): for session, user in list(self._session_users_by_session.items()): if not isinstance(user, SessionUser): continue if user.created + (24 * 60 * 60) < monotonic_time(): self._logger.info("Cleaning up user session {} for user {}".format(session, user.get_id())) self.logout_user(user, stale=True)
def __init__(self, width, height, _printer): super().__init__(width, height) self._printer = _printer self.press_time = monotonic_time() self.expired = False self.timer = threading.Timer(10, self.timer_expired) self.timer.start()
def _track_ping(self): if not self._settings.get_boolean(["enabled"]): return uptime = int(monotonic_time() - self._startup_time) printer_state = self._printer.get_state_id() self._track("ping", octoprint_uptime=uptime, printer_state=printer_state)
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 = monotonic_time() + _timeout while monotonic_time() < until: time.sleep(self._busyInterval) self._send("busy:processing") else: time.sleep(_timeout)
def handle_button(self, label): if label == 'cancel': if self.expired or (monotonic_time() - self.press_time) < 1.0: return {'IGNORE'} self._printer.cancel_print() self.expired = True return {'BACK'}
def _remove_stale_pending(self): with self._pending_lock: cutoff = monotonic_time() - CUTOFF_TIME len_before = len(self._pending_decisions) self._pending_decisions = filter(lambda x: x.created >= cutoff, self._pending_decisions) len_after = len(self._pending_decisions) if len_after < len_before: self._logger.info("Deleted {} stale pending authorization requests".format(len_before - len_after))
def __init__(self, user): wrapt.ObjectProxy.__init__(self, user) import string import random import time chars = string.ascii_uppercase + string.ascii_lowercase + string.digits self._self_session = "".join(random.choice(chars) for _ in range(10)) self._self_created = monotonic_time()
def __init__(self, app_id, app_token, user_id, user_token, timeout_callback=None): self.app_id = app_id self.app_token = app_token self.user_id = user_id self.user_token = user_token self.created = monotonic_time() if callable(timeout_callback): self.poll_timeout = ResettableTimer(POLL_TIMEOUT, timeout_callback, [user_token]) self.poll_timeout.start()
def _remove_stale_pending(self): with self._pending_lock: cutoff = monotonic_time() - CUTOFF_TIME len_before = len(self._pending_decisions) self._pending_decisions = list( filter(lambda x: x.created >= cutoff, self._pending_decisions)) len_after = len(self._pending_decisions) if len_after < len_before: self._logger.info( "Deleted {} stale pending authorization requests".format( len_before - len_after))
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 self._startup_time = monotonic_time()
def __init__(self, app_id, app_token, user_id, user_token, timeout_callback=None): self.app_id = app_id self.app_token = app_token self.user_id = user_id self.user_token = user_token self.created = monotonic_time() if callable(timeout_callback): self.poll_timeout = ResettableTimer( POLL_TIMEOUT, timeout_callback, [user_token] ) self.poll_timeout.start()
def _get_channel_data_from_network(self, key, config): """Fetch channel feed from network.""" import requests url = config["url"] try: start = monotonic_time() r = requests.get(url, timeout=30) r.raise_for_status() self._logger.info("Loaded channel {} from {} in {:.2}s".format( key, config["url"], monotonic_time() - start)) except Exception: self._logger.exception("Could not fetch channel {} from {}".format( key, config["url"])) return None response = r.text channel_path = self._get_channel_cache_path(key) with io.open(channel_path, mode="wt", encoding="utf-8") as f: f.write(response) return feedparser.parse(response)
def _on_z_change(self, event, payload): # check if height difference equals z-hop, if so don't take a picture if self._retraction_zhop != 0 and payload["old"] is not None and payload["new"] is not None: diff = round(abs(payload["new"] - payload["old"]), 3) zhop = round(self._retraction_zhop, 3) if diff == zhop: return # check if last picture has been less than min_delay ago, if so don't take a picture (anti vase mode...) now = monotonic_time() if self._min_delay and self._last_snapshot and self._last_snapshot + self._min_delay > now: self._logger.debug("Rate limited z-change, not taking a snapshot") return self.capture_image() self._last_snapshot = now
def _on_z_change(self, event, payload): # check if height difference equals z-hop, if so don't take a picture if self._retraction_zhop != 0 and payload[ "old"] is not None and payload["new"] is not None: diff = round(abs(payload["new"] - payload["old"]), 3) zhop = round(self._retraction_zhop, 3) if diff == zhop: return # check if last picture has been less than min_delay ago, if so don't take a picture (anti vase mode...) now = monotonic_time() if self._min_delay and self._last_snapshot and self._last_snapshot + self._min_delay > now: self._logger.debug("Rate limited z-change, not taking a snapshot") return self.capture_image() self._last_snapshot = now
def _get_channel_data_from_network(self, key, config): """Fetch channel feed from network.""" import requests url = config["url"] try: start = monotonic_time() r = requests.get(url, timeout=30) r.raise_for_status() self._logger.info(u"Loaded channel {} from {} in {:.2}s".format(key, config["url"], monotonic_time() - start)) except Exception as e: self._logger.exception( u"Could not fetch channel {} from {}: {}".format(key, config["url"], str(e))) return None response = r.text channel_path = self._get_channel_cache_path(key) with codecs.open(channel_path, mode="w", encoding="utf-8") as f: f.write(response) return feedparser.parse(response)
def _processIncoming(self): next_wait_timeout = monotonic_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 monotonic_time() > next_wait_timeout: self._send("wait") next_wait_timeout = monotonic_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 = monotonic_time() + self._waitInterval if data is None: continue if self._dont_answer: self._dont_answer = False continue data = to_unicode(data.strip(), errors="replace") # 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")
def _cleanup_sessions(self): for session, user in self._session_users_by_session.items(): if not isinstance(user, SessionUser): continue if user.created + (24 * 60 * 60) < monotonic_time(): self.logout_user(user)
def gcode_command( path, speedx, speedy, speedz, offset, maxt, throttle, throttle_lines, g90_extruder, bedz, progress, layers, ): """Runs a GCODE file analysis.""" import time import yaml from octoprint.util import monotonic_time from octoprint.util.gcodeInterpreter import gcode throttle_callback = None if throttle: def throttle_callback(filePos, readBytes): if filePos % throttle_lines == 0: # only apply throttle every $throttle_lines lines time.sleep(throttle) offsets = offset if offsets is None: offsets = [] elif isinstance(offset, tuple): offsets = list(offsets) offsets = [(0, 0)] + offsets if len(offsets) < maxt: offsets += [(0, 0)] * (maxt - len(offsets)) start_time = monotonic_time() progress_callback = None if progress: def progress_callback(percentage): click.echo("PROGRESS:{}".format(percentage)) interpreter = gcode(progress_callback=progress_callback, incl_layers=layers) interpreter.load( path, speedx=speedx, speedy=speedy, offsets=offsets, throttle=throttle_callback, max_extruders=maxt, g90_extruder=g90_extruder, bed_z=bedz, ) click.echo("DONE:{}s".format(monotonic_time() - start_time)) result = interpreter.get_result() if empty_result(result): click.echo( "EMPTY:There are no extrusions in the file, nothing to analyse") sys.exit(0) if not validate_result(result): click.echo( "ERROR:Invalid analysis result, please create a bug report in OctoPrint's " "issue tracker and be sure to also include the GCODE file with which this " "happened") sys.exit(-1) click.echo("RESULTS:") click.echo( yaml.safe_dump( interpreter.get_result(), default_flow_style=False, indent=2, allow_unicode=True, ))
def _track_ping(self): if not self._settings.get_boolean([b"enabled"]): return uptime = int(monotonic_time() - self._startup_time) self._track("ping", octoprint_uptime=uptime)
def touch(self): self._self_touched = monotonic_time()
def gcode_received(self, comm, line, *args, **kwargs): # Try to figure out buffer sizes for underrun detection by looking at the N0 M110 N0 response # Important: This runs before on_after_startup if self.planner_buffer_size == 0 and "ok N0 " in line: matches = ADVANCED_OK.search(line) if matches: # ok output always returns BLOCK_BUFFER_SIZE - 1 due to # FORCE_INLINE static uint8_t moves_free() { return BLOCK_BUFFER_SIZE - 1 - movesplanned(); } # for whatever reason planner_buffer_size = int(matches.group('planner_buffer_avail')) + 1 # We add +1 here as ok will always return BUFSIZE-1 as we've just sent it a command command_buffer_size = int(matches.group('command_buffer_avail')) + 1 self.set_buffer_sizes(planner_buffer_size, command_buffer_size) self.set_status('Buffer sizes detected') if self.did_resend and not comm._resendActive: self.did_resend = False self.set_status('Resend over, resuming...') if "ok " in line: matches = ADVANCED_OK.search(line) if matches is None or matches.group('line') is None: return line ok_line_number = int(matches.group('line')) current_line_number = comm._current_line command_buffer_avail = int(matches.group('command_buffer_avail')) planner_buffer_avail = int(matches.group('planner_buffer_avail')) queue_size = comm._send_queue._qsize() inflight_target = self.sd_inflight_target if comm.isStreaming() else self.inflight_target inflight = current_line_number - ok_line_number inflight += comm._clear_to_send._counter # If there's a clear_to_send pending, we need to count it as inflight cause it will be soon should_report = False should_send = False # If we're in a resend state, try to lower inflight commands by consuming ok's if comm._resendActive and self.enabled: if not self.did_resend: self.resends_detected += 1 self.did_resend = True self.set_status('Resend detected, backing off') self.last_cts = monotonic_time() + POST_RESEND_WAIT # Hack to delay before resuming CTS after resend event to give printer some time to breathe if inflight > (inflight_target / 2): self._logger.warn("using a clear to decrease inflight, inflight: {}, line: {}".format(inflight, line)) comm._ok_timeout = monotonic_time() + 0.05 # Reduce the timeout in case we eat too many OKs return None # detect underruns if printing if not comm.isStreaming(): if command_buffer_avail == self.command_buffer_size - 1: self.command_underruns_detected += 1 if planner_buffer_avail == self.planner_buffer_size - 1: self.planner_underruns_detected += 1 if (monotonic_time() - self.last_report) > REPORT_INTERVAL: should_report = True if command_buffer_avail > 1: # aim to keep at least one spot free if inflight < inflight_target and (monotonic_time() - self.last_cts) > self.min_cts_interval: should_send = True if should_send and self.enabled: # Ensure _clear_to_send._max is at least 2, otherwise triggering _clear_to_send won't do anything if comm._clear_to_send._max < 2: self._logger.warn("setting 'ok buffer size' / comm._clear_to_send._max to 2 cause plugin doesn't work at 1") comm._clear_to_send._max = 2 # If the command queue is empty, triggering clear_to_send won't do anything # so we try to make sure something's in there if queue_size == 0: self._logger.debug("command queue empty, prod comm to send more with _continue_sending()") comm._continue_sending() self._logger.debug("detected available command buffer, triggering a send") # this enables the send loop to send if it's waiting comm._clear_to_send.set() # Is there a point calling this if _clear_to_send is at max? self.clear_to_sends_triggered += 1 self.last_cts = monotonic_time() should_report = True if should_report: self.send_message("update", { "current_line_number": current_line_number, "acked_line_number": ok_line_number, "inflight": inflight, "planner_buffer_avail": planner_buffer_avail, "command_buffer_avail": command_buffer_avail, "resends_detected": self.resends_detected, "planner_underruns_detected": self.planner_underruns_detected, "command_underruns_detected": self.command_underruns_detected, "cts_triggered": self.clear_to_sends_triggered, "send_queue_size": queue_size, }) self._logger.debug("current line: {} ok line: {} buffer avail: {} inflight: {} cts: {} cts_max: {} queue: {}".format(current_line_number, ok_line_number, command_buffer_avail, inflight, comm._clear_to_send._counter, comm._clear_to_send._max, queue_size)) self.last_report = monotonic_time() if self.enabled: self.set_status('Active') else: self.set_status('Monitoring') return line
def __init__(self): self._start_time = monotonic_time()
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(u"-"*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.chamberTemp = self._ambient_temperature self.chamberTargetTemp = 0.0 self.lastTempAt = monotonic_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()