|
|
|
|
@@ -3,8 +3,6 @@
|
|
|
|
|
#include "libslic3r/Preset.hpp"
|
|
|
|
|
#include "libslic3r/PresetBundle.hpp"
|
|
|
|
|
#include "slic3r/GUI/GUI_App.hpp"
|
|
|
|
|
#include "slic3r/GUI/DeviceManager.hpp"
|
|
|
|
|
|
|
|
|
|
#include "nlohmann/json.hpp"
|
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
|
#include <boost/asio/connect.hpp>
|
|
|
|
|
@@ -17,17 +15,15 @@
|
|
|
|
|
#include <chrono>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <cctype>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
namespace beast = boost::beast;
|
|
|
|
|
namespace http = beast::http;
|
|
|
|
|
namespace beast = boost::beast;
|
|
|
|
|
namespace http = beast::http;
|
|
|
|
|
namespace websocket = beast::websocket;
|
|
|
|
|
namespace net = boost::asio;
|
|
|
|
|
using tcp = net::ip::tcp;
|
|
|
|
|
|
|
|
|
|
namespace net = boost::asio;
|
|
|
|
|
using tcp = net::ip::tcp;
|
|
|
|
|
|
|
|
|
|
struct WsEndpoint
|
|
|
|
|
{
|
|
|
|
|
@@ -94,19 +90,19 @@ namespace Slic3r {
|
|
|
|
|
|
|
|
|
|
const std::string MoonrakerPrinterAgent_VERSION = "1.0.0";
|
|
|
|
|
|
|
|
|
|
MoonrakerPrinterAgent::MoonrakerPrinterAgent(std::string log_dir) : m_cloud_agent(nullptr)
|
|
|
|
|
{
|
|
|
|
|
(void) log_dir;
|
|
|
|
|
}
|
|
|
|
|
MoonrakerPrinterAgent::MoonrakerPrinterAgent(std::string log_dir) : m_cloud_agent(nullptr) { (void) log_dir; }
|
|
|
|
|
|
|
|
|
|
MoonrakerPrinterAgent::~MoonrakerPrinterAgent()
|
|
|
|
|
{
|
|
|
|
|
disconnect_printer(); // This will handle thread cleanup
|
|
|
|
|
disconnect_printer(); // This will handle thread cleanup
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AgentInfo MoonrakerPrinterAgent::get_agent_info_static()
|
|
|
|
|
{
|
|
|
|
|
return AgentInfo{.id = "moonraker", .name = "Moonraker Printer Agent", .version = MoonrakerPrinterAgent_VERSION, .description = "Klipper/Moonraker printer agent"};
|
|
|
|
|
return AgentInfo{.id = "moonraker",
|
|
|
|
|
.name = "Moonraker Printer Agent",
|
|
|
|
|
.version = MoonrakerPrinterAgent_VERSION,
|
|
|
|
|
.description = "Klipper/Moonraker printer agent"};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MoonrakerPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
|
|
|
|
|
@@ -131,23 +127,15 @@ int MoonrakerPrinterAgent::send_message_to_printer(std::string dev_id, std::stri
|
|
|
|
|
|
|
|
|
|
int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (dev_id.empty() || dev_ip.empty()) {
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing dev_id or dev_ip";
|
|
|
|
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init_device_info(dev_id, dev_ip, username, password, use_ssl);
|
|
|
|
|
|
|
|
|
|
if (device_info.dev_id != dev_id) {
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer dev_id mismatch: expected " << device_info.dev_id << ", got " << dev_id;
|
|
|
|
|
dispatch_local_connect(ConnectStatusFailed, dev_id, "dev_id_mismatch");
|
|
|
|
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if connection already in progress
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(connect_mutex);
|
|
|
|
|
init_device_info(dev_id, dev_ip, username, password, use_ssl);
|
|
|
|
|
if (connect_in_progress.load()) {
|
|
|
|
|
// Don't reject - wait for previous connection to complete
|
|
|
|
|
// This can happen if MonitorPanel triggers connect while previous connect is still running
|
|
|
|
|
@@ -180,9 +168,7 @@ int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_i
|
|
|
|
|
last_print_state.clear();
|
|
|
|
|
|
|
|
|
|
// Launch connection in background thread
|
|
|
|
|
connect_thread = std::thread([this, dev_id]() {
|
|
|
|
|
perform_connection_async(dev_id, device_info.base_url, device_info.api_key);
|
|
|
|
|
});
|
|
|
|
|
connect_thread = std::thread([this, dev_id]() { perform_connection_async(dev_id, device_info.base_url, device_info.api_key); });
|
|
|
|
|
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
@@ -192,6 +178,7 @@ int MoonrakerPrinterAgent::disconnect_printer()
|
|
|
|
|
// Stop connection thread if running
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(connect_mutex);
|
|
|
|
|
device_info = MoonrakerDeviceInfo{};
|
|
|
|
|
if (connect_in_progress.load()) {
|
|
|
|
|
connect_stop_requested.store(true);
|
|
|
|
|
// Wake up any sleeping
|
|
|
|
|
@@ -208,10 +195,7 @@ int MoonrakerPrinterAgent::disconnect_printer()
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MoonrakerPrinterAgent::check_cert()
|
|
|
|
|
{
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
int MoonrakerPrinterAgent::check_cert() { return BAMBU_NETWORK_SUCCESS; }
|
|
|
|
|
|
|
|
|
|
void MoonrakerPrinterAgent::install_device_cert(std::string dev_id, bool lan_only)
|
|
|
|
|
{
|
|
|
|
|
@@ -238,17 +222,6 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link,
|
|
|
|
|
{
|
|
|
|
|
(void) sec_link;
|
|
|
|
|
|
|
|
|
|
std::string base_url = normalize_base_url(dev_ip, "");
|
|
|
|
|
if (base_url.empty()) {
|
|
|
|
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string error;
|
|
|
|
|
if (!fetch_device_info(base_url, device_info.api_key, device_info, error)) {
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: bind_detect failed: " << error;
|
|
|
|
|
return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
detect.dev_id = device_info.dev_id.empty() ? dev_ip : device_info.dev_id;
|
|
|
|
|
detect.model_id = device_info.model_id.empty() ? device_info.model_name : device_info.model_id;
|
|
|
|
|
// Prefer fetched hostname, then preset model name, then generic fallback
|
|
|
|
|
@@ -315,7 +288,10 @@ int MoonrakerPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn upda
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MoonrakerPrinterAgent::start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
|
|
|
|
int MoonrakerPrinterAgent::start_local_print_with_record(PrintParams params,
|
|
|
|
|
OnUpdateStatusFn update_fn,
|
|
|
|
|
WasCancelledFn cancel_fn,
|
|
|
|
|
OnWaitFn wait_fn)
|
|
|
|
|
{
|
|
|
|
|
(void) params;
|
|
|
|
|
(void) update_fn;
|
|
|
|
|
@@ -324,11 +300,15 @@ int MoonrakerPrinterAgent::start_local_print_with_record(PrintParams params, OnU
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
|
|
|
|
int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params,
|
|
|
|
|
OnUpdateStatusFn update_fn,
|
|
|
|
|
WasCancelledFn cancel_fn,
|
|
|
|
|
OnWaitFn wait_fn)
|
|
|
|
|
{
|
|
|
|
|
(void) wait_fn;
|
|
|
|
|
|
|
|
|
|
if (update_fn) update_fn(PrintingStageCreate, 0, "Preparing...");
|
|
|
|
|
if (update_fn)
|
|
|
|
|
update_fn(PrintingStageCreate, 0, "Preparing...");
|
|
|
|
|
|
|
|
|
|
std::string filename = params.filename;
|
|
|
|
|
if (filename.empty()) {
|
|
|
|
|
@@ -346,13 +326,15 @@ int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpda
|
|
|
|
|
return BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update_fn) update_fn(PrintingStageFinished, 100, "File uploaded");
|
|
|
|
|
if (update_fn)
|
|
|
|
|
update_fn(PrintingStageFinished, 100, "File uploaded");
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
|
|
|
|
|
{
|
|
|
|
|
if (update_fn) update_fn(PrintingStageCreate, 0, "Preparing...");
|
|
|
|
|
if (update_fn)
|
|
|
|
|
update_fn(PrintingStageCreate, 0, "Preparing...");
|
|
|
|
|
|
|
|
|
|
// Check cancellation
|
|
|
|
|
if (cancel_fn && cancel_fn()) {
|
|
|
|
|
@@ -382,7 +364,8 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF
|
|
|
|
|
upload_filename = sanitize_filename(upload_filename);
|
|
|
|
|
|
|
|
|
|
// Upload file
|
|
|
|
|
if (update_fn) update_fn(PrintingStageUpload, 0, "Uploading G-code...");
|
|
|
|
|
if (update_fn)
|
|
|
|
|
update_fn(PrintingStageUpload, 0, "Uploading G-code...");
|
|
|
|
|
if (!upload_gcode(gcode_path, upload_filename, device_info.base_url, device_info.api_key, update_fn, cancel_fn)) {
|
|
|
|
|
return BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED;
|
|
|
|
|
}
|
|
|
|
|
@@ -393,13 +376,15 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start print via gcode script (simpler than JSON-RPC)
|
|
|
|
|
if (update_fn) update_fn(PrintingStageSending, 0, "Starting print...");
|
|
|
|
|
if (update_fn)
|
|
|
|
|
update_fn(PrintingStageSending, 0, "Starting print...");
|
|
|
|
|
std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename;
|
|
|
|
|
if (!send_gcode(device_info.dev_id, gcode)) {
|
|
|
|
|
return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update_fn) update_fn(PrintingStageFinished, 100, "Print started");
|
|
|
|
|
if (update_fn)
|
|
|
|
|
update_fn(PrintingStageFinished, 100, "Print started");
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -469,14 +454,11 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
|
|
|
|
|
{
|
|
|
|
|
(void) dev_id;
|
|
|
|
|
}
|
|
|
|
|
bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) { return false; }
|
|
|
|
|
|
|
|
|
|
int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::string& json_str)
|
|
|
|
|
{
|
|
|
|
|
auto json = nlohmann::json::parse(json_str, nullptr, false);
|
|
|
|
|
{
|
|
|
|
|
auto json = nlohmann::json::parse(json_str, nullptr, false);
|
|
|
|
|
if (json.is_discarded()) {
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Invalid JSON request";
|
|
|
|
|
return BAMBU_NETWORK_ERR_INVALID_RESULT;
|
|
|
|
|
@@ -553,7 +535,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
|
|
|
|
|
// Bed temperature - UI sends "temp" field
|
|
|
|
|
if (cmd == "set_bed_temp") {
|
|
|
|
|
if (json["print"].contains("temp") && json["print"]["temp"].is_number()) {
|
|
|
|
|
int temp = json["print"]["temp"].get<int>();
|
|
|
|
|
int temp = json["print"]["temp"].get<int>();
|
|
|
|
|
std::string gcode = "SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=" + std::to_string(temp);
|
|
|
|
|
send_gcode(dev_id, gcode);
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
@@ -563,13 +545,13 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
|
|
|
|
|
// Nozzle temperature - UI sends "target_temp" and "extruder_index" fields
|
|
|
|
|
if (cmd == "set_nozzle_temp") {
|
|
|
|
|
if (json["print"].contains("target_temp") && json["print"]["target_temp"].is_number()) {
|
|
|
|
|
int temp = json["print"]["target_temp"].get<int>();
|
|
|
|
|
int extruder_idx = 0; // Default to main extruder
|
|
|
|
|
int temp = json["print"]["target_temp"].get<int>();
|
|
|
|
|
int extruder_idx = 0; // Default to main extruder
|
|
|
|
|
if (json["print"].contains("extruder_index") && json["print"]["extruder_index"].is_number()) {
|
|
|
|
|
extruder_idx = json["print"]["extruder_index"].get<int>();
|
|
|
|
|
}
|
|
|
|
|
std::string heater = (extruder_idx == 0) ? "extruder" : "extruder" + std::to_string(extruder_idx);
|
|
|
|
|
std::string gcode = "SET_HEATER_TEMPERATURE HEATER=" + heater + " TARGET=" + std::to_string(temp);
|
|
|
|
|
std::string gcode = "SET_HEATER_TEMPERATURE HEATER=" + heater + " TARGET=" + std::to_string(temp);
|
|
|
|
|
send_gcode(dev_id, gcode);
|
|
|
|
|
return BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
@@ -606,10 +588,10 @@ bool MoonrakerPrinterAgent::init_device_info(std::string dev_id, std::string dev
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
MoonrakerDeviceInfo& info,
|
|
|
|
|
std::string& error) const
|
|
|
|
|
bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
MoonrakerDeviceInfo& info,
|
|
|
|
|
std::string& error) const
|
|
|
|
|
{
|
|
|
|
|
auto fetch_json = [&](const std::string& url, nlohmann::json& out) {
|
|
|
|
|
std::string response_body;
|
|
|
|
|
@@ -658,113 +640,17 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nlohmann::json result = json.contains("result") ? json["result"] : json;
|
|
|
|
|
info.dev_name = result.value("machine_name", result.value("hostname", ""));
|
|
|
|
|
info.version = result.value("software_version", result.value("firmware_version", ""));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MoonrakerPrinterAgent::fetch_server_info(const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
std::string& version,
|
|
|
|
|
std::string& error) const
|
|
|
|
|
{
|
|
|
|
|
std::string response_body;
|
|
|
|
|
bool success = false;
|
|
|
|
|
std::string http_error;
|
|
|
|
|
|
|
|
|
|
auto http = Http::get(join_url(base_url, "/server/info"));
|
|
|
|
|
if (!api_key.empty()) {
|
|
|
|
|
http.header("X-Api-Key", api_key);
|
|
|
|
|
}
|
|
|
|
|
http.timeout_connect(5)
|
|
|
|
|
.timeout_max(10)
|
|
|
|
|
.on_complete([&](std::string body, unsigned status) {
|
|
|
|
|
if (status == 200) {
|
|
|
|
|
response_body = body;
|
|
|
|
|
success = true;
|
|
|
|
|
} else {
|
|
|
|
|
http_error = "HTTP error: " + std::to_string(status);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.on_error([&](std::string body, std::string err, unsigned status) {
|
|
|
|
|
http_error = err;
|
|
|
|
|
if (status > 0) {
|
|
|
|
|
http_error += " (HTTP " + std::to_string(status) + ")";
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.perform_sync();
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
error = http_error.empty() ? "Connection failed" : http_error;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
|
|
|
|
|
if (json.is_discarded()) {
|
|
|
|
|
error = "Invalid JSON response";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nlohmann::json result = json.contains("result") ? json["result"] : json;
|
|
|
|
|
if (result.contains("moonraker_version") && result["moonraker_version"].is_string()) {
|
|
|
|
|
version = result["moonraker_version"].get<std::string>();
|
|
|
|
|
} else if (result.contains("version") && result["version"].is_string()) {
|
|
|
|
|
version = result["version"].get<std::string>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MoonrakerPrinterAgent::fetch_server_info_json(const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
nlohmann::json& info,
|
|
|
|
|
std::string& error) const
|
|
|
|
|
{
|
|
|
|
|
std::string response_body;
|
|
|
|
|
bool success = false;
|
|
|
|
|
std::string http_error;
|
|
|
|
|
|
|
|
|
|
auto http = Http::get(join_url(base_url, "/server/info"));
|
|
|
|
|
if (!api_key.empty()) {
|
|
|
|
|
http.header("X-Api-Key", api_key);
|
|
|
|
|
}
|
|
|
|
|
http.timeout_connect(5)
|
|
|
|
|
.timeout_max(10)
|
|
|
|
|
.on_complete([&](std::string body, unsigned status) {
|
|
|
|
|
if (status == 200) {
|
|
|
|
|
response_body = body;
|
|
|
|
|
success = true;
|
|
|
|
|
} else {
|
|
|
|
|
http_error = "HTTP error: " + std::to_string(status);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.on_error([&](std::string body, std::string err, unsigned status) {
|
|
|
|
|
http_error = err;
|
|
|
|
|
if (status > 0) {
|
|
|
|
|
http_error += " (HTTP " + std::to_string(status) + ")";
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.perform_sync();
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
error = http_error.empty() ? "Connection failed" : http_error;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info = nlohmann::json::parse(response_body, nullptr, false, true);
|
|
|
|
|
if (info.is_discarded()) {
|
|
|
|
|
error = "Invalid JSON response";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
info.dev_name = result.value("machine_name", result.value("hostname", ""));
|
|
|
|
|
info.version = result.value("moonraker_version", "");
|
|
|
|
|
info.klippy_state = result.value("klippy_state", "");
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
nlohmann::json& status,
|
|
|
|
|
std::string& error) const
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
nlohmann::json& status,
|
|
|
|
|
std::string& error) const
|
|
|
|
|
{
|
|
|
|
|
std::string url = join_url(base_url, "/printer/objects/query?print_stats&virtual_sdcard&extruder&heater_bed&fan");
|
|
|
|
|
|
|
|
|
|
@@ -817,7 +703,7 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url,
|
|
|
|
|
bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode) const
|
|
|
|
|
{
|
|
|
|
|
nlohmann::json payload;
|
|
|
|
|
payload["script"] = gcode;
|
|
|
|
|
payload["script"] = gcode;
|
|
|
|
|
std::string payload_str = payload.dump();
|
|
|
|
|
|
|
|
|
|
std::string response_body;
|
|
|
|
|
@@ -856,10 +742,10 @@ bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::str
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
std::set<std::string>& objects,
|
|
|
|
|
std::string& error) const
|
|
|
|
|
bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
std::set<std::string>& objects,
|
|
|
|
|
std::string& error) const
|
|
|
|
|
{
|
|
|
|
|
std::string response_body;
|
|
|
|
|
bool success = false;
|
|
|
|
|
@@ -916,15 +802,6 @@ bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url,
|
|
|
|
|
|
|
|
|
|
int MoonrakerPrinterAgent::send_version_info(const std::string& dev_id)
|
|
|
|
|
{
|
|
|
|
|
std::string version;
|
|
|
|
|
std::string error;
|
|
|
|
|
if (!fetch_server_info(device_info.base_url, device_info.api_key, version, error)) {
|
|
|
|
|
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Failed to fetch server info: " << error;
|
|
|
|
|
}
|
|
|
|
|
if (version.empty()) {
|
|
|
|
|
version = "moonraker";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nlohmann::json payload;
|
|
|
|
|
payload["info"]["command"] = "get_version";
|
|
|
|
|
payload["info"]["result"] = "success";
|
|
|
|
|
@@ -932,7 +809,7 @@ int MoonrakerPrinterAgent::send_version_info(const std::string& dev_id)
|
|
|
|
|
|
|
|
|
|
nlohmann::json module;
|
|
|
|
|
module["name"] = "ota";
|
|
|
|
|
module["sw_ver"] = version;
|
|
|
|
|
module["sw_ver"] = device_info.version;
|
|
|
|
|
module["product_name"] = "Moonraker";
|
|
|
|
|
payload["info"]["module"].push_back(module);
|
|
|
|
|
|
|
|
|
|
@@ -965,9 +842,9 @@ void MoonrakerPrinterAgent::announce_printhost_device()
|
|
|
|
|
|
|
|
|
|
// Try to fetch actual device name from Moonraker
|
|
|
|
|
// Priority: 1) Moonraker hostname, 2) Preset model name, 3) Generic fallback
|
|
|
|
|
std::string dev_name;
|
|
|
|
|
std::string dev_name;
|
|
|
|
|
MoonrakerDeviceInfo info;
|
|
|
|
|
std::string fetch_error;
|
|
|
|
|
std::string fetch_error;
|
|
|
|
|
if (fetch_device_info(device_info.base_url, device_info.api_key, info, fetch_error) && !info.dev_name.empty()) {
|
|
|
|
|
dev_name = info.dev_name;
|
|
|
|
|
} else {
|
|
|
|
|
@@ -1057,9 +934,7 @@ void MoonrakerPrinterAgent::start_status_stream(const std::string& dev_id, const
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ws_stop.store(false);
|
|
|
|
|
ws_thread = std::thread([this, dev_id, base_url, api_key]() {
|
|
|
|
|
run_status_stream(dev_id, base_url, api_key);
|
|
|
|
|
});
|
|
|
|
|
ws_thread = std::thread([this, dev_id, base_url, api_key]() { run_status_stream(dev_id, base_url, api_key); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MoonrakerPrinterAgent::stop_status_stream()
|
|
|
|
|
@@ -1083,17 +958,17 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reconnection logic
|
|
|
|
|
ws_reconnect_requested.store(false); // Reset reconnect flag
|
|
|
|
|
int retry_count = 0;
|
|
|
|
|
const int max_retries = 10;
|
|
|
|
|
ws_reconnect_requested.store(false); // Reset reconnect flag
|
|
|
|
|
int retry_count = 0;
|
|
|
|
|
const int max_retries = 10;
|
|
|
|
|
const int base_delay_ms = 1000;
|
|
|
|
|
|
|
|
|
|
while (!ws_stop.load() && retry_count < max_retries) {
|
|
|
|
|
bool connection_lost = false; // Flag to distinguish clean shutdown from unexpected disconnect
|
|
|
|
|
bool connection_lost = false; // Flag to distinguish clean shutdown from unexpected disconnect
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
net::io_context ioc;
|
|
|
|
|
tcp::resolver resolver{ioc};
|
|
|
|
|
net::io_context ioc;
|
|
|
|
|
tcp::resolver resolver{ioc};
|
|
|
|
|
beast::tcp_stream stream{ioc};
|
|
|
|
|
|
|
|
|
|
stream.expires_after(std::chrono::seconds(10));
|
|
|
|
|
@@ -1117,13 +992,13 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba
|
|
|
|
|
|
|
|
|
|
// Send client identification
|
|
|
|
|
nlohmann::json identify;
|
|
|
|
|
identify["jsonrpc"] = "2.0";
|
|
|
|
|
identify["method"] = "server.connection.identify";
|
|
|
|
|
identify["jsonrpc"] = "2.0";
|
|
|
|
|
identify["method"] = "server.connection.identify";
|
|
|
|
|
identify["params"]["client_name"] = "OrcaSlicer";
|
|
|
|
|
identify["params"]["version"] = MoonrakerPrinterAgent_VERSION;
|
|
|
|
|
identify["params"]["type"] = "agent";
|
|
|
|
|
identify["params"]["url"] = "https://github.com/SoftFever/OrcaSlicer";
|
|
|
|
|
identify["id"] = 0;
|
|
|
|
|
identify["params"]["version"] = MoonrakerPrinterAgent_VERSION;
|
|
|
|
|
identify["params"]["type"] = "agent";
|
|
|
|
|
identify["params"]["url"] = "https://github.com/SoftFever/OrcaSlicer";
|
|
|
|
|
identify["id"] = 0;
|
|
|
|
|
ws.write(net::buffer(identify.dump()));
|
|
|
|
|
|
|
|
|
|
std::set<std::string> subscribe_objects = {"print_stats", "virtual_sdcard"};
|
|
|
|
|
@@ -1163,19 +1038,19 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba
|
|
|
|
|
} else {
|
|
|
|
|
subscribe_objects.insert("extruder");
|
|
|
|
|
subscribe_objects.insert("heater_bed");
|
|
|
|
|
subscribe_objects.insert("toolhead"); // Add toolhead as fallback
|
|
|
|
|
subscribe_objects.insert("fan"); // Try to subscribe to fan as fallback
|
|
|
|
|
subscribe_objects.insert("toolhead"); // Add toolhead as fallback
|
|
|
|
|
subscribe_objects.insert("fan"); // Try to subscribe to fan as fallback
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nlohmann::json subscribe;
|
|
|
|
|
subscribe["jsonrpc"] = "2.0";
|
|
|
|
|
subscribe["method"] = "printer.objects.subscribe";
|
|
|
|
|
subscribe["jsonrpc"] = "2.0";
|
|
|
|
|
subscribe["method"] = "printer.objects.subscribe";
|
|
|
|
|
nlohmann::json objects = nlohmann::json::object();
|
|
|
|
|
for (const auto& name : subscribe_objects) {
|
|
|
|
|
objects[name] = nullptr;
|
|
|
|
|
}
|
|
|
|
|
subscribe["params"]["objects"] = std::move(objects);
|
|
|
|
|
subscribe["id"] = 1;
|
|
|
|
|
subscribe["id"] = 1;
|
|
|
|
|
ws.write(net::buffer(subscribe.dump()));
|
|
|
|
|
|
|
|
|
|
// Read loop
|
|
|
|
|
@@ -1186,8 +1061,7 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba
|
|
|
|
|
ws.read(buffer, ec);
|
|
|
|
|
if (ec == beast::error::timeout) {
|
|
|
|
|
const auto now_ms = static_cast<uint64_t>(
|
|
|
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
|
|
|
std::chrono::steady_clock::now().time_since_epoch()).count());
|
|
|
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());
|
|
|
|
|
const auto last_ms = ws_last_emit_ms.load();
|
|
|
|
|
if (last_ms == 0 || now_ms - last_ms >= 10000) {
|
|
|
|
|
nlohmann::json message;
|
|
|
|
|
@@ -1261,22 +1135,20 @@ void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const s
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool updated = false;
|
|
|
|
|
bool is_critical = false; // Track if this is a critical update that bypasses throttle
|
|
|
|
|
bool updated = false;
|
|
|
|
|
bool is_critical = false; // Track if this is a critical update that bypasses throttle
|
|
|
|
|
|
|
|
|
|
// Check for subscription response (has "result.status") - initial subscription is critical
|
|
|
|
|
if (json.contains("result") && json["result"].contains("status") &&
|
|
|
|
|
json["result"]["status"].is_object()) {
|
|
|
|
|
if (json.contains("result") && json["result"].contains("status") && json["result"]["status"].is_object()) {
|
|
|
|
|
update_status_cache(json["result"]["status"]);
|
|
|
|
|
updated = true;
|
|
|
|
|
is_critical = true; // Initial subscription response - dispatch immediately
|
|
|
|
|
updated = true;
|
|
|
|
|
is_critical = true; // Initial subscription response - dispatch immediately
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for status update notifications
|
|
|
|
|
if (json.contains("method") && json["method"].is_string()) {
|
|
|
|
|
const std::string method = json["method"].get<std::string>();
|
|
|
|
|
if (method == "notify_status_update" && json.contains("params") &&
|
|
|
|
|
json["params"].is_array() && !json["params"].empty() &&
|
|
|
|
|
if (method == "notify_status_update" && json.contains("params") && json["params"].is_array() && !json["params"].empty() &&
|
|
|
|
|
json["params"][0].is_object()) {
|
|
|
|
|
update_status_cache(json["params"][0]);
|
|
|
|
|
updated = true;
|
|
|
|
|
@@ -1285,14 +1157,14 @@ void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const s
|
|
|
|
|
nlohmann::json updates;
|
|
|
|
|
updates["print_stats"]["state"] = "standby";
|
|
|
|
|
update_status_cache(updates);
|
|
|
|
|
updated = true;
|
|
|
|
|
is_critical = true; // Klippy events are critical
|
|
|
|
|
updated = true;
|
|
|
|
|
is_critical = true; // Klippy events are critical
|
|
|
|
|
} else if (method == "notify_klippy_shutdown") {
|
|
|
|
|
nlohmann::json updates;
|
|
|
|
|
updates["print_stats"]["state"] = "error";
|
|
|
|
|
update_status_cache(updates);
|
|
|
|
|
updated = true;
|
|
|
|
|
is_critical = true; // Klippy events are critical
|
|
|
|
|
updated = true;
|
|
|
|
|
is_critical = true; // Klippy events are critical
|
|
|
|
|
}
|
|
|
|
|
// Handle Klippy disconnect - update status and trigger reconnection
|
|
|
|
|
else if (method == "notify_klippy_disconnected") {
|
|
|
|
|
@@ -1300,8 +1172,8 @@ void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const s
|
|
|
|
|
nlohmann::json updates;
|
|
|
|
|
updates["print_stats"]["state"] = "error";
|
|
|
|
|
update_status_cache(updates);
|
|
|
|
|
updated = true;
|
|
|
|
|
is_critical = true; // Klippy events are critical
|
|
|
|
|
updated = true;
|
|
|
|
|
is_critical = true; // Klippy events are critical
|
|
|
|
|
// Set flag to trigger reconnection after dispatching the status update
|
|
|
|
|
ws_reconnect_requested.store(true);
|
|
|
|
|
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Klippy disconnected, triggering reconnection";
|
|
|
|
|
@@ -1313,29 +1185,25 @@ void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const s
|
|
|
|
|
std::string current_state;
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
|
|
|
|
if (status_cache.contains("print_stats") &&
|
|
|
|
|
status_cache["print_stats"].contains("state") &&
|
|
|
|
|
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("state") &&
|
|
|
|
|
status_cache["print_stats"]["state"].is_string()) {
|
|
|
|
|
current_state = status_cache["print_stats"]["state"].get<std::string>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!current_state.empty() && current_state != last_print_state) {
|
|
|
|
|
is_critical = true;
|
|
|
|
|
is_critical = true;
|
|
|
|
|
last_print_state = current_state;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (updated) {
|
|
|
|
|
const auto now_ms = static_cast<uint64_t>(
|
|
|
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
|
|
|
std::chrono::steady_clock::now().time_since_epoch()).count());
|
|
|
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());
|
|
|
|
|
const auto last_dispatch_ms = ws_last_dispatch_ms.load();
|
|
|
|
|
|
|
|
|
|
// Dispatch if: critical change OR throttle interval elapsed
|
|
|
|
|
const bool should_dispatch = is_critical ||
|
|
|
|
|
last_dispatch_ms == 0 ||
|
|
|
|
|
now_ms - last_dispatch_ms >= STATUS_UPDATE_INTERVAL_MS;
|
|
|
|
|
const bool should_dispatch = is_critical || last_dispatch_ms == 0 || now_ms - last_dispatch_ms >= STATUS_UPDATE_INTERVAL_MS;
|
|
|
|
|
|
|
|
|
|
if (should_dispatch) {
|
|
|
|
|
nlohmann::json message;
|
|
|
|
|
@@ -1346,7 +1214,7 @@ void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const s
|
|
|
|
|
|
|
|
|
|
dispatch_message(dev_id, message.dump());
|
|
|
|
|
ws_last_dispatch_ms.store(now_ms);
|
|
|
|
|
ws_last_emit_ms.store(now_ms); // Also update heartbeat timer
|
|
|
|
|
ws_last_emit_ms.store(now_ms); // Also update heartbeat timer
|
|
|
|
|
}
|
|
|
|
|
// else: skip dispatch, cache is updated for next dispatch cycle
|
|
|
|
|
}
|
|
|
|
|
@@ -1396,10 +1264,14 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
|
|
|
|
int mc_print_stage = 0;
|
|
|
|
|
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("state")) {
|
|
|
|
|
std::string mr_state = status_cache["print_stats"]["state"].get<std::string>();
|
|
|
|
|
if (mr_state == "printing") mc_print_stage = 1;
|
|
|
|
|
else if (mr_state == "paused") mc_print_stage = 2;
|
|
|
|
|
else if (mr_state == "complete") mc_print_stage = 3;
|
|
|
|
|
else if (mr_state == "error") mc_print_stage = 4;
|
|
|
|
|
if (mr_state == "printing")
|
|
|
|
|
mc_print_stage = 1;
|
|
|
|
|
else if (mr_state == "paused")
|
|
|
|
|
mc_print_stage = 2;
|
|
|
|
|
else if (mr_state == "complete")
|
|
|
|
|
mc_print_stage = 3;
|
|
|
|
|
else if (mr_state == "error")
|
|
|
|
|
mc_print_stage = 4;
|
|
|
|
|
}
|
|
|
|
|
payload["print"]["mc_print_stage"] = mc_print_stage;
|
|
|
|
|
|
|
|
|
|
@@ -1407,7 +1279,7 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
|
|
|
|
// UI expects numeric HMS codes - setting to 1 shows generic error dialog
|
|
|
|
|
// Only set if real mapping from Moonraker error strings to HMS codes is defined
|
|
|
|
|
payload["print"]["mc_print_error_code"] = 0;
|
|
|
|
|
payload["print"]["print_error"] = 0;
|
|
|
|
|
payload["print"]["print_error"] = 0;
|
|
|
|
|
|
|
|
|
|
// Map homed axes to bit field: X=bit0, Y=bit1, Z=bit2
|
|
|
|
|
// WARNING: This only sets bits 0-2, clearing support flags (bit 3+)
|
|
|
|
|
@@ -1416,21 +1288,23 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
|
|
|
|
int home_flag = 0;
|
|
|
|
|
if (status_cache.contains("toolhead") && status_cache["toolhead"].contains("homed_axes")) {
|
|
|
|
|
std::string homed = status_cache["toolhead"]["homed_axes"].get<std::string>();
|
|
|
|
|
if (homed.find('X') != std::string::npos) home_flag |= 1; // bit 0
|
|
|
|
|
if (homed.find('Y') != std::string::npos) home_flag |= 2; // bit 1
|
|
|
|
|
if (homed.find('Z') != std::string::npos) home_flag |= 4; // bit 2
|
|
|
|
|
if (homed.find('X') != std::string::npos)
|
|
|
|
|
home_flag |= 1; // bit 0
|
|
|
|
|
if (homed.find('Y') != std::string::npos)
|
|
|
|
|
home_flag |= 2; // bit 1
|
|
|
|
|
if (homed.find('Z') != std::string::npos)
|
|
|
|
|
home_flag |= 4; // bit 2
|
|
|
|
|
}
|
|
|
|
|
payload["print"]["home_flag"] = home_flag;
|
|
|
|
|
|
|
|
|
|
// Moonraker doesn't provide temperature ranges via API - use hardcoded defaults
|
|
|
|
|
payload["print"]["nozzle_temp_range"] = {100, 370}; // Typical Klipper range
|
|
|
|
|
payload["print"]["bed_temp_range"] = {0, 120}; // Typical bed range
|
|
|
|
|
payload["print"]["nozzle_temp_range"] = {100, 370}; // Typical Klipper range
|
|
|
|
|
payload["print"]["bed_temp_range"] = {0, 120}; // Typical bed range
|
|
|
|
|
|
|
|
|
|
payload["print"]["support_send_to_sd"] = true;
|
|
|
|
|
// Detect bed_leveling support from available objects (bed_mesh or probe)
|
|
|
|
|
// Default to 0 (not supported) if neither object exists
|
|
|
|
|
bool has_bed_leveling = (available_objects.count("bed_mesh") != 0 ||
|
|
|
|
|
available_objects.count("probe") != 0);
|
|
|
|
|
bool has_bed_leveling = (available_objects.count("bed_mesh") != 0 || available_objects.count("probe") != 0);
|
|
|
|
|
payload["print"]["support_bed_leveling"] = has_bed_leveling ? 1 : 0;
|
|
|
|
|
|
|
|
|
|
const nlohmann::json* extruder = nullptr;
|
|
|
|
|
@@ -1475,12 +1349,12 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
|
|
|
|
} else {
|
|
|
|
|
pwm = static_cast<int>(speed + 0.5);
|
|
|
|
|
}
|
|
|
|
|
pwm = std::clamp(pwm, 0, 255);
|
|
|
|
|
pwm = std::clamp(pwm, 0, 255);
|
|
|
|
|
payload["print"]["fan_gear"] = pwm;
|
|
|
|
|
} else if (fan.contains("power") && fan["power"].is_number()) {
|
|
|
|
|
double power = fan["power"].get<double>();
|
|
|
|
|
int pwm = static_cast<int>(power * 255.0 + 0.5);
|
|
|
|
|
pwm = std::clamp(pwm, 0, 255);
|
|
|
|
|
double power = fan["power"].get<double>();
|
|
|
|
|
int pwm = static_cast<int>(power * 255.0 + 0.5);
|
|
|
|
|
pwm = std::clamp(pwm, 0, 255);
|
|
|
|
|
payload["print"]["fan_gear"] = pwm;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1496,8 +1370,7 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int mc_percent = -1;
|
|
|
|
|
if (status_cache.contains("virtual_sdcard") &&
|
|
|
|
|
status_cache["virtual_sdcard"].contains("progress") &&
|
|
|
|
|
if (status_cache.contains("virtual_sdcard") && status_cache["virtual_sdcard"].contains("progress") &&
|
|
|
|
|
status_cache["virtual_sdcard"]["progress"].is_number()) {
|
|
|
|
|
const double progress = status_cache["virtual_sdcard"]["progress"].get<double>();
|
|
|
|
|
if (progress >= 0.0) {
|
|
|
|
|
@@ -1508,22 +1381,19 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
|
|
|
|
payload["print"]["mc_percent"] = mc_percent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status_cache.contains("print_stats") &&
|
|
|
|
|
status_cache["print_stats"].contains("total_duration") &&
|
|
|
|
|
status_cache["print_stats"].contains("print_duration") &&
|
|
|
|
|
status_cache["print_stats"]["total_duration"].is_number() &&
|
|
|
|
|
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("total_duration") &&
|
|
|
|
|
status_cache["print_stats"].contains("print_duration") && status_cache["print_stats"]["total_duration"].is_number() &&
|
|
|
|
|
status_cache["print_stats"]["print_duration"].is_number()) {
|
|
|
|
|
const double total = status_cache["print_stats"]["total_duration"].get<double>();
|
|
|
|
|
const double elapsed = status_cache["print_stats"]["print_duration"].get<double>();
|
|
|
|
|
if (total > 0.0 && elapsed >= 0.0) {
|
|
|
|
|
const auto remaining_minutes = std::max(0, static_cast<int>((total - elapsed) / 60.0));
|
|
|
|
|
const auto remaining_minutes = std::max(0, static_cast<int>((total - elapsed) / 60.0));
|
|
|
|
|
payload["print"]["mc_remaining_time"] = remaining_minutes;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto now_ms = static_cast<uint64_t>(
|
|
|
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
|
|
|
std::chrono::system_clock::now().time_since_epoch()).count());
|
|
|
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
|
|
|
|
|
payload["t_utc"] = now_ms;
|
|
|
|
|
|
|
|
|
|
return payload;
|
|
|
|
|
@@ -1563,13 +1433,12 @@ void MoonrakerPrinterAgent::dispatch_message(const std::string& dev_id, const st
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MoonrakerPrinterAgent::upload_gcode(
|
|
|
|
|
const std::string& local_path,
|
|
|
|
|
const std::string& filename,
|
|
|
|
|
const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
OnUpdateStatusFn update_fn,
|
|
|
|
|
WasCancelledFn cancel_fn)
|
|
|
|
|
bool MoonrakerPrinterAgent::upload_gcode(const std::string& local_path,
|
|
|
|
|
const std::string& filename,
|
|
|
|
|
const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
OnUpdateStatusFn update_fn,
|
|
|
|
|
WasCancelledFn cancel_fn)
|
|
|
|
|
{
|
|
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
|
|
|
|
|
|
@@ -1582,7 +1451,7 @@ bool MoonrakerPrinterAgent::upload_gcode(
|
|
|
|
|
|
|
|
|
|
// Check file size
|
|
|
|
|
std::uintmax_t file_size = fs::file_size(source_path);
|
|
|
|
|
if (file_size > 1024 * 1024 * 1024) { // 1GB limit
|
|
|
|
|
if (file_size > 1024 * 1024 * 1024) { // 1GB limit
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: File too large: " << file_size << " bytes";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
@@ -1590,7 +1459,7 @@ bool MoonrakerPrinterAgent::upload_gcode(
|
|
|
|
|
// Sanitize filename to prevent path traversal attacks
|
|
|
|
|
std::string safe_filename = sanitize_filename(filename);
|
|
|
|
|
|
|
|
|
|
bool result = true;
|
|
|
|
|
bool result = true;
|
|
|
|
|
std::string http_error;
|
|
|
|
|
|
|
|
|
|
// Use Http::form_add and Http::form_add_file
|
|
|
|
|
@@ -1598,11 +1467,11 @@ bool MoonrakerPrinterAgent::upload_gcode(
|
|
|
|
|
if (!api_key.empty()) {
|
|
|
|
|
http.header("X-Api-Key", api_key);
|
|
|
|
|
}
|
|
|
|
|
http.form_add("root", "gcodes") // Upload to gcodes directory
|
|
|
|
|
.form_add("print", "false") // Don't auto-start print
|
|
|
|
|
http.form_add("root", "gcodes") // Upload to gcodes directory
|
|
|
|
|
.form_add("print", "false") // Don't auto-start print
|
|
|
|
|
.form_add_file("file", source_path.string(), safe_filename)
|
|
|
|
|
.timeout_connect(5)
|
|
|
|
|
.timeout_max(300) // 5 minutes for large files
|
|
|
|
|
.timeout_max(300) // 5 minutes for large files
|
|
|
|
|
.on_complete([&](std::string body, unsigned status) {
|
|
|
|
|
(void) body;
|
|
|
|
|
(void) status;
|
|
|
|
|
@@ -1610,7 +1479,7 @@ bool MoonrakerPrinterAgent::upload_gcode(
|
|
|
|
|
.on_error([&](std::string body, std::string err, unsigned status) {
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Upload error: " << err << " HTTP " << status;
|
|
|
|
|
http_error = err;
|
|
|
|
|
result = false;
|
|
|
|
|
result = false;
|
|
|
|
|
})
|
|
|
|
|
.on_progress([&](Http::Progress progress, bool& cancel) {
|
|
|
|
|
// Check for cancellation via WasCancelledFn
|
|
|
|
|
@@ -1652,16 +1521,15 @@ int MoonrakerPrinterAgent::cancel_print(const std::string& dev_id)
|
|
|
|
|
return send_gcode(dev_id, gcode) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MoonrakerPrinterAgent::send_jsonrpc_command(
|
|
|
|
|
const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
const nlohmann::json& request,
|
|
|
|
|
std::string& response) const
|
|
|
|
|
bool MoonrakerPrinterAgent::send_jsonrpc_command(const std::string& base_url,
|
|
|
|
|
const std::string& api_key,
|
|
|
|
|
const nlohmann::json& request,
|
|
|
|
|
std::string& response) const
|
|
|
|
|
{
|
|
|
|
|
std::string request_str = request.dump();
|
|
|
|
|
std::string url = join_url(base_url, "/printer/print/start");
|
|
|
|
|
std::string url = join_url(base_url, "/printer/print/start");
|
|
|
|
|
|
|
|
|
|
bool success = false;
|
|
|
|
|
bool success = false;
|
|
|
|
|
std::string http_error;
|
|
|
|
|
|
|
|
|
|
auto http = Http::post(url);
|
|
|
|
|
@@ -1675,14 +1543,12 @@ bool MoonrakerPrinterAgent::send_jsonrpc_command(
|
|
|
|
|
.on_complete([&](std::string body, unsigned status) {
|
|
|
|
|
if (status == 200) {
|
|
|
|
|
response = body;
|
|
|
|
|
success = true;
|
|
|
|
|
success = true;
|
|
|
|
|
} else {
|
|
|
|
|
http_error = "HTTP " + std::to_string(status);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.on_error([&](std::string body, std::string err, unsigned status) {
|
|
|
|
|
http_error = err;
|
|
|
|
|
})
|
|
|
|
|
.on_error([&](std::string body, std::string err, unsigned status) { http_error = err; })
|
|
|
|
|
.perform_sync();
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
@@ -1692,39 +1558,22 @@ bool MoonrakerPrinterAgent::send_jsonrpc_command(
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MoonrakerPrinterAgent::perform_connection_async(
|
|
|
|
|
const std::string& dev_id,
|
|
|
|
|
const std::string& base_url,
|
|
|
|
|
const std::string& api_key)
|
|
|
|
|
void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id, const std::string& base_url, const std::string& api_key)
|
|
|
|
|
{
|
|
|
|
|
int result = BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
|
|
|
|
|
int result = BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
|
|
|
|
|
std::string error_msg;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Check Klippy state
|
|
|
|
|
nlohmann::json server_info;
|
|
|
|
|
if (!fetch_server_info_json(base_url, api_key, server_info, error_msg)) {
|
|
|
|
|
if (!fetch_device_info(base_url, api_key, device_info, error_msg)) {
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Failed to fetch server info: " << error_msg;
|
|
|
|
|
dispatch_local_connect(ConnectStatusFailed, dev_id, "server_info_failed");
|
|
|
|
|
finish_connection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nlohmann::json result_json = server_info.contains("result")
|
|
|
|
|
? server_info["result"] : server_info;
|
|
|
|
|
std::string klippy_state = result_json.value("klippy_state", "");
|
|
|
|
|
|
|
|
|
|
// Check state
|
|
|
|
|
if (klippy_state != "ready" && result == BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED) {
|
|
|
|
|
std::string state_message = result_json.value("state_message", "Unknown error");
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Klippy not ready: " << klippy_state
|
|
|
|
|
<< " - " << state_message;
|
|
|
|
|
error_msg = "klippy_not_ready:" + klippy_state;
|
|
|
|
|
dispatch_local_connect(ConnectStatusFailed, dev_id, error_msg);
|
|
|
|
|
// Orca todo: revist here, for now don't send error, this is set current MachineObject to null
|
|
|
|
|
// dispatch_local_connect(ConnectStatusFailed, dev_id, "server_info_failed");
|
|
|
|
|
finish_connection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Orca todo: disable websocket for now, as we don't use MonitorPanel for Moonraker printers yet
|
|
|
|
|
#if 0
|
|
|
|
|
// Query initial status
|
|
|
|
|
nlohmann::json initial_status;
|
|
|
|
|
if (query_printer_status(base_url, api_key, initial_status, error_msg)) {
|
|
|
|
|
@@ -1738,6 +1587,7 @@ void MoonrakerPrinterAgent::perform_connection_async(
|
|
|
|
|
|
|
|
|
|
// Start WebSocket status stream
|
|
|
|
|
start_status_stream(dev_id, base_url, api_key);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Success!
|
|
|
|
|
result = BAMBU_NETWORK_SUCCESS;
|
|
|
|
|
@@ -1745,7 +1595,7 @@ void MoonrakerPrinterAgent::perform_connection_async(
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Connection exception: " << e.what();
|
|
|
|
|
error_msg = std::string("exception: ") + e.what();
|
|
|
|
|
result = BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
|
|
|
|
|
result = BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dispatch final result to UI
|
|
|
|
|
@@ -1754,7 +1604,8 @@ void MoonrakerPrinterAgent::perform_connection_async(
|
|
|
|
|
dispatch_printer_connected(dev_id);
|
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: connect_printer completed - dev_id=" << dev_id;
|
|
|
|
|
} else if (result != BAMBU_NETWORK_ERR_CANCELED) {
|
|
|
|
|
dispatch_local_connect(ConnectStatusFailed, dev_id, error_msg);
|
|
|
|
|
// Orca todo: revist here, for now don't send error, this is set current MachineObject to null
|
|
|
|
|
// dispatch_local_connect(ConnectStatusFailed, dev_id, error_msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finish_connection();
|
|
|
|
|
@@ -1811,7 +1662,6 @@ std::string MoonrakerPrinterAgent::join_url(const std::string& base_url, const s
|
|
|
|
|
return base_url + path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Sanitize filename to prevent path traversal attacks
|
|
|
|
|
// Extracts only the basename, removing any path components
|
|
|
|
|
std::string MoonrakerPrinterAgent::sanitize_filename(const std::string& filename)
|
|
|
|
|
@@ -1820,7 +1670,7 @@ std::string MoonrakerPrinterAgent::sanitize_filename(const std::string& filename
|
|
|
|
|
return "print.gcode";
|
|
|
|
|
}
|
|
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
|
fs::path p(filename);
|
|
|
|
|
fs::path p(filename);
|
|
|
|
|
std::string basename = p.filename().string();
|
|
|
|
|
if (basename.empty() || basename == "." || basename == "..") {
|
|
|
|
|
return "print.gcode";
|
|
|
|
|
|