diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8a412f6a9b..eaa5b1edcd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3208,7 +3208,9 @@ std::map Sidebar::build_filament_ams_list(MachineObject // For pull-mode agents (e.g., HTTP REST API), refresh DevFilaSystem first auto* agent = wxGetApp().getDeviceManager()->get_agent(); if (agent && agent->get_filament_sync_mode() == FilamentSyncMode::pull) { - agent->fetch_filament_info(obj->get_dev_id()); + if (!agent->fetch_filament_info(obj->get_dev_id())) { + return filament_ams_list; + } } auto build_tray_config = [](DevAmsTray const &tray, std::string const &name, std::string ams_id, std::string slot_id) { @@ -16631,8 +16633,15 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar { printer_name.Replace("Bambu Lab", "", false); wxString content; + bool device_page = (wxGetApp().mainframe == nullptr) && (wxGetApp().mainframe->m_monitor->IsShown()); if (type == PrinterWarningType::NOT_CONNECTED) { - content = wxString::Format(_L("Printer not connected. Please go to the device page to connect %s before syncing."), printer_name); + if (device_page) { + content = wxString::Format(_L("Printer not connected. Please go to the device page to connect %s before syncing."), + printer_name); + } else { + content = wxString::Format( + _L("OrcaSlicer can't connect to %s. Please check if the printer is powered on and connected to the network."), printer_name); + } } else if (type == PrinterWarningType::INCONSISTENT) { content = wxString::Format(_L("The currently connected printer on the device page is not %s. Please switch to %s before syncing."), printer_name, printer_name); } else if (type == PrinterWarningType::UNINSTALL_FILAMENT) { diff --git a/src/slic3r/Utils/IPrinterAgent.hpp b/src/slic3r/Utils/IPrinterAgent.hpp index 3af9cf32f8..76c082af50 100644 --- a/src/slic3r/Utils/IPrinterAgent.hpp +++ b/src/slic3r/Utils/IPrinterAgent.hpp @@ -252,7 +252,7 @@ public: * Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull. * Populates the MachineObject's DevFilaSystem with fetched filament data. */ - virtual void fetch_filament_info(std::string dev_id) {} + virtual bool fetch_filament_info(std::string dev_id) { return false; } }; } // namespace Slic3r diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index e5c10bb74b..768d0c2f31 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -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 #include @@ -17,17 +15,15 @@ #include #include #include -#include #include 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 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 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 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 temp = json["print"]["temp"].get(); 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 extruder_idx = 0; // Default to main extruder + int temp = json["print"]["target_temp"].get(); + 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(); } 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(); - } else if (result.contains("version") && result["version"].is_string()) { - version = result["version"].get(); - } - - 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& objects, - std::string& error) const +bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url, + const std::string& api_key, + std::set& 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 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( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); + std::chrono::duration_cast(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(); - 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 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(); } } 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( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); + std::chrono::duration_cast(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(); - 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(); - 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(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(); - int pwm = static_cast(power * 255.0 + 0.5); - pwm = std::clamp(pwm, 0, 255); + double power = fan["power"].get(); + int pwm = static_cast(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(); 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(); const double elapsed = status_cache["print_stats"]["print_duration"].get(); if (total > 0.0 && elapsed >= 0.0) { - const auto remaining_minutes = std::max(0, static_cast((total - elapsed) / 60.0)); + const auto remaining_minutes = std::max(0, static_cast((total - elapsed) / 60.0)); payload["print"]["mc_remaining_time"] = remaining_minutes; } } const auto now_ms = static_cast( - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()); + std::chrono::duration_cast(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"; diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index 94d32a4016..29b7f98715 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -71,7 +71,7 @@ public: // Pull-mode agent (on-demand filament sync) virtual FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; } - virtual void fetch_filament_info(std::string dev_id) override; + virtual bool fetch_filament_info(std::string dev_id) override; protected: struct MoonrakerDeviceInfo @@ -84,6 +84,7 @@ protected: std::string model_name; std::string dev_name; std::string version; + std::string klippy_state; bool use_ssl = false; } device_info; @@ -105,7 +106,6 @@ private: int send_version_info(const std::string& dev_id); int send_access_code(const std::string& dev_id); - bool fetch_server_info(const std::string& base_url, const std::string& api_key, std::string& version, std::string& error) const; bool fetch_object_list(const std::string& base_url, const std::string& api_key, std::set& objects, std::string& error) const; bool query_printer_status(const std::string& base_url, const std::string& api_key, nlohmann::json& status, std::string& error) const; bool send_gcode(const std::string& dev_id, const std::string& gcode) const; @@ -135,10 +135,6 @@ private: bool send_jsonrpc_command(const std::string& base_url, const std::string& api_key, const nlohmann::json& request, std::string& response) const; - // Server info (returns JSON, not just version string) - bool fetch_server_info_json(const std::string& base_url, const std::string& api_key, - nlohmann::json& info, std::string& error) const; - // Connection thread management void perform_connection_async(const std::string& dev_id, const std::string& base_url, diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index b893593ce2..16d82ba8dc 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -597,11 +597,12 @@ FilamentSyncMode NetworkAgent::get_filament_sync_mode() const return FilamentSyncMode::none; // Default when no agent } -void NetworkAgent::fetch_filament_info(std::string dev_id) +bool NetworkAgent::fetch_filament_info(std::string dev_id) { if (m_printer_agent) { - m_printer_agent->fetch_filament_info(dev_id); + return m_printer_agent->fetch_filament_info(dev_id); } + return false; } int NetworkAgent::get_user_presets(std::map>* user_presets) diff --git a/src/slic3r/Utils/NetworkAgent.hpp b/src/slic3r/Utils/NetworkAgent.hpp index 09cee2a45e..402497fc19 100644 --- a/src/slic3r/Utils/NetworkAgent.hpp +++ b/src/slic3r/Utils/NetworkAgent.hpp @@ -115,7 +115,7 @@ public: int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); FilamentSyncMode get_filament_sync_mode() const; - void fetch_filament_info(std::string dev_id); + bool fetch_filament_info(std::string dev_id); int get_user_presets(std::map>* user_presets); std::string request_setting_id(std::string name, std::map* values_map, unsigned int* http_code); int put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code); diff --git a/src/slic3r/Utils/QidiPrinterAgent.cpp b/src/slic3r/Utils/QidiPrinterAgent.cpp index b5dce54c3c..b0f4847c4c 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.cpp +++ b/src/slic3r/Utils/QidiPrinterAgent.cpp @@ -24,18 +24,18 @@ AgentInfo QidiPrinterAgent::get_agent_info_static() return AgentInfo{.id = "qidi", .name = "Qidi Printer Agent", .version = QidiPrinterAgent_VERSION, .description = "Qidi printer agent"}; } -void QidiPrinterAgent::fetch_filament_info(std::string dev_id) +bool QidiPrinterAgent::fetch_filament_info(std::string dev_id) { // Look up MachineObject via DeviceManager auto* dev_manager = GUI::wxGetApp().getDeviceManager(); if (!dev_manager) { BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::fetch_filament_info: DeviceManager is null"; - return; + return false; } MachineObject* obj = dev_manager->get_my_machine(dev_id); if (!obj) { BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::fetch_filament_info: MachineObject not found for dev_id=" << dev_id; - return; + return false; } std::vector slots; @@ -43,7 +43,7 @@ void QidiPrinterAgent::fetch_filament_info(std::string dev_id) std::string error; if (!fetch_slot_info(device_info.base_url, device_info.api_key, slots, box_count, error)) { BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch slot info: " << error; - return; + return false; } QidiFilamentDict dict; @@ -141,6 +141,7 @@ void QidiPrinterAgent::fetch_filament_info(std::string dev_id) // Call the parser to populate DevFilaSystem DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem(), false); + return true; } bool QidiPrinterAgent::fetch_slot_info(const std::string& base_url, @@ -162,8 +163,8 @@ bool QidiPrinterAgent::fetch_slot_info(const std::string& base_url, if (!api_key.empty()) { http.header("X-Api-Key", api_key); } - http.timeout_connect(10) - .timeout_max(30) + http.timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status) { if (status == 200) { response_body = body; @@ -246,8 +247,8 @@ bool QidiPrinterAgent::fetch_filament_dict(const std::string& base_url, if (!api_key.empty()) { http.header("X-Api-Key", api_key); } - http.timeout_connect(10) - .timeout_max(30) + http.timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status) { if (status == 200) { response_body = body; diff --git a/src/slic3r/Utils/QidiPrinterAgent.hpp b/src/slic3r/Utils/QidiPrinterAgent.hpp index 8657443288..27556ebd92 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.hpp +++ b/src/slic3r/Utils/QidiPrinterAgent.hpp @@ -19,7 +19,7 @@ public: AgentInfo get_agent_info() override { return get_agent_info_static(); } // Override filament sync (Qidi-specific implementation) - void fetch_filament_info(std::string dev_id) override; + bool fetch_filament_info(std::string dev_id) override; private: // Qidi-specific device info (extends base MoonrakerDeviceInfo with model_id)