diff --git a/README.md b/README.md index ab587e4..7c87433 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ allows * while printing, gaining information regarding the current progress of the print job (height, percentage etc) * reading the communication log and send arbitrary codes to be executed by the printer * moving the X, Y and Z axis (jog controls, although very ugly ones right now) -* changing the speed modifiers for inner & outer wall, fill and support * optional: visual monitoring of the printer via webcam stream integrated into the UI (using e.g. MJPG-Streamer) * optional: creation of timelapse recordings of the printjob via webcam stream (using e.g. MJPG-Streamer) -- currently two timelaspe methods are implemented, triggering a shot on z-layer change or every "n" seconds @@ -66,50 +65,81 @@ on Linux, at `%APPDATA%/OctoPrint` on Windows and at `~/Library/Application Supp The following example config should explain the available options: - [serial] - # Use the following option to define the default serial port, defaults to unset (= AUTO) - port = /dev/ttyACM0 + # Use the following settings to configure the serial connection to the printer + serial: + # Use the following option to define the default serial port, defaults to unset (= AUTO) + port: /dev/ttyACM0 - # Use the following option to define the default baudrate, defaults to unset (= AUTO) - baudrate = 115200 + # Use the following option to define the default baudrate, defaults to unset (= AUTO) + baudrate: 115200 - [server] - # Use this option to define the host to which to bind the server, defaults to "0.0.0.0" (= all interfaces) - host = 0.0.0.0 + # Use the following settings to configure the web server + server: + # Use this option to define the host to which to bind the server, defaults to "0.0.0.0" (= all interfaces) + host: 0.0.0.0 - # Use this option to define the port to which to bind the server, defaults to 5000 - port = 5000 + # Use this option to define the port to which to bind the server, defaults to 5000 + port: 5000 - [webcam] - # Use this option to enable display of a webcam stream in the UI, e.g. via MJPG-Streamer. - # Webcam support will be disabled if not set - stream = http://:/?action=stream + # Use the following settings to configure webcam support + webcam: + # Use this option to enable display of a webcam stream in the UI, e.g. via MJPG-Streamer. + # Webcam support will be disabled if not set + stream: http://:/?action=stream - # Use this option to enable timelapse support via snapshot, e.g. via MJPG-Streamer. - # Timelapse support will be disabled if not set - snapshot = http://:/?action=snapshot + # Use this option to enable timelapse support via snapshot, e.g. via MJPG-Streamer. + # Timelapse support will be disabled if not set + snapshot: http://:/?action=snapshot - # Path to ffmpeg binary to use for creating timelapse recordings. - # Timelapse support will be disabled if not set - ffmpeg = /path/to/ffmpeg + # Path to ffmpeg binary to use for creating timelapse recordings. + # Timelapse support will be disabled if not set + ffmpeg: /path/to/ffmpeg - [feature] - # Whether to enable gcode analysis for displaying needed filament and estimated print time. Disabling this (set - # to False) will speed up the loading of gcode files before printing significantly, but the mentioned statistical - # data will not be available - analyzeGcode = True + # Use the following settings to enable or disable OctoPrint features + feature: + # Whether to enable gcode analysis for displaying needed filament and estimated print time. Disabling this (set + # to false) will speed up the loading of gcode files before printing significantly, but the mentioned statistical + # data will not be available + analyzeGcode: true - [folder] - # Absolute path where to store gcode uploads. Defaults to the uploads folder in the OctoPrint settings folder - uploads = /path/to/upload/folder + # Use the following settings to set custom paths for folders used by OctoPrint + folder: + # Absolute path where to store gcode uploads. Defaults to the uploads folder in the OctoPrint settings folder + uploads: /path/to/upload/folder - # Absolute path where to store finished timelapse recordings. Defaults to the timelapse folder in the OctoPrint - # settings dir - timelapse = /path/to/timelapse/folder + # Absolute path where to store finished timelapse recordings. Defaults to the timelapse folder in the OctoPrint + # settings dir + timelapse: /path/to/timelapse/folder + + # Absolute path where to store temporary timelapse files. Defaults to the timelapse/tmp folder in the OctoPrint + # settings dir + timelapse_tmp: /path/timelapse/tmp/folder + + # Use the following settings to add custom controls to the "Controls" tab within OctoPrint + # + # Controls consist at least of a name, a type and type-specific further attributes. Currently recognized types are + # - section: Creates a visual section in the UI, you can use this to separate functional blocks + # - command: Creates a button that sends a defined GCODE command to the printer when clicked + # - parametrized_command: Creates a button that sends a parametrized GCODE command to the printer, parameters + # needed for the command are added to the UI as input fields and are named + # + # The following example defines a control for enabling the cooling fan with a variable speed defined by the user + # (default 255) and a control for disabling the fan, all within a section named "Fan". + controls: + - name: Fan + type: section + children: + - name: Enable Fan + type: parametrized_command + command: M106 S%(speed)s + input: + - name: Speed (0-255) + parameter: speed + default: 255 + - name: Disable Fan + type: command + command: M107 - # Absolute path where to store temporary timelapse files. Defaults to the timelapse/tmp folder in the OctoPrint - # settings dir - timelapse_tmp = /path/timelapse/tmp/folder Setup on a Raspberry Pi running Raspbian ---------------------------------------- @@ -158,10 +188,10 @@ This should hopefully run through without any compilation errors. You should the If you now point your browser to `http://:8080/?action=stream`, you should see a moving picture at 5fps. Open `~/.octoprint/config.ini` and add the following lines to it: - [webcam] - stream = http://:8080/?action=stream - snapshot = http://127.0.0.1:8080/?action=snapshot - ffmpeg = /usr/bin/avconv + webcam: + stream: http://:8080/?action=stream + snapshot: http://127.0.0.1:8080/?action=snapshot + ffmpeg: /usr/bin/avconv Restart the OctoPrint server and reload its frontend. You should now see a Webcam tab with content. @@ -179,6 +209,7 @@ It also uses the following libraries and frameworks for backend and frontend: * Flask: http://flask.pocoo.org/ * Tornado: http://www.tornadoweb.org/ * Tornadio2: https://github.com/MrJoes/tornadio2 +* PyYAML: http://pyyaml.org/ * Socket.io: http://socket.io/ * jQuery: http://jquery.com/ * Bootstrap: http://twitter.github.com/bootstrap/ @@ -201,14 +232,14 @@ What do I have to do after the rename from Printer WebUI to OctoPrint? ---------------------------------------------------------------------- If you did checkout OctoPrint from its previous location at https://github.com/foosel/PrinterWebUI.git, you'll have to -update your so-called remote references in git in order to make 'git pull' use the new repository location as origin. +update your so-called remote references in git in order to make `git pull` use the new repository location as origin. To do so you'll only need to execute the following command in your OctoPrint/PrinterWebUI folder: git remote set-url origin https://github.com/foosel/OctoPrint.git -After that you might also want to rename your base directory (which probably still is called 'PrinterWebUI') to 'OctoPrint' -and delete the folder 'printer_webui' in your base folder (which stays there thanks to Python's compiled bytecode files -even after a rename of the Python package to 'octoprint'). +After that you might also want to rename your base directory (which probably still is called `PrinterWebUI`) to `OctoPrint` +and delete the folder `printer_webui` in your base folder (which stays there thanks to Python's compiled bytecode files +even after a rename of the Python package to `octoprint`). -After that you are set, the configuration files are migrated automatically :) \ No newline at end of file +After that you are set, the configuration files are migrated automatically. \ No newline at end of file diff --git a/octoprint/server.py b/octoprint/server.py index 8ad0be5..5f25764 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -120,6 +120,19 @@ def disconnect(): @app.route(BASEURL + "control/command", methods=["POST"]) def printerCommand(): command = request.form["command"] + + # if parameters for the command are given, retrieve them from the request and format the command string with them + parameters = {} + for requestParameter in request.values.keys(): + if not requestParameter.startswith("parameter_"): + continue + + parameterName = requestParameter[len("parameter_"):] + parameterValue = request.values[requestParameter] + parameters[parameterName] = parameterValue + if len(parameters) > 0: + command = command % parameters + printer.command(command) return jsonify(SUCCESS) @@ -182,6 +195,10 @@ def jog(): return jsonify(SUCCESS) +@app.route(BASEURL + "control/speed", methods=["GET"]) +def getSpeedValues(): + return jsonify(feedrate = printer.feedrateState()) + @app.route(BASEURL + "control/speed", methods=["POST"]) def speed(): if not printer.isOperational(): @@ -192,12 +209,11 @@ def speed(): value = int(request.values[key]) printer.setFeedrateModifier(key, value) - return jsonify(feedrate = printer.feedrateState()) + return getSpeedValues() @app.route(BASEURL + "control/custom", methods=["GET"]) def getCustomControls(): customControls = settings().getObject("controls") - print("custom controls: %r" % customControls) return jsonify(controls = customControls) #~~ GCODE file handling diff --git a/octoprint/settings.py b/octoprint/settings.py index c3eae4f..72af359 100644 --- a/octoprint/settings.py +++ b/octoprint/settings.py @@ -45,46 +45,7 @@ old_default_settings = { default_settings = old_default_settings.copy() default_settings.update({ - "controls": [ - { - "name": "Motors", - "type": "section", - "children": [ - { - "name": "Enable Motors", - "type": "command", - "command": "M17" - }, - { - "name": "Disable Motors", - "type": "command", - "command": "M18" - } - ] - }, - { - "name": "Fan", - "type": "section", - "children": [ - { - "name": "Enable Fan", - "type": "parameterized_command", - "command": "M106 S%(speed)", - "input": [{ - "name": "Speed (0-255)", - "parameter": "speed", - "type": "integer", - "range": [0, 255] - }] - }, - { - "name": "Disable Fan", - "type": "command", - "command": "M107" - } - ] - } - ] + "controls": [] }) class Settings(object): @@ -128,7 +89,7 @@ class Settings(object): self._config[section][option] = config.get(section, option) self._dirty = True self.save(force=True) - #os.rename(oldFilename, oldFilename + ".bck") + os.rename(oldFilename, oldFilename + ".bck") else: self._config = {} @@ -195,7 +156,7 @@ class Settings(object): def set(self, section, key, value): if section not in default_settings.keys(): - return None + return if self._config.has_key(section): sectionConfig = self._config[section] @@ -204,6 +165,14 @@ class Settings(object): sectionConfig[key] = value self._config[section] = sectionConfig + self._dirty = True + + def setObject(self, key, value): + if key not in default_settings.keys(): + return + + self._config[key] = value + self._dirty = True def _resolveSettingsDir(applicationName): # taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js index 7a635e2..2d71a4f 100644 --- a/octoprint/static/js/ui.js +++ b/octoprint/static/js/ui.js @@ -387,16 +387,66 @@ function ControlsViewModel() { } self._fromResponse = function(response) { - self.controls(response.controls); + self.controls(self._enhanceControls(response.controls)); + } + + self._enhanceControls = function(controls) { + for (var i = 0; i < controls.length; i++) { + controls[i] = self._enhanceControl(controls[i]); + } + return controls; + } + + self._enhanceControl = function(control) { + if (control.type == "parametrized_command") { + for (var i = 0; i < control.input.length; i++) { + control.input[i].value = control.input[i].default; + } + } else if (control.type == "section") { + control.children = self._enhanceControls(control.children); + } + return control; + } + + self.sendJogCommand = function(axis, distance) { + $.ajax({ + url: AJAX_BASEURL + "control/jog", + type: "POST", + dataType: "json", + data: axis + "=" + distance + }) + } + + self.sendHomeCommand = function(axis) { + $.ajax({ + url: AJAX_BASEURL + "control/jog", + type: "POST", + dataType: "json", + data: "home" + axis + }) } self.sendCustomCommand = function(command) { - if (command) { + if (!command) + return; + + if (command.type == "command") { $.ajax({ url: AJAX_BASEURL + "control/command", type: "POST", dataType: "json", - data: "command=" + command + data: "command=" + command.command + }) + } else if (command.type="parametrized_command") { + var data = {"command": command.command}; + for (var i = 0; i < command.input.length; i++) { + data["parameter_" + command.input[i].parameter] = command.input[i].value; + } + $.ajax({ + url: AJAX_BASEURL + "control/command", + type: "POST", + dataType: "json", + data: data }) } } @@ -406,8 +456,9 @@ function ControlsViewModel() { case "section": return "customControls_sectionTemplate"; case "command": - case "parameterized_command": return "customControls_commandTemplate"; + case "parametrized_command": + return "customControls_parametrizedCommandTemplate"; default: return "customControls_emptyTemplate"; } @@ -449,19 +500,28 @@ function SpeedViewModel() { self.isLoading(data.flags.loading); } - /* - if (response.feedrate) { - self.outerWall(response.feedrate.outerWall); - self.innerWall(response.feedrate.innerWall); - self.fill(response.feedrate.fill); - self.support(response.feedrate.support); - } else { - self.outerWall(undefined); - self.innerWall(undefined); - self.fill(undefined); - self.support(undefined); + self.requestData = function() { + $.ajax({ + url: AJAX_BASEURL + "control/speed", + type: "GET", + dataType: "json", + success: self._fromResponse + }); + } + + self._fromResponse = function(response) { + if (response.feedrate) { + self.outerWall(response.feedrate.outerWall); + self.innerWall(response.feedrate.innerWall); + self.fill(response.feedrate.fill); + self.support(response.feedrate.support); + } else { + self.outerWall(undefined); + self.innerWall(undefined); + self.fill(undefined); + self.support(undefined); + } } - */ } function TerminalViewModel() { @@ -667,6 +727,7 @@ function WebcamViewModel() { dataType: "json", success: self.fromResponse }); + $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime()); } self.fromResponse = function(response) { @@ -847,33 +908,6 @@ $(function() { temperatureViewModel.updatePlot(); }); - //~~ Jog controls - - function jogCommand(axis, distance) { - $.ajax({ - url: AJAX_BASEURL + "control/jog", - type: "POST", - dataType: "json", - data: axis + "=" + distance - }) - } - function homeCommand(axis) { - $.ajax({ - url: AJAX_BASEURL + "control/jog", - type: "POST", - dataType: "json", - data: "home" + axis - }) - } - $("#jog_x_inc").click(function() {jogCommand("x", "10")}); - $("#jog_x_dec").click(function() {jogCommand("x", "-10")}); - $("#jog_y_inc").click(function() {jogCommand("y", "10")}); - $("#jog_y_dec").click(function() {jogCommand("y", "-10")}); - $("#jog_z_inc").click(function() {jogCommand("z", "10")}); - $("#jog_z_dec").click(function() {jogCommand("z", "-10")}); - $("#jog_xy_home").click(function() {homeCommand("XY")}); - $("#jog_z_home").click(function() {homeCommand("Z")}); - //~~ Speed controls function speedCommand(structure) { @@ -932,7 +966,7 @@ $(function() { ko.applyBindings(printerStateViewModel, document.getElementById("state")); ko.applyBindings(gcodeFilesViewModel, document.getElementById("files")); ko.applyBindings(temperatureViewModel, document.getElementById("temp")); - ko.applyBindings(controlsViewModel, document.getElementById("jog")); + ko.applyBindings(controlsViewModel, document.getElementById("controls")); ko.applyBindings(terminalViewModel, document.getElementById("term")); ko.applyBindings(speedViewModel, document.getElementById("speed")); diff --git a/octoprint/templates/index.html b/octoprint/templates/index.html index 4b8e607..6f1e25b 100644 --- a/octoprint/templates/index.html +++ b/octoprint/templates/index.html @@ -10,8 +10,9 @@ - + +