moonraker/test/client/js/main.js

1360 lines
45 KiB
JavaScript

// Main javascript for for Klippy Web Server Example
//
// Copyright (C) 2019 Eric Callahan <arksine.code@gmail.com>
//
// This file may be distributed under the terms of the GNU GPLv3 license
import JsonRPC from "./json-rpc.js?v=0.1.2";
// API Definitions
var api = {
printer_info: {
url: "/printer/info",
method: "get_printer_info"
},
gcode_script: {
url: "/printer/gcode/script",
method: "post_printer_gcode_script"
},
gcode_help: {
url: "/printer/gcode/help",
method: "get_printer_gcode_help"
},
start_print: {
url: "/printer/print/start",
method: "post_printer_print_start"
},
cancel_print: {
url: "/printer/print/cancel",
method: "post_printer_print_cancel"
},
pause_print: {
url: "/printer/print/pause",
method: "post_printer_print_pause"
},
resume_print: {
url: "/printer/print/resume",
method: "post_printer_print_resume"
},
query_endstops: {
url: "/printer/query_endstops/status",
method: "get_printer_query_endstops_status"
},
object_list: {
url: "/printer/objects/list",
method: "get_printer_objects_list"
},
object_status: {
url: "/printer/objects/query",
method: "get_printer_objects_query"
},
object_subscription: {
url: "/printer/objects/subscribe",
method: {
post: "post_printer_objects_subscribe",
get: "get_printer_objects_subscribe"
},
},
temperature_store: {
url: "/server/temperature_store",
method: "get_server_temperature_store"
},
estop: {
url: "/printer/emergency_stop",
method: "post_printer_emergency_stop"
},
restart: {
url: "/printer/restart",
method: "post_printer_restart"
},
firmware_restart: {
url: "/printer/firmware_restart",
method: "post_printer_firmware_restart"
},
// File Management Apis
file_list:{
url: "/server/files/list",
method: "get_file_list"
},
metadata: {
url: "/server/files/metadata",
method: "get_file_metadata"
},
directory: {
url: "/server/files/directory"
},
upload: {
url: "/server/files/upload"
},
gcode_files: {
url: "/server/files/gcodes/"
},
klippy_log: {
url: "/server/files/klippy.log"
},
moonraker_log: {
url: "/server/files/moonraker.log"
},
cfg_files: {
url: "/server/files/config/"
},
cfg_examples: {
url: "/server/files/config_examples/"
},
// Machine APIs
reboot: {
url: "/machine/reboot",
method: "post_machine_reboot"
},
shutdown: {
url: "/machine/shutdown",
method: "post_machine_shutdown"
},
// Access APIs
apikey: {
url: "/access/api_key"
},
oneshot_token: {
url: "/access/oneshot_token"
}
}
var websocket = null;
var apikey = null;
var paused = false;
var klippy_ready = false;
var api_type = 'http';
var is_printing = false;
var file_list_type = "gcodes";
var json_rpc = new JsonRPC();
function round_float (value) {
if (typeof value == "number" && !Number.isInteger(value)) {
return value.toFixed(2);
}
return value;
}
//****************UI Update Functions****************/
var line_count = 0;
function update_term(msg) {
let start = '<div id="line' + line_count + '">';
$("#term").append(start + msg + "</div>");
line_count++;
if (line_count >= 50) {
let rm = line_count - 50
$("#line" + rm).remove();
}
if ($("#cbxAuto").is(":checked")) {
$("#term").stop().animate({
scrollTop: $("#term")[0].scrollHeight
}, 800);
}
}
const max_stream_div_width = 5;
var stream_div_width = max_stream_div_width;
var stream_div_height = 0;
function update_streamdiv(obj, attr, val) {
if (stream_div_width >= max_stream_div_width) {
stream_div_height++;
stream_div_width = 0;
$('#streamdiv').append("<div id='sdrow" + stream_div_height +
"' style='display: flex'></div>");
}
let id = obj.replace(/\s/g, "_") + "_" + attr;
if ($("#" + id).length == 0) {
$('#sdrow' + stream_div_height).append("<div style='width: 10em; border: 2px solid black'>"
+ obj + " " + attr + ":<div id='" + id + "'></div></div>");
stream_div_width++;
}
let out = "";
if (val instanceof Array) {
val.forEach((value, idx, array) => {
out += round_float(value);
if (idx < array.length -1) {
out += ", "
}
});
} else {
out = round_float(val);
}
$("#" + id).text(out);
}
function update_filelist(filelist) {
$("#filelist").empty();
for (let file of filelist) {
$("#filelist").append(
"<option value='" + file.filename + "'>" +
file.filename + "</option>");
}
}
var last_progress = 0;
function update_progress(loaded, total) {
let progress = parseInt(loaded / total * 100);
if (progress - last_progress > 1 || progress >= 100) {
if (progress >= 100) {
last_progress = 0;
progress = 100;
console.log("File transfer complete")
} else {
last_progress = progress;
}
$('#upload_progress').text(progress);
$('#progressbar').val(progress);
}
}
function update_error(cmd, msg) {
if (msg instanceof Object)
msg = JSON.stringify(msg);
// Handle server error responses
update_term("Command [" + cmd + "] resulted in an error: " + msg);
console.log("Error processing " + cmd +": " + msg);
}
function handle_klippy_state(state) {
// Klippy state can be "ready", "disconnect", and "shutdown". This
// differs from Printer State in that it represents the status of
// the Host software
switch(state) {
case "ready":
// It would be possible to use this event to notify the
// client that the printer has started, however the server
// may not start in time for clients to receive this event.
// It is being kept in case
if (!klippy_ready)
update_term("Klippy Ready");
break;
case "shutdown":
// Either M112 was entered or there was a printer error. We
// probably want to notify the user and disable certain controls.
klippy_ready = false;
update_term("Klipper has shutdown, check klippy.log for info");
break;
}
}
//***********End UI Update Functions****************/
//***********Websocket-Klipper API Functions (JSON-RPC)************/
function get_file_list() {
let args = {root: file_list_type}
json_rpc.call_method_with_kwargs(api.file_list.method, args)
.then((result) => {
// result is an "ok" acknowledgment that the gcode has
// been successfully processed
update_filelist(result);
})
.catch((error) => {
update_error(api.file_list.method, error);
});
}
function get_klippy_info() {
// A "get_klippy_info" websocket request. It returns
// the hostname (which should be equal to location.host), the
// build version, and if the Host is ready for commands. Its a
// good idea to fetch this information after the websocket connects.
// If the Host is in a "ready" state, we can do some initialization
json_rpc.call_method(api.printer_info.method)
.then((result) => {
if (result.state == "ready") {
if (!klippy_ready) {
update_term("Klippy Hostname: " + result.hostname +
" | CPU: " + result.cpu_info +
" | Build Version: " + result.software_version);
klippy_ready = true;
// Klippy has transitioned from not ready to ready.
// It is now safe to fetch the file list.
get_file_list();
// Add our subscriptions the the UI is configured to do so.
if ($("#cbxSub").is(":checked")) {
// If autosubscribe is check, request the subscription now
const sub = {
objects: {
gcode_move: ["gcode_position", "speed", "speed_factor", "extrude_factor"],
idle_timeout: null,
pause_resume: null,
toolhead: null,
virtual_sdcard: null,
heater_bed: null,
extruder: ["temperature", "target"],
fan: null,
print_stats: null}
};
add_subscription(sub);
} else {
get_status({idle_timeout: null, pause_resume: null});
}
}
} else {
if (result.state == "error") {
update_term(result.state_message);
} else {
update_term("Waiting for Klippy ready status...");
}
console.log("Klippy Not Ready, checking again in 2s: ");
setTimeout(() => {
get_klippy_info();
}, 2000);
}
})
.catch((error) => {
update_error(api.printer_info.method, error);
setTimeout(() => {
get_klippy_info();
}, 2000);
});
}
function run_gcode(gcode) {
json_rpc.call_method_with_kwargs(
api.gcode_script.method, {script: gcode})
.then((result) => {
// result is an "ok" acknowledgment that the gcode has
// been successfully processed
update_term(result);
})
.catch((error) => {
update_error(api.gcode_script.method, error);
});
}
function get_status(printer_objects) {
// Note that this is just an example of one particular use of get_status.
// In a robust client you would likely pass a callback to this function
// so that you can respond to various status requests. It would also
// be possible to subscribe to status requests and update the UI accordingly
json_rpc.call_method_with_kwargs(api.object_status.method, printer_objects)
.then((result) => {
if ("idle_timeout" in result) {
// Its a good idea that the user understands that some functionality,
// such as file manipulation, is disabled during a print. This can be
// done by disabling buttons or by notifying the user via a popup if they
// click on an action that is not allowed.
if ("state" in result.idle_timeout) {
let state = result.idle_timeout.state.toLowerCase();
is_printing = (state == "printing");
if (!$('#cbxFileTransfer').is(":checked")) {
$('.toggleable').prop(
'disabled', (api_type == 'websocket' || is_printing));
}
$('#btnstartprint').prop('disabled', is_printing);
}
}
if ("pause_resume" in result) {
if ("is_paused" in result.pause_resume) {
paused = result.pause_resume.is_paused;
let label = paused ? "Resume Print" : "Pause Print";
$('#btnpauseresume').text(label);
}
}
console.log(result);
})
.catch((error) => {
update_error(api.object_status.method, error);
});
}
function get_object_list() {
json_rpc.call_method(api.object_list.method)
.then((result) => {
// result will be a dictionary containing all available printer
// objects available for query or subscription
console.log(result);
})
.catch((error) => {
update_error(api.object_list.method, error);
});
}
function add_subscription(printer_objects) {
json_rpc.call_method_with_kwargs(
api.object_subscription.method.post, printer_objects)
.then((result) => {
// result is the the state from all fetched data
handle_status_update(result.status)
console.log(result);
})
.catch((error) => {
update_error(api.object_subscription.method.post, error);
});
}
function get_subscribed() {
json_rpc.call_method(api.object_subscription.method.get)
.then((result) => {
// result is a dictionary containing all currently subscribed
// printer objects/attributes
console.log(result);
})
.catch((error) => {
update_error(api.object_subscription.method.get, error);
});
}
function get_endstops() {
json_rpc.call_method(api.query_endstops.method)
.then((result) => {
// A response to a "get_endstops" websocket request.
// The result contains an object of key/value pairs,
// where the key is the endstop (ie:x, y, or z) and the
// value is either "open" or "TRIGGERED".
console.log(result);
})
.catch((error) => {
update_error(api.query_endstops.method, error);
});
}
function start_print(file_name) {
json_rpc.call_method_with_kwargs(
api.start_print.method, {'filename': file_name})
.then((result) => {
// result is an "ok" acknowledgement that the
// print has started
console.log(result);
})
.catch((error) => {
update_error(api.start_print.method, error);
});
}
function cancel_print() {
json_rpc.call_method(api.cancel_print.method)
.then((result) => {
// result is an "ok" acknowledgement that the
// print has been canceled
console.log(result);
})
.catch((error) => {
update_error(api.cancel_print.method, error);
});
}
function pause_print() {
json_rpc.call_method(api.pause_print.method)
.then((result) => {
// result is an "ok" acknowledgement that the
// print has been paused
console.log("Pause Command Executed")
})
.catch((error) => {
update_error(api.pause_print.method, error);
});
}
function resume_print() {
json_rpc.call_method(api.resume_print.method)
.then((result) => {
// result is an "ok" acknowledgement that the
// print has been resumed
console.log("Resume Command Executed")
})
.catch((error) => {
update_error(api.resume_print.method, error);
});
}
function get_metadata(file_name) {
json_rpc.call_method_with_kwargs(
api.metadata.method, {'filename': file_name})
.then((result) => {
// result is an "ok" acknowledgement that the
// print has started
console.log(result);
})
.catch((error) => {
update_error(api.metadata.method, error);
});
}
function get_gcode_help() {
json_rpc.call_method(api.gcode_help.method)
.then((result) => {
// result is an "ok" acknowledgement that the
// print has been resumed
console.log(result)
})
.catch((error) => {
update_error(api.gcode_help.method, error);
});
}
function emergency_stop() {
json_rpc.call_method(api.estop.method)
.then((result) => {
// result is an "ok" acknowledgement that the
// print has been resumed
console.log(result)
})
.catch((error) => {
update_error(api.estop.method, error);
});
}
function restart() {
// We are unlikely to receive a response from a restart
// request as the websocket will disconnect, so we will
// call json_rpc.notify instead of call_function.
json_rpc.notify(api.restart.method);
}
function firmware_restart() {
// As above, we would not likely receive a response from
// a firmware_restart request
json_rpc.notify(api.firmware_restart.method);
}
function reboot() {
json_rpc.notify(api.reboot.method);
}
function shutdown() {
json_rpc.notify(api.shutdown.method);
}
//***********End Websocket-Klipper API Functions (JSON-RPC)********/
//***********Klipper Event Handlers (JSON-RPC)*********************/
function handle_gcode_response(response) {
// This event contains all gcode responses that would
// typically be printed to the terminal. Its possible
// That multiple lines can be bundled in one response,
// so if displaying we want to be sure we split them.
let messages = response.split("\n");
for (let msg of messages) {
update_term(msg);
}
}
json_rpc.register_method("notify_gcode_response", handle_gcode_response);
function handle_status_update(status) {
// This is subscribed status data. Here we do a nested
// for-each to determine the klippy object name ("name"),
// the attribute we want ("attr"), and the attribute's
// value ("val")
for (let name in status) {
let obj = status[name];
for (let attr in obj) {
let full_name = name + "." + attr;
let val = obj[attr];
switch(full_name) {
case "print_stats.filename":
$('#filename').prop("hidden", val == "");
$('#filename').text("Loaded File: " + val);
break;
case "pause_resume.is_paused":
if (paused != val) {
paused = val;
let label = paused ? "Resume Print" : "Pause Print";
$('#btnpauseresume').text(label);
console.log("Paused State Changed: " + val);
update_streamdiv(name, attr, val);
}
break;
case "idle_timeout.state":
let state = val.toLowerCase();
if (state != is_printing) {
is_printing = (state == "printing");
if (!$('#cbxFileTransfer').is(":checked")) {
$('.toggleable').prop(
'disabled', (api_type == 'websocket' || is_printing));
}
$('#btnstartprint').prop('disabled', is_printing);
update_streamdiv(name, attr, val);
}
break;
case "webhooks.state":
handle_klippy_state(val);
default:
update_streamdiv(name, attr, val);
}
}
}
}
json_rpc.register_method("notify_status_update", handle_status_update);
function handle_klippy_disconnected() {
// Klippy has disconnected from the MCU and is prepping to
// restart. The client will receive this signal right before
// the websocket disconnects. If we need to do any kind of
// cleanup on the client to prepare for restart this would
// be a good place.
klippy_ready = false;
update_term("Klippy Disconnected");
setTimeout(() => {
get_klippy_info();
}, 2000);
}
json_rpc.register_method("notify_klippy_disconnected", handle_klippy_disconnected);
function handle_file_list_changed(file_info) {
// This event fires when a client has either added or removed
// a gcode file.
if (file_list_type == file_info.item.root)
get_file_list(file_info.item.root);
console.log("Filelist Changed:");
console.log(file_info);
}
json_rpc.register_method("notify_filelist_changed", handle_file_list_changed);
//***********End Klipper Event Handlers (JSON-RPC)*****************/
// The function below is an example of one way to use JSON-RPC's batch send
// method. Generally speaking it is better and easier to use individual
// requests, as matching requests with responses in a batch requires more
// work from the developer
function send_gcode_batch(gcodes) {
// The purpose of this function is to provide an example of a JSON-RPC
// "batch request". This function takes an array of gcodes and sends
// them as a batch command. This would behave like a Klipper Gcode Macro
// with one signficant difference...if one gcode in the batch requests
// results in an error, Klipper will continue to process subsequent gcodes.
// A Klipper Gcode Macro will immediately stop execution of the macro
// if an error is encountered.
let batch = [];
for (let gc of gcodes) {
batch.push(
{
method: 'post_printer_gcode_script',
type: 'request',
params: {script: gc}
});
}
// The batch request returns a promise with all results
json_rpc.send_batch_request(batch)
.then((results) => {
for (let res of results) {
// Each result is an object with three keys:
// method: The method executed for this result
// index: The index of the original request
// result: The successful result
// Use the index to look up the gcode parameter in the original
// request
let orig_gcode = batch[res.index].params[0];
console.log("Batch Gcode " + orig_gcode +
" successfully executed with result: " + res.result);
}
})
.catch((err) => {
// Like the result, the error is an object. However there
// is an "error" in place of the "result key"
let orig_gcode = batch[err.index].params[0];
console.log("Batch Gcode <" + orig_gcode +
"> failed with error: " + err.error.message);
});
}
// The function below demonstrates a more useful method of sending
// a client side gcode macro. Like a Klipper macro, gcode execution
// will stop immediately upon encountering an error. The advantage
// of a client supporting their own macros is that there is no need
// to restart the klipper host after creating or deleting them.
async function send_gcode_macro(gcodes) {
for (let gc of gcodes) {
try {
let result = await json_rpc.call_method_with_kwargs(
'post_printer_gcode_script', {script: gc});
} catch (err) {
console.log("Error executing gcode macro: " + err.message);
break;
}
}
}
// A simple reconnecting websocket
class KlippyWebsocket {
constructor(addr) {
this.base_address = addr;
this.connected = false;
this.ws = null;
this.onmessage = null;
this.onopen = null;
this.connect();
}
connect() {
// Doing the websocket connection here allows the websocket
// to reconnect if its closed. This is nice as it allows the
// client to easily recover from Klippy restarts without user
// intervention
if (apikey != null) {
// Fetch a oneshot token to pass websocket authorization
let token_settings = {
url: api.oneshot_token.url,
headers: {
"X-Api-Key": apikey
}
}
$.get(token_settings, (data, status) => {
let token = data.result;
let url = this.base_address + "/websocket?token=" + token;
this.ws = new WebSocket(url);
this._set_callbacks();
}).fail(() => {
console.log("Failed to retreive oneshot token");
})
} else {
this.ws = new WebSocket(this.base_address + "/websocket");
this._set_callbacks();
}
}
_set_callbacks() {
this.ws.onopen = () => {
this.connected = true;
console.log("Websocket connected");
if (this.onopen != null)
this.onopen();
};
this.ws.onclose = (e) => {
klippy_ready = false;
this.connected = false;
console.log("Websocket Closed, reconnecting in 1s: ", e.reason);
setTimeout(() => {
this.connect();
}, 1000);
};
this.ws.onerror = (err) => {
klippy_ready = false;
console.log("Websocket Error: ", err.message);
this.ws.close();
};
this.ws.onmessage = (e) => {
// Tornado Server Websockets support text encoded frames.
// The onmessage callback will send the data straight to
// JSON-RPC
this.onmessage(e.data);
};
}
send(data) {
// Only allow send if connected
if (this.connected) {
this.ws.send(data);
} else {
console.log("Websocket closed, cannot send data");
}
}
close() {
// TODO: Cancel the timeout
this.ws.close();
}
};
function create_websocket(url) {
if (websocket != null)
websocket.close()
let prefix = window.location.protocol == "https" ? "wss://" : "ws://";
let ws_url = prefix + location.host
websocket = new KlippyWebsocket(ws_url);
websocket.onopen = () => {
// Depending on the state of the printer, all enpoints may not be
// available when the websocket is first opened. The "get_klippy_info"
// method is available, and should be used to determine if Klipper is
// in the "ready" state. When Klipper is "ready", all endpoints should
// be registered and available.
// These could be implemented JSON RPC Batch requests and send both
// at the same time, however it is easier to simply do them
// individually
get_klippy_info();
};
json_rpc.register_transport(websocket);
}
function check_authorization() {
// send a HTTP "run gcode" command
let settings = {
url: api.printer_info.url,
statusCode: {
401: function() {
// Show APIKey Popup
let result = window.prompt("Enter a valid API Key:", "");
if (result == null || result.length != 32) {
console.log("Invalid API Key: " + result);
apikey = null;
} else {
apikey = result;
}
check_authorization();
}
}
}
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (data, status) => {
// Create a websocket if /printer/info successfully returns
create_websocket();
})
}
function do_download(url) {
$('#hidden_link').attr('href', url);
$('#hidden_link')[0].click();
}
window.onload = () => {
// Handle changes between the HTTP and Websocket API
$('.reqws').prop('disabled', true);
$('input[type=radio][name=test_type]').on('change', function() {
api_type = $(this).val();
let disable_transfer = (!$('#cbxFileTransfer').is(":checked") && is_printing);
$('.toggleable').prop(
'disabled', (api_type == 'websocket' || disable_transfer));
$('.reqws').prop('disabled', (api_type == 'http'));
$('#apimethod').prop('hidden', (api_type == "websocket"));
$('#apiargs').prop('hidden', (api_type == "http"));
});
$('input[type=radio][name=file_type]').on('change', function() {
file_list_type = $(this).val();
get_file_list();
});
$('#cbxFileTransfer').on('change', function () {
let disabled = false;
if (!$(this).is(":checked")) {
disabled = (api_type == 'websocket' || is_printing);
}
$('.toggleable').prop( 'disabled', disabled);
});
// Send a gcode. Note that in the test client nearly every control
// checks a radio button to see if the request should be sent via
// the REST API or the Websocket API. A real client will choose one
// or the other, so the "api_type" check will be unnecessary
$('#gcform').submit((evt) => {
let line = $('#gcform [type=text]').val();
$('#gcform [type=text]').val('');
update_term(line);
if (api_type == 'http') {
// send a HTTP "run gcode" command
let settings = {url: api.gcode_script.url + "?script=" + line};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (data, status) => {
update_term(data.result);
});
} else {
// Send a websocket "run gcode" command.
run_gcode(line);
}
return false;
});
// Send a command to the server. This can be either an HTTP
// get request formatted as the endpoint(ie: /objects) or
// a websocket command. The websocket command needs to be
// formatted as if it were already json encoded.
$('#apiform').submit((evt) => {
// Send to a user defined endpoint and log the response
if (api_type == 'http') {
let sendtype = $("input[type=radio][name=api_cmd_type]:checked").val();
let url = $('#apirequest').val();
let settings = {url: url}
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
if (sendtype == "get") {
console.log("Sending GET " + url);
$.get(settings, (resp, status) => {
console.log(resp);
});
} else if (sendtype == "post") {
console.log("Sending POST " + url);
$.post(settings, (resp, status) => {
console.log(resp);
});
} else if (sendtype == "delete") {
console.log("Sending DELETE " + url);
settings.method = "DELETE";
settings.success = (resp, status) => {
console.log(resp);
};
$.ajax(settings);
}
} else {
let method = $('#apirequest').val().trim();
let args = $('#apiargs').val();
if (args != "") {
try {
args = JSON.parse("{" + args + "}");
} catch (error) {
console.log("Unable to parse arguments");
return
}
json_rpc.call_method_with_kwargs(method, args)
.then((result) => {
console.log(result);
})
.catch((error) => {
update_error(method, error);
});
} else {
json_rpc.call_method(method)
.then((result) => {
console.log(result);
})
.catch((error) => {
update_error(method, error);
});
}
}
return false;
});
// Hidden file element's click is forwarded to the button
$('#btnupload').click(() => {
if (api_type == "http") {
$('#upload-file').click();
} else {
console.log("File Upload not supported over websocket")
}
});
// Uploads a selected file to the server
$('#upload-file').change(() => {
update_progress(0, 100);
let file = $('#upload-file').prop('files')[0];
if (file) {
console.log("Sending Upload Request...");
// It might not be a bad idea to validate that this is
// a gcode file here, and reject and other files.
// If you want to allow multiple selections, the below code should be
// done in a loop, and the 'let file' above should be the entire
// array of files and not the first element
if (api_type == 'http') {
let fdata = new FormData();
fdata.append("file", file);
fdata.append("root", file_list_type);
let settings = {
url: api.upload.url,
data: fdata,
cache: false,
contentType: false,
processData: false,
method: 'POST',
xhr: () => {
let xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", (evt) => {
if (evt.lengthComputable) {
update_progress(evt.loaded, evt.total);
}
}, false);
return xhr;
},
success: (resp, status) => {
console.log(resp);
return false;
}
};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.ajax(settings);
} else {
console.log("File Upload not supported over websocket")
}
$('#upload-file').val('');
}
});
// Download a file from the server. This implementation downloads
// whatever is selected in the <select> element
$('#btndownload').click(() => {
update_progress(0, 100);
let filename = $("#filelist").val();
if (filename) {
if (api_type == 'http') {
let url = api.gcode_files.url + filename;
if (file_list_type == "config") {
url = api.cfg_files.url + filename;
} else if (file_list_type == "config_examples") {
url = api.cfg_examples.url + filename;
}
let dl_url = "http://" + location.host + url;
if (apikey != null) {
let settings = {
url: api.oneshot_token.url,
headers: {"X-Api-Key": apikey}
};
$.get(settings, (resp, status) => {
let token = resp.result;
dl_url += "?token=" + token;
do_download(dl_url);
return false;
});
} else {
do_download(dl_url);
}
} else {
console.log("File Download not supported over websocket")
}
}
});
// Delete a file from the server. This implementation deletes
// whatever is selected in the <select> element
$("#btndelete").click(() =>{
let filename = $("#filelist").val();
if (filename) {
if (api_type == 'http') {
let url = api.gcode_files.url + filename;
if (file_list_type == "config") {
url = api.cfg_files.url + filename;
} else if (file_list_type != "gcodes") {
console.log("Cannot delete file");
return false;
}
let settings = {
url: url,
method: 'DELETE',
success: (resp, status) => {
console.log(resp);
return false;
}
};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.ajax(settings);
} else {
console.log("File Delete not supported over websocket")
}
}
});
// Start a print. This implementation starts the print for the
// file selected in the <select> element
$("#btnstartprint").click(() =>{
let filename = $("#filelist").val();
if (filename) {
if (api_type == 'http') {
let settings = {url: api.start_print.url + "?filename=" + filename};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
start_print(filename);
}
}
});
// Pause/Resume a currently running print. The specific gcode executed
// is configured in printer.cfg.
$("#btnpauseresume").click(() =>{
if (api_type == 'http') {
let settings = {url: paused ? api.resume_print.url : api.pause_print.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (resp, status) => {
console.log(resp.result);
return false;
});
} else {
if (paused) {
resume_print();
} else {
pause_print();
}
}
});
// Cancel a currently running print. The specific gcode executed
// is configured in printer.cfg.
$("#btncancelprint").click(() =>{
if (api_type == 'http') {
let settings = {url: api.cancel_print.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
cancel_print();
}
});
// Get File Metadata
$("#btngetmetadata").click(() =>{
let filename = $("#filelist").val();
if (filename) {
if (api_type == 'http') {
let url = api.metadata.url + "?filename=" + filename;
let settings = {url: url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
get_metadata(filename);
}
}
});
// Refresh File List
$("#btngetfiles").click(() =>{
if (api_type == 'http') {
let url = api.file_list.url + "?root=" + file_list_type;
let settings = {url: url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
get_file_list();
}
});
$('#btnqueryendstops').click(() => {
if (api_type == 'http') {
let settings = {url: api.query_endstops.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
get_endstops();
}
});
// Post Subscription Request
$('#btnsubscribe').click(() => {
if (api_type == 'http') {
let url = api.object_subscription.url + "?gcode_move=gcode_position,speed,speed_factor,extrude_factor" +
"&toolhead&virtual_sdcard&heater_bed&extruder=temperature,target&fan&idle_timeout&pause_resume" +
"&print_stats";
let settings = {url: url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (data, status) => {
console.log(data);
});
} else {
const sub = {
objects: {
gcode_move: ["gcode_position", "speed", "speed_factor", "extrude_factor"],
idle_timeout: null,
pause_resume: null,
toolhead: null,
virtual_sdcard: null,
heater_bed: null,
extruder: ["temperature", "target"],
fan: null,
print_stats: null}
};
add_subscription(sub);
}
});
// Get subscription info
$('#btngetsub').click(() => {
if (api_type == 'http') {
let settings = {url: api.object_subscription.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (resp, status) => {
console.log(resp);
});
} else {
get_subscribed();
}
});
$('#btngethelp').click(() => {
if (api_type == 'http') {
let settings = {url: api.gcode_help.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
get_gcode_help();
}
});
$('#btngetobjs').click(() => {
if (api_type == 'http') {
let settings = {url: api.object_list.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
get_object_list();
}
});
$('#btnsendbatch').click(() => {
let default_gcs = "M118 This works,RESPOND TYPE=invalid,M118 Execs Despite an Error";
let result = window.prompt("Enter a set of comma separated gcodes:", default_gcs);
if (result == null || result == "") {
console.log("Batch GCode Send Cancelled");
return;
}
let gcodes = result.trim().split(',');
send_gcode_batch(gcodes);
});
$('#btnsendmacro').click(() => {
let default_gcs = "M118 This works,RESPOND TYPE=invalid,M118 Should Not Exec";
let result = window.prompt("Enter a set of comma separated gcodes:", default_gcs);
if (result == null || result == "") {
console.log("Gcode Macro Cancelled");
return;
}
let gcodes = result.trim().split(',');
send_gcode_macro(gcodes);
});
$('#btnestop').click(() => {
if (api_type == 'http') {
let settings = {url: api.estop.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
emergency_stop();
}
});
$('#btnrestart').click(() => {
if (api_type == 'http') {
let settings = {url: api.restart.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
restart();
}
});
$('#btnfirmwarerestart').click(() => {
if (api_type == 'http') {
let settings = {url: api.firmware_restart.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
firmware_restart();
}
});
$('#btnreboot').click(() => {
if (api_type == 'http') {
let settings = {url: api.reboot.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
reboot();
}
});
$('#btnshutdown').click(() => {
if (api_type == 'http') {
let settings = {url: api.shutdown.url};
if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.post(settings, (resp, status) => {
console.log(resp);
return false;
});
} else {
shutdown();
}
});
$('#btngetlog').click(() => {
if (api_type == 'http') {
let dl_url = "http://" + location.host + api.klippy_log.url;
if (apikey != null) {
let settings = {
url: api.oneshot_token.url,
headers: {"X-Api-Key": apikey}
};
$.get(settings, (resp, status) => {
let token = resp.result;
dl_url += "?token=" + token;
do_download(dl_url);
return false;
});
} else {
do_download(dl_url);
}
} else {
console.log("Get Log not supported over websocket")
}
});
$('#btnmoonlog').click(() => {
if (api_type == 'http') {
let dl_url = "http://" + location.host + api.moonraker_log.url;
if (apikey != null) {
let settings = {
url: api.oneshot_token.url,
headers: {"X-Api-Key": apikey}
};
$.get(settings, (resp, status) => {
let token = resp.result;
dl_url += "?token=" + token;
do_download(dl_url);
return false;
});
} else {
do_download(dl_url);
}
} else {
console.log("Get Log not supported over websocket")
}
});
check_authorization();
};