/* Interface logic for the Duet Web Control v1.11 * * written by Christian Hammacher * * licensed under the terms of the GPL v2 * see http://www.gnu.org/licenses/gpl-2.0.html */ var jsVersion = "1.11"; var sessionPassword = "reprap"; var translationWarning = false; // Set this to "true" if you want to look for missing translation entries /* Settings */ var settings = { autoConnect: true, // automatically connect once the page has loaded updateInterval: 250, // in ms haltedReconnectDelay: 5000, // in ms updateReconnectDelay: 20000, // in ms extendedStatusInterval: 10, // nth status request will include extended values maxRequestTime: 8000, // maximum time to wait for a status response in ms notificationTimeout: 5000, // in ms halfZMovements: false, // use half Z movements logSuccess: false, // log all sucessful G-Codes in the console uppercaseGCode: true, // convert G-Codes to upper-case before sending them useKiB: true, // display file sizes in KiB instead of KB showFanControl: false, // show fan controls showFanRPM: false, // show fan RPM in sensors showATXControl: false, // show ATX control confirmStop: false, // ask for confirmation when pressing Emergency STOP useDarkTheme: false, // load dark theme by Fotomas moveFeedrate: 6000, // in mm/min defaultActiveTemps: [0, 170, 195, 205, 210, 235, 245, 250], defaultStandbyTemps: [0, 95, 120, 155, 170], defaultBedTemps: [0, 55, 65, 90, 110, 120], defaultGCodes: [ ["M0", "Stop"], ["M1", "Sleep"], ["M84", "Motors Off"], ["M561", "Disable bed compensation"] ], language: "en", webcamURL: "", webcamInterval: 5000 // in ms }; var defaultSettings = jQuery.extend(true, {}, settings); // need to do this to get a valid copy /* Variables */ var isConnected = false, justConnected, isUploading, updateTaskLive, stopUpdating; var ajaxRequests = [], extendedStatusCounter, lastStatusResponse, configResponse, configFile; var lastSentGCode; var fileInfo, currentLayerTime, maxLayerTime, lastLayerPrintDuration; var geometry, probeSlowDownValue, probeTriggerValue; var heatedBed, chamber, numHeads, numExtruderDrives, toolMapping; var coldExtrudeTemp, coldRetractTemp; var recordedBedTemperatures, recordedChamberTemperatures, recordedHeadTemperatures, layerData; var isPrinting, isPaused, printHasFinished; var currentPage = "control", refreshTempChart = false, refreshPrintChart = false, waitingForPrintStart; var translationData, darkThemeInclude; var currentGCodeDirectory, knownGCodeFiles, gcodeUpdateIndex, gcodeLastDirectory; var currentMacroDirectory, nownMacroFiles, macroUpdateIndex, macroLastDirectory; var fanSliderActive, speedSliderActive, extrSliderActive; function resetGuiData() { setPauseStatus(false); setPrintStatus(false); setGeometry("cartesian"); justConnected = isUploading = updateTaskLive = false; stopUpdating = true; fileInfo = lastStatusResponse = configResponse = configFile = undefined; lastSentGCode = ""; probeSlowDownValue = probeTriggerValue = undefined; heatedBed = 1; chamber = 0; numHeads = 2; // 6 Heaters max, only 2 are visible on load numExtruderDrives = 2; // 6 Extruder Drives max, only 2 are visible on load coldExtrudeTemp = 160; coldRetractTemp = 90; recordedBedTemperatures = []; recordedChamberTemperatures = []; layerData = []; recordedHeadTemperatures = [[], [], [], [], [], []]; maxLayerTime = lastLayerPrintDuration = 0; setToolMapping(undefined); knownGCodeFiles = knownMacroFiles = []; gcodeUpdateIndex = macroUpdateIndex = -1; currentGCodeDirectory = "/gcodes"; currentMacroDirectory = "/macros"; waitingForPrintStart = fanSliderActive = speedSliderActive = extrSliderActive = false; } /* Connect / Disconnect */ function connect(password, regularConnect) { // Close all notifications before we connect... if (regularConnect) { $.notifyClose(); } $(".btn-connect").removeClass("btn-info").addClass("btn-warning disabled").find("span:not(.glyphicon)").text(T("Connecting...")); $(".btn-connect span.glyphicon").removeClass("glyphicon-log-in").addClass("glyphicon-transfer"); $.ajax("rr_connect?password=" + password, { dataType: "json", error: function() { showMessage("danger", T("Error"), T("Could not establish a connection to the Duet firmware! Please check your settings and try again."), 0); $(".btn-connect").removeClass("btn-warning disabled").addClass("btn-info").find("span:not(.glyphicon)").text(T("Connect")); $(".btn-connect span.glyphicon").removeClass("glyphicon-transfer").addClass("glyphicon-log-in"); }, success: function(data) { if (data.err == 2) { // Looks like the firmware ran out of HTTP sessions showMessage("danger", T("Error"), T("Could not connect to Duet, because there are no more HTTP sessions available."), 0); $(".btn-connect").removeClass("btn-warning disabled").addClass("btn-info").find("span:not(.glyphicon)").text(T("Connect")); $(".btn-connect span.glyphicon").removeClass("glyphicon-transfer").addClass("glyphicon-log-in"); } else if (regularConnect) { if (data.err == 0) { // No password authentication required sessionPassword = password; postConnect(); } else { // We can connect, but we need a password first showPasswordPrompt(); } } else { if (data.err == 0) { // Connect successful sessionPassword = password; postConnect(); } else { showMessage("danger", T("Error"), T("Invalid password!"), 0); $(".btn-connect").removeClass("btn-warning disabled").addClass("btn-info").find("span:not(.glyphicon)").text(T("Connect")); $(".btn-connect span.glyphicon").removeClass("glyphicon-transfer").addClass("glyphicon-log-in"); } } } }); } function postConnect() { log("success", "" + T("Connection established!") + ""); isConnected = justConnected = true; extendedStatusCounter = settings.extendedStatusInterval; // ask for extended status response on first poll startUpdates(); if (currentPage == "files") { updateGCodeFiles(); } else if (currentPage == "control" || currentPage == "macros") { updateMacroFiles(); } else if (currentPage == "settings") { getConfigResponse(); if ($("#page_config").is(".active")) { getConfigFile(); } } $(".btn-connect").removeClass("btn-warning disabled").addClass("btn-success").find("span:not(.glyphicon)").text(T("Disconnect")); $(".btn-connect span.glyphicon").removeClass("glyphicon-transfer").addClass("glyphicon-log-out"); enableControls(); validateAddTool(); } function disconnect() { if (isConnected) { log("danger", "" + T("Disconnected.") + ""); } isConnected = false; $(".btn-connect").removeClass("btn-success").addClass("btn-info").find("span:not(.glyphicon)").text(T("Connect")); $(".btn-connect span.glyphicon").removeClass("glyphicon-log-out").addClass("glyphicon-log-in"); $.ajax("rr_disconnect", { dataType: "json", global: false }); ajaxRequests.forEach(function(request) { request.abort(); }); ajaxRequests = []; resetGuiData(); resetGui(); updateGui(); disableControls(); validateAddTool(); setToolMapping(undefined); } /* AJAX */ function startUpdates() { stopUpdating = false; if (!updateTaskLive) { updateStatus(); } } function stopUpdates() { stopUpdating = updateTaskLive; } function updateStatus() { if (stopUpdating) { updateTaskLive = false; return; } updateTaskLive = true; var ajaxRequest = "rr_status"; if (extendedStatusCounter >= settings.extendedStatusInterval || (currentPage == "settings" && (!isPrinting || extendedStatusCounter != 0))) { extendedStatusCounter = 0; ajaxRequest += "?type=2"; } else if (isPrinting) { ajaxRequest += "?type=3"; } else { ajaxRequest += "?type=1"; } extendedStatusCounter++; $.ajax(ajaxRequest, { dataType: "json", success: function(status) { // Don't process this one if we're no longer connected if (!isConnected) { return; } var needGuiUpdate = false; /*** Extended status response ***/ // Cold Extrusion + Retraction Temperatures if (status.hasOwnProperty("coldExtrudeTemp")) { coldExtrudeTemp = status.coldExtrudeTemp; } if (status.hasOwnProperty("coldRetractTemp")) { coldRetractTemp = status.coldRetractTemp; } // Endstops if (status.hasOwnProperty("endstops")) { var endstops = status.endstops; for(i=0; i<9; i++) { var displayText; if (endstops & (1 << i)) { displayText = T("Yes"); } else { displayText = T("No"); } $("#tr_drive_" + i + " > td:nth-child(2)").text(displayText); } } // Printer Geometry if (status.hasOwnProperty("geometry")) { setGeometry(status.geometry); } // Machine Name if (status.hasOwnProperty("name")) { setTitle(status.name); } // Probe Parameters (maybe hide probe info for type 0 someday?) if (status.hasOwnProperty("probe")) { probeTriggerValue = status.probe.threshold; probeSlowDownValue = probeTriggerValue * 0.9; // see Platform::Stopped in dc42/ch firmware forks var probeType; switch (status.probe.type) { case 0: probeType = T("Switch (0)"); break; case 1: probeType = T("Unmodulated (1)"); break; case 2: probeType = T("Modulated (2)"); break; case 3: probeType = T("Alternative (3)"); break; case 4: probeType = T("Two Switches (4)"); break; default: probeType = T("Unknown ({0})", status.probe.type); break; } $("#dd_probe_type").text(probeType); $("#dd_probe_height").text(status.probe.height + " mm"); $("#dd_probe_value").text(probeTriggerValue); } // Tool Mapping if (status.hasOwnProperty("tools")) { setToolMapping(status.tools); } // CPU temperature if (status.hasOwnProperty("cputemp")) { $(".cpu-temp").removeClass("hidden"); $("#td_cputemp").html(status.cputemp.toFixed(1) + " °C"); } /*** Default status response ***/ // Status var printing = false, paused = false; switch (status.status) { case 'F': // Flashing new firmware setStatusLabel("Updating", "success"); break; case 'H': // Halted setStatusLabel("Halted", "danger"); break; case 'D': // Pausing / Decelerating setStatusLabel("Pausing", "warning"); printing = true; paused = true; break; case 'S': // Paused / Stopped setStatusLabel("Paused", "info"); printing = true; paused = true; break; case 'R': // Resuming setStatusLabel("Resuming", "warning"); printing = true; paused = true; break; case 'P': // Printing setStatusLabel("Printing", "success"); printing = true; break; case 'B': // Busy setStatusLabel("Busy", "warning"); break; case 'I': // Idle setStatusLabel("Idle", "default"); break; } setPrintStatus(printing); setPauseStatus(paused); justConnected = false; // Set homed axes setAxesHomed(status.coords.axesHomed); // Update extruder drives if (status.coords.extr.length != numExtruderDrives) { numExtruderDrives = status.coords.extr.length; needGuiUpdate = true; } for(var i=1; i<=numExtruderDrives; i++) { $("#td_extr_" + i).html(status.coords.extr[i - 1].toFixed(1)); } if (numExtruderDrives > 0) { $("#td_extr_total").html(status.coords.extr.reduce(function(a, b) { return a + b; })); } else { $("#td_extr_total").html("n/a"); } // XYZ coordinates if (geometry == "delta" && !status.coords.axesHomed[0]) { $("#td_x, #td_y, #td_z").html("n/a"); } else { $("#td_x").html(status.coords.xyz[0].toFixed(2)); $("#td_y").html(status.coords.xyz[1].toFixed(2)); $("#td_z").html(status.coords.xyz[2].toFixed(2)); } // Current Tool if (lastStatusResponse != undefined && lastStatusResponse.currentTool != status.currentTool) { var btn = $("div.panel-body[data-tool='" + lastStatusResponse.currentTool + "'] > button.btn-select-tool"); btn.attr("title", T("Select this tool")); btn.find("span:last-child").text(T("Select")); btn.find("span.glyphicon").removeClass("glyphicon-remove").addClass("glyphicon-pencil"); btn = $("div.panel-body[data-tool='" + status.currentTool + "'] > button.btn-select-tool"); btn.attr("title", T("Select this tool")); btn.find("span.glyphicon").removeClass("glyphicon-remove").addClass("glyphicon-pencil"); btn.find("span:last-child").text(T("Deselect")); } // Output if (status.hasOwnProperty("output")) { if (status.output.hasOwnProperty("beepDuration") && status.output.hasOwnProperty("beepFrequency")) { beep(status.output.beepFrequency, status.output.beepDuration); } if (status.output.hasOwnProperty("message")) { showMessage("info", T("Message from Duet firmware"), status.output.message, 0); } } // ATX Power setATXPower(status.params.atxPower); // Fan Control var newFanValue = (status.params.fanPercent.constructor === Array) ? status.params.fanPercent[0] : status.params.fanPercent; if (!fanSliderActive && (lastStatusResponse == undefined || $("#slider_fan_print").slider("getValue") != newFanValue)) { if ($("#override_fan").is(":checked") && settings.showFanControl) { sendGCode("M106 S" + ($("#slider_fan_print").slider("getValue") / 100.0)); } else { $("#slider_fan_control").slider("setValue", newFanValue); $("#slider_fan_print").slider("setValue", newFanValue); } } // Speed Factor if (!speedSliderActive && (lastStatusResponse == undefined || $("#slider_speed").slider("getValue") != status.params.speedFactor)) { $("#slider_speed").slider("setValue", status.params.speedFactor); } if (!extrSliderActive) { for(var i=0; i"); response = response.replace(/\n/g, "
"); // Log this message in the G-Code console var prefix = (lastSentGCode != "") ? "" + lastSentGCode + "
" : ""; log(style, prefix + response.replace(/Error:/g, "Error:")); // If the console isn't visible, show a notification too if (currentPage != "console") { if (lastSentGCode != "") { if (isError) { showMessage(style, T("{0} has returned an error:", lastSentGCode), response.substring(6).trim()); } else if (response != "") { showMessage(style, lastSentGCode, response); } else { showMessage(style, "", "" + lastSentGCode + ""); } } else { showMessage(style, "", response.replace(/Error:/g, "Error:")); } } } // Reset info about last sent G-Code again lastSentGCode = ""; } }); } // Sensors setProbeValue(status.sensors.probeValue, status.sensors.probeSecondary); $("#td_fanrpm").html(status.sensors.fanRPM); // Heated bed var bedTemp = undefined; if (status.temps.hasOwnProperty("bed")) { if (!heatedBed) { heatedBed = 1; needGuiUpdate = true; } bedTemp = status.temps.bed.current; setCurrentTemperature("bed", status.temps.bed.current); setTemperatureInput("bed", status.temps.bed.active, 1); setHeaterState("bed", status.temps.bed.state, status.currentTool); } else if (heatedBed) { heatedBed = 0; needGuiUpdate = true; } // Chamber var chamberTemp = undefined; if (status.temps.hasOwnProperty("chamber")) { if (!chamber) { chamber = 1; needGuiUpdate = true; } chamberTemp = status.temps.chamber.current; setCurrentTemperature("chamber", chamberTemp); setTemperatureInput("chamber", status.temps.chamber.active, 1); setHeaterState("chamber", status.temps.chamber.state, status.currentTool); } else if (chamber) { chamber = 0; needGuiUpdate = true; } // Heads if (status.temps.heads.current.length != numHeads) { numHeads = status.temps.heads.current.length; needGuiUpdate = true; } for(var i=0; i 0 && fileInfo.layerHeight > 0) { var numLayers; if (status.firstLayerHeight > 0) { numLayers = ((fileInfo.height - status.firstLayerHeight) / fileInfo.layerHeight) + 1; } else { numLayers = (fileInfo.height / fileInfo.layerHeight); } numLayers = numLayers.toFixed(); progressText.push(T("Layer: {0} of {1}", status.currentLayer, numLayers)); } // Try to calculate the progress by checking the filament usage if (fileInfo.filament.length > 0) { var totalFileFilament = (fileInfo.filament.reduce(function(a, b) { return a + b; })).toFixed(1); var totalRawFilament = (status.extrRaw.reduce(function(a, b) { return a + b; })).toFixed(1); progress = ((totalRawFilament / totalFileFilament) * 100.0).toFixed(1); var remainingFilament = (totalFileFilament - totalRawFilament); if (progress < 0) { progress = 0; } else if (progress > 100) { progress = 100; totalRawFilament = totalFileFilament; remainingFilament = 0; printJustFinished = printHasFinished = true; } progressText.push(T("Filament Usage: {0}mm of {1}mm", totalRawFilament, totalFileFilament)); // TODO: Make this optional progressText[progressText.length - 1] += " " + T("({0}mm remaining)", remainingFilament.toFixed(1)); } // Otherwise by comparing the current Z position to the total height else if (fileInfo.height > 0) { progress = ((status.coords.xyz[2] / fileInfo.height) * 100.0).toFixed(1); if (progress < 0) { progress = 0; } else if (progress > 100) { progress = 100; printJustFinished = printHasFinished = true; } } // Use the file-based progress as a fallback option else { progress = status.fractionPrinted; if (progress < 0) { progress = 100; printJustFinished = printHasFinished = true; } } setProgress(progress, T("Printing {0}, {1}% Complete", fileInfo.fileName, progress), (progressText.length > 0) ? progressText.reduce(function(a, b) { return a + ", " + b; }) : ""); } // Print Chart if (status.currentLayer > 1) { var realPrintTime = (status.printDuration - status.warmUpDuration - status.firstLayerDuration); if (layerData.length == 0) { if (status.currentLayer > 2) { // add avg values on reconnect addLayerData(status.firstLayerDuration, false); realPrintTime -= status.currentLayerTime; for(var layer=2; layer layerData.length) { addLayerData(realPrintTime - lastLayerPrintDuration, true); lastLayerPrintDuration = realPrintTime; } } // Warm-Up Time if (status.warmUpDuration > 0) { if (status.warmUpDuration < 0.5) { $("#td_warmup_time").html(T("none")); } else { $("#td_warmup_time").html(convertSeconds(status.warmUpDuration)); } } else if (!printHasFinished) { $("#td_warmup_time").html("n/a"); } // Current Layer Time if (!printHasFinished) { if (status.currentLayerTime > 0 || status.currentLayer > 1) { currentLayerTime = status.currentLayerTime; $("#td_layertime").html(convertSeconds(status.currentLayerTime)); } else if (status.firstLayerDuration > 0) { currentLayerTime = status.firstLayerDuration; $("#td_layertime").html(convertSeconds(status.firstLayerDuration)); } else { $("#td_layertime").html("n/a"); } } else { $("#td_layertime").html("n/a"); } // Print Duration if (status.printDuration > 0) { $("#td_print_duration").html(convertSeconds(status.printDuration)); } else if (!printHasFinished) { $("#td_print_duration").html("n/a"); } // First Layer Height (maybe we need to update the layer height info) if (status.firstLayerHeight > 0 && $("#dd_layer_height").html().indexOf("/") == -1) { $("#dd_layer_height").html(status.firstLayerHeight + " mm / " + $("#dd_layer_height").html()); } // Print Estimations if (printHasFinished) { ["filament", "layer", "file"].forEach(function(id) { if ($("#tl_" + id).html() != "n/a") { $("#tl_" + id).html("00s"); $("#et_" + id).html((new Date()).toLocaleTimeString()); } }); } else { if (fileInfo.filament.length > 0) { setTimeLeft("filament", status.timesLeft.filament); } else { setTimeLeft("filament", undefined); } setTimeLeft("file", status.timesLeft.file); if (fileInfo.height > 0) { setTimeLeft("layer", status.timesLeft.layer); } else { setTimeLeft("layer", undefined); } } } // Update the GUI when we have processed the whole status response if (needGuiUpdate) { updateGui(); } drawTemperatureChart(); // Set timer for next status update if (status.status == 'F') { isConnected = updateTaskLive = false; log("info", "" + T("Updating Firmware...") + ""); showUpdateMessage(); setTimeout(function() { connect(sessionPassword, false); }, settings.updateReconnectDelay); } else if (status.status == 'H') { isConnected = updateTaskLive = false; log("danger", "" + T("Emergency Stop!") + ""); setTimeout(function() { connect(sessionPassword, false); }, settings.haltedReconnectDelay); } else { setTimeout(updateStatus, settings.updateInterval); } // Save the last status response lastStatusResponse = status; } }); } function getConfigFile() { // Lock the config file as long as the request is being processed $("#text_config").prop("readonly", true); // Request config file from the Duet $.ajax("rr_configfile", { dataType: "html", global: false, success: function(response) { if (response != "") { configFile = response; $("#div_config > h1").addClass("hidden"); $("#text_config").val(response).prop("readonly", false).removeClass("hidden").trigger("input"); $("#row_save_settings").removeClass("hidden"); } } }); } function getConfigResponse() { $.ajax("rr_config", { dataType: "json", success: function(response) { configResponse = response; $("#firmware_name").text(response.firmwareName); $("#firmware_version").text(response.firmwareVersion + " (" + response.firmwareDate + ")"); if (response.hasOwnProperty("configFile")) { $("#div_config > h1").addClass("hidden"); $("#text_config").removeClass("hidden").prop("readonly", true).val(response.configFile).trigger("input"); } else { $("#div_config > h1").removeClass("hidden").text(T("loading")); $("#text_config").addClass("hidden"); } for(var drive = 0; drive < response.accelerations.length; drive++) { if (drive < response.axisMins.length) { $("#tr_drive_" + drive + " > td:nth-child(3)").text(response.axisMins[drive] + " mm"); } if (drive < response.axisMaxes.length) { $("#tr_drive_" + drive + " > td:nth-child(4)").text(response.axisMaxes[drive] + " mm"); } $("#tr_drive_" + drive + " > td:nth-child(5)").text(response.minFeedrates[drive] + " mm/s"); $("#tr_drive_" + drive + " > td:nth-child(6)").text(response.maxFeedrates[drive] + " mm/s"); $("#tr_drive_" + drive + " > td:nth-child(7)").text(response.accelerations[drive] + " mm/s²"); if (response.hasOwnProperty("currents")) { $("#tr_drive_" + drive + " > td:nth-child(8)").text(response.currents[drive] + " mA"); } } if (response.hasOwnProperty("idleCurrentFactor")) { $("#dd_idle_current").text(response.idleCurrentFactor.toFixed(0) + "%"); } if (response.hasOwnProperty("idleTimeout")) { var idleTimeoutText = (response.idleTimeout == 0) ? T("never") : convertSeconds(response.idleTimeout); $("#dd_idle_timeout").text(idleTimeoutText); } } }); } function updateGCodeFiles() { if (!isConnected) { gcodeUpdateIndex = -1; $(".span-refresh-files").addClass("hidden"); $("#table_gcode_files").css("cursor", ""); clearGCodeDirectory(); clearGCodeFiles(); return; } if (gcodeUpdateIndex == -1) { stopUpdates(); gcodeUpdateIndex = 0; gcodeLastDirectory = undefined; clearGCodeFiles(); $.ajax("rr_files?dir=" + encodeURIComponent(currentGCodeDirectory) + "&flagDirs=1", { dataType: "json", success: function(response) { if (isConnected) { knownGCodeFiles = response.files.sort(function (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); }); var i = 0; while (i < knownGCodeFiles.length) { if (knownGCodeFiles[i][0] == '*') { var dirName = knownGCodeFiles[i].substring(1); setGCodeDirectoryItem(addGCodeFile(dirName)); knownGCodeFiles.splice(i, 1); } else { addGCodeFile(knownGCodeFiles[i]); i++; } } if (knownGCodeFiles.length == 0) { if (currentPage == "files") { $(".span-refresh-files").removeClass("hidden"); } startUpdates(); } else { $("#table_gcode_files").css("cursor", "wait"); updateGCodeFiles(); } } } }); } else if (gcodeUpdateIndex < knownGCodeFiles.length) { var row = $("#table_gcode_files tr[data-item='" + knownGCodeFiles[gcodeUpdateIndex] + "']"); $.ajax("rr_fileinfo?name=" + encodeURIComponent(currentGCodeDirectory + "/" + knownGCodeFiles[gcodeUpdateIndex]), { dataType: "json", row: row, dir: currentGCodeDirectory, success: function(response) { if (!isConnected || this.dir != currentGCodeDirectory) { return; } gcodeUpdateIndex++; if (response.err == 0) { // File setGCodeFileItem(this.row, response.size, response.height, response.firstLayerHeight, response.layerHeight, response.filament, response.generatedBy); } else { // Directory setGCodeDirectoryItem(this.row); } if (currentPage == "files") { if (gcodeUpdateIndex >= knownGCodeFiles.length) { $(".span-refresh-files").removeClass("hidden"); $("#table_gcode_files").css("cursor", ""); startUpdates(); } else { updateGCodeFiles(); } } else { startUpdates(); } } }); } } function updateMacroFiles() { if (!isConnected) { macroUpdateIndex = -1; $(".span-refresh-macros").addClass("hidden"); $("#table_macro_files").css("cursor", ""); clearMacroFiles(); return; } if (macroUpdateIndex == -1) { stopUpdates(); macroUpdateIndex = 0; macroLastDirectory = undefined; clearMacroFiles(); $.ajax("rr_files?dir=" + encodeURIComponent(currentMacroDirectory) + "&flagDirs=1", { dataType: "json", success: function(response) { if (isConnected) { knownMacroFiles = response.files.sort(function (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); }); var i = 0; while (i < knownMacroFiles.length) { if (knownMacroFiles[i][0] == '*') { var dirName = knownMacroFiles[i].substring(1); setMacroDirectoryItem(addMacroFile(dirName)); knownMacroFiles.splice(i, 1); } else { addMacroFile(knownMacroFiles[i]); i++; } } if (knownMacroFiles.length == 0) { if (currentPage == "macros") { $(".span-refresh-macros").removeClass("hidden"); } startUpdates(); } else { $("#table_macro_files").css("cursor", "wait"); updateMacroFiles(); } } } }); } else if (macroUpdateIndex < knownMacroFiles.length) { var row = $("#table_macro_files tr[data-item='" + knownMacroFiles[macroUpdateIndex] + "']"); $.ajax("rr_fileinfo?name=" + encodeURIComponent(currentMacroDirectory + "/" + knownMacroFiles[macroUpdateIndex]), { dataType: "json", row: row, dir: currentMacroDirectory, success: function(response) { if (!isConnected || this.dir != currentMacroDirectory) { return; } macroUpdateIndex++; if (response.err == 0) { // File setMacroFileItem(this.row, response.size); } else { // Directory setMacroDirectoryItem(this.row); } if (macroUpdateIndex >= knownMacroFiles.length) { if (currentPage == "macros") { $(".span-refresh-macros").removeClass("hidden"); } $("#table_macro_files").css("cursor", ""); startUpdates(); } else if (currentPage == "control" || currentPage == "macros") { updateMacroFiles(); } else { startUpdates(); } } }); } } // Send G-Code directly to the firmware function sendGCode(gcode, fromInput) { lastSentGCode = gcode; // Although rr_gcode gives us a JSON response, it doesn't provide any results. // We only need to worry about an AJAX error event. $.ajax("rr_gcode?gcode=" + encodeURIComponent(gcode), { dataType: "json" }); } /* AJAX Events */ $(document).ajaxSend(function(event, jqxhr, settings) { ajaxRequests.push(jqxhr); }); $(document).ajaxComplete(function(event, jqxhr, settings) { ajaxRequests = $.grep(ajaxRequests, function(item) { item != jqxhr; }); }); $(document).ajaxError(function(event, jqxhr, settings, thrownError) { if (thrownError == "abort") { // Ignore this error if this request was cancelled intentionally return; } if (isConnected) { var msg = T("An AJAX error has been reported, so the current session has been terminated.

Please check if your printer is still on and try to connect again."); if (thrownError != "") { msg += "

" + T("Error reason: {0}", thrownError); } showMessage("danger", T("Communication Error"), msg, 0); disconnect(); // Try to log the faulty response to console if (jqxhr.responseText != undefined) { console.log("Error! The following JSON response could not be parsed:"); console.log(jqxhr.responseText); } } }); /* Settings */ function checkBoundaries(value, defaultValue, minValue, maxValue) { if (isNaN(value)) { return defaultValue; } if (value < minValue) { return minValue; } if (value > maxValue) { return maxValue; } return value; } function loadSettings(usingCookie) { var loadedSettings; // Older versions of DWC used cookies to store the settings. This is disadvantageous for multiple reasons. // That's why we try to migrate these settings to localStorage, which allows a smaller HTTP request footprint. if (!usingCookie) { if (localStorage.getItem("settings") == null) { var cookieScript = document.createElement("script"); cookieScript.type = "text/javascript"; cookieScript.src = "js/jquery.cookie.min.js"; cookieScript.onload = function() { loadSettings(true) }; document.body.appendChild(cookieScript); return; } else { loadedSettings = localStorage.getItem("settings"); } } else { loadedSettings = $.cookie("settings"); $.removeCookie("settings"); } // Try to parse the loaded settings (if any) if (loadedSettings != undefined && loadedSettings.length > 0) { loadedSettings = JSON.parse(loadedSettings); // Webcam URL if (loadedSettings.hasOwnProperty("webcamURL")) { settings.webcamURL = loadedSettings.webcamURL; } // UI Timing if (loadedSettings.hasOwnProperty("updateInterval")) { settings.updateInterval = loadedSettings.updateInterval; } if (loadedSettings.hasOwnProperty("haltedReconnectDelay")) { settings.haltedReconnectDelay = loadedSettings.haltedReconnectDelay; } if (loadedSettings.hasOwnProperty("updatedReconnectDelay")) { settings.updateReconnectDelay = loadedSettings.updateReconnectDelay; } if (loadedSettings.hasOwnProperty("extendedStatusInterval")) { settings.extendedStatusInterval = loadedSettings.extendedStatusInterval; } if (loadedSettings.hasOwnProperty("maxRequestTime")) { settings.maxRequestTime = loadedSettings.maxRequestTime; } if (loadedSettings.hasOwnProperty("webcamInterval")) { settings.webcamInterval = loadedSettings.webcamInterval; } if (loadedSettings.hasOwnProperty("notificationTimeout")) { settings.notificationTimeout = loadedSettings.notificationTimeout; } // Behavior if (loadedSettings.hasOwnProperty("autoConnect")) { settings.autoConnect = loadedSettings.autoConnect; } if (loadedSettings.hasOwnProperty("halfZMovements")) { settings.halfZMovements = loadedSettings.halfZMovements; } if (loadedSettings.hasOwnProperty("logSuccess")) { settings.logSuccess = loadedSettings.logSuccess; } if (loadedSettings.hasOwnProperty("uppercaseGCode")) { settings.uppercaseGCode = loadedSettings.uppercaseGCode; } if (loadedSettings.hasOwnProperty("useKiB")) { settings.useKiB = loadedSettings.useKiB; } if (loadedSettings.hasOwnProperty("showFanControl")) { settings.showFanControl = loadedSettings.showFanControl; } if (loadedSettings.hasOwnProperty("showFanRPM")) { settings.showFanRPM = loadedSettings.showFanRPM; } if (loadedSettings.hasOwnProperty("showATXControl")) { settings.showATXControl = loadedSettings.showATXControl; } if (loadedSettings.hasOwnProperty("confirmStop")) { settings.confirmStop = loadedSettings.confirmStop; } if (loadedSettings.hasOwnProperty("useDarkTheme")) { settings.useDarkTheme = loadedSettings.useDarkTheme; } if (loadedSettings.hasOwnProperty("moveFeedrate")) { settings.moveFeedrate = loadedSettings.moveFeedrate; } // Default list items if (loadedSettings.hasOwnProperty("defaultActiveTemps")) { settings.defaultActiveTemps = loadedSettings.defaultActiveTemps; } if (loadedSettings.hasOwnProperty("defaultStandbyTemps")) { settings.defaultStandbyTemps = loadedSettings.defaultStandbyTemps; } if (loadedSettings.hasOwnProperty("defaultBedTemps")) { settings.defaultBedTemps = loadedSettings.defaultBedTemps; } if (loadedSettings.hasOwnProperty("defaultGCodes")) { settings.defaultGCodes = loadedSettings.defaultGCodes; } // Other (fallback in case language.xml couldn't be loaded) if (loadedSettings.hasOwnProperty("language")) { settings.language = loadedSettings.language; } } // Final migration, so we don't use the cookie JS next time if (usingCookie) { localStorage.setItem("settings", JSON.stringify(settings)); } settingsLoaded(); } function saveSettings() { // Webcam URL settings.webcamURL = $("#webcam_url").val(); // Appearance and Behavior settings.autoConnect = $("#auto_connect").is(":checked"); settings.halfZMovements = $("#half_z").is(":checked"); settings.logSuccess = $("#log_success").is(":checked"); settings.uppercaseGCode = $("#uppercase_gcode").is(":checked"); settings.useKiB = $("#use_kib").is(":checked"); settings.showFanControl = $("#fan_sliders").is(":checked"); settings.showFanRPM = $("#fan_rpm_display").is(":checked"); settings.showATXControl = $("#show_atx").is(":checked"); settings.confirmStop = $("#confirm_stop").is(":checked"); settings.useDarkTheme = $("#dark_theme").is(":checked"); settings.moveFeedrate = checkBoundaries($("#move_feedrate").val(), defaultSettings.moveFeedrate, 0); if (settings.language != $("#btn_language").data("language")) { showMessage("success", T("Language has changed"), T("You have changed the current language. Please reload the web interface to apply this change."), 0); } settings.language = $("#btn_language").data("language"); // UI Timing settings.updateInterval = checkBoundaries($("#update_interval").val(), defaultSettings.updateInterval, 50); settings.extendedStatusInterval = checkBoundaries($("#extended_status_interval").val(), defaultSettings.extendedStatusInterval, 1, 99999); settings.haltedReconnectDelay = checkBoundaries($("#reconnect_halt_delay").val(), defaultSettings.haltedReconnectDelay, 1000); settings.updateReconnectDelay = checkBoundaries($("#reconnect_update_delay").val(), defaultSettings.updateReconnectDelay, 1000); settings.maxRequestTime = checkBoundaries($("#ajax_timeout").val(), defaultSettings.maxRequestTime, 100); settings.webcamInterval = checkBoundaries($("#webcam_interval").val(), defaultSettings.webcamInterval, 100); settings.notificationTimeout = checkBoundaries($("#notification_timeout").val(), defaultSettings.notificationTimeout, 0); // Default G-Codes settings.defaultGCodes = []; $("#table_gcodes > tbody > tr").each(function() { settings.defaultGCodes.push([$(this).find("label").text(), $(this).find("td:eq(1)").text()]); }); settings.defaultGCodes = settings.defaultGCodes.sort(function(a, b) { if (a[0][0] != b[0][0]) { return a[0].charCodeAt(0) - b[0].charCodeAt(0); } var x = a[0].match(/(\d+)/g)[0]; var y = b[0].match(/(\d+)/g)[0]; if (x == undefined || y == undefined) { return parseInt(a[0]) - parseInt(b[0]); } return x - y; }); // Default Heater Temperatures settings.defaultActiveTemps = []; $("#ul_active_temps > li").each(function() { settings.defaultActiveTemps.push($(this).data("temperature")); }); settings.defaultActiveTemps = settings.defaultActiveTemps.sort(function(a, b) { return a - b; }); settings.defaultStandbyTemps = []; $("#ul_standby_temps > li").each(function() { settings.defaultStandbyTemps.push($(this).data("temperature")); }); settings.defaultStandbyTemps = settings.defaultStandbyTemps.sort(function(a, b) { return a - b; }); // Default Bed Temperatures settings.defaultBedTemps = []; $("#ul_bed_temps > li").each(function() { settings.defaultBedTemps.push($(this).data("temperature")); }); settings.defaultBedTemps = settings.defaultBedTemps.sort(function(a, b) { return a - b; }); // Config file (on demand) - NB: We only update it while it's displayed, else users might mess up their configs... if (configFile != undefined && configFile != $("#text_config").val() && $('a[href="#page_config"]').parent().hasClass("active")) { configFile = $("#text_config").val(); var uploadFile = new File([configFile], "config.g", { type: "application/octet-stream" }); startUpload("generic", [uploadFile], false); } // Save Settings localStorage.setItem("settings", JSON.stringify(settings)); } function applySettings() { /* Behavior */ // Webcam if (settings.webcamURL != "") { $("#panel_webcam").removeClass("hidden"); updateWebcam(true); } else { $("#panel_webcam").addClass("hidden"); } // Half Z Movements var decreaseChildren = $("#td_decrease_z a"); var decreaseVal = (settings.halfZMovements) ? 50 : 100; decreaseChildren.each(function(index) { decreaseChildren.eq(index).data("z", decreaseVal * (-1)).contents().last().replaceWith(" Z-" + decreaseVal); decreaseVal /= 10; }); var increaseChildren = $("#td_increase_z a"); var increaseVal = (settings.halfZMovements) ? 0.05 : 0.1; increaseChildren.each(function(index) { increaseChildren.eq(index).data("z", increaseVal).contents().first().replaceWith("Z+" + increaseVal + " "); increaseVal *= 10; }); // Show/Hide Fan Control if (settings.showFanControl) { $(".fan-control").removeClass("hidden"); } else { $(".fan-control").addClass("hidden"); } // Show/Hide Fan RPM if (settings.showFanRPM) { $(".fan-rpm").removeClass("hidden"); } else { $(".fan-rpm").addClass("hidden"); } // Show/Hide ATX Power if (settings.showATXControl) { $(".atx-control").removeClass("hidden"); } else { $(".atx-control").addClass("hidden"); } // Possibly hide entire misc control panel if (!settings.showFanControl && !settings.showATXControl) { $("#panel_control_misc").addClass("hidden"); } else { $("#panel_control_misc").removeClass("hidden"); } // Apply or revoke theme if (settings.useDarkTheme) { if (darkThemeInclude == undefined) { darkThemeInclude = $(''); darkThemeInclude.appendTo('head'); $("#theme_notice").removeClass("hidden"); } } else { if (darkThemeInclude != undefined) { darkThemeInclude.remove(); darkThemeInclude = undefined; applyThemeColors(false); $("#theme_notice").addClass("hidden"); } } /* Set values on the Settings page */ // Webcam link $("#webcam_url").val(settings.webcamURL); // Appearance and Behavior $("#auto_connect").prop("checked", settings.autoConnect); $("#half_z").prop("checked", settings.halfZMovements); $("#log_success").prop("checked", settings.logSuccess); $("#uppercase_gcode").prop("checked", settings.uppercaseGCode); $("#use_kib").prop("checked", settings.useKiB); $("#fan_sliders").prop("checked", settings.showFanControl); $("#fan_rpm_display").prop("checked", settings.showFanRPM); $("#show_atx").prop("checked", settings.showATXControl); $("#confirm_stop").prop("checked", settings.confirmStop); $("#dark_theme").prop("checked", settings.useDarkTheme); $("#move_feedrate").val(settings.moveFeedrate); // UI Timing $("#update_interval").val(settings.updateInterval); $("#extended_status_interval").val(settings.extendedStatusInterval); $("#reconnect_halt_delay").val(settings.haltedReconnectDelay); $("#reconnect_update_delay").val(settings.updateReconnectDelay); $("#ajax_timeout").val(settings.maxRequestTime); $.ajaxSetup({ timeout: settings.maxRequestTime }); $("#webcam_interval").val(settings.webcamInterval); $("#notification_timeout").val(settings.notificationTimeout); /* Default list items */ // Default head temperatures clearHeadTemperatures(); settings.defaultActiveTemps.forEach(function(temp) { addHeadTemperature(temp, "active"); }); settings.defaultStandbyTemps.forEach(function(temp) { addHeadTemperature(temp, "standby"); }); // Default bed temperatures clearBedTemperatures(); settings.defaultBedTemps.forEach(function(temp) { addBedTemperature(temp); }); // Default G-Codes clearDefaultGCodes(); settings.defaultGCodes.forEach(function(entry) { addDefaultGCode(entry[1], entry[0]); }); } /* File Uploads */ var uploadType, uploadFiles, uploadRows, uploadedFileCount; var uploadTotalBytes, uploadedTotalBytes; var uploadStartTime, uploadRequest, uploadFileSize, uploadFileName, uploadPosition; var uploadedDWC, uploadIncludedConfig, uploadFirmwareFile; function startUpload(type, files, fromCallback) { // Initialize some values stopUpdates(); isUploading = true; uploadType = type; uploadTotalBytes = uploadedTotalBytes = uploadedFileCount = 0; uploadFiles = files; $.each(files, function() { uploadTotalBytes += this.size; }); uploadRows = []; if (!fromCallback) { uploadedDWC = false; } uploadIncludedConfig = false; uploadFirmwareFile = undefined; // Safety check for Upload and Print if (type == "print" && files.length > 1) { showMessage("warning", T("Error"), T("You can only upload and print one file at once!")); return; } // Unzip files if necessary if (type == "macro" || type == "generic") { var containsZip = false; $.each(files, function() { if (this.name.toLowerCase().match("\\.zip$") != null) { uploadedDWC |= this.name.toLowerCase().match("^duetwebcontrol.*\\.zip") != null; var fileReader = new FileReader(); fileReader.onload = (function(theFile) { return function(e) { try { var zip = new JSZip(e.target.result); var zipFiles = []; $.each(zip.files, function(index, zipEntry) { if (!zipEntry.dir && zipEntry.name.match("\/\\.git") == null && zipEntry.name.match("README") == null) { var zipName = zipEntry.name.split("/"); zipName = zipName[zipName.length - 1]; var unpackedFile = new File([zipEntry.asArrayBuffer()], zipName, { type: "application/octet-stream", lastModified: zipEntry.date }); zipFiles.push(unpackedFile); } }); if (zipFiles.length == 0) { showMessage("warning", T("Error"), T("The archive {0} does not contain any files!", theFile.name)); } else { startUpload(type, zipFiles, true); } } catch(e) { showMessage("danger", T("Error"), T("Could not read contents of file {0}!", theFile.name)); } } })(this); fileReader.readAsArrayBuffer(this); containsZip = true; return false; } }); if (containsZip) { // We're relying on an async task which will trigger this method again when required return; } } // Reset modal dialog $("#modal_upload").data("backdrop", "static") $("#modal_upload .close, #modal_upload button[data-dismiss='modal']").addClass("hidden"); $("#btn_cancel_upload, #modal_upload p").removeClass("hidden"); $("#modal_upload h4").text(T("Uploading File(s), {0}% Complete", 0)); // Add files to the table $("#table_upload_files > tbody").remove(); $.each(files, function() { if (type == "generic") { uploadIncludedConfig |= (this.name == "config.g"); if (this.name.toUpperCase().match("^REPRAPFIRMWARE.*\.BIN") != null) { uploadFirmwareFile = this.name; } } var row = ' ' + this.name + ''; row += '' + formatSize(this.size) + ''; row += '
'; $("#table_upload_files").append(row); uploadRows.push($("#table_upload_files > tbody > tr:last-child")); }); $("#modal_upload").modal("show"); // Start file upload uploadNextFile(); } function uploadNextFile() { // Prepare some upload values var file = uploadFiles[uploadedFileCount]; uploadFileName = file.name; uploadFileSize = file.size; uploadStartTime = new Date(); uploadPosition = 0; // Determine the right path var targetPath = ""; switch (uploadType) { case "gcode": // Upload G-Code case "print": // Upload & Print G-Code targetPath = currentGCodeDirectory + "/" + uploadFileName; break; case "macro": // Upload Macro targetPath = currentMacroDirectory + "/" + uploadFileName; break; default: // Generic Upload (on the Settings page) var fileExt = uploadFileName.split('.').pop().toLowerCase(); switch (fileExt) { case "ico": case "html": case "htm": case "xml": targetPath = "/www/" + uploadFileName; break; case "css": case "map": targetPath = "/www/css/" + uploadFileName; break; case "eot": case "svg": case "ttf": case "woff": case "woff2": targetPath = "/www/fonts/" + uploadFileName; break; case "jpeg": case "jpg": case "png": targetPath = "/www/img/" + uploadFileName; break; case "js": targetPath = "/www/js/" + uploadFileName; break; default: targetPath = "/sys/" + uploadFileName; } } // Update the GUI uploadRows[0].find(".progress-bar > span").text(T("Starting")); uploadRows[0].find(".glyphicon").removeClass("glyphicon-asterisk").addClass("glyphicon-cloud-upload"); // Begin another POST file upload uploadRequest = $.ajax("rr_upload?name=" + encodeURIComponent(targetPath), { data: file, dataType: "json", processData: false, contentType: false, timeout: 0, type: "POST", success: function(data) { if (isUploading) { finishCurrentUpload(data.err == 0); } }, xhr: function() { var xhr = new window.XMLHttpRequest(); xhr.upload.addEventListener("progress", function(event) { if (isUploading && event.lengthComputable) { // Calculate current upload speed (Date is based on milliseconds) uploadSpeed = event.loaded / (((new Date()) - uploadStartTime) / 1000); // Update global progress uploadedTotalBytes += (event.loaded - uploadPosition); uploadPosition = event.loaded; var uploadTitle = T("Uploading File(s), {0}% Complete", ((uploadedTotalBytes / uploadTotalBytes) * 100).toFixed(0)); if (uploadSpeed > 0) { uploadTitle += " (" + formatSize(uploadSpeed) + "/s)"; } $("#modal_upload h4").text(uploadTitle); // Update progress bar var progress = ((event.loaded / event.total) * 100).toFixed(0); uploadRows[0].find(".progress-bar").css("width", progress + "%"); uploadRows[0].find(".progress-bar > span").text(progress + " %"); } }, false); return xhr; } }); } function finishCurrentUpload(success) { // Keep the progress updated if (!success) { uploadedTotalBytes += (uploadFileSize - uploadPosition); } // Update glyphicon and progress bar uploadRows[0].find(".glyphicon").removeClass("glyphicon-cloud-upload").addClass(success ? "glyphicon-ok" : "glyphicon-alert"); uploadRows[0].find(".progress-bar").removeClass("progress-bar-info").addClass(success ? "progress-bar-success" : "progress-bar-danger").css("width", "100%"); uploadRows[0].find(".progress-bar > span").text(success ? "100 %" : T("ERROR")); // Go on with upload logic if we're still busy if (isUploading) { uploadedFileCount++; if (uploadFiles.length > uploadedFileCount) { // Purge last-uploaded file row uploadRows.shift(); // Upload the next one uploadNextFile(); } else { // We're done finishUpload(true); } } } function cancelUpload() { isUploading = false; finishCurrentUpload(false); finishUpload(false); $("#modal_upload h4").text(T("Upload Cancelled!")); uploadRequest.abort(); startUpdates(); } function finishUpload(success) { // Reset upload variables isUploading = false; uploadFiles = uploadRows = []; $("#input_file_upload").val(""); // Set some values in the modal dialog $("#modal_upload h4").text(T("Upload Complete!")); $("#btn_cancel_upload, #modal_upload p").addClass("hidden"); $("#modal_upload .close, #modal_upload button[data-dismiss='modal']").removeClass("hidden"); if (success) { // If everything went well, update the GUI immediately uploadHasFinished(true); } else { // In case an upload has been aborted, give the firmware some time to recover setTimeout(function() { uploadHasFinished(false); }, 1000); } } function uploadHasFinished(success) { // Make sure the G-Codes and Macro pages are updated if (uploadType == "gcode" || uploadType == "print") { gcodeUpdateIndex = -1; if (currentPage == "files") { updateGCodeFiles(); } } else if (uploadType == "macro") { macroUpdateIndex = -1; if (currentPage == "control" || currentPage == "macros") { updateMacroFiles(); } } // Start polling again startUpdates(); // Deal with different upload types if (success) { // Check if a print is supposed to be started if (uploadType == "print") { waitingForPrintStart = true; if (currentGCodeDirectory == "/gcodes") { sendGCode("M32 " + uploadFileName); } else { sendGCode("M32 " + currentGCodeDirectory.substring(8) + "/" + uploadFileName); } } // Ask for page reload if DWC has been updated if (uploadedDWC) { $("#modal_upload").modal("hide"); showConfirmationDialog(T("Reload Page?"), T("You have just updated Duet Web Control. Would you like to reload the page now?"), function() { location.reload(); }); } // Ask for software reset if it's safe to do else if (lastStatusResponse != undefined && lastStatusResponse.status == 'I') { if (uploadIncludedConfig) { $("#modal_upload").modal("hide"); showConfirmationDialog(T("Reboot Duet?"), T("You have just uploaded a config file. Would you like to perform a software reset now?"), function() { sendGCode("M999"); }); } if (uploadFirmwareFile != undefined) { $("#modal_upload").modal("hide"); showConfirmationDialog(T("Perform Firmware Update?"), T("You have just uploaded a firmware file. Would you like to update your Duet now?"), startFirmwareUpdate); } } } } function startFirmwareUpdate() { if (uploadFirmwareFile.toUpperCase() != "REPRAPFIRMWARE.BIN") { // The firmware filename is hardcoded in the IAP binary, so try to rename the uploaded file first $.ajax("rr_move?old=" + encodeURIComponent("/sys/" + uploadFirmwareFile) + "&new=" + encodeURIComponent("/sys/RepRapFirmware.bin"), { dataType: "json", success: function(response) { if (response.err == 0) { // Rename succeeded and flashing can be performed now sendGCode("M997"); } else { // Looks like /sys/RepRapFirmware.bin already exists, attempt to delete it and try again $.ajax("rr_delete?name=" + encodeURIComponent("/sys/RepRapFirmware.bin"), { dataType: "json", success: function(response) { if (response.err == 0) { // File delete succeeded, attempt to start the firmware update once again startFirmwareUpdate(); } else { // Something went wrong showMessage("danger", T("Error"), T("Could not rename firmware file!")); } } }); } } }); } else { // Filename is okay, start flashing immediately sendGCode("M997"); } } /* Data helpers */ function requestFileInfo() { $.ajax("rr_fileinfo", { dataType: "json", success: function(response) { if (isConnected && response.err == 2) { // The firmware is still busy parsing the file, so try again until it's ready setTimeout(function() { if (isConnected) { requestFileInfo(); } }, 250); } else if (response.err == 0) { // File info is valid, use it fileInfo = response; $("#span_progress_left").html(T("Printing {0}", response.fileName)); $("#dd_size").html(formatSize(response.size)); $("#dd_height").html((response.height > 0) ? (response.height + " mm") : "n/a"); var layerHeight = (response.layerHeight > 0) ? (response.layerHeight + " mm") : "n/a"; if (response.firstLayerHeight > 0) { $("#dd_layer_height").html(response.firstLayerHeight + " mm / " + layerHeight); } else { $("#dd_layer_height").html(layerHeight); } if (response.filament.length == 0) { $("#dd_filament").html("n/a"); } else { var filament = response.filament.reduce(function(a, b) { return a + " mm, " + b; }) + " mm"; $("#dd_filament").html(filament); } $("#dd_generatedby").html((response.generatedBy == "") ? "n/a" : response.generatedBy); $("#td_print_duration").html(convertSeconds(response.printDuration)); } } }); } function setToolMapping(mapping) { if (toolMapping != mapping) { toolMapping = mapping; // Clean up current tools $("#page_tools").children(":not(:first-child)").remove(); // Create new panels for each tool if (toolMapping != undefined) { for(var i=0; i'; div += '
'; div += '
' + T("Heaters:") + '
' + heaters + '
'; div += '
' + T("Drives:") + '
' + drives + '
'; div += '
'; if (lastStatusResponse != undefined && lastStatusResponse.currentTool == number) { div += ''; } else { div += ''; } div += ' '; div += '
'; $("#page_tools").append(div); } } // Keep the GUI updated validateAddTool(); } } function getTool(number) { if (toolMapping == undefined) { return undefined; } for(var i=0; i 0) { translateEntry(root, this.childNodes[i], key); } } } }); } function translateEntry(root, item, key) { if (item != undefined) { var originalText = item[key]; if (originalText != undefined && originalText.trim() != "") { var text = originalText.trim(); for(var i=0; i