diff --git a/src/slic3r/Utils/Moonraker.cpp b/src/slic3r/Utils/Moonraker.cpp index ee437595ef..a8b67871ff 100644 --- a/src/slic3r/Utils/Moonraker.cpp +++ b/src/slic3r/Utils/Moonraker.cpp @@ -42,12 +42,25 @@ wxString Moonraker::get_test_failed_msg(wxString &msg) const std::string Moonraker::make_url(const std::string &path) const { - if (m_host.find("http://") == 0 || m_host.find("https://") == 0) { - if (m_host.back() == '/') - return (boost::format("%1%%2%") % m_host % path).str(); - return (boost::format("%1%/%2%") % m_host % path).str(); + const bool has_scheme = m_host.find("http://") == 0 || m_host.find("https://") == 0; + const bool use_ssl = m_host.find("https://") == 0; + std::string base = has_scheme ? m_host : ("http://" + m_host); + + const size_t authority_start = base.find("://") + 3; + const size_t authority_end = base.find('/', authority_start); + const std::string authority = base.substr(authority_start, authority_end - authority_start); + const size_t closing_bracket = authority.rfind(']'); + const bool has_port = closing_bracket != std::string::npos + ? closing_bracket + 1 < authority.size() && authority[closing_bracket + 1] == ':' + : authority.find(':') != std::string::npos; + if (!has_port) { + const std::string default_port = use_ssl ? ":7130" : ":7125"; + base.insert(authority_end == std::string::npos ? base.size() : authority_end, default_port); } - return (boost::format("http://%1%/%2%") % m_host % path).str(); + + if (base.back() == '/') + return (boost::format("%1%%2%") % base % path).str(); + return (boost::format("%1%/%2%") % base % path).str(); } void Moonraker::set_auth(Http &http) const @@ -57,6 +70,8 @@ void Moonraker::set_auth(Http &http) const // filled the user/password fields — those are PrusaLink/OctoPrint conventions. if (!m_apikey.empty()) http.header("X-Api-Key", m_apikey); + const bool use_ssl = m_host.find("https://") == 0; + http.tls_verify(use_ssl); if (!m_cafile.empty()) http.ca_file(m_cafile); } diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 85d20688d8..a537f17a15 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -1,6 +1,7 @@ #include "MoonrakerPrinterAgent.hpp" #include "Http.hpp" #include "IPrinterAgent.hpp" +#include "bambu_networking.hpp" #include "libslic3r/Preset.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Utils.hpp" @@ -84,7 +85,7 @@ bool parse_ws_endpoint(const std::string& base_url, WsEndpoint& endpoint) } endpoint.host = url; - endpoint.port = endpoint.secure ? "443" : "80"; + endpoint.port = endpoint.secure ? "7130" : "7125"; if (auto colon = url.rfind(':'); colon != std::string::npos && url.find(']') == std::string::npos) { endpoint.host = url.substr(0, colon); endpoint.port = url.substr(colon + 1); @@ -123,9 +124,16 @@ struct MoonrakerWebsocket::Impl using PlainPtr = std::unique_ptr; using SecurePtr = std::unique_ptr; - explicit Impl(bool secure, std::string api_key) : secure(secure), api_key(std::move(api_key)), ssl_context(net::ssl::context::tls_client) + explicit Impl(bool secure, std::string api_key, std::string ca_file) + : secure(secure), api_key(std::move(api_key)), ca_file(std::move(ca_file)), ssl_context(net::ssl::context::tls_client) { if (this->secure) { + if (!this->ca_file.empty()) { + ssl_context.load_verify_file(this->ca_file); + } else { + ssl_context.set_default_verify_paths(); + } + ssl_context.set_verify_mode(net::ssl::verify_peer); websocket = std::make_unique(ioc, ssl_context); } else { websocket = std::make_unique(beast::tcp_stream{ioc}); @@ -134,12 +142,15 @@ struct MoonrakerWebsocket::Impl bool secure; std::string api_key; + std::string ca_file; net::io_context ioc; net::ssl::context ssl_context; std::variant websocket; }; -MoonrakerWebsocket::MoonrakerWebsocket(bool secure, std::string api_key) : m_impl(std::make_unique(secure, std::move(api_key))) {} +MoonrakerWebsocket::MoonrakerWebsocket(bool secure, std::string api_key, std::string ca_file) + : m_impl(std::make_unique(secure, std::move(api_key), std::move(ca_file))) +{} MoonrakerWebsocket::~MoonrakerWebsocket() = default; @@ -167,9 +178,6 @@ void MoonrakerWebsocket::tls_handshake(const std::string& host) throw std::runtime_error("Moonraker WSS: failed to set TLS server name"); } - // Match Http's existing printer-host behavior: encrypt the connection - // while accepting self-signed/local printer certificates. - tls_stream.set_verify_mode(net::ssl::verify_none); tls_stream.handshake(net::ssl::stream_base::client); } @@ -361,15 +369,17 @@ int MoonrakerPrinterAgent::connect_printer(const PrinterConnectionParams& params BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing dev_id or dev_ip"; return BAMBU_NETWORK_ERR_INVALID_HANDLE; } - std::string base_url; - std::string api_key; + ConnectionSettings connection; uint64_t gen; { std::lock_guard lock(connect_mutex); - init_device_info(params.dev_id, params.host, params.username, params.password, params.use_ssl, params.port); + init_device_info(params); gen = ++connect_generation; - base_url = device_info.base_url; - api_key = device_info.api_key; + connection.dev_id = device_info.dev_id; + connection.base_url = device_info.base_url; + connection.api_key = device_info.api_key; + connection.use_ssl = device_info.use_ssl; + connection.ca_file = device_info.ca_file; if (connect_thread.joinable()) { connect_thread.detach(); } @@ -393,7 +403,9 @@ int MoonrakerPrinterAgent::connect_printer(const PrinterConnectionParams& params // Launch connection in background thread (capture by value to avoid data races) { std::lock_guard lock(connect_mutex); - connect_thread = std::thread([this, dev_id = params.dev_id, base_url, api_key, gen]() { perform_connection_async(dev_id, base_url, api_key, gen); }); + connect_thread = std::thread([this, dev_id = params.dev_id, connection = std::move(connection), gen]() mutable { + perform_connection_async(dev_id, std::move(connection), gen); + }); } return BAMBU_NETWORK_SUCCESS; @@ -441,7 +453,9 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, // so the name falls back to the IP instead of blank. (matches // feature/printer-agent-port-pristine; the IP is what shipped before the port) // note: dummy id/creds; use_ssl false because Moonraker/print-host is http. - init_device_info(dev_ip, dev_ip, "", "", false); + init_device_info(PrinterConnectionParams{ + dev_ip, dev_ip, "", "", "", false, "" + }); 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; @@ -503,6 +517,7 @@ int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnWaitFn wait_fn) { (void) wait_fn; + const ConnectionSettings connection = get_connection_settings(); if (update_fn) update_fn(PrintingStageCreate, 0, "Preparing..."); @@ -519,7 +534,7 @@ int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, std::string safe_filename = sanitize_filename(filename); // Upload only, don't start print - if (!upload_gcode(params.filename, safe_filename, device_info.base_url, device_info.api_key, update_fn, cancel_fn)) { + if (!upload_gcode(params.filename, safe_filename, connection, update_fn, cancel_fn)) { return BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED; } @@ -530,6 +545,7 @@ int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) { + const ConnectionSettings connection = get_connection_settings(); if (update_fn) update_fn(PrintingStageCreate, 0, "Preparing..."); @@ -580,7 +596,7 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF // Upload file 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)) { + if (!upload_gcode(gcode_path, upload_filename, connection, update_fn, cancel_fn)) { return BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED; } @@ -592,9 +608,8 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF // Start print via Moonraker's print API, referencing the file we just uploaded. if (update_fn) update_fn(PrintingStageSending, 0, "Starting print..."); - - std::string start_error; - if (!start_print_file(device_info.base_url, device_info.api_key, upload_filename, start_error)) { + std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename; + if (!send_gcode(connection.dev_id, gcode, connection)) { return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED; } @@ -816,11 +831,12 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync std::vector trays; int max_lane_index = 0; + const ConnectionSettings connection = get_connection_settings(); // Try Moonraker filament data (more generic, supports any filament changer // software that reports lane data to Moonraker like AFC and recent Happy // Hare as of Feb 15, 2026) - if (fetch_moonraker_filament_data(trays, max_lane_index)) { + if (fetch_moonraker_filament_data(connection, trays, max_lane_index)) { BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Moonraker filament system with " << (max_lane_index + 1) << " lanes"; int ams_count = (max_lane_index + 4) / 4; @@ -829,7 +845,7 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync } // Attempt Happy Hare first (more widely adopted, supports more filament changers) - if (fetch_hh_filament_info(trays, max_lane_index)) { + if (fetch_hh_filament_info(connection, trays, max_lane_index)) { BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with " << (max_lane_index + 1) << " gates"; int ams_count = (max_lane_index + 4) / 4; @@ -858,20 +874,22 @@ std::string MoonrakerPrinterAgent::get_camera_url() const void MoonrakerPrinterAgent::refresh_webcam_info() const { - std::string base_url; - std::string api_key; + ConnectionSettings connection; uint64_t generation; { std::lock_guard lock(connect_mutex); - base_url = device_info.base_url; - api_key = device_info.api_key; + connection.dev_id = device_info.dev_id; + connection.base_url = device_info.base_url; + connection.api_key = device_info.api_key; + connection.use_ssl = device_info.use_ssl; + connection.ca_file = device_info.ca_file; generation = connect_generation.load(); } const uint64_t now_ms = static_cast( std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); - if (base_url.empty()) { + if (connection.base_url.empty()) { std::lock_guard lock(payload_mutex); webcam_stream_url.clear(); webcam_stream_mode = CameraStreamMode::none; @@ -890,7 +908,7 @@ void MoonrakerPrinterAgent::refresh_webcam_info() const webcam_info_last_lookup_ms = now_ms; } - fetch_webcam_info(base_url, api_key, generation); + fetch_webcam_info(connection, generation); } std::string MoonrakerPrinterAgent::trim_and_upper(const std::string& input) @@ -1040,18 +1058,20 @@ std::string MoonrakerPrinterAgent::normalize_color_value(const std::string& colo } // Fetch filament info from moonraker database -bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector& trays, int& max_lane_index) +bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(const ConnectionSettings& connection, + std::vector& trays, int& max_lane_index) { // Fetch lane data from Moonraker database - std::string url = join_url(device_info.base_url, "/server/database/item?namespace=lane_data"); + std::string url = join_url(connection.base_url, "/server/database/item?namespace=lane_data"); std::string response_body; bool success = false; std::string http_error; auto http = Http::get(url); - if (!device_info.api_key.empty()) { - http.header("X-Api-Key", device_info.api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(5) .timeout_max(10) @@ -1138,18 +1158,20 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector& trays, int& max_lane_index) +bool MoonrakerPrinterAgent::fetch_hh_filament_info(const ConnectionSettings& connection, + std::vector& trays, int& max_lane_index) { // Query Happy Hare MMU status - std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu"); + std::string url = join_url(connection.base_url, "/printer/objects/query?mmu"); std::string response_body; bool success = false; std::string http_error; auto http = Http::get(url); - if (!device_info.api_key.empty()) { - http.header("X-Api-Key", device_info.api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(5) .timeout_max(10) @@ -1268,8 +1290,7 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector& tra int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::string& json_str) { auto connection_snapshot = [this]() { - std::lock_guard lock(connect_mutex); - return std::make_pair(device_info.base_url, device_info.api_key); + return get_connection_settings(); }; auto json = nlohmann::json::parse(json_str, nullptr, false); if (json.is_discarded()) { @@ -1347,10 +1368,9 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: ledctrl - no light object found, dropping"; return ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE; } - auto [base_url, api_key] = connection_snapshot(); - enqueue_command([this, dev_id, gcode = std::move(gcode), base_url = std::move(base_url), - api_key = std::move(api_key), requested_light_on]() { - if (send_gcode(dev_id, gcode, base_url, api_key)) { + auto connection = connection_snapshot(); + enqueue_command([this, dev_id, gcode = std::move(gcode), connection = std::move(connection), requested_light_on]() { + if (send_gcode(dev_id, gcode, connection)) { std::lock_guard lock(payload_mutex); assumed_light_on = requested_light_on; } @@ -1412,23 +1432,23 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: // Print control commands if (cmd == "pause") { - auto [base_url, api_key] = connection_snapshot(); - enqueue_command([this, base_url = std::move(base_url), api_key = std::move(api_key)] { - post_print_action("pause", base_url, api_key); + auto connection = connection_snapshot(); + enqueue_command([this, connection = std::move(connection)] { + post_print_action("pause", connection); }); return BAMBU_NETWORK_SUCCESS; } if (cmd == "resume") { - auto [base_url, api_key] = connection_snapshot(); - enqueue_command([this, base_url = std::move(base_url), api_key = std::move(api_key)] { - post_print_action("resume", base_url, api_key); + auto connection = connection_snapshot(); + enqueue_command([this, connection = std::move(connection)] { + post_print_action("resume", connection); }); return BAMBU_NETWORK_SUCCESS; } if (cmd == "stop") { - auto [base_url, api_key] = connection_snapshot(); - enqueue_command([this, base_url = std::move(base_url), api_key = std::move(api_key)] { - post_print_action("cancel", base_url, api_key); + auto connection = connection_snapshot(); + enqueue_command([this, connection = std::move(connection)] { + post_print_action("cancel", connection); }); return BAMBU_NETWORK_SUCCESS; } @@ -1502,7 +1522,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } -bool MoonrakerPrinterAgent::init_device_info(const std::string& dev_id, const std::string& dev_ip, const std::string& username, const std::string& password, bool use_ssl, const std::string& port) +bool MoonrakerPrinterAgent::init_device_info(const PrinterConnectionParams& params) { device_info = MoonrakerDeviceInfo{}; auto* preset_bundle = GUI::wxGetApp().preset_bundle; @@ -1513,19 +1533,40 @@ bool MoonrakerPrinterAgent::init_device_info(const std::string& dev_id, const st auto& preset = preset_bundle->printers.get_edited_preset(); const auto& printer_cfg = preset.config; - device_info.dev_ip = dev_ip; - device_info.api_key = password; - device_info.use_ssl = use_ssl; + device_info.dev_ip = params.host; + device_info.api_key = params.password; + device_info.use_ssl = params.use_ssl; device_info.model_name = printer_cfg.opt_string("printer_model"); device_info.model_id = preset.get_printer_type(preset_bundle); - device_info.base_url = normalize_base_url(use_ssl, dev_ip, port); - device_info.dev_id = dev_id; + device_info.base_url = normalize_base_url(params.use_ssl, params.host, params.port); + device_info.dev_id = params.dev_id; device_info.version = ""; device_info.dev_name = device_info.dev_id; + device_info.ca_file = params.ca_file; return true; } +MoonrakerPrinterAgent::ConnectionSettings MoonrakerPrinterAgent::get_connection_settings() const +{ + std::lock_guard lock(connect_mutex); + ConnectionSettings connection; + connection.dev_id = device_info.dev_id; + connection.base_url = device_info.base_url; + connection.api_key = device_info.api_key; + connection.use_ssl = device_info.use_ssl; + connection.ca_file = device_info.ca_file; + return connection; +} + +void MoonrakerPrinterAgent::configure_http(Http& http, const ConnectionSettings& connection) const +{ + http.tls_verify(connection.use_ssl); + if (!connection.ca_file.empty()) { + http.ca_file(connection.ca_file); + } +} + float MoonrakerPrinterAgent::parse_nozzle_diameter(const nlohmann::json& response) { const nlohmann::json* status = nullptr; @@ -1571,8 +1612,7 @@ float MoonrakerPrinterAgent::parse_nozzle_diameter(const nlohmann::json& respons return 0.0f; } -bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, - const std::string& api_key, +bool MoonrakerPrinterAgent::fetch_device_info(const ConnectionSettings& connection, MoonrakerDeviceInfo& info, std::string& error) const { @@ -1582,8 +1622,9 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, std::string http_error; auto http = Http::get(url); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(5) .timeout_max(10) @@ -1617,7 +1658,7 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, }; nlohmann::json json; - std::string url = join_url(base_url, "/server/info"); + std::string url = join_url(connection.base_url, "/server/info"); if (!fetch_json(url, json, error)) { return false; } @@ -1632,7 +1673,7 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, // remain connectable when they do not expose configfile through the API. nlohmann::json config_response; std::string config_error; - if (fetch_json(join_url(base_url, "/printer/objects/query?configfile"), config_response, config_error)) { + if (fetch_json(join_url(connection.base_url, "/printer/objects/query?configfile"), config_response, config_error)) { info.nozzle_diameter = parse_nozzle_diameter(config_response); } else { BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: nozzle configuration unavailable: " << config_error; @@ -1641,20 +1682,20 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, return true; } -bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url, - const std::string& api_key, +bool MoonrakerPrinterAgent::query_printer_status(const ConnectionSettings& connection, 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"); + std::string url = join_url(connection.base_url, "/printer/objects/query?print_stats&virtual_sdcard&extruder&heater_bed&fan"); std::string response_body; bool success = false; std::string http_error; auto http = Http::get(url); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(5) .timeout_max(10) @@ -1696,18 +1737,12 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url, bool MoonrakerPrinterAgent::send_ws_rpc(const std::string& method, const nlohmann::json& params) { - std::string base_url; - std::string api_key; - { - std::lock_guard lock(connect_mutex); - base_url = device_info.base_url; - api_key = device_info.api_key; - } + const ConnectionSettings connection = get_connection_settings(); WsEndpoint endpoint; - if (!parse_ws_endpoint(base_url, endpoint)) { + if (!parse_ws_endpoint(connection.base_url, endpoint)) { BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: send_ws_rpc has no usable websocket for base_url=" - << base_url; + << connection.base_url; return false; } @@ -1721,18 +1756,19 @@ bool MoonrakerPrinterAgent::send_ws_rpc(const std::string& method, const nlohman const std::string body = request.dump(); std::vector ports{endpoint.port}; - if (endpoint.port != "7125") { - ports.emplace_back("7125"); + const std::string default_port = endpoint.secure ? "7130" : "7125"; + if (endpoint.port != default_port) { + ports.emplace_back(default_port); } for (const auto& port : ports) { try { - MoonrakerWebsocket ws{endpoint.secure, api_key}; + MoonrakerWebsocket ws{endpoint.secure, connection.api_key, connection.ca_file}; ws.connect(endpoint.host, port, std::chrono::seconds(5)); ws.tls_handshake(endpoint.host); std::string host_header = endpoint.host; - if (!port.empty() && port != (endpoint.secure ? "443" : "80")) { + if (!port.empty() && port != default_port) { host_header += ":" + port; } ws.handshake(host_header, endpoint.target); @@ -1757,7 +1793,7 @@ bool MoonrakerPrinterAgent::send_ws_rpc(const std::string& method, const nlohman return false; } -bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const std::string& api_key, uint64_t generation) const +bool MoonrakerPrinterAgent::fetch_webcam_info(const ConnectionSettings& connection, uint64_t generation) const { std::string camera_url; std::string webcam_name; @@ -1768,9 +1804,10 @@ bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const bool success = false; std::string http_error; - auto http = Http::get(join_url(base_url, "/server/webcams/list")); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + auto http = Http::get(join_url(connection.base_url, "/server/webcams/list")); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(5) .timeout_max(10) @@ -1793,6 +1830,7 @@ bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const if (!success) { error = http_error.empty() ? "Connection failed" : http_error; } else { + BOOST_LOG_TRIVIAL(info) << "[Moonraker Diagnostic] " << connection.base_url << ":" << response_body; auto json = nlohmann::json::parse(response_body, nullptr, false, true); if (json.is_discarded()) { error = "Invalid JSON response"; @@ -1802,7 +1840,8 @@ bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const error = "Unexpected JSON structure"; } else { for (const auto& webcam : result["webcams"]) { - if (webcam.is_object() && webcam.value("enabled", false)) { + if (webcam.is_object()) + { if (webcam.contains("stream_url") && webcam["stream_url"].is_string() && !webcam["stream_url"].get().empty()) { camera_url = webcam["stream_url"].get(); @@ -1829,11 +1868,11 @@ bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const if (error.empty()) { if (camera_url.rfind("http", 0) != 0 && !camera_url.empty() && camera_url.front() == '/') { // why: Moonraker's API port serves a JSON 404 for /webcam; relative camera URLs use the printer web root. - const size_t scheme_end = base_url.find("://"); + const size_t scheme_end = connection.base_url.find("://"); const size_t authority_start = scheme_end == std::string::npos ? 0 : scheme_end + 3; - const size_t authority_end = base_url.find('/', authority_start); - const std::string scheme = scheme_end == std::string::npos ? "" : base_url.substr(0, scheme_end + 3); - std::string authority = base_url.substr(authority_start, authority_end - authority_start); + const size_t authority_end = connection.base_url.find('/', authority_start); + const std::string scheme = scheme_end == std::string::npos ? "" : connection.base_url.substr(0, scheme_end + 3); + std::string authority = connection.base_url.substr(authority_start, authority_end - authority_start); const size_t port_start = authority.rfind(':'); if (port_start != std::string::npos && port_start + 1 < authority.size() && std::all_of(authority.begin() + port_start + 1, authority.end(), [](char c) { return c >= '0' && c <= '9'; })) { @@ -1869,18 +1908,11 @@ bool MoonrakerPrinterAgent::post_print_action(const std::string& action) const { // why: snapshot then release - holding connect_mutex across the blocking HTTP call // would stall any UI-thread handle_request waiting to take its own snapshot. - std::string base_url, api_key; - { - std::lock_guard lock(connect_mutex); - base_url = device_info.base_url; - api_key = device_info.api_key; - } - return post_print_action(action, base_url, api_key); + return post_print_action(action, get_connection_settings()); } bool MoonrakerPrinterAgent::post_print_action(const std::string& action, - const std::string& base_url, - const std::string& api_key) const + const ConnectionSettings& connection) const { // why: /printer/print/{pause,resume,cancel} map to Klipper's pause_resume // webhook (a direct interrupt). A raw PAUSE/RESUME/CANCEL_PRINT queued through @@ -1888,13 +1920,14 @@ bool MoonrakerPrinterAgent::post_print_action(const std::string& action, // printer is busy (long move, heating, inside a macro). // note: empty JSON body avoids a body-less POST (curl would treat it as a // streamed upload) - same reason start_print_file sends a body. - const std::string full_url = join_url(base_url, "/printer/print/" + action); + const std::string full_url = join_url(connection.base_url, "/printer/print/" + action); bool success = false; std::string http_error; auto http = Http::post(full_url); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.header("Content-Type", "application/json") .set_post_body(std::string("{}")) @@ -1925,20 +1958,53 @@ bool MoonrakerPrinterAgent::post_print_action(const std::string& action, return true; } +void MoonrakerPrinterAgent::send_gcode_async(const std::string& dev_id, const std::string& gcode, + std::function on_result) const +{ + (void) dev_id; + const ConnectionSettings connection = get_connection_settings(); + + auto http = Http::post(join_url(connection.base_url, "/printer/gcode/script")); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); + } + http.header("Content-Type", "application/json") + .set_post_body(nlohmann::json{{"script", gcode}}.dump()) + .timeout_connect(5) + .timeout_max(10) + .on_complete([on_result](std::string body, unsigned status_code) { + (void) body; + const bool success = status_code == 200; + if (!success) { + BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: HTTP error " << status_code; + } + if (on_result) { + on_result(success); + } + }) + .on_error([on_result](std::string body, std::string err, unsigned status_code) { + (void) body; + std::string error = err; + if (status_code > 0) { + error += " (HTTP " + std::to_string(status_code) + ")"; + } + BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: " << error; + if (on_result) { + on_result(false); + } + }) + .perform(); +} + bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode) const { // why: snapshot then release - see post_print_action. - std::string base_url, api_key; - { - std::lock_guard lock(connect_mutex); - base_url = device_info.base_url; - api_key = device_info.api_key; - } - return send_gcode(dev_id, gcode, base_url, api_key); + return send_gcode(dev_id, gcode, get_connection_settings()); } bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode, - const std::string& base_url, const std::string& api_key) const + const ConnectionSettings& connection) const { nlohmann::json payload; payload["script"] = gcode; @@ -1948,10 +2014,11 @@ bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::str bool success = false; std::string http_error; - auto full_url = join_url(base_url, "/printer/gcode/script"); + auto full_url = join_url(connection.base_url, "/printer/gcode/script"); auto http = Http::post(full_url); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.header("Content-Type", "application/json") .set_post_body(payload_str) @@ -1981,8 +2048,7 @@ 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, +bool MoonrakerPrinterAgent::fetch_object_list(const ConnectionSettings& connection, std::set& objects, std::string& error) const { @@ -1990,9 +2056,10 @@ bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url, bool success = false; std::string http_error; - auto http = Http::get(join_url(base_url, "/printer/objects/list")); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + auto http = Http::get(join_url(connection.base_url, "/printer/objects/list")); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(5) .timeout_max(10) @@ -2084,7 +2151,8 @@ void MoonrakerPrinterAgent::announce_printhost_device() std::string dev_name; MoonrakerDeviceInfo info; std::string fetch_error; - if (fetch_device_info(device_info.base_url, device_info.api_key, info, fetch_error) && !info.dev_name.empty()) { + const ConnectionSettings connection = get_connection_settings(); + if (fetch_device_info(connection, info, fetch_error) && !info.dev_name.empty()) { dev_name = info.dev_name; } else { dev_name = device_info.model_name.empty() ? "Moonraker Printer" : device_info.model_name; @@ -2164,15 +2232,17 @@ void MoonrakerPrinterAgent::dispatch_printer_connected(const std::string& dev_id } } -void MoonrakerPrinterAgent::start_status_stream(const std::string& dev_id, const std::string& base_url, const std::string& api_key) +void MoonrakerPrinterAgent::start_status_stream(const std::string& dev_id, ConnectionSettings connection) { stop_status_stream(); - if (base_url.empty()) { + if (connection.base_url.empty()) { return; } 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, connection = std::move(connection)]() mutable { + run_status_stream(dev_id, std::move(connection)); + }); } void MoonrakerPrinterAgent::stop_status_stream() @@ -2191,11 +2261,11 @@ void MoonrakerPrinterAgent::stop_status_stream() } } -void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string base_url, std::string api_key) +void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, ConnectionSettings connection) { WsEndpoint endpoint; - if (!parse_ws_endpoint(base_url, endpoint)) { - BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: websocket endpoint invalid for base_url=" << base_url; + if (!parse_ws_endpoint(connection.base_url, endpoint)) { + BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: websocket endpoint invalid for base_url=" << connection.base_url; return; } // Reconnection logic @@ -2208,7 +2278,7 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba bool connection_lost = false; // Flag to distinguish clean shutdown from unexpected disconnect try { - MoonrakerWebsocket ws{endpoint.secure, api_key}; + MoonrakerWebsocket ws{endpoint.secure, connection.api_key, connection.ca_file}; ws.connect(endpoint.host, endpoint.port, std::chrono::seconds(10)); ws.tls_handshake(endpoint.host); @@ -2230,7 +2300,7 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba } std::string host_header = endpoint.host; - if (!endpoint.port.empty() && endpoint.port != (endpoint.secure ? "443" : "80")) { + if (!endpoint.port.empty() && endpoint.port != (endpoint.secure ? "7130" : "7125")) { host_header += ":" + endpoint.port; } ws.handshake(host_header, endpoint.target); @@ -2250,7 +2320,7 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba std::set subscribe_objects = {"print_stats", "virtual_sdcard"}; std::set available_objects; std::string list_error; - if (fetch_object_list(base_url, api_key, available_objects, list_error)) { + if (fetch_object_list(connection, available_objects, list_error)) { { std::lock_guard lock(payload_mutex); this->available_objects = std::move(available_objects); @@ -2348,7 +2418,7 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba ams_last_fetch_ms.store(now_ms); } } - handle_ws_message(dev_id, std::move(payload), base_url, api_key); + handle_ws_message(dev_id, std::move(payload), connection); // Check if handle_ws_message triggered reconnection request` if (ws_reconnect_requested.exchange(false)) { connection_lost = true; @@ -2391,7 +2461,7 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba } } -void MoonrakerPrinterAgent::handle_ws_message(std::string dev_id, std::string payload, std::string base_url, std::string api_key) +void MoonrakerPrinterAgent::handle_ws_message(std::string dev_id, std::string payload, ConnectionSettings connection) { auto json = nlohmann::json::parse(payload, nullptr, false); if (json.is_discarded()) { @@ -2462,7 +2532,7 @@ void MoonrakerPrinterAgent::handle_ws_message(std::string dev_id, std::string pa } if (updated) { - refresh_thumbnail_url(base_url, api_key); + refresh_thumbnail_url(connection); const auto now_ms = static_cast( std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); @@ -2516,7 +2586,7 @@ void MoonrakerPrinterAgent::update_status_cache(const nlohmann::json& updates) // build_print_payload_locked() - that runs on the ws thread holding payload_mutex, which the UI // thread also takes. Cached per filename, and a miss caches empty so a printer whose gcode has no // embedded thumbnail is not re-queried on every push. -void MoonrakerPrinterAgent::refresh_thumbnail_url(std::string base_url, std::string api_key) +void MoonrakerPrinterAgent::refresh_thumbnail_url(const ConnectionSettings& connection) { std::string filename; { @@ -2541,9 +2611,10 @@ void MoonrakerPrinterAgent::refresh_thumbnail_url(std::string base_url, std::str std::string response_body; bool request_succeeded = false; - auto http = Http::get(join_url(base_url, "/server/files/thumbnails?filename=" + Http::url_encode(filename))); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + auto http = Http::get(join_url(connection.base_url, "/server/files/thumbnails?filename=" + Http::url_encode(filename))); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(2) .timeout_max(4) @@ -2602,7 +2673,7 @@ void MoonrakerPrinterAgent::refresh_thumbnail_url(std::string base_url, std::str } segment_start = segment_end + 1; } - url = join_url(base_url, root + encoded_path); + url = join_url(connection.base_url, root + encoded_path); } std::lock_guard lock(payload_mutex); @@ -2838,8 +2909,7 @@ 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, + const ConnectionSettings& connection, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) { @@ -2866,9 +2936,10 @@ bool MoonrakerPrinterAgent::upload_gcode(const std::string& local_path, std::string http_error; // Use Http::form_add and Http::form_add_file - auto http = Http::post(join_url(base_url, "/server/files/upload")); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + auto http = Http::post(join_url(connection.base_url, "/server/files/upload")); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.form_add("root", "gcodes") // Upload to gcodes directory .form_add("print", "false") // Don't auto-start print @@ -2925,8 +2996,7 @@ int MoonrakerPrinterAgent::cancel_print(const std::string& dev_id) return post_print_action("cancel") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED; } -bool MoonrakerPrinterAgent::start_print_file(const std::string& base_url, - const std::string& api_key, +bool MoonrakerPrinterAgent::start_print_file(const ConnectionSettings& connection, const std::string& filename, std::string& error_msg) const { @@ -2934,7 +3004,7 @@ bool MoonrakerPrinterAgent::start_print_file(const std::string& base_url, // sent both as a query parameter and in the JSON body: Moonraker accepts // either, and sending a body avoids a body-less POST (which curl would treat // as a streamed upload and try to read via the file-read callback). - std::string url = join_url(base_url, "/printer/print/start") + + std::string url = join_url(connection.base_url, "/printer/print/start") + "?filename=" + Http::url_encode(filename); nlohmann::json payload; @@ -2943,8 +3013,9 @@ bool MoonrakerPrinterAgent::start_print_file(const std::string& base_url, bool success = false; auto http = Http::post(url); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.header("Content-Type", "application/json") .set_post_body(payload.dump()) @@ -2984,7 +3055,7 @@ bool MoonrakerPrinterAgent::start_print_file(const std::string& base_url, nlohmann::json status; std::string query_err; - if (!query_printer_status(base_url, api_key, status, query_err)) { + if (!query_printer_status(connection, status, query_err)) { continue; } @@ -3003,7 +3074,9 @@ bool MoonrakerPrinterAgent::start_print_file(const std::string& base_url, return false; } -void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id, const std::string& base_url, const std::string& api_key, uint64_t generation) +void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id, + ConnectionSettings connection, + uint64_t generation) { auto is_stale = [&]() { return generation != connect_generation.load(); }; @@ -3017,7 +3090,7 @@ void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id, try { MoonrakerDeviceInfo fetched_info; - if (!fetch_device_info(base_url, api_key, fetched_info, error_msg)) { + if (!fetch_device_info(connection, fetched_info, error_msg)) { BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Failed to fetch server info: " << 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"); @@ -3040,7 +3113,7 @@ void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id, #if 1 // Query initial status nlohmann::json initial_status; - if (query_printer_status(base_url, api_key, initial_status, error_msg)) { + if (query_printer_status(connection, initial_status, error_msg)) { { update_status_cache(initial_status); } @@ -3050,7 +3123,7 @@ void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id, } // Start WebSocket status stream - start_status_stream(dev_id, base_url, api_key); + start_status_stream(dev_id, std::move(connection)); #endif // Success! @@ -3082,7 +3155,7 @@ std::string MoonrakerPrinterAgent::normalize_base_url(bool use_ssl, const std::s { std::string value = use_ssl ? "https://" : "http://"; value += host; - value += port.empty() ? "" : (":" + port); + value += ":" + (port.empty() ? (use_ssl ? "7130" : "7125") : port); return value; } diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index c71a3b8c5a..d1ff866dad 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -24,6 +24,8 @@ namespace Slic3r { class ICloudServiceAgent; } namespace Slic3r { +class Http; + bool moonraker_is_light_name(const std::string& name); class MoonrakerWebsocket @@ -37,7 +39,7 @@ public: error, }; - MoonrakerWebsocket(bool secure, std::string api_key); + MoonrakerWebsocket(bool secure, std::string api_key, std::string ca_file); ~MoonrakerWebsocket(); void connect(const std::string& host, const std::string& port, std::chrono::seconds timeout); @@ -105,6 +107,15 @@ public: std::string get_camera_url() const override; protected: + struct ConnectionSettings + { + std::string dev_id; + std::string base_url; + std::string api_key; + bool use_ssl = false; + std::string ca_file; + }; + struct MoonrakerDeviceInfo { std::string dev_id; @@ -118,6 +129,7 @@ protected: std::string klippy_state; float nozzle_diameter = 0.0f; bool use_ssl = false; + std::string ca_file; } device_info; // Tray data for AMS payload building @@ -135,8 +147,10 @@ protected: void build_ams_payload(int ams_count, int max_lane_index, const std::vector& trays); // Methods that derived classes may need to override or access - virtual bool init_device_info(const std::string& dev_id, const std::string& dev_ip, const std::string& username, const std::string& password, bool use_ssl, const std::string& port); - virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const; + virtual bool init_device_info(const PrinterConnectionParams& params); + virtual bool fetch_device_info(const ConnectionSettings& connection, MoonrakerDeviceInfo& info, std::string& error) const; + ConnectionSettings get_connection_settings() const; + void configure_http(Http& http, const ConnectionSettings& connection) const; static float parse_nozzle_diameter(const nlohmann::json& response); // State access for derived classes @@ -163,10 +177,10 @@ protected: // Send a G-code script via Moonraker (/printer/gcode/script) bool send_gcode(const std::string& dev_id, const std::string& gcode) const; bool send_gcode(const std::string& dev_id, const std::string& gcode, - const std::string& base_url, const std::string& api_key) const; + const ConnectionSettings& connection) const; bool post_print_action(const std::string& action) const; bool post_print_action(const std::string& action, - const std::string& base_url, const std::string& api_key) const; + const ConnectionSettings& connection) const; bool send_ws_rpc(const std::string& method, const nlohmann::json& params); @@ -181,18 +195,21 @@ private: int send_version_info(const std::string& dev_id); int send_access_code(const std::string& dev_id); - 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 fetch_object_list(const ConnectionSettings& connection, std::set& objects, std::string& error) const; + bool query_printer_status(const ConnectionSettings& connection, nlohmann::json& status, std::string& error) const; + bool send_gcode_sync(const std::string& dev_id, const std::string& gcode) const; + void send_gcode_async(const std::string& dev_id, const std::string& gcode, + std::function on_result = {}) const; void announce_printhost_device(); void dispatch_local_connect(int state, const std::string& dev_id, const std::string& msg); void dispatch_printer_connected(const std::string& dev_id); void dispatch_message(const std::string& dev_id, const std::string& payload); - void start_status_stream(const std::string& dev_id, const std::string& base_url, const std::string& api_key); + void start_status_stream(const std::string& dev_id, ConnectionSettings connection); void stop_status_stream(); - void run_status_stream(std::string dev_id, std::string base_url, std::string api_key); - void handle_ws_message(std::string dev_id, std::string payload, std::string base_url, std::string api_key); - void refresh_thumbnail_url(std::string base_url, std::string api_key); + void run_status_stream(std::string dev_id, ConnectionSettings connection); + void handle_ws_message(std::string dev_id, std::string payload, ConnectionSettings connection); + void refresh_thumbnail_url(const ConnectionSettings& connection); void update_status_cache(const nlohmann::json& updates); nlohmann::json build_print_payload_locked() const; @@ -203,29 +220,28 @@ private: // File upload bool upload_gcode(const std::string& local_path, const std::string& filename, - const std::string& base_url, const std::string& api_key, + const ConnectionSettings& connection, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); // Start a print of a previously uploaded G-code file (path relative to the // Moonraker gcodes root). - bool start_print_file(const std::string& base_url, const std::string& api_key, + bool start_print_file(const ConnectionSettings& connection, const std::string& filename, std::string& error_msg) const; // Connection thread management void perform_connection_async(const std::string& dev_id, - const std::string& base_url, - const std::string& api_key, + ConnectionSettings connection, uint64_t generation); // why: a printer with no /server/webcams/list entry can still name its stream directly; // subclasses (e.g. printers with a fixed webcam path) can override this instead. virtual std::string webcam_stream_override(const std::string& base_url) const { return {}; } void refresh_webcam_info() const; - bool fetch_webcam_info(const std::string& base_url, const std::string& api_key, uint64_t generation) const; + bool fetch_webcam_info(const ConnectionSettings& connection, uint64_t generation) const; // System-specific filament fetch methods - bool fetch_hh_filament_info(std::vector& trays, int& max_lane_index); - bool fetch_moonraker_filament_data(std::vector& trays, int& max_lane_index); + bool fetch_hh_filament_info(const ConnectionSettings& connection, std::vector& trays, int& max_lane_index); + bool fetch_moonraker_filament_data(const ConnectionSettings& connection, std::vector& trays, int& max_lane_index); // JSON helper methods static std::string safe_json_string(const nlohmann::json& obj, const char* key); diff --git a/src/slic3r/Utils/QidiPrinterAgent.cpp b/src/slic3r/Utils/QidiPrinterAgent.cpp index 78e3434657..2d25253d8d 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.cpp +++ b/src/slic3r/Utils/QidiPrinterAgent.cpp @@ -70,14 +70,13 @@ bool QidiPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode // Snapshot only what the fetch needs, rather than reading device_info live from the // background thread below — device_info can be concurrently rewritten by a reconnect // on another thread while this fetch is still in flight. - std::string base_url = device_info.base_url; - std::string api_key = device_info.api_key; + ConnectionSettings connection = get_connection_settings(); std::string model_id = device_info.model_id; std::string model_name = device_info.model_name; filament_fetch_in_flight.fetch_add(1, std::memory_order_relaxed); - std::thread([this, base_url, api_key, model_id, model_name]() { + std::thread([this, connection = std::move(connection), model_id, model_name]() mutable { InFlightGuard guard{filament_fetch_in_flight}; std::string error; @@ -86,7 +85,7 @@ bool QidiPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode std::string series_id; { MoonrakerDeviceInfo info; - if (fetch_device_info(base_url, api_key, info, error)) { + if (fetch_device_info(connection, info, error)) { series_id = infer_series_id(info.model_id, info.dev_name); } } @@ -97,14 +96,14 @@ bool QidiPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode // 2. Fetch filament dictionary QidiFilamentDict dict; - if (!fetch_filament_dict(base_url, api_key, dict, error)) { + if (!fetch_filament_dict(connection, dict, error)) { BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch filament dict: " << error; } // 3. Fetch slot info and build AmsTrayData directly std::vector trays; int box_count = 0; - if (!fetch_slot_info(base_url, api_key, dict, series_id, trays, box_count, error)) { + if (!fetch_slot_info(connection, dict, series_id, trays, box_count, error)) { BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch slot info: " << error; return; } @@ -190,15 +189,14 @@ int QidiPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn up return MoonrakerPrinterAgent::start_sdcard_print(std::move(params), update_fn, cancel_fn); } -bool QidiPrinterAgent::fetch_slot_info(const std::string& base_url, - const std::string& api_key, +bool QidiPrinterAgent::fetch_slot_info(const ConnectionSettings& connection, const QidiFilamentDict& dict, const std::string& series_id, std::vector& trays, int& box_count, std::string& error) { - std::string url = join_url(base_url, "/printer/objects/query?save_variables=variables"); + std::string url = join_url(connection.base_url, "/printer/objects/query?save_variables=variables"); for (int i = 0; i < 16; ++i) { url += "&box_stepper%20slot" + std::to_string(i) + "=runout_button"; } @@ -208,8 +206,9 @@ bool QidiPrinterAgent::fetch_slot_info(const std::string& base_url, std::string http_error; auto http = Http::get(url); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(5) .timeout_max(10) @@ -337,20 +336,20 @@ bool QidiPrinterAgent::parse_slot_response(const std::string& response_body, return true; } -bool QidiPrinterAgent::fetch_filament_dict(const std::string& base_url, - const std::string& api_key, +bool QidiPrinterAgent::fetch_filament_dict(const ConnectionSettings& connection, QidiFilamentDict& dict, std::string& error) const { - std::string url = join_url(base_url, "/server/files/config/officiall_filas_list.cfg"); + std::string url = join_url(connection.base_url, "/server/files/config/officiall_filas_list.cfg"); std::string response_body; bool success = false; std::string http_error; auto http = Http::get(url); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + configure_http(http, connection); + if (!connection.api_key.empty()) { + http.header("X-Api-Key", connection.api_key); } http.timeout_connect(5) .timeout_max(10) diff --git a/src/slic3r/Utils/QidiPrinterAgent.hpp b/src/slic3r/Utils/QidiPrinterAgent.hpp index a74cf317d2..cbbdce8cab 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.hpp +++ b/src/slic3r/Utils/QidiPrinterAgent.hpp @@ -47,14 +47,13 @@ private: }; // Qidi-specific methods - bool fetch_slot_info(const std::string& base_url, - const std::string& api_key, + bool fetch_slot_info(const ConnectionSettings& connection, const QidiFilamentDict& dict, const std::string& series_id, std::vector& trays, int& box_count, std::string& error); - bool fetch_filament_dict(const std::string& base_url, const std::string& api_key, QidiFilamentDict& dict, std::string& error) const; + bool fetch_filament_dict(const ConnectionSettings& connection, QidiFilamentDict& dict, std::string& error) const; std::string normalize_filament_type(const std::string& filament_type); std::string infer_series_id(const std::string& model_id, const std::string& dev_name); std::string normalize_model_key(std::string value);