def _processTemperatureQuery(self): includeTarget = not settings().getBoolean(["devel", "virtualPrinter", "repetierStyleTargetTemperature"]) # send simulated temperature data if settings().getInt(["devel", "virtualPrinter", "numExtruders"]) > 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", "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: self.readList.append("ok T:%.2f /%.2f %s @:64\n" % (self.temp[self.currentExtruder], self.targetTemp[self.currentExtruder] + 1, allTempsString)) else: self.readList.append("ok T:%.2f %s @:64\n" % (self.temp[self.currentExtruder], allTempsString)) else: self.readList.append("ok %s @:64\n" % allTempsString) else: if includeTarget: self.readList.append("ok T:%.2f /%.2f B:%.2f /%.2f @:64\n" % (self.temp[0], self.targetTemp[0], self.bedTemp, self.bedTargetTemp)) else: self.readList.append("ok T:%.2f B:%.2f @:64\n" % (self.temp[0], self.bedTemp))
def __init__(self): self.readList = ['start\n', 'Marlin: Virtual Marlin!\n', '\x80\n', 'SD init fail\n'] # no sd card as default startup scenario self.currentExtruder = 0 self.temp = [0.0] * settings().getInt(["devel", "virtualPrinter", "numExtruders"]) self.targetTemp = [0.0] * settings().getInt(["devel", "virtualPrinter", "numExtruders"]) self.lastTempAt = time.time() self.bedTemp = 1.0 self.bedTargetTemp = 1.0 self._virtualSd = settings().getBaseFolder("virtualSd") self._sdCardReady = False self._sdPrinter = None self._sdPrintingSemaphore = threading.Event() self._selectedSdFile = None self._selectedSdFileSize = None self._selectedSdFilePos = None self._writingToSd = False self._newSdFilePos = None self._heatupThread = None self.currentLine = 0 self.lastN = 0 waitThread = threading.Thread(target=self._sendWaitAfterTimeout) waitThread.start()
def main(): from optparse import OptionParser defaultHost = settings().get("server", "host") defaultPort = settings().get("server", "port") parser = OptionParser(usage="usage: %prog [options]") parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Enable debug mode") parser.add_option( "--host", action="store", type="string", default=defaultHost, dest="host", help="Specify the host on which to bind the server, defaults to %s if not set" % (defaultHost), ) parser.add_option( "--port", action="store", type="int", default=defaultPort, dest="port", help="Specify the port on which to bind the server, defaults to %s if not set" % (defaultPort), ) (options, args) = parser.parse_args() run(host=options.host, port=options.port, debug=options.debug)
def uploadFirmwareFile(target): print("lkj uploadFirmwareFile target:%s" % str(target)) target = FileDestinations.LOCAL if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) print("lkj post target:%s" % str(target)) print("lkj post request.values:%s" % str(request.values)) print("lkj post request.files:%s" % str(request.files)) input_name = "file" input_upload_name = input_name + "." + settings().get(["server", "uploads", "nameSuffix"]) input_upload_path = input_name + "." + settings().get(["server", "uploads", "pathSuffix"]) if input_upload_name in request.values and input_upload_path in request.values: print("lkj here 1") import shutil upload = util.Object() upload.filename = request.values[input_upload_name] upload.save = lambda new_path: shutil.move(request.values[input_upload_path], new_path) elif input_name in request.files: print("lkj here 2") upload = request.files[input_name] else: return make_response("No file included", 400) print("lkj post 2") # determine future filename of file to be uploaded, abort if it can't be uploaded try: futureFilename = fileManager.sanitize_name(FileDestinations.LOCAL, upload.filename) except: futureFilename = None print("lkj futureFilename:%s" % futureFilename) try : added_file = fileManager.add_firmware_file(FileDestinations.LOCAL, upload.filename, upload, allow_overwrite=True) except : added_file = None if added_file is None: print("lkj post added_file is None") return make_response("Could not upload the file %s" % upload.filename, 500) print("lkj added_file: %s" % added_file) files = {} done = True files.update({ FileDestinations.LOCAL: { "name": added_file, "origin": FileDestinations.LOCAL } }) r = make_response(jsonify(files=files, done=done), 201) #lkj from octoprint.server import printer if printer.isOperational(): #cmd = ("M205", param=added_file) #printer.command() pass #r.headers["Location"] = added_file return r
def _parseHotendCommand(self, line): tool = 0 toolMatch = re.search('T([0-9]+)', line) if toolMatch: try: tool = int(toolMatch.group(1)) except: pass if tool >= settings().getInt(["devel", "virtualPrinter", "numExtruders"]): self._sendOk() return try: self.targetTemp[tool] = float(re.search('S([0-9]+)', line).group(1)) except: pass if "M109" in line: self._heatupThread = threading.Thread(target=self._waitForHeatup, args=["tool%d" % tool]) self._heatupThread.start() return self._sendOk() if settings().getBoolean(["devel", "virtualPrinter", "repetierStyleTargetTemperature"]): self.readList.append("TargetExtr%d:%d" % (tool, self.targetTemp[tool]))
def _initSubscriptions(self): """ Subscribes all events as defined in "events > $triggerType > subscriptions" in the settings with their respective commands. """ if not settings().get(["events"]): return if not settings().getBoolean(["events", "enabled"]): return eventsToSubscribe = [] for subscription in settings().get(["events", "subscriptions"]): if not "event" in subscription.keys() or not "command" in subscription.keys() \ or not "type" in subscription.keys() or not subscription["type"] in ["system", "gcode"]: self._logger.info("Invalid command trigger, missing either event, type or command or type is invalid: %r" % subscription) continue if "enabled" in subscription.keys() and not subscription["enabled"]: self._logger.info("Disabled command trigger: %r" % subscription) continue event = subscription["event"] command = subscription["command"] commandType = subscription["type"] if not event in self._subscriptions.keys(): self._subscriptions[event] = [] self._subscriptions[event].append((command, commandType)) if not event in eventsToSubscribe: eventsToSubscribe.append(event) self.subscribe(eventsToSubscribe)
def connect(self, port=None, baudrate=None): 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 serialList(): baselist=[] if os.name=="nt": try: key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") i=0 while(1): baselist+=[_winreg.EnumValue(key,i)[1]] i+=1 except: pass baselist = baselist \ + glob.glob("/dev/ttyUSB*") \ + glob.glob("/dev/ttyACM*") \ + glob.glob("/dev/ttyAMA*") \ + glob.glob("/dev/tty.usb*") \ + glob.glob("/dev/cu.*") \ + glob.glob("/dev/cuaU*") \ + glob.glob("/dev/rfcomm*") additionalPorts = settings().get(["serial", "additionalPorts"]) for additional in additionalPorts: baselist += glob.glob(additional) prev = settings().get(["serial", "port"]) if prev in baselist: baselist.remove(prev) baselist.insert(0, prev) if settings().getBoolean(["devel", "virtualPrinter", "enabled"]): baselist.append("VIRTUAL") return baselist
def __init__(self, post_roll=0, fps=25): self._logger = logging.getLogger(__name__) self._image_number = None self._in_timelapse = False self._gcode_file = None self._post_roll = post_roll self._on_post_roll_done = None self._capture_dir = settings().getBaseFolder("timelapse_tmp") self._movie_dir = settings().getBaseFolder("timelapse") self._snapshot_url = settings().get(["webcam", "snapshot"]) self._ffmpeg_threads = settings().get(["webcam", "ffmpegThreads"]) self._fps = fps self._render_thread = None self._capture_mutex = threading.Lock() self._capture_queue = Queue.Queue() self._capture_queue_active = True self._capture_queue_thread = threading.Thread(target=self._capture_queue_worker) self._capture_queue_thread.daemon = True self._capture_queue_thread.start() # subscribe events eventManager().subscribe(Events.PRINT_STARTED, self.on_print_started) eventManager().subscribe(Events.PRINT_FAILED, self.on_print_done) eventManager().subscribe(Events.PRINT_DONE, self.on_print_done) eventManager().subscribe(Events.PRINT_RESUMED, self.on_print_resumed) for (event, callback) in self.event_subscriptions(): eventManager().subscribe(event, callback)
def run(self): # Global as I can't work out a way to get it into PrinterStateConnection global printer global gcodeManager from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.web import Application, FallbackHandler # first initialize the settings singleton and make sure it uses given configfile and basedir if available self._initSettings(self._configfile, self._basedir) # then initialize logging self._initLogging(self._debug) gcodeManager = gcodefiles.GcodeManager() printer = Printer(gcodeManager) if self._host is None: self._host = settings().get(["server", "host"]) if self._port is None: self._port = settings().getInt(["server", "port"]) logging.getLogger(__name__).info("Listening on http://%s:%d" % (self._host, self._port)) app.debug = self._debug self._router = tornadio2.TornadioRouter(PrinterStateConnection) self._tornado_app = Application(self._router.urls + [ (".*", FallbackHandler, {"fallback": WSGIContainer(app)}) ]) self._server = HTTPServer(self._tornado_app) self._server.listen(self._port, address=self._host) IOLoop.instance().start()
def decorated_view(*args, **kwargs): # if OctoPrint hasn't been set up yet, abort if settings().getBoolean(["server", "firstRun"]) and (octoprint.server.userManager is None or not octoprint.server.userManager.hasBeenCustomized()): return make_response("OctoPrint isn't setup yet", 403) # if API is globally enabled, enabled for this request and an api key is provided that is not the current UI API key, try to use that apikey = getApiKey(request) if settings().get(["api", "enabled"]) and apiEnabled and apikey is not None and apikey != octoprint.server.UI_API_KEY: if apikey == settings().get(["api", "key"]): # master key was used user = ApiUser() else: # user key might have been used user = octoprint.server.userManager.findUser(apikey=apikey) if user is None: return make_response("Invalid API key", 401) if login_user(user, remember=False): identity_changed.send(current_app._get_current_object(), identity=Identity(user.get_id())) return func(*args, **kwargs) # call regular login_required decorator #TODO: remove this temporary disablement of login requirement #return login_required(func)(*args, **kwargs) return func(*args, **kwargs)
def __call__(self, environ, start_response): script_name = environ.get('HTTP_X_SCRIPT_NAME', '') if not script_name: script_name = settings().get(["server", "baseUrl"]) if script_name: environ['SCRIPT_NAME'] = script_name path_info = environ['PATH_INFO'] if path_info.startswith(script_name): environ['PATH_INFO'] = path_info[len(script_name):] scheme = environ.get('HTTP_X_SCHEME', '') if not scheme: scheme = settings().get(["server", "scheme"]) if scheme: environ['wsgi.url_scheme'] = scheme host = environ.get('HTTP_X_FORWARDED_HOST', '') if not host: host = settings().get(["server", "forwardedHost"]) if host: environ['HTTP_HOST'] = host return self.app(environ, start_response)
def __init__(self, postRoll=0): self._logger = logging.getLogger(__name__) self._imageNumber = None self._inTimelapse = False self._gcodeFile = None self._postRoll = postRoll self._postRollStart = None self._onPostRollDone = None self._captureDir = settings().getBaseFolder("timelapse_tmp") self._movieDir = settings().getBaseFolder("timelapse") self._snapshotUrl = settings().get(["webcam", "snapshot"]) self._fps = 25 self._renderThread = None self._captureMutex = threading.Lock() # subscribe events eventManager().subscribe(Events.PRINT_STARTED, self.onPrintStarted) eventManager().subscribe(Events.PRINT_FAILED, self.onPrintDone) eventManager().subscribe(Events.PRINT_DONE, self.onPrintDone) eventManager().subscribe(Events.PRINT_RESUMED, self.onPrintResumed) for (event, callback) in self.eventSubscriptions(): eventManager().subscribe(event, callback)
def delete_old_unrendered_timelapses(): basedir = settings().getBaseFolder("timelapse_tmp") clean_after_days = settings().getInt(["webcam", "cleanTmpAfterDays"]) cutoff = time.time() - clean_after_days * 24 * 60 * 60 prefixes_to_clean = [] for filename in os.listdir(basedir): try: path = os.path.join(basedir, filename) prefix = _extract_prefix(filename) if prefix is None: # might be an old tmp_00000.jpg kinda frame. we can't # render those easily anymore, so delete that stuff if _old_capture_format_re.match(filename): os.remove(path) continue if prefix in prefixes_to_clean: continue if os.path.getmtime(path) < cutoff: prefixes_to_clean.append(prefix) except: logging.getLogger(__name__).exception("Error while processing file {} during cleanup".format(filename)) for prefix in prefixes_to_clean: delete_unrendered_timelapse(prefix)
def start_print(self, pos=None): """ Starts the currently loaded print job. Only starts if the printer is connected and operational, not currently printing and a printjob is loaded """ if self._comm is None or not self._comm.isOperational() or self._comm.isPrinting(): return if self._selectedFile is None: return # we are happy if the average of the estimates stays within 60s of the prior one threshold = settings().getFloat(["estimation", "printTime", "stableThreshold"]) rolling_window = None countdown = None if self._selectedFile["sd"]: # we are interesting in a rolling window of roughly the last 15s, so the number of entries has to be derived # by that divided by the sd status polling interval rolling_window = 15 / settings().get(["serial", "timeout", "sdStatus"]) # we are happy when one rolling window has been stable countdown = rolling_window self._timeEstimationData = TimeEstimationHelper(rolling_window=rolling_window, threshold=threshold, countdown=countdown) self._fileManager.delete_recovery_data() self._lastProgressReport = None self._setProgressData(completion=0) self._setCurrentZ(None) self._comm.startPrint(pos=pos)
def _createMovie(self, success=True): ffmpeg = settings().get(["webcam", "ffmpeg"]) bitrate = settings().get(["webcam", "bitrate"]) if ffmpeg is None or bitrate is None: self._logger.warn("Cannot create movie, path to ffmpeg or desired bitrate is unset") return input = os.path.join(self._captureDir, "tmp_%05d.jpg") if success: output = os.path.join(self._movieDir, "%s_%s.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S"))) else: output = os.path.join(self._movieDir, "%s_%s-failed.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S"))) # prepare ffmpeg command command = [ ffmpeg, '-i', input, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', str(self._fps), '-y', '-b:v', bitrate, '-f', 'vob'] filters = [] # flip video if configured if settings().getBoolean(["webcam", "flipH"]): filters.append('hflip') if settings().getBoolean(["webcam", "flipV"]): filters.append('vflip') # add watermark if configured watermarkFilter = None if settings().getBoolean(["webcam", "watermark"]): watermark = os.path.join(os.path.dirname(__file__), "static", "img", "watermark.png") if sys.platform == "win32": # Because ffmpeg hiccups on windows' drive letters and backslashes we have to give the watermark # path a special treatment. Yeah, I couldn't believe it either... watermark = watermark.replace("\\", "/").replace(":", "\\\\:") watermarkFilter = "movie=%s [wm]; [%%(inputName)s][wm] overlay=10:main_h-overlay_h-10" % watermark filterstring = None if len(filters) > 0: if watermarkFilter is not None: filterstring = "[in] %s [postprocessed]; %s [out]" % (",".join(filters), watermarkFilter % {"inputName": "postprocessed"}) else: filterstring = "[in] %s [out]" % ",".join(filters) elif watermarkFilter is not None: filterstring = watermarkFilter % {"inputName": "in"} + " [out]" if filterstring is not None: self._logger.debug("Applying videofilter chain: %s" % filterstring) command.extend(["-vf", filterstring]) # finalize command with output file self._logger.debug("Rendering movie to %s" % output) command.append(output) eventManager().fire(Events.MOVIE_RENDERING, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output)}) try: subprocess.check_call(command) eventManager().fire(Events.MOVIE_DONE, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output)}) except subprocess.CalledProcessError as (e): self._logger.warn("Could not render movie, got return code %r" % e.returncode) eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output), "returncode": e.returncode})
def passive_login(): if octoprint.server.userManager is not None: user = octoprint.server.userManager.login_user(flask.ext.login.current_user) else: user = flask.ext.login.current_user if user is not None and not user.is_anonymous(): flask.g.user = user flask.ext.principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask.ext.principal.Identity(user.get_id())) return flask.jsonify(user.asDict()) elif settings().getBoolean(["accessControl", "autologinLocal"]) \ and settings().get(["accessControl", "autologinAs"]) is not None \ and settings().get(["accessControl", "localNetworks"]) is not None: autologinAs = settings().get(["accessControl", "autologinAs"]) localNetworks = netaddr.IPSet([]) for ip in settings().get(["accessControl", "localNetworks"]): localNetworks.add(ip) try: remoteAddr = get_remote_address(flask.request) if netaddr.IPAddress(remoteAddr) in localNetworks: user = octoprint.server.userManager.findUser(autologinAs) if user is not None: flask.g.user = user flask.ext.login.login_user(user) flask.ext.principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask.ext.principal.Identity(user.get_id())) return flask.jsonify(user.asDict()) except: logger = logging.getLogger(__name__) logger.exception("Could not autologin user %s for networks %r" % (autologinAs, localNetworks)) return ("", 204)
def _createMovie(self): ffmpeg = settings().get(["webcam", "ffmpeg"]) bitrate = settings().get(["webcam", "bitrate"]) if ffmpeg is None or bitrate is None: self._logger.warn("Cannot create movie, path to ffmpeg is unset") return input = os.path.join(self._captureDir, "tmp_%05d.jpg") output = os.path.join(self._movieDir, "%s_%s.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S"))) # prepare ffmpeg command command = [ ffmpeg, '-i', input, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', '25', '-y', '-b:v', bitrate, '-f', 'vob'] # add watermark if configured if settings().getBoolean(["webcam", "watermark"]): watermark = os.path.join(os.path.dirname(__file__), "static", "img", "watermark.png") if sys.platform == "win32": # Because ffmpeg hiccups on windows' drive letters and backslashes we have to give the watermark # path a special treatment. Yeah, I couldn't believe it either... watermark = watermark.replace("\\", "/").replace(":", "\\\\:") command.extend(['-vf', 'movie=%s [wm]; [in][wm] overlay=10:main_h-overlay_h-10 [out]' % watermark]) # finalize command with output file command.append(output) subprocess.call(command) self._logger.debug("Rendering movie to %s" % output)
def run(self): # Global as I can't work out a way to get it into PrinterStateConnection global printer global gcodeManager global userManager from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.web import Application, FallbackHandler # first initialize the settings singleton and make sure it uses given configfile and basedir if available self._initSettings(self._configfile, self._basedir) # then initialize logging self._initLogging(self._debug) logger = logging.getLogger(__name__) gcodeManager = gcodefiles.GcodeManager() printer = Printer(gcodeManager) if settings().getBoolean(["accessControl", "enabled"]): userManagerName = settings().get(["accessControl", "userManager"]) try: clazz = util.getClass(userManagerName) userManager = clazz() except AttributeError, e: logger.exception("Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName)
def set_default(self, identifier): all_identifiers = self._load_all_identifiers().keys() if identifier is not None and not identifier in all_identifiers: return settings().set(["printerProfile", "default"], identifier) settings().save()
def _initSubscriptions(self, triggerType): """ Subscribes all events as defined in "events > $triggerType > subscriptions" in the settings with their respective commands. """ if not settings().get(["events", triggerType]): return if not settings().getBoolean(["events", triggerType, "enabled"]): return eventsToSubscribe = [] for subscription in settings().get(["events", triggerType, "subscriptions"]): if not "event" in subscription.keys() or not "command" in subscription.keys(): self._logger.info("Invalid %s, missing either event or command: %r" % (triggerType, subscription)) continue event = subscription["event"] command = subscription["command"] if not event in self._subscriptions.keys(): self._subscriptions[event] = [] self._subscriptions[event].append(command) if not event in eventsToSubscribe: eventsToSubscribe.append(event) self.subscribe(eventsToSubscribe)
def run(self): if not self._allowRoot: self._checkForRoot() global userManager global eventManager global loginManager global debug global softwareManager global VERSION from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.web import Application, FallbackHandler from astroprint.printfiles.watchdogs import UploadCleanupWatchdogHandler debug = self._debug # first initialize the settings singleton and make sure it uses given configfile and basedir if available self._initSettings(self._configfile, self._basedir) s = settings() # then initialize logging self._initLogging(self._debug, self._logConf) logger = logging.getLogger(__name__) if s.getBoolean(["accessControl", "enabled"]): userManagerName = settings().get(["accessControl", "userManager"]) try: clazz = util.getClass(userManagerName) userManager = clazz() except AttributeError, e: logger.exception("Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName)
def delete_old_unrendered_timelapses(): global _cleanup_lock basedir = settings().getBaseFolder("timelapse_tmp") clean_after_days = settings().getInt(["webcam", "cleanTmpAfterDays"]) cutoff = time.time() - clean_after_days * 24 * 60 * 60 prefixes_to_clean = [] with _cleanup_lock: for entry in scandir(basedir): try: prefix = _extract_prefix(entry.name) if prefix is None: # might be an old tmp_00000.jpg kinda frame. we can't # render those easily anymore, so delete that stuff if _old_capture_format_re.match(entry.name): os.remove(entry.path) continue if prefix in prefixes_to_clean: continue # delete if both creation and modification time are older than the cutoff if max(entry.stat().st_ctime, entry.stat().st_mtime) < cutoff: prefixes_to_clean.append(prefix) except: if logging.getLogger(__name__).isEnabledFor(logging.DEBUG): logging.getLogger(__name__).exception("Error while processing file {} during cleanup".format(entry.name)) for prefix in prefixes_to_clean: delete_unrendered_timelapse(prefix) logging.getLogger(__name__).info("Deleted old unrendered timelapse {}".format(prefix))
def apiLoad(): logger = logging.getLogger(__name__) if not settings().get(["api", "enabled"]): abort(401) if not "apikey" in request.values.keys(): abort(401) if request.values["apikey"] != settings().get(["api", "key"]): abort(403) if not "file" in request.files.keys(): abort(400) # Perform an upload file = request.files["file"] filename = gcodeManager.addFile(file) if filename is None: logger.warn("Upload via API failed") abort(500) # Immediately perform a file select and possibly print too printAfterSelect = False if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues: printAfterSelect = True filepath = gcodeManager.getAbsolutePath(filename) if filepath is not None: printer.selectFile(filepath, False, printAfterSelect) return jsonify(SUCCESS)
def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_entry_points=None, plugin_disabled_list=None): global _instance if _instance is None: if init: if plugin_folders is None: plugin_folders = (settings().getBaseFolder("plugins"), os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "plugins"))) if plugin_types is None: plugin_types = [StartupPlugin, ShutdownPlugin, TemplatePlugin, SettingsPlugin, SimpleApiPlugin, AssetPlugin, BlueprintPlugin, EventHandlerPlugin, SlicerPlugin, AppPlugin, ProgressPlugin] if plugin_entry_points is None: plugin_entry_points = "octoprint.plugin" if plugin_disabled_list is None: all_plugin_settings = settings().get(["plugins"]) plugin_disabled_list = [] for key in all_plugin_settings: if "enabled" in all_plugin_settings[key] and not all_plugin_settings[key]: plugin_disabled_list.append(key) _instance = PluginManager(plugin_folders, plugin_types, plugin_entry_points, plugin_disabled_list=plugin_disabled_list) else: raise ValueError("Plugin Manager not initialized yet") return _instance
def get_user_for_authorization_header(header): if not settings().getBoolean(["accessControl", "trustBasicAuthentication"]): return None if header is None: return None if not header.startswith("Basic "): # we currently only support Basic Authentication return None header = header.replace('Basic ', '', 1) try: header = base64.b64decode(header) except TypeError: return None name, password = header.split(':', 1) if not octoprint.server.userManager.enabled: return None user = octoprint.server.userManager.findUser(userid=name) if settings().getBoolean(["accessControl", "checkBasicAuthenticationPassword"]) \ and not octoprint.server.userManager.checkPassword(name, password): # password check enabled and password don't match return None return user
def configureTimelapse(config=None, persist=False): global current if config is None: config = settings().get(["webcam", "timelapse"]) if current is not None: current.unload() type = config["type"] postRoll = 0 if "postRoll" in config and config["postRoll"] >= 0: postRoll = config["postRoll"] fps = 25 if "fps" in config and config["fps"] > 0: fps = config["fps"] if type is None or "off" == type: current = None elif "zchange" == type: current = ZTimelapse(post_roll=postRoll, fps=fps) elif "timed" == type: interval = 10 if "options" in config and "interval" in config["options"] and config["options"]["interval"] > 0: interval = config["options"]["interval"] current = TimedTimelapse(post_roll=postRoll, interval=interval, fps=fps) notifyCallbacks(current) if persist: settings().set(["webcam", "timelapse"], config) settings().save()
def cameraManager(): global _instance if _instance is None: if platform == "linux" or platform == "linux2": number_of_video_device = 0 #/dev/video``0´´ manager = settings().get(['camera', 'manager']) if manager == 'gstreamer': try: from astroprint.camera.v4l2.gstreamer import GStreamerManager _instance = GStreamerManager(number_of_video_device) except ImportError, ValueError: #another manager was selected or the gstreamer library is not present on this #system, in that case we pick a mjpeg manager _instance = None s = settings() s.set(['camera', 'manager'], 'mjpeg') s.save() if _instance is None: from astroprint.camera.v4l2.mjpeg import MjpegManager _instance = MjpegManager(number_of_video_device) elif platform == "darwin": from astroprint.camera.mac import CameraMacManager _instance = CameraMacManager()
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 default_view(): wizard = wizard_active(_templates[locale]) enable_accesscontrol = userManager.enabled accesscontrol_active = enable_accesscontrol and userManager.hasBeenCustomized() render_kwargs.update(dict( webcamStream=settings().get(["webcam", "stream"]), enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]), enableAccessControl=enable_accesscontrol, accessControlActive=accesscontrol_active, enableSdSupport=settings().get(["feature", "sdSupport"]), gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]), gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]), wizard=wizard, now=now, )) # no plugin took an interest, we'll use the default UI def make_default_ui(): r = make_response(render_template("index.jinja2", **render_kwargs)) if wizard: # if we have active wizard dialogs, set non caching headers r = util.flask.add_non_caching_response_headers(r) return r cached = get_cached_view("_default", make_default_ui) preemptively_cached = get_preemptively_cached_view("_default", cached, dict(), dict()) return preemptively_cached()
def __init__(self): self.settings = settings() self.sslManager = SslManager()
class Server(): def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, debug=False, allowRoot=False): self._configfile = configfile self._basedir = basedir self._host = host self._port = port self._debug = debug self._allowRoot = allowRoot def run(self): if not self._allowRoot: self._checkForRoot() global printer global gcodeManager global userManager global eventManager global loginManager global debug from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.web import Application, FallbackHandler debug = self._debug # first initialize the settings singleton and make sure it uses given configfile and basedir if available self._initSettings(self._configfile, self._basedir) # then initialize logging self._initLogging(self._debug) logger = logging.getLogger(__name__) eventManager = events.eventManager() gcodeManager = gcodefiles.GcodeManager() printer = Printer(gcodeManager) # configure timelapse octoprint.timelapse.configureTimelapse() # setup system and gcode command triggers events.SystemCommandTrigger(printer) events.GcodeCommandTrigger(printer) if self._debug: events.DebugEventListener() if settings().getBoolean(["accessControl", "enabled"]): userManagerName = settings().get(["accessControl", "userManager"]) try: clazz = util.getClass(userManagerName) userManager = clazz() except AttributeError, e: logger.exception( "Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName) app.wsgi_app = ReverseProxied(app.wsgi_app) app.secret_key = "k3PuVYgtxNm8DXKKTw2nWmFQQun9qceV" loginManager = LoginManager() loginManager.session_protection = "strong" loginManager.user_callback = load_user if userManager is None: loginManager.anonymous_user = users.DummyUser principals.identity_loaders.appendleft(users.dummy_identity_loader) loginManager.init_app(app) if self._host is None: self._host = settings().get(["server", "host"]) if self._port is None: self._port = settings().getInt(["server", "port"]) logger.info("Listening on http://%s:%d" % (self._host, self._port)) app.debug = self._debug from octoprint.server.ajax import ajax from octoprint.server.api import api app.register_blueprint(ajax, url_prefix="/ajax") app.register_blueprint(api, url_prefix="/api") self._router = SockJSRouter(self._createSocketConnection, "/sockjs") self._tornado_app = Application( self._router.urls + [(r"/downloads/timelapse/([^/]*\.mpg)", LargeResponseHandler, { "path": settings().getBaseFolder("timelapse"), "as_attachment": True }), (r"/downloads/gcode/([^/]*\.(gco|gcode))", LargeResponseHandler, { "path": settings().getBaseFolder("uploads"), "as_attachment": True }), (r".*", FallbackHandler, { "fallback": WSGIContainer(app.wsgi_app) })]) self._server = HTTPServer(self._tornado_app) self._server.listen(self._port, address=self._host) eventManager.fire("Startup") if settings().getBoolean(["serial", "autoconnect"]): (port, baudrate) = settings().get( ["serial", "port"]), settings().getInt(["serial", "baudrate"]) connectionOptions = getConnectionOptions() if port in connectionOptions["ports"]: printer.connect(port, baudrate) try: IOLoop.instance().start() except: logger.fatal( "Now that is embarrassing... Something really really went wrong here. Please report this including the stacktrace below in OctoPrint's bugtracker. Thanks!" ) logger.exception("Stacktrace follows:")
def _initLogging(self, debug): config = { "version": 1, "formatters": { "simple": { "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" } }, "handlers": { "console": { "class": "logging.StreamHandler", "level": "DEBUG", "formatter": "simple", "stream": "ext://sys.stdout" }, "file": { "class": "logging.handlers.TimedRotatingFileHandler", "level": "DEBUG", "formatter": "simple", "when": "D", "backupCount": "1", "filename": os.path.join(settings().getBaseFolder("logs"), "octoprint.log") }, "serialFile": { "class": "logging.handlers.RotatingFileHandler", "level": "DEBUG", "formatter": "simple", "maxBytes": 2 * 1024 * 1024, # let's limit the serial log to 2MB in size "filename": os.path.join(settings().getBaseFolder("logs"), "serial.log") } }, "loggers": { #"octoprint.timelapse": { # "level": "DEBUG" #}, #"octoprint.events": { # "level": "DEBUG" #}, "SERIAL": { "level": "CRITICAL", "handlers": ["serialFile"], "propagate": False } }, "root": { "level": "INFO", "handlers": ["console", "file"] } } if debug: config["root"]["level"] = "DEBUG" logging.config.dictConfig(config) if settings().getBoolean(["serial", "log"]): # enable debug logging to serial.log logging.getLogger("SERIAL").setLevel(logging.DEBUG) logging.getLogger("SERIAL").debug("Enabling serial logging")
def getInstalledLanguagePacks(): translation_folder = settings().getBaseFolder("translations") if not os.path.exists(translation_folder): return jsonify(language_packs=dict(_core=[])) core_packs = [] plugin_packs = defaultdict( lambda: dict(identifier=None, display=None, languages=[])) for entry in scandir(translation_folder): if not entry.is_dir(): continue def load_meta(path, locale): meta = dict() meta_path = os.path.join(path, "meta.yaml") if os.path.isfile(meta_path): import yaml try: with open(meta_path) as f: meta = yaml.safe_load(f) except: pass else: import datetime if "last_update" in meta and isinstance( meta["last_update"], datetime.datetime): meta["last_update"] = ( meta["last_update"] - datetime.datetime(1970, 1, 1)).total_seconds() l = Locale.parse(locale) meta["locale"] = locale meta["locale_display"] = l.display_name meta["locale_english"] = l.english_name return meta if entry.name == "_plugins": for plugin_entry in scandir(entry.path): if not plugin_entry.is_dir(): continue if not plugin_entry.name in plugin_manager().plugins: continue plugin_info = plugin_manager().plugins[plugin_entry.name] plugin_packs[ plugin_entry.name]["identifier"] = plugin_entry.name plugin_packs[plugin_entry.name]["display"] = plugin_info.name for language_entry in scandir(plugin_entry.path): plugin_packs[plugin_entry.name]["languages"].append( load_meta(language_entry.path, language_entry.name)) else: core_packs.append(load_meta(entry.path, entry.name)) result = dict( _core=dict(identifier="_core", display="Core", languages=core_packs)) result.update(plugin_packs) return jsonify(language_packs=result)
def setSettings(): if "application/json" in request.headers["Content-Type"]: data = request.json s = settings() if "api" in data.keys(): if "enabled" in data["api"].keys(): s.set(["api", "enabled"], data["api"]["enabled"]) if "key" in data["api"].keys(): s.set(["api", "key"], data["api"]["key"], True) if "appearance" in data.keys(): if "name" in data["appearance"].keys(): s.set(["appearance", "name"], data["appearance"]["name"]) if "color" in data["appearance"].keys(): s.set(["appearance", "color"], data["appearance"]["color"]) if "printer" in data.keys(): if "movementSpeedX" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "x"], data["printer"]["movementSpeedX"]) if "movementSpeedY" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "y"], data["printer"]["movementSpeedY"]) if "movementSpeedZ" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "z"], data["printer"]["movementSpeedZ"]) if "movementSpeedE" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "e"], data["printer"]["movementSpeedE"]) if "webcam" in data.keys(): if "streamUrl" in data["webcam"].keys(): s.set(["webcam", "stream"], data["webcam"]["streamUrl"]) if "snapshotUrl" in data["webcam"].keys(): s.set(["webcam", "snapshot"], data["webcam"]["snapshotUrl"]) if "ffmpegPath" in data["webcam"].keys(): s.set(["webcam", "ffmpeg"], data["webcam"]["ffmpegPath"]) if "bitrate" in data["webcam"].keys(): s.set(["webcam", "bitrate"], data["webcam"]["bitrate"]) if "watermark" in data["webcam"].keys(): s.setBoolean(["webcam", "watermark"], data["webcam"]["watermark"]) if "flipH" in data["webcam"].keys(): s.setBoolean(["webcam", "flipH"], data["webcam"]["flipH"]) if "flipV" in data["webcam"].keys(): s.setBoolean(["webcam", "flipV"], data["webcam"]["flipV"]) if "feature" in data.keys(): if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"]) if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"]) if "alwaysSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "alwaysSendChecksum"], data["feature"]["alwaysSendChecksum"]) if "sdSupport" in data["feature"].keys(): s.setBoolean(["feature", "sdSupport"], data["feature"]["sdSupport"]) if "serial" in data.keys(): if "autoconnect" in data["serial"].keys(): s.setBoolean(["serial", "autoconnect"], data["serial"]["autoconnect"]) if "port" in data["serial"].keys(): s.set(["serial", "port"], data["serial"]["port"]) if "baudrate" in data["serial"].keys(): s.setInt(["serial", "baudrate"], data["serial"]["baudrate"]) if "timeoutConnection" in data["serial"].keys(): s.setFloat(["serial", "timeout", "connection"], data["serial"]["timeoutConnection"]) if "timeoutDetection" in data["serial"].keys(): s.setFloat(["serial", "timeout", "detection"], data["serial"]["timeoutDetection"]) if "timeoutCommunication" in data["serial"].keys(): s.setFloat(["serial", "timeout", "communication"], data["serial"]["timeoutCommunication"]) oldLog = s.getBoolean(["serial", "log"]) if "log" in data["serial"].keys(): s.setBoolean(["serial", "log"], data["serial"]["log"]) if oldLog and not s.getBoolean(["serial", "log"]): # disable debug logging to serial.log logging.getLogger("SERIAL").debug("Disabling serial logging") logging.getLogger("SERIAL").setLevel(logging.CRITICAL) elif not oldLog and s.getBoolean(["serial", "log"]): # enable debug logging to serial.log logging.getLogger("SERIAL").setLevel(logging.DEBUG) logging.getLogger("SERIAL").debug("Enabling serial logging") if "folder" in data.keys(): if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"]) if "timelapse" in data["folder"].keys(): s.setBaseFolder("timelapse", data["folder"]["timelapse"]) if "timelapseTmp" in data["folder"].keys(): s.setBaseFolder("timelapse_tmp", data["folder"]["timelapseTmp"]) if "logs" in data["folder"].keys(): s.setBaseFolder("logs", data["folder"]["logs"]) if "temperature" in data.keys(): if "profiles" in data["temperature"].keys(): s.set(["temperature", "profiles"], data["temperature"]["profiles"]) if "system" in data.keys(): if "actions" in data["system"].keys(): s.set(["system", "actions"], data["system"]["actions"]) if "events" in data["system"].keys(): s.set(["system", "events"], data["system"]["events"]) s.save() return getSettings()
def is_sd_ready(self): if not settings().getBoolean(["feature", "sdSupport" ]) or self._comm is None: return False else: return self._comm.isSdReady()
def __init__(self, fileManager, analysisQueue, printerProfileManager): from collections import deque self._logger = logging.getLogger(__name__) self._analysisQueue = analysisQueue self._fileManager = fileManager self._printerProfileManager = printerProfileManager # state # TODO do we really need to hold the temperature here? self._temp = None self._bedTemp = None self._targetTemp = None self._targetBedTemp = None self._temps = TemperatureHistory( cutoff=settings().getInt(["temperature", "cutoff"]) * 60) self._tempBacklog = [] self._latestMessage = None self._messages = deque([], 300) self._messageBacklog = [] self._latestLog = None self._log = deque([], 300) self._logBacklog = [] self._state = None self._currentZ = None self._progress = None self._printTime = None self._printTimeLeft = None self._printAfterSelect = False # sd handling self._sdPrinting = False self._sdStreaming = False self._sdFilelistAvailable = threading.Event() self._streamingFinishedCallback = None self._selectedFile = None self._timeEstimationData = None # comm self._comm = None # callbacks self._callbacks = [] # progress plugins self._lastProgressReport = None self._progressPlugins = plugin_manager().get_implementations( ProgressPlugin) self._stateMonitor = StateMonitor( interval=0.5, on_update=self._sendCurrentDataCallbacks, on_add_temperature=self._sendAddTemperatureCallbacks, on_add_log=self._sendAddLogCallbacks, on_add_message=self._sendAddMessageCallbacks) self._stateMonitor.reset(state={ "text": self.get_state_string(), "flags": self._getStateFlags() }, job_data={ "file": { "name": None, "size": None, "origin": None, "date": None }, "estimatedPrintTime": None, "lastPrintTime": None, "filament": { "length": None, "volume": None } }, progress={ "completion": None, "filepos": None, "printTime": None, "printTimeLeft": None }, current_z=None) eventManager().subscribe(Events.METADATA_ANALYSIS_FINISHED, self._on_event_MetadataAnalysisFinished) eventManager().subscribe(Events.METADATA_STATISTICS_UPDATED, self._on_event_MetadataStatisticsUpdated)
def connectionCommand(): valid_commands = {"connect": [], "disconnect": []} command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response if command == "connect": connection_options = get_connection_options() port = None port1 = None projector = None baudrate = None printerProfile = None if "port" in data.keys(): port = data["port"] if port not in connection_options["ports"]: return make_response("Invalid port: %s" % port, 400) if "baudrate" in data.keys(): baudrate = data["baudrate"] if baudrate not in connection_options["baudrates"]: return make_response("Invalid baudrate: %d" % baudrate, 400) if "printerProfile" in data.keys(): printerProfile = data["printerProfile"] if not printerProfileManager.exists(printerProfile): return make_response( "Invalid printer profile: %s" % printerProfile, 400) if "save" in data.keys() and data["save"]: port1 = data["port1"] projector = data["projector"] settings().set(["serial", "port"], port) settings().set(["serial", "port1"], port1) settings().set(["serial", "projector"], projector) settings().setInt(["serial", "baudrate"], baudrate) printerProfileManager.set_default(printerProfile) if "autoconnect" in data.keys(): settings().setBoolean(["serial", "autoconnect"], data["autoconnect"]) settings().save() printer.connect(port=port, baudrate=baudrate, profile=printerProfile) elif command == "disconnect": printer.disconnect() return NO_CONTENT
def _load(self, gcodeFile, printer_profile, throttle=None): filePos = 0 readBytes = 0 pos = Vector3D(0.0, 0.0, 0.0) posOffset = Vector3D(0.0, 0.0, 0.0) currentE = [0.0] totalExtrusion = [0.0] maxExtrusion = [0.0] currentExtruder = 0 totalMoveTimeMinute = 0.0 absoluteE = True scale = 1.0 posAbs = True fwretractTime = 0 fwretractDist = 0 fwrecoverTime = 0 feedrate = min(printer_profile["axes"]["x"]["speed"], printer_profile["axes"]["y"]["speed"]) if feedrate == 0: # some somewhat sane default if axes speeds are insane... feedrate = 2000 offsets = printer_profile["extruder"]["offsets"] for line in gcodeFile: if self._abort: raise AnalysisAborted() filePos += 1 readBytes += len(line) if isinstance(gcodeFile, (file)): percentage = float(readBytes) / float(self._fileSize) elif isinstance(gcodeFile, (list)): percentage = float(filePos) / float(len(gcodeFile)) else: percentage = None try: if self.progressCallback is not None and (filePos % 1000 == 0) and percentage is not None: self.progressCallback(percentage) except: pass if ';' in line: comment = line[line.find(';')+1:].strip() if comment.startswith("filament_diameter"): filamentValue = comment.split("=", 1)[1].strip() try: self._filamentDiameter = float(filamentValue) except ValueError: try: self._filamentDiameter = float(filamentValue.split(",")[0].strip()) except ValueError: self._filamentDiameter = 0.0 elif comment.startswith("CURA_PROFILE_STRING") or comment.startswith("CURA_OCTO_PROFILE_STRING"): if comment.startswith("CURA_PROFILE_STRING"): prefix = "CURA_PROFILE_STRING:" else: prefix = "CURA_OCTO_PROFILE_STRING:" curaOptions = self._parseCuraProfileString(comment, prefix) if "filament_diameter" in curaOptions: try: self._filamentDiameter = float(curaOptions["filament_diameter"]) except: self._filamentDiameter = 0.0 line = line[0:line.find(';')] G = getCodeInt(line, 'G') M = getCodeInt(line, 'M') T = getCodeInt(line, 'T') if G is not None: if G == 0 or G == 1: #Move x = getCodeFloat(line, 'X') y = getCodeFloat(line, 'Y') z = getCodeFloat(line, 'Z') e = getCodeFloat(line, 'E') f = getCodeFloat(line, 'F') oldPos = pos newPos = Vector3D(x if x is not None else pos.x, y if y is not None else pos.y, z if z is not None else pos.z) if posAbs: pos = newPos * scale + posOffset else: pos += newPos * scale if f is not None and f != 0: feedrate = f if e is not None: if absoluteE: # make sure e is relative e -= currentE[currentExtruder] # If move includes extrusion, calculate new min/max coordinates of model if e > 0.0: # extrusion -> relevant for print area & dimensions self._minMax.record(pos) totalExtrusion[currentExtruder] += e currentE[currentExtruder] += e maxExtrusion[currentExtruder] = max(maxExtrusion[currentExtruder], totalExtrusion[currentExtruder]) else: e = 0.0 # move time in x, y, z, will be 0 if no movement happened moveTimeXYZ = abs((oldPos - pos).length / feedrate) # time needed for extruding, will be 0 if no extrusion happened extrudeTime = abs(e / feedrate) # time to add is maximum of both totalMoveTimeMinute += max(moveTimeXYZ, extrudeTime) elif G == 4: #Delay S = getCodeFloat(line, 'S') if S is not None: totalMoveTimeMinute += S / 60.0 P = getCodeFloat(line, 'P') if P is not None: totalMoveTimeMinute += P / 60.0 / 1000.0 elif G == 10: #Firmware retract totalMoveTimeMinute += fwretractTime elif G == 11: #Firmware retract recover totalMoveTimeMinute += fwrecoverTime elif G == 20: #Units are inches scale = 25.4 elif G == 21: #Units are mm scale = 1.0 elif G == 28: #Home x = getCodeFloat(line, 'X') y = getCodeFloat(line, 'Y') z = getCodeFloat(line, 'Z') center = Vector3D(0.0, 0.0, 0.0) if x is None and y is None and z is None: pos = center else: pos = Vector3D(pos) if x is not None: pos.x = center.x if y is not None: pos.y = center.y if z is not None: pos.z = center.z elif G == 90: #Absolute position posAbs = True elif G == 91: #Relative position posAbs = False elif G == 92: x = getCodeFloat(line, 'X') y = getCodeFloat(line, 'Y') z = getCodeFloat(line, 'Z') e = getCodeFloat(line, 'E') if e is not None: currentE[currentExtruder] = e if x is not None: posOffset.x = pos.x - x if y is not None: posOffset.y = pos.y - y if z is not None: posOffset.z = pos.z - z elif M is not None: if M == 82: #Absolute E absoluteE = True elif M == 83: #Relative E absoluteE = False elif M == 207 or M == 208: #Firmware retract settings s = getCodeFloat(line, 'S') f = getCodeFloat(line, 'F') if s is not None and f is not None: if M == 207: fwretractTime = s / f fwretractDist = s else: fwrecoverTime = (fwretractDist + s) / f elif T is not None: if T > settings().getInt(["gcodeAnalysis", "maxExtruders"]): self._logger.warn("GCODE tried to select tool %d, that looks wrong, ignoring for GCODE analysis" % T) else: posOffset.x -= offsets[currentExtruder][0] if currentExtruder < len(offsets) else 0 posOffset.y -= offsets[currentExtruder][1] if currentExtruder < len(offsets) else 0 currentExtruder = T posOffset.x += offsets[currentExtruder][0] if currentExtruder < len(offsets) else 0 posOffset.y += offsets[currentExtruder][1] if currentExtruder < len(offsets) else 0 if len(currentE) <= currentExtruder: for i in range(len(currentE), currentExtruder + 1): currentE.append(0.0) if len(maxExtrusion) <= currentExtruder: for i in range(len(maxExtrusion), currentExtruder + 1): maxExtrusion.append(0.0) if len(totalExtrusion) <= currentExtruder: for i in range(len(totalExtrusion), currentExtruder + 1): totalExtrusion.append(0.0) if throttle is not None: throttle() if self.progressCallback is not None: self.progressCallback(100.0) self.extrusionAmount = maxExtrusion self.extrusionVolume = [0] * len(maxExtrusion) for i in range(len(maxExtrusion)): radius = self._filamentDiameter / 2 self.extrusionVolume[i] = (self.extrusionAmount[i] * (math.pi * radius * radius)) / 1000 self.totalMoveTimeMinute = totalMoveTimeMinute
def firstRunSetup(): global userManager if not settings().getBoolean(["server", "firstRun"]): abort(403) if "ac" in request.values.keys() and request.values["ac"] in valid_boolean_trues and \ "user" in request.values.keys() and "pass1" in request.values.keys() and \ "pass2" in request.values.keys() and request.values["pass1"] == request.values["pass2"]: # configure access control settings().setBoolean(["accessControl", "enabled"], True) userManager.addUser(request.values["user"], request.values["pass1"], True, ["user", "admin"]) settings().setBoolean(["server", "firstRun"], False) elif "ac" in request.values.keys( ) and not request.values["ac"] in valid_boolean_trues: # disable access control settings().setBoolean(["accessControl", "enabled"], False) settings().setBoolean(["server", "firstRun"], False) userManager = None loginManager.anonymous_user = users.DummyUser principals.identity_loaders.appendleft(users.dummy_identity_loader) settings().save() return jsonify(SUCCESS)
def downloadTimelapse(filename): if util.isAllowedFile(filename, set(["mpg"])): return send_from_directory(settings().getBaseFolder("timelapse"), filename, as_attachment=True)
def getSettings(): s = settings() [movementSpeedX, movementSpeedY, movementSpeedZ, movementSpeedE ] = s.get(["printerParameters", "movementSpeed", ["x", "y", "z", "e"]]) connectionOptions = getConnectionOptions() return jsonify({ "api": { "enabled": s.getBoolean(["api", "enabled"]), "key": s.get(["api", "key"]) }, "appearance": { "name": s.get(["appearance", "name"]), "color": s.get(["appearance", "color"]) }, "printer": { "movementSpeedX": movementSpeedX, "movementSpeedY": movementSpeedY, "movementSpeedZ": movementSpeedZ, "movementSpeedE": movementSpeedE, }, "webcam": { "streamUrl": s.get(["webcam", "stream"]), "snapshotUrl": s.get(["webcam", "snapshot"]), "ffmpegPath": s.get(["webcam", "ffmpeg"]), "bitrate": s.get(["webcam", "bitrate"]), "watermark": s.getBoolean(["webcam", "watermark"]), "flipH": s.getBoolean(["webcam", "flipH"]), "flipV": s.getBoolean(["webcam", "flipV"]) }, "feature": { "gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]), "waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]), "alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]), "sdSupport": s.getBoolean(["feature", "sdSupport"]) }, "serial": { "port": connectionOptions["portPreference"], "baudrate": connectionOptions["baudratePreference"], "portOptions": connectionOptions["ports"], "baudrateOptions": connectionOptions["baudrates"], "autoconnect": s.getBoolean(["serial", "autoconnect"]), "timeoutConnection": s.getFloat(["serial", "timeout", "connection"]), "timeoutDetection": s.getFloat(["serial", "timeout", "detection"]), "timeoutCommunication": s.getFloat(["serial", "timeout", "communication"]), "log": s.getBoolean(["serial", "log"]) }, "folder": { "uploads": s.getBaseFolder("uploads"), "timelapse": s.getBaseFolder("timelapse"), "timelapseTmp": s.getBaseFolder("timelapse_tmp"), "logs": s.getBaseFolder("logs") }, "temperature": { "profiles": s.get(["temperature", "profiles"]) }, "system": { "actions": s.get(["system", "actions"]), "events": s.get(["system", "events"]) } })
def getCustomControls(): customControls = settings().get(["controls"]) return jsonify(controls=customControls)
def readGcodeFile(filename): return send_from_directory(settings().getBaseFolder("uploads"), filename, as_attachment=True)
class Server(): def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, debug=False, allowRoot=False): self._configfile = configfile self._basedir = basedir self._host = host self._port = port self._debug = debug self._allowRoot = allowRoot def run(self): if not self._allowRoot: self._checkForRoot() # Global as I can't work out a way to get it into PrinterStateConnection global printer global gcodeManager global userManager global eventManager global loginManager from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.web import Application, FallbackHandler # first initialize the settings singleton and make sure it uses given configfile and basedir if available self._initSettings(self._configfile, self._basedir) # then initialize logging self._initLogging(self._debug) logger = logging.getLogger(__name__) eventManager = events.eventManager() gcodeManager = gcodefiles.GcodeManager() printer = Printer(gcodeManager) # setup system and gcode command triggers events.SystemCommandTrigger(printer) events.GcodeCommandTrigger(printer) if self._debug: events.DebugEventListener() if settings().getBoolean(["accessControl", "enabled"]): userManagerName = settings().get(["accessControl", "userManager"]) try: clazz = util.getClass(userManagerName) userManager = clazz() except AttributeError, e: logger.exception( "Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName) app.secret_key = "k3PuVYgtxNm8DXKKTw2nWmFQQun9qceV" loginManager = LoginManager() loginManager.session_protection = "strong" loginManager.user_callback = load_user if userManager is None: loginManager.anonymous_user = users.DummyUser principals.identity_loaders.appendleft(users.dummy_identity_loader) loginManager.init_app(app) if self._host is None: self._host = settings().get(["server", "host"]) if self._port is None: self._port = settings().getInt(["server", "port"]) logger.info("Listening on http://%s:%d" % (self._host, self._port)) app.debug = self._debug self._router = tornadio2.TornadioRouter(self._createSocketConnection) self._tornado_app = Application(self._router.urls + [(".*", FallbackHandler, { "fallback": WSGIContainer(app) })]) self._server = HTTPServer(self._tornado_app) self._server.listen(self._port, address=self._host) eventManager.fire("Startup") if settings().getBoolean(["serial", "autoconnect"]): (port, baudrate) = settings().get( ["serial", "port"]), settings().getInt(["serial", "baudrate"]) connectionOptions = getConnectionOptions() if port in connectionOptions["ports"]: printer.connect(port, baudrate) IOLoop.instance().start()
def decorated_view(*args, **kwargs): if settings().getBoolean([ "server", "firstRun" ]) and (userManager is None or not userManager.hasBeenCustomized()): return make_response("OctoPrint isn't setup yet", 403) return login_required(func)(*args, **kwargs)
def _process_templates(): first_run = settings().getBoolean(["server", "firstRun"]) ##~~ prepare templates templates = defaultdict(lambda: dict(order=[], entries=dict())) # rules for transforming template configs to template entries template_rules = dict( navbar=dict(div=lambda x: "navbar_plugin_" + x, template=lambda x: x + "_navbar.jinja2", to_entry=lambda data: data), sidebar=dict(div=lambda x: "sidebar_plugin_" + x, template=lambda x: x + "_sidebar.jinja2", to_entry=lambda data: (data["name"], data)), tab=dict(div=lambda x: "tab_plugin_" + x, template=lambda x: x + "_tab.jinja2", to_entry=lambda data: (data["name"], data)), settings=dict(div=lambda x: "settings_plugin_" + x, template=lambda x: x + "_settings.jinja2", to_entry=lambda data: (data["name"], data)), usersettings=dict(div=lambda x: "usersettings_plugin_" + x, template=lambda x: x + "_usersettings.jinja2", to_entry=lambda data: (data["name"], data)), wizard=dict(div=lambda x: "wizard_plugin_" + x, template=lambda x: x + "_wizard.jinja2", to_entry=lambda data: (data["name"], data)), about=dict(div=lambda x: "about_plugin_" + x, template=lambda x: x + "_about.jinja2", to_entry=lambda data: (data["name"], data)), generic=dict(template=lambda x: x + ".jinja2", to_entry=lambda data: data)) # sorting orders template_sorting = dict( navbar=dict(add="prepend", key=None), sidebar=dict(add="append", key="name"), tab=dict(add="append", key="name"), settings=dict( add="custom_append", key="name", custom_add_entries=lambda missing: dict(section_plugins=(gettext( "Plugins"), None)), custom_add_order=lambda missing: ["section_plugins"] + missing), usersettings=dict(add="append", key="name"), wizard=dict(add="append", key="name", key_extractor=lambda d, k: "0:{}".format(d[0]) if "mandatory" in d[1] and d[1]["mandatory"] else "1:{}". format(d[0])), about=dict(add="append", key="name"), generic=dict(add="append", key=None)) hooks = pluginManager.get_hooks("octoprint.ui.web.templatetypes") for name, hook in hooks.items(): try: result = hook(dict(template_sorting), dict(template_rules)) except: _logger.exception( "Error while retrieving custom template type definitions from plugin {name}" .format(**locals())) else: if not isinstance(result, list): continue for entry in result: if not isinstance(entry, tuple) or not len(entry) == 3: continue key, order, rule = entry # order defaults if "add" not in order: order["add"] = "prepend" if "key" not in order: order["key"] = "name" # rule defaults if "div" not in rule: # default div name: <hook plugin>_<template_key>_plugin_<plugin> div = "{name}_{key}_plugin_".format(**locals()) rule["div"] = lambda x: div + x if "template" not in rule: # default template name: <plugin>_plugin_<hook plugin>_<template key>.jinja2 template = "_plugin_{name}_{key}.jinja2".format(**locals()) rule["template"] = lambda x: x + template if "to_entry" not in rule: # default to_entry assumes existing "name" property to be used as label for 2-tuple entry data structure (<name>, <properties>) rule["to_entry"] = lambda data: (data["name"], data) template_rules["plugin_" + name + "_" + key] = rule template_sorting["plugin_" + name + "_" + key] = order template_types = template_rules.keys() # navbar templates["navbar"]["entries"] = dict( fabapp=dict(template="navbar/fabapp.jinja2", _div="navbar_fabapp"), settings=dict(template="navbar/settings.jinja2", _div="navbar_settings", styles=["display: none"], data_bind="visible: loginState.isAdmin"), systemmenu=dict(template="navbar/systemmenu.jinja2", _div="navbar_systemmenu", styles=["display: none"], classes=["dropdown"], data_bind="visible: loginState.isAdmin", custom_bindings=False), login=dict(template="navbar/login.jinja2", _div="navbar_login", classes=["dropdown"], custom_bindings=False), ) # sidebar templates["sidebar"]["entries"] = dict( connection=(gettext("Connection"), dict(template="sidebar/connection.jinja2", _div="connection", icon="signal", styles_wrapper=["display: none"], data_bind="visible: loginState.isUser")), state=(gettext("State"), dict(template="sidebar/state.jinja2", _div="state", icon="info-sign")), files=(gettext("Files"), dict(template="sidebar/files.jinja2", _div="files", icon="list", classes_content=["overflow_visible"], template_header="sidebar/files_header.jinja2"))) # tabs templates["tab"]["entries"] = dict( temperature=(gettext("Temperature"), dict(template="tabs/temperature.jinja2", _div="temp")), control=(gettext("Control"), dict(template="tabs/control.jinja2", _div="control")), gcodeviewer=(gettext("GCode Viewer"), dict(template="tabs/gcodeviewer.jinja2", _div="gcode")), terminal=(gettext("Terminal"), dict(template="tabs/terminal.jinja2", _div="term")), timelapse=(gettext("Timelapse"), dict(template="tabs/timelapse.jinja2", _div="timelapse"))) # settings dialog templates["settings"]["entries"] = dict( section_printer=(gettext("Printer"), None), serial=(gettext("Serial Connection"), dict(template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection", custom_bindings=False)), printerprofiles=( gettext("Printer Profiles"), dict(template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles", custom_bindings=False)), temperatures=(gettext("Temperatures"), dict(template="dialogs/settings/temperatures.jinja2", _div="settings_temperature", custom_bindings=False)), terminalfilters=( gettext("Terminal Filters"), dict(template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters", custom_bindings=False)), gcodescripts=(gettext("GCODE Scripts"), dict(template="dialogs/settings/gcodescripts.jinja2", _div="settings_gcodeScripts", custom_bindings=False)), section_features=(gettext("Features"), None), features=(gettext("Features"), dict(template="dialogs/settings/features.jinja2", _div="settings_features", custom_bindings=False)), webcam=(gettext("Webcam & Timelapse"), dict(template="dialogs/settings/webcam.jinja2", _div="settings_webcam", custom_bindings=False)), gcodevisualizer=( gettext("GCODE Visualizer"), dict(template="dialogs/settings/gcodevisualizer.jinja2", _div="settings_gcodegcodevisualizer", custom_bindings=False)), api=(gettext("API"), dict(template="dialogs/settings/api.jinja2", _div="settings_api", custom_bindings=False)), section_octoprint=(gettext("OctoPrint"), None), accesscontrol=(gettext("Access Control"), dict(template="dialogs/settings/accesscontrol.jinja2", _div="settings_users", custom_bindings=False)), folders=(gettext("Folders"), dict(template="dialogs/settings/folders.jinja2", _div="settings_folders", custom_bindings=False)), appearance=(gettext("Appearance"), dict(template="dialogs/settings/appearance.jinja2", _div="settings_appearance", custom_bindings=False)), logs=(gettext("Logs"), dict(template="dialogs/settings/logs.jinja2", _div="settings_logs")), server=(gettext("Server"), dict(template="dialogs/settings/server.jinja2", _div="settings_server", custom_bindings=False)), ) # user settings dialog templates["usersettings"]["entries"] = dict( access=(gettext("Access"), dict(template="dialogs/usersettings/access.jinja2", _div="usersettings_access", custom_bindings=False)), interface=(gettext("Interface"), dict(template="dialogs/usersettings/interface.jinja2", _div="usersettings_interface", custom_bindings=False)), ) # wizard if first_run: def custom_insert_order(existing, missing): if "firstrunstart" in missing: missing.remove("firstrunstart") if "firstrunend" in missing: missing.remove("firstrunend") return ["firstrunstart"] + existing + missing + ["firstrunend"] template_sorting["wizard"].update( dict(add="custom_insert", custom_insert_entries=lambda missing: dict(), custom_insert_order=custom_insert_order)) templates["wizard"]["entries"] = dict( firstrunstart=(gettext("Start"), dict( template="dialogs/wizard/firstrun_start.jinja2", _div="wizard_firstrun_start")), firstrunend=(gettext("Finish"), dict(template="dialogs/wizard/firstrun_end.jinja2", _div="wizard_firstrun_end")), ) # about dialog templates["about"]["entries"] = dict( about=("About OctoPrint", dict(template="dialogs/about/about.jinja2", _div="about_about", custom_bindings=False)), license=("OctoPrint License", dict(template="dialogs/about/license.jinja2", _div="about_license", custom_bindings=False)), thirdparty=("Third Party Licenses", dict(template="dialogs/about/thirdparty.jinja2", _div="about_thirdparty", custom_bindings=False)), authors=("Authors", dict(template="dialogs/about/authors.jinja2", _div="about_authors", custom_bindings=False)), changelog=("Changelog", dict(template="dialogs/about/changelog.jinja2", _div="about_changelog", custom_bindings=False)), supporters=("Supporters", dict(template="dialogs/about/supporters.jinja2", _div="about_sponsors", custom_bindings=False))) # extract data from template plugins template_plugins = pluginManager.get_implementations( octoprint.plugin.TemplatePlugin) plugin_vars = dict() plugin_names = set() seen_wizards = settings().get(["server", "seenWizards" ]) if not first_run else dict() for implementation in template_plugins: name = implementation._identifier plugin_names.add(name) wizard_required = False wizard_ignored = False try: vars = implementation.get_template_vars() configs = implementation.get_template_configs() if isinstance(implementation, octoprint.plugin.WizardPlugin): wizard_required = implementation.is_wizard_required() wizard_ignored = octoprint.plugin.WizardPlugin.is_wizard_ignored( seen_wizards, implementation) except: _logger.exception( "Error while retrieving template data for plugin {}, ignoring it" .format(name)) continue if not isinstance(vars, dict): vars = dict() if not isinstance(configs, (list, tuple)): configs = [] for var_name, var_value in vars.items(): plugin_vars["plugin_" + name + "_" + var_name] = var_value includes = _process_template_configs(name, implementation, configs, template_rules) if not wizard_required or wizard_ignored: includes["wizard"] = list() for t in template_types: for include in includes[t]: if t == "navbar" or t == "generic": data = include else: data = include[1] key = data["_key"] if "replaces" in data: key = data["replaces"] templates[t]["entries"][key] = include #~~ order internal templates and plugins # make sure that # 1) we only have keys in our ordered list that we have entries for and # 2) we have all entries located somewhere within the order for t in template_types: default_order = settings().get( ["appearance", "components", "order", t], merged=True, config=dict()) or [] configured_order = settings().get( ["appearance", "components", "order", t], merged=True) or [] configured_disabled = settings().get( ["appearance", "components", "disabled", t]) or [] # first create the ordered list of all component ids according to the configured order templates[t]["order"] = [ x for x in configured_order if x in templates[t]["entries"] and not x in configured_disabled ] # now append the entries from the default order that are not already in there templates[t]["order"] += [ x for x in default_order if not x in templates[t]["order"] and x in templates[t]["entries"] and not x in configured_disabled ] all_ordered = set(templates[t]["order"]) all_disabled = set(configured_disabled) # check if anything is missing, if not we are done here missing_in_order = set(templates[t]["entries"].keys()).difference( all_ordered).difference(all_disabled) if len(missing_in_order) == 0: continue # works with entries that are dicts and entries that are 2-tuples with the # entry data at index 1 def config_extractor(item, key, default_value=None): if isinstance(item, dict) and key in item: return item[key] if key in item else default_value elif isinstance(item, tuple) and len(item) > 1 and isinstance( item[1], dict) and key in item[1]: return item[1][key] if key in item[1] else default_value return default_value # finally add anything that's not included in our order yet if template_sorting[t]["key"] is not None: # we'll use our config extractor as default key extractor extractor = config_extractor # if template type provides custom extractor, make sure its exceptions are handled if "key_extractor" in template_sorting[t] and callable( template_sorting[t]["key_extractor"]): def create_safe_extractor(extractor): def f(x, k): try: return extractor(x, k) except: _logger.exception( "Error while extracting sorting keys for template {}" .format(t)) return None return f extractor = create_safe_extractor( template_sorting[t]["key_extractor"]) sort_key = template_sorting[t]["key"] def key_func(x): config = templates[t]["entries"][x] entry_order = config_extractor(config, "order", default_value=None) return entry_order is None, entry_order, extractor( config, sort_key) sorted_missing = sorted(missing_in_order, key=key_func) else: def key_func(x): config = templates[t]["entries"][x] entry_order = config_extractor(config, "order", default_value=None) return entry_order is None, entry_order sorted_missing = sorted(missing_in_order, key=key_func) if template_sorting[t]["add"] == "prepend": templates[t]["order"] = sorted_missing + templates[t]["order"] elif template_sorting[t]["add"] == "append": templates[t]["order"] += sorted_missing elif template_sorting[t][ "add"] == "custom_prepend" and "custom_add_entries" in template_sorting[ t] and "custom_add_order" in template_sorting[t]: templates[t]["entries"].update( template_sorting[t]["custom_add_entries"](sorted_missing)) templates[t]["order"] = template_sorting[t]["custom_add_order"]( sorted_missing) + templates[t]["order"] elif template_sorting[t][ "add"] == "custom_append" and "custom_add_entries" in template_sorting[ t] and "custom_add_order" in template_sorting[t]: templates[t]["entries"].update( template_sorting[t]["custom_add_entries"](sorted_missing)) templates[t]["order"] += template_sorting[t]["custom_add_order"]( sorted_missing) elif template_sorting[t][ "add"] == "custom_insert" and "custom_insert_entries" in template_sorting[ t] and "custom_insert_order" in template_sorting[t]: templates[t]["entries"].update( template_sorting[t]["custom_insert_entries"](sorted_missing)) templates[t]["order"] = template_sorting[t]["custom_insert_order"]( templates[t]["order"], sorted_missing) return templates, plugin_names, plugin_vars
def uploadGcodeFile(target): input_name = "file" input_upload_name = input_name + "." + settings().get( ["server", "uploads", "nameSuffix"]) input_upload_path = input_name + "." + settings().get( ["server", "uploads", "pathSuffix"]) if input_upload_name in request.values and input_upload_path in request.values: if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) upload = octoprint.filemanager.util.DiskFileWrapper( request.values[input_upload_name], request.values[input_upload_path]) # Store any additional user data the caller may have passed. userdata = None if "userdata" in request.values: import json try: userdata = json.loads(request.values["userdata"]) except: return make_response("userdata contains invalid JSON", 400) if target == FileDestinations.SDCARD and not settings().getBoolean( ["feature", "sdSupport"]): return make_response("SD card support is disabled", 404) sd = target == FileDestinations.SDCARD selectAfterUpload = "select" in list(request.values.keys( )) and request.values["select"] in valid_boolean_trues printAfterSelect = "print" in list(request.values.keys( )) and request.values["print"] in valid_boolean_trues if sd: # validate that all preconditions for SD upload are met before attempting it if not (printer.is_operational() and not (printer.is_printing() or printer.is_paused())): return make_response( "Can not upload to SD card, printer is either not operational or already busy", 409) if not printer.is_sd_ready(): return make_response( "Can not upload to SD card, not yet initialized", 409) # determine future filename of file to be uploaded, abort if it can't be uploaded try: # FileDestinations.LOCAL = should normally be target, but can't because SDCard handling isn't implemented yet futurePath, futureFilename = fileManager.sanitize( FileDestinations.LOCAL, upload.filename) except: futurePath = None futureFilename = None if futureFilename is None: return make_response( "Can not upload file %s, wrong format?" % upload.filename, 415) if "path" in request.values and request.values["path"]: # we currently only support uploads to sdcard via local, so first target is local instead of "target" futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, request.values["path"]) # prohibit overwriting currently selected file while it's being printed futureFullPath = fileManager.join_path(FileDestinations.LOCAL, futurePath, futureFilename) futureFullPathInStorage = fileManager.path_in_storage( FileDestinations.LOCAL, futureFullPath) if not printer.can_modify_file(futureFullPathInStorage, sd): return make_response( "Trying to overwrite file that is currently being printed: %s" % futureFullPath, 409) reselect = printer.is_current_file(futureFullPathInStorage, sd) def fileProcessingFinished(filename, absFilename, destination): """ Callback for when the file processing (upload, optional slicing, addition to analysis queue) has finished. Depending on the file's destination triggers either streaming to SD card or directly calls selectAndOrPrint. """ if destination == FileDestinations.SDCARD and octoprint.filemanager.valid_file_type( filename, "gcode"): return filename, printer.add_sd_file(filename, absFilename, selectAndOrPrint) else: selectAndOrPrint(filename, absFilename, destination) return filename def selectAndOrPrint(filename, absFilename, destination): """ Callback for when the file is ready to be selected and optionally printed. For SD file uploads this is only the case after they have finished streaming to the printer, which is why this callback is also used for the corresponding call to addSdFile. Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the exact file is already selected, such reloading it. """ if octoprint.filemanager.valid_file_type( added_file, "gcode") and (selectAfterUpload or printAfterSelect or reselect): printer.select_file(absFilename, destination == FileDestinations.SDCARD, printAfterSelect) try: added_file = fileManager.add_file(FileDestinations.LOCAL, futureFullPathInStorage, upload, allow_overwrite=True) except octoprint.filemanager.storage.StorageError as e: if e.code == octoprint.filemanager.storage.StorageError.INVALID_FILE: return make_response( "Could not upload the file \"{}\", invalid type".format( upload.filename), 400) else: return make_response( "Could not upload the file \"{}\"".format(upload.filename), 500) if octoprint.filemanager.valid_file_type(added_file, "stl"): filename = added_file done = True else: filename = fileProcessingFinished( added_file, fileManager.path_on_disk(FileDestinations.LOCAL, added_file), target) done = not sd if userdata is not None: # upload included userdata, add this now to the metadata fileManager.set_additional_metadata(FileDestinations.LOCAL, added_file, "userdata", userdata) sdFilename = None if isinstance(filename, tuple): filename, sdFilename = filename eventManager.fire( Events.UPLOAD, { "name": futureFilename, "path": filename, "target": target, # TODO deprecated, remove in 1.4.0 "file": filename }) files = {} location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=filename, _external=True) files.update({ FileDestinations.LOCAL: { "name": futureFilename, "path": filename, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + filename } } }) if sd and sdFilename: location = url_for(".readGcodeFile", target=FileDestinations.SDCARD, filename=sdFilename, _external=True) files.update({ FileDestinations.SDCARD: { "name": sdFilename, "path": sdFilename, "origin": FileDestinations.SDCARD, "refs": { "resource": location } } }) r = make_response(jsonify(files=files, done=done), 201) r.headers["Location"] = location return r elif "foldername" in request.values: foldername = request.values["foldername"] if not target in [FileDestinations.LOCAL]: return make_response("Unknown target: %s" % target, 400) futurePath, futureName = fileManager.sanitize(target, foldername) if not futureName or not futurePath: return make_response("Can't create a folder with an empty name", 400) if "path" in request.values and request.values["path"]: futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, request.values["path"]) futureFullPath = fileManager.join_path(target, futurePath, futureName) if octoprint.filemanager.valid_file_type(futureName): return make_response( "Can't create a folder named %s, please try another name" % futureName, 409) try: added_folder = fileManager.add_folder(target, futureFullPath) except octoprint.filemanager.storage.StorageError as e: if e.code == octoprint.filemanager.storage.StorageError.INVALID_DIRECTORY: return make_response( "Could not create folder {}, invalid directory".format( futureName)) else: return make_response( "Could not create folder {}".format(futureName)) location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=added_folder, _external=True) folder = dict(name=futureName, path=added_folder, origin=target, refs=dict(resource=location)) r = make_response(jsonify(folder=folder, done=True), 201) r.headers["Location"] = location return r else: return make_response("No file to upload and no folder to create", 400)
def getCustomControls(): # TODO: document me customControls = settings().get(["controls"]) return jsonify(controls=customControls)
def index(): global _templates, _plugin_names, _plugin_vars preemptive_cache_enabled = settings().getBoolean( ["devel", "cache", "preemptive"]) locale = g.locale.language if g.locale else "en" # helper to check if wizards are active def wizard_active(templates): return templates is not None and bool(templates["wizard"]["order"]) # we force a refresh if the client forces one or if we have wizards cached force_refresh = util.flask.cache_check_headers( ) or "_refresh" in request.values or wizard_active(_templates.get(locale)) # if we need to refresh our template cache or it's not yet set, process it if force_refresh or _templates.get( locale) is None or _plugin_names is None or _plugin_vars is None: _templates[locale], _plugin_names, _plugin_vars = _process_templates() now = datetime.datetime.utcnow() enable_accesscontrol = userManager.enabled enable_gcodeviewer = settings().getBoolean(["gcodeViewer", "enabled"]) enable_timelapse = bool(settings().get(["webcam", "snapshot"]) and settings().get(["webcam", "ffmpeg"])) def default_template_filter(template_type, template_key): if template_type == "navbar": return template_key != "login" or enable_accesscontrol elif template_type == "tab": return (template_key != "gcodeviewer" or enable_gcodeviewer) and \ (template_key != "timelapse" or enable_timelapse) elif template_type == "settings": return template_key != "accesscontrol" or enable_accesscontrol elif template_type == "usersettings": return enable_accesscontrol else: return True default_additional_etag = [ enable_accesscontrol, enable_gcodeviewer, enable_timelapse ] def get_preemptively_cached_view(key, view, data=None, additional_request_data=None, additional_unless=None): if (data is None and additional_request_data is None) or g.locale is None: return view d = _preemptive_data(key, data=data, additional_request_data=additional_request_data) def unless(): return _preemptive_unless(base_url=request.url_root, additional_unless=additional_unless) # finally decorate our view return util.flask.preemptively_cached(cache=preemptiveCache, data=d, unless=unless)(view) def get_cached_view(key, view, additional_key_data=None, additional_files=None, additional_etag=None, custom_files=None, custom_etag=None, custom_lastmodified=None): if additional_etag is None: additional_etag = [] def cache_key(): return _cache_key(key, additional_key_data=additional_key_data) def check_etag_and_lastmodified(): files = collect_files() lastmodified = compute_lastmodified(files) lastmodified_ok = util.flask.check_lastmodified(lastmodified) etag_ok = util.flask.check_etag( compute_etag(files=files, lastmodified=lastmodified, additional=[cache_key()] + additional_etag)) return lastmodified_ok and etag_ok def validate_cache(cached): etag_different = compute_etag( additional=[cache_key()] + additional_etag) != cached.get_etag()[0] return force_refresh or etag_different def collect_files(): if callable(custom_files): try: files = custom_files() if files: return files except: _logger.exception( "Error while trying to retrieve tracked files for plugin {}" .format(key)) templates = _get_all_templates() assets = _get_all_assets() translations = _get_all_translationfiles( g.locale.language if g.locale else "en", "messages") files = templates + assets + translations if callable(additional_files): try: af = additional_files() if af: files += af except: _logger.exception( "Error while trying to retrieve additional tracked files for plugin {}" .format(key)) return sorted(set(files)) def compute_lastmodified(files=None): if callable(custom_lastmodified): try: lastmodified = custom_lastmodified() if lastmodified: return lastmodified except: _logger.exception( "Error while trying to retrieve custom LastModified value for plugin {}" .format(key)) if files is None: files = collect_files() return _compute_date(files) def compute_etag(files=None, lastmodified=None, additional=None): if callable(custom_etag): try: etag = custom_etag() if etag: return etag except: _logger.exception( "Error while trying to retrieve custom ETag value for plugin {}" .format(key)) if files is None: files = collect_files() if lastmodified is None: lastmodified = compute_lastmodified(files) if lastmodified and not isinstance(lastmodified, basestring): from werkzeug.http import http_date lastmodified = http_date(lastmodified) if additional is None: additional = [] import hashlib hash = hashlib.sha1() hash.update(octoprint.__version__) hash.update(octoprint.server.UI_API_KEY) hash.update(",".join(sorted(files))) if lastmodified: hash.update(lastmodified) for add in additional: hash.update(str(add)) return hash.hexdigest() decorated_view = view decorated_view = util.flask.lastmodified( lambda _: compute_lastmodified())(decorated_view) decorated_view = util.flask.etagged(lambda _: compute_etag( additional=[cache_key()] + additional_etag))(decorated_view) decorated_view = util.flask.cached( timeout=-1, refreshif=validate_cache, key=cache_key, unless_response=lambda response: util.flask. cache_check_response_headers(response) or util.flask. cache_check_status_code(response, _valid_status_for_cache))( decorated_view) decorated_view = util.flask.conditional(check_etag_and_lastmodified, NOT_MODIFIED)(decorated_view) return decorated_view def plugin_view(p): cached = get_cached_view( p._identifier, p.on_ui_render, additional_key_data=p.get_ui_additional_key_data_for_cache, additional_files=p.get_ui_additional_tracked_files, custom_files=p.get_ui_custom_tracked_files, custom_etag=p.get_ui_custom_etag, custom_lastmodified=p.get_ui_custom_lastmodified, additional_etag=p.get_ui_additional_etag(default_additional_etag)) if preemptive_cache_enabled and p.get_ui_preemptive_caching_enabled(): view = get_preemptively_cached_view( p._identifier, cached, p.get_ui_data_for_preemptive_caching, p.get_ui_additional_request_data_for_preemptive_caching, p.get_ui_preemptive_caching_additional_unless) else: view = cached template_filter = p.get_ui_custom_template_filter( default_template_filter) if template_filter is not None and callable(template_filter): filtered_templates = _filter_templates(_templates[locale], template_filter) else: filtered_templates = _templates[locale] render_kwargs = _get_render_kwargs(filtered_templates, _plugin_names, _plugin_vars, now) return view(now, request, render_kwargs) def default_view(): filtered_templates = _filter_templates(_templates[locale], default_template_filter) wizard = wizard_active(filtered_templates) accesscontrol_active = enable_accesscontrol and userManager.hasBeenCustomized( ) render_kwargs = _get_render_kwargs(filtered_templates, _plugin_names, _plugin_vars, now) render_kwargs.update( dict( webcamStream=settings().get(["webcam", "stream"]), enableTemperatureGraph=settings().get( ["feature", "temperatureGraph"]), enableAccessControl=enable_accesscontrol, accessControlActive=accesscontrol_active, enableSdSupport=settings().get(["feature", "sdSupport"]), gcodeMobileThreshold=settings().get( ["gcodeViewer", "mobileSizeThreshold"]), gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]), wizard=wizard, now=now, )) # no plugin took an interest, we'll use the default UI def make_default_ui(): r = make_response(render_template("index.jinja2", **render_kwargs)) if wizard: # if we have active wizard dialogs, set non caching headers r = util.flask.add_non_caching_response_headers(r) return r cached = get_cached_view("_default", make_default_ui, additional_etag=default_additional_etag) preemptively_cached = get_preemptively_cached_view( "_default", cached, dict(), dict()) return preemptively_cached() response = None forced_view = request.headers.get("X-Force-View", None) if forced_view: # we have view forced by the preemptive cache _logger.debug("Forcing rendering of view {}".format(forced_view)) if forced_view != "_default": plugin = pluginManager.get_plugin_info(forced_view, require_enabled=True) if plugin is not None and isinstance(plugin.implementation, octoprint.plugin.UiPlugin): response = plugin_view(plugin.implementation) else: response = default_view() else: # select view from plugins and fall back on default view if no plugin will handle it ui_plugins = pluginManager.get_implementations( octoprint.plugin.UiPlugin, sorting_context="UiPlugin.on_ui_render") for plugin in ui_plugins: if plugin.will_handle_ui(request): # plugin claims responsibility, let it render the UI response = plugin_view(plugin) if response is not None: break else: _logger.warn( "UiPlugin {} returned an empty response".format( plugin._identifier)) else: response = default_view() if response is None: return abort(404) return response
def _render(self): """Rendering runnable.""" ffmpeg = settings().get(["webcam", "ffmpeg"]) bitrate = settings().get(["webcam", "bitrate"]) if ffmpeg is None or bitrate is None: self._logger.warning( "Cannot create movie, path to ffmpeg or desired bitrate is unset" ) return if self._videocodec == 'mpeg2video': extension = "mpg" else: extension = "mp4" input = os.path.join( self._capture_dir, self._capture_format.format( prefix=self._prefix, postfix=self._postfix if self._postfix is not None else "")) output_name = self._output_format.format( prefix=self._prefix, postfix=self._postfix if self._postfix is not None else "", extension=extension) temporary = os.path.join(self._output_dir, ".{}".format(output_name)) output = os.path.join(self._output_dir, output_name) for i in range(4): if os.path.exists(input % i): break else: self._logger.warning("Cannot create a movie, no frames captured") self._notify_callback("fail", output, returncode=0, stdout="", stderr="", reason="no_frames") return hflip = settings().getBoolean(["webcam", "flipH"]) vflip = settings().getBoolean(["webcam", "flipV"]) rotate = settings().getBoolean(["webcam", "rotate90"]) watermark = None if settings().getBoolean(["webcam", "watermark"]): watermark = os.path.join(os.path.dirname(__file__), "static", "img", "watermark.png") if sys.platform == "win32": # Because ffmpeg hiccups on windows' drive letters and backslashes we have to give the watermark # path a special treatment. Yeah, I couldn't believe it either... watermark = watermark.replace("\\", "/").replace(":", "\\\\:") # prepare ffmpeg command command_str = self._create_ffmpeg_command_string(ffmpeg, self._fps, bitrate, self._threads, input, temporary, self._videocodec, hflip=hflip, vflip=vflip, rotate=rotate, watermark=watermark) self._logger.debug("Executing command: {}".format(command_str)) with self.render_job_lock: try: self._notify_callback("start", output) self._logger.debug("Parsing ffmpeg output") c = CommandlineCaller() c.on_log_stderr = self._process_ffmpeg_output returncode, stdout_text, stderr_text = c.call(command_str, delimiter=b'\r', buffer_size=512) self._logger.debug("Done with parsing") if returncode == 0: shutil.move(temporary, output) self._notify_callback("success", output) else: self._logger.warning( "Could not render movie, got return code %r: %s" % (returncode, stderr_text)) self._notify_callback("fail", output, returncode=returncode, stdout=stdout_text, stderr=stderr_text, reason="returncode") except Exception: self._logger.exception( "Could not render movie due to unknown error") self._notify_callback("fail", output, reason="unknown") finally: try: if os.path.exists(temporary): os.remove(temporary) except Exception: self._logger.warn( "Could not delete temporary timelapse {}".format( temporary)) self._notify_callback("always", output)
def printerSdState(): if not settings().getBoolean(["feature", "sdSupport"]): return make_response("SD support is disabled", 404) return jsonify(ready=printer.isSdReady())
def last_modified_unrendered(): return os.stat(settings().getBaseFolder("timelapse_tmp", check_writable=False)).st_mtime
def configure_timelapse(config=None, persist=False): global current if config is None: config = settings().get(["webcam", "timelapse"], merged=True) if current is not None: current.unload() snapshot_url = settings().get(["webcam", "snapshot"]) ffmpeg_path = settings().get(["webcam", "ffmpeg"]) timelapse_enabled = settings().getBoolean(["webcam", "timelapseEnabled"]) timelapse_precondition = snapshot_url is not None and snapshot_url.strip() != "" \ and ffmpeg_path is not None and ffmpeg_path.strip() != "" type = config["type"] if not timelapse_precondition and timelapse_precondition: logging.getLogger(__name__).warn( "Essential timelapse settings unconfigured (snapshot URL or FFMPEG path) " "but timelapse enabled.") if not timelapse_enabled or not timelapse_precondition or type is None or "off" == type: current = None else: postRoll = 0 if "postRoll" in config and config["postRoll"] >= 0: postRoll = config["postRoll"] fps = 25 if "fps" in config and config["fps"] > 0: fps = config["fps"] if "zchange" == type: retractionZHop = 0 if "options" in config and "retractionZHop" in config[ "options"] and config["options"]["retractionZHop"] >= 0: retractionZHop = config["options"]["retractionZHop"] minDelay = 5 if "options" in config and "minDelay" in config[ "options"] and config["options"]["minDelay"] > 0: minDelay = config["options"]["minDelay"] current = ZTimelapse(post_roll=postRoll, retraction_zhop=retractionZHop, min_delay=minDelay, fps=fps) elif "timed" == type: interval = 10 if "options" in config and "interval" in config[ "options"] and config["options"]["interval"] > 0: interval = config["options"]["interval"] current = TimedTimelapse(post_roll=postRoll, interval=interval, fps=fps) notify_callbacks(current) if persist: settings().set(["webcam", "timelapse"], config) settings().save()
def __init__(self): self.settings = settings()
def last_modified_finished(): return os.stat(settings().getBaseFolder("timelapse", check_writable=False)).st_mtime
def uploadGcodeFile(target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Invalid target: %s" % target, 400) if "gcode_file" in request.files.keys(): file = request.files["gcode_file"] sd = target == FileDestinations.SDCARD selectAfterUpload = "select" in request.values.keys( ) and request.values["select"] in valid_boolean_trues printAfterSelect = "print" in request.values.keys( ) and request.values["print"] in valid_boolean_trues # determine current job currentFilename = None currentSd = None currentJob = printer.getCurrentJob() if currentJob is not None and "filename" in currentJob.keys( ) and "sd" in currentJob.keys(): currentFilename = currentJob["filename"] currentSd = currentJob["sd"] # determine future filename of file to be uploaded, abort if it can't be uploaded futureFilename = gcodeManager.getFutureFilename(file) if futureFilename is None or ( not settings().getBoolean(["cura", "enabled"]) and not gcodefiles.isGcodeFileName(futureFilename)): return make_response( "Can not upload file %s, wrong format?" % file.filename, 400) # prohibit overwriting currently selected file while it's being printed if futureFilename == currentFilename and sd == currentSd and printer.isPrinting( ) or printer.isPaused(): return make_response( "Trying to overwrite file that is currently being printed: %s" % currentFilename, 403) filename = None def fileProcessingFinished(filename, absFilename, destination): """ Callback for when the file processing (upload, optional slicing, addition to analysis queue) has finished. Depending on the file's destination triggers either streaming to SD card or directly calls selectOrPrint. """ sd = destination == FileDestinations.SDCARD if sd: printer.addSdFile(filename, absFilename, selectAndOrPrint) else: selectAndOrPrint(absFilename, destination) def selectAndOrPrint(nameToSelect, destination): """ Callback for when the file is ready to be selected and optionally printed. For SD file uploads this only the case after they have finished streaming to the printer, which is why this callback is also used for the corresponding call to addSdFile. Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the exact file is already selected, such reloading it. """ sd = destination == FileDestinations.SDCARD if selectAfterUpload or printAfterSelect or ( currentFilename == filename and currentSd == sd): printer.selectFile(nameToSelect, sd, printAfterSelect) destination = FileDestinations.SDCARD if sd else FileDestinations.LOCAL filename, done = gcodeManager.addFile(file, destination, fileProcessingFinished) if filename is None: return make_response( "Could not upload the file %s" % file.filename, 500) eventManager.fire("Upload", filename) return jsonify(files=gcodeManager.getAllFileData(), filename=filename, done=done) else: return make_response("No gcode_file included", 400)
def _initSettings(self, configfile, basedir): settings(init=True, basedir=basedir, configfile=configfile)
def __init__(self): self.broadcastTraffic = 0 #Number of clients that wish to receive serial link traffic self.doIdleTempReports = True #Let's the client know if periodic temperature reports should be queries to the printer self._comm = None self._selectedFile = None self._printAfterSelect = False self._currentZ = None self._progress = None self._printTime = None self._printTimeLeft = None self._currentLayer = None self._currentPrintJobId = None self._profileManager = printerProfileManager() self._fileManager= printFileManagerMap[self._fileManagerClass.name]() self._fileManager.registerCallback(self) self._state = self.STATE_NONE self._logger = logging.getLogger(__name__) self._temp = {} self._bedTemp = None self._temps = deque([], 300) self._shutdown = False self._messages = deque([], 300) # callbacks self._callbacks = [] #self._lastProgressReport = None self._stateMonitor = StateMonitor( ratelimit= 1.0, updateCallback= self._sendCurrentDataCallbacks, addTemperatureCallback= self._sendAddTemperatureCallbacks, addLogCallback= self._sendAddLogCallbacks, addMessageCallback= self._sendAddMessageCallbacks ) self._stateMonitor.reset( state={"text": self.getStateString(), "flags": self._getStateFlags()}, jobData={ "file": { "name": None, "size": None, "origin": None, "date": None }, "estimatedPrintTime": None, "filament": { "length": None, "volume": None } }, progress={"completion": None, "filepos": None, "printTime": None, "printTimeLeft": None}, currentZ=None ) eventManager().subscribe(Events.METADATA_ANALYSIS_FINISHED, self.onMetadataAnalysisFinished); s = settings() # don't try to connect when the device hasn't been setup if not s.getBoolean(['server', 'firstRun']): self.connect(s.get(["serial", "port"]), s.get(["serial", "baudrate"]))
def _generate_device_registration(self): s = settings() name_defaults = dict(appearance=dict(name="OctoPrint")) _node_name = s.get(["appearance", "name"], defaults=name_defaults) _node_uuid = self._settings.get(["unique_id"]) _node_id = (_node_uuid[:6]).upper() _config_device = { "ids": [_node_id], "cns": [["mac", self._get_mac_address()]], "name": _node_name, "mf": "Clifford Roche", "mdl": "HomeAssistant Discovery for OctoPrint", "sw": self._plugin_version } ##~~ Configure Connected Sensor _topic_connected = "homeassistant/binary_sensor/" + _node_id + "_CONNECTED/config" _config_connected = { "name": _node_name + " Connected", "uniq_id": _node_id + "_CONNECTED", "stat_t": "~" + self._generate_topic("eventTopic", "Connected"), "json_attr_t": "~" + self._generate_topic("eventTopic", "Connected"), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "pl_on": "Connected", "pl_off": "Disconnected", "val_tpl": '{{value_json._event}}', "device": _config_device, "dev_cla": "connectivity", "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_connected, _config_connected, allow_queueing=True) ##~~ Configure Printing Sensor _topic_printing = "homeassistant/binary_sensor/" + _node_id + "_PRINTING/config" _config_printing = { "name": _node_name + " Printing", "uniq_id": _node_id + "_PRINTING", "stat_t": "~" + self._generate_topic("progressTopic", "printing"), "json_attr_t": "~" + self._generate_topic("progressTopic", "printing"), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "pl_on": "True", "pl_off": "False", "val_tpl": '{{value_json.progress > 0}}', "device": _config_device, "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_printing, _config_printing, allow_queueing=True) ##~~ Configure Last Event Sensor _topic_last_event = "homeassistant/sensor/" + _node_id + "_EVENT/config" _config_last_event = { "name": _node_name + " Last Event", "uniq_id": _node_id + "_EVENT", "stat_t": "~" + self._generate_topic("eventTopic", "+"), "json_attr_t": "~" + self._generate_topic("eventTopic", "+"), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "val_tpl": "{{value_json._event}}", "device": _config_device, "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_last_event, _config_last_event, allow_queueing=True) ##~~ Configure Print Status _topic_printing_p = "homeassistant/sensor/" + _node_id + "_PRINTING_P/config" _config_printing_p = { "name": _node_name + " Print Progress", "uniq_id": _node_id + "_PRINTING_P", "stat_t": "~" + self._generate_topic("progressTopic", "printing"), "json_attr_t": "~" + self._generate_topic("progressTopic", "printing"), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "unit_of_meas": "%", "val_tpl": "{{value_json.progress|float|default(0,true)}}", "device": _config_device, "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_printing_p, _config_printing_p, allow_queueing=True) ##~~ Configure Print File _topic_printing_f = "homeassistant/sensor/" + _node_id + "_PRINTING_F/config" _config_printing_f = { "name": _node_name + " Print File", "uniq_id": _node_id + "_PRINTING_F", "stat_t": "~" + self._generate_topic("progressTopic", "printing"), "json_attr_t": "~" + self._generate_topic("progressTopic", "printing"), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "val_tpl": "{{value_json.path}}", "device": _config_device, "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_printing_f, _config_printing_f, allow_queueing=True) ##~~ Configure Slicing Status _topic_slicing_p = "homeassistant/sensor/" + _node_id + "_SLICING_P/config" _config_slicing_p = { "name": _node_name + " Slicing Progress", "uniq_id": _node_id + "_SLICING_P", "stat_t": "~" + self._generate_topic("progressTopic", "slicing"), "json_attr_t": "~" + self._generate_topic("progressTopic", "slicing"), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "unit_of_meas": "%", "val_tpl": "{{value_json.progress|float|default(0,true)}}", "device": _config_device, "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_slicing_p, _config_slicing_p, allow_queueing=True) ##~~ Configure Slicing File _topic_slicing_f = "homeassistant/sensor/" + _node_id + "_SLICING_F/config" _config_slicing_f = { "name": _node_name + " Slicing File", "uniq_id": _node_id + "_SLICING_F", "stat_t": "~" + self._generate_topic("progressTopic", "slicing"), "json_attr_t": "~" + self._generate_topic("progressTopic", "slicing"), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "val_tpl": "{{value_json.source_path}}", "device": _config_device, "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_slicing_f, _config_slicing_f, allow_queueing=True) ##~~ Tool Temperature _e = self._printer_profile_manager.get_current_or_default()["extruder"]["count"] for x in range(_e): _topic_e_temp = "homeassistant/sensor/" + _node_id + "_TOOL" + str(x) + "/config" _config_e_temp = { "name": _node_name + " Tool " + str(x) + " Temperature", "uniq_id": _node_id + "_TOOL" + str(x), "stat_t": "~" + self._generate_topic("temperatureTopic", "tool" + str(x)), "json_attr_t": "~" + self._generate_topic("temperatureTopic", "tool" + str(x)), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "unit_of_meas": "°C", "val_tpl": "{{value_json.actual|float}}", "device": _config_device, "dev_cla": "temperature", "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_e_temp, _config_e_temp, allow_queueing=True) ##~~ Bed Temperature _topic_bed_temp = "homeassistant/sensor/" + _node_id + "_BED/config" _config_bed_temp = { "name": _node_name + " Bed Temperature", "uniq_id": _node_id + "_BED", "stat_t": "~" + self._generate_topic("temperatureTopic", "bed"), "json_attr_t": "~" + self._generate_topic("temperatureTopic", "bed"), "avty_t": "~mqtt", "pl_avail": "connected", "pl_not_avail": "disconnected", "unit_of_meas": "°C", "val_tpl": "{{value_json.actual|float}}", "device": _config_device, "dev_cla": "temperature", "~": self._generate_topic("baseTopic", "", full=True) } self.mqtt_publish(_topic_bed_temp, _config_bed_temp, allow_queueing=True) ##~~ For people who do not have retain setup, need to do this again to make sensors available _connected_topic = self._generate_topic("lwTopic", "", full=True) self.mqtt_publish(_connected_topic, "connected", allow_queueing=True)