From 7cb1805272c5378524afb17a2959ce978b8674c8 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Wed, 12 Aug 2026 13:50:47 +0800 Subject: [PATCH] refactor: push bbl workflows to bbl printer agent --- src/slic3r/GUI/DeviceCore/DevManager.cpp | 2 + src/slic3r/GUI/DeviceManager.cpp | 73 ++++++ src/slic3r/GUI/DeviceManager.hpp | 53 ++-- src/slic3r/GUI/Jobs/PrintJob.cpp | 55 ++-- src/slic3r/GUI/MediaFilePanel.cpp | 35 +-- src/slic3r/GUI/MediaPlayCtrl.cpp | 140 +++++----- src/slic3r/GUI/PartSkipDialog.cpp | 68 ++--- src/slic3r/GUI/PartSkipDialog.hpp | 7 +- src/slic3r/GUI/ReleaseNote.cpp | 5 + src/slic3r/GUI/SendToPrinter.cpp | 174 ++++++++---- src/slic3r/GUI/SendToPrinter.hpp | 10 +- src/slic3r/Utils/BBLCloudServiceAgent.cpp | 39 ++- src/slic3r/Utils/BBLCloudServiceAgent.hpp | 2 +- src/slic3r/Utils/BBLPrinterAgent.cpp | 291 ++++++++++++++++++++- src/slic3r/Utils/BBLPrinterAgent.hpp | 82 +++++- src/slic3r/Utils/FileTransferUtils.cpp | 193 +------------- src/slic3r/Utils/FileTransferUtils.hpp | 108 +------- src/slic3r/Utils/ICloudServiceAgent.hpp | 6 +- src/slic3r/Utils/IPrinterAgent.hpp | 117 ++++++++- src/slic3r/Utils/NetworkAgent.cpp | 25 +- src/slic3r/Utils/NetworkAgent.hpp | 59 ++++- src/slic3r/Utils/OrcaCloudServiceAgent.cpp | 6 +- src/slic3r/Utils/OrcaCloudServiceAgent.hpp | 2 +- 23 files changed, 967 insertions(+), 585 deletions(-) diff --git a/src/slic3r/GUI/DeviceCore/DevManager.cpp b/src/slic3r/GUI/DeviceCore/DevManager.cpp index edc958ec53..8904235faa 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.cpp +++ b/src/slic3r/GUI/DeviceCore/DevManager.cpp @@ -13,6 +13,8 @@ #include "libslic3r/Time.hpp" +#include "IPrinterAgent.hpp" + using namespace nlohmann; namespace { diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 8a576cfc77..cce7170039 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -50,6 +50,7 @@ #include "DeviceCore/DevStatus.h" #include "DeviceCore/DevUpgrade.h" +#include "IPrinterAgent.hpp" #define CALI_DEBUG #define MINUTE_30 1800000 //ms @@ -2854,6 +2855,78 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ j = j_pre; } +#pragma region CAP_SUBSCRIPTIONS + if (j_pre.contains("capabilities")) { + auto& caps = j_pre["capabilities"]; + // if (caps.contains("extruder_count")) { + // GetExtderSystem()->GetTotalExtderCount(); + // } + if (caps.contains("supports_extruder_control")) + is_enable_np = caps.value("supports_extruder_control", 0); + if (caps.contains("supports_part_skip")) + is_support_partskip = caps.value("supports_part_skip", 0); + if (caps.contains("has_door_sensor")) + is_support_door_open_check = caps.value("has_door_sensor", 0); + if (caps.contains("supports_auto_recovery")) + is_support_auto_recovery_step_loss = caps.value("supports_auto_recovery", 0); + if (caps.contains("supports_prompt_sound")) + is_support_prompt_sound = caps.value("supports_prompt_sound", 0); + if (caps.contains("supports_spaghetti_detection")) + is_support_spaghetti_detection = caps.value("supports_spaghetti_detection", 0); + if (caps.contains("supports_purge_chute_pileup_detection")) + is_support_purgechutepileup_detection = caps.value("supports_purge_chute_pileup_detection", 0); + if (caps.contains("supports_nozzle_clumping_detection")) + is_support_nozzleclumping_detection = caps.value("supports_nozzle_clumping_detection", 0); + if (caps.contains("supports_build_plate_marker_detection")) + is_support_build_plate_marker_detect = caps.value("supports_build_plate_marker_detection", 0); + if (caps.contains("supports_ams_humidity")) + is_support_ams_humidity = caps.value("supports_ams_humidity", 0); + if (caps.contains("supports_pa_calibration_manual")) + is_support_pa_calibration = caps.value("supports_pa_calibration_manual", 0); + if (caps.contains("supports_flow_rate_calibration_manual")) + is_support_flow_calibration = caps.value("supports_flow_rate_calibration_manual", 0); + + if (caps.contains("supports_hotend_rack")) + m_nozzle_system->SetSupportNozzleRack(caps.value("supports_hotend_rack", 0)); + if (caps.contains("has_camera")) + has_ipcam = caps.value("has_camera", 0); + + // Printing with no filament loaded + if (caps.contains("is_support_ams_air_print_detection")) + is_support_air_print_detection = caps.value("is_support_ams_air_print_detection", 0); + + if (caps.contains("is_support_airprinting_detection")) + is_support_airprinting_detection = caps.value("is_support_airprinting_detection", 0); + if (caps.contains("camera_resolutions")) + camera_resolution_supported = caps.value("camera_resolutions", std::vector{}); + // if (caps.contains("ams_unit_count")) + // DevFilaSystemParser::ParseV1_0(caps["ams_unit_count"], this, m_fila_system.get(), true); + + // Remaining documented "capabilities" keys are intentionally not wired here yet. + // + // Already cached elsewhere, but not as a single direct is_support_* bool member + // (a differently-named member, a count, a method on another class, or two competing + // members), so leave these to their existing plumbing: + // extruder_count -> DevExtderSystem::m_total_extder_count + // ams_unit_count -> DevFilaSystem::GetAmsCount() + // + // No existing member at all -- would need a new is_support_* field and a design + // decision on naming/semantics before adding: + // has_lidar + // supports_plate_align_detection + // supports_fod_detection + // supports_displacement_detection + // supports_ai_monitoring -> only an enabled-state member exists (xcam_ai_monitoring) + // supports_first_layer_inspection -> only an enabled-state member exists (xcam_first_layer_inspector) + // + // Genuinely complex (array/object values, or no authoritative single source to derive from): + // supported_nozzle_types, supports_nozzle_blob_detection, has_ams, ams_slot_count, + // supports_ams_rfid, supports_multiple_bed_types, supports_filament_mapping, + // supports_pa_calibration_auto, supports_flow_rate_calibration_auto, + // supports_max_volumetric_speed_calibration, supports_ota_update + } +#pragma endregion + uint64_t t_utc = j.value("t_utc", 0ULL); if (t_utc > 0) { last_utc_time = std::chrono::system_clock::time_point(t_utc * 1ms); diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 33635fbe6e..cdb3b29a6f 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -100,6 +100,32 @@ struct DevPrintTaskRatingInfo; // given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware. bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter); +enum LiveviewLocal { + LVL_None, + LVL_Disable, + LVL_Local, + LVL_Rtsps, + LVL_Rtsp +}; + +enum LiveviewRemote { + LVR_None, + LVR_Tutk, + LVR_Agora, + LVR_TutkAgora +}; + +enum FileLocal { + FL_None, + FL_Local +}; + +enum FileRemote { + FR_None, + FR_Tutk, + FR_Agora, + FR_TutkAgora +}; class MachineObject { @@ -538,29 +564,10 @@ public: time_t xcam_first_layer_hold_start = 0; std::string local_rtsp_url; std::string tutk_state; - enum LiveviewLocal { - LVL_None, - LVL_Disable, - LVL_Local, - LVL_Rtsps, - LVL_Rtsp - } liveview_local{ LVL_None }; - enum LiveviewRemote { - LVR_None, - LVR_Tutk, - LVR_Agora, - LVR_TutkAgora - } liveview_remote{ LVR_None }; - enum FileLocal { - FL_None, - FL_Local - } file_local{ FL_None }; - enum FileRemote { - FR_None, - FR_Tutk, - FR_Agora, - FR_TutkAgora - } file_remote{ FR_None }; + LiveviewLocal liveview_local{ LiveviewLocal::LVL_None }; + LiveviewRemote liveview_remote{ LiveviewRemote::LVR_None}; + FileLocal file_local{ FileLocal::FL_None }; + FileRemote file_remote{ FileRemote::FR_None }; enum PlateMakerDectect : int { diff --git a/src/slic3r/GUI/Jobs/PrintJob.cpp b/src/slic3r/GUI/Jobs/PrintJob.cpp index 2dd4cbc88d..59cc7912ad 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.cpp +++ b/src/slic3r/GUI/Jobs/PrintJob.cpp @@ -12,6 +12,8 @@ #include "slic3r/GUI/DeviceCore/DevManager.h" #include "slic3r/GUI/DeviceCore/DevUtil.h" +#include "IPrinterAgent.hpp" + namespace Slic3r { namespace GUI { @@ -203,41 +205,6 @@ void PrintJob::process(Ctl &ctl) params.use_ssl_for_mqtt = m_local_use_ssl; params.username = m_agent->default_lan_username(); params.password = m_access_code; - // Allow disabling the eMMC print path via AppConfig. Plugin 02.03.00.62's - // eMMC tunnel code hangs indefinitely at the upload phase with some - // printers (e.g., Bambu H2D), so we default to disabled. Users with - // working eMMC support can opt-in by setting disable_emmc_print = 0. - bool disable_emmc = true; - if (wxGetApp().app_config) { - auto v = wxGetApp().app_config->get("disable_emmc_print"); - if (v == "0" || v == "false") - disable_emmc = false; - } - params.try_emmc_print = this->could_emmc_print && !disable_emmc; - - // check access code and ip address - if (this->connection_type == "lan" && m_print_type == "from_normal") { - params.dev_id = m_dev_id; - params.project_name = "verify_job"; - params.filename = job_data._temp_path.string(); - params.connection_type = this->connection_type; - - result = m_agent->start_send_gcode_to_sdcard(params, nullptr, nullptr, nullptr); - - if (result != 0) { - BOOST_LOG_TRIVIAL(error) << "LAN connection verification failed:" - << " result=" << result - << ", dev_ip=" << m_dev_ip - << ", dev_id=" << m_dev_id - << ", password_length=" << m_access_code.size(); - m_enter_ip_address_fun_fail(); - m_job_finished = true; - return; - } - - params.project_name = ""; - params.filename = ""; - } params.dev_id = m_dev_id; params.ftp_folder = m_ftp_folder; @@ -264,6 +231,17 @@ void PrintJob::process(Ctl &ctl) params.auto_offset_cali = this->auto_offset_cali; params.extruder_cali_manual_mode = this->extruder_cali_manual_mode; params.task_ext_change_assist = this->task_ext_change_assist; + // Allow disabling the eMMC print path via AppConfig. Plugin 02.03.00.62's + // eMMC tunnel code hangs indefinitely at the upload phase with some + // printers (e.g., Bambu H2D), so we default to disabled. Users with + // working eMMC support can opt-in by setting disable_emmc_print = 0. + bool disable_emmc = true; + if (wxGetApp().app_config) { + auto v = wxGetApp().app_config->get("disable_emmc_print"); + if (v == "0" || v == "false") + disable_emmc = false; + } + params.try_emmc_print = this->could_emmc_print && !disable_emmc; if (m_print_type == "from_sdcard_view") { params.dst_file = m_dst_path; @@ -618,6 +596,13 @@ void PrintJob::process(Ctl &ctl) } if (result < 0) { + if (result == ORCA_NETWORK_ERR_ACCESS_VERIFICATION_FAILED) { + if (m_enter_ip_address_fun_fail) + m_enter_ip_address_fun_fail(); + m_job_finished = true; + return; + } + curr_percent = -1; // The printer is still fetching its encryption flag (a transient state), so ask the diff --git a/src/slic3r/GUI/MediaFilePanel.cpp b/src/slic3r/GUI/MediaFilePanel.cpp index a37974fbb4..ee7b7143d3 100644 --- a/src/slic3r/GUI/MediaFilePanel.cpp +++ b/src/slic3r/GUI/MediaFilePanel.cpp @@ -11,6 +11,7 @@ #include "Widgets/ProgressDialog.hpp" #include #include +#include #include "DeviceCore/DevStorage.h" #ifdef __WXMSW__ @@ -206,7 +207,7 @@ MediaFilePanel::MediaFilePanel(wxWindow * parent) Bind(wxEVT_SHOW, onShowHide); parent->GetParent()->Bind(wxEVT_SHOW, onShowHide); - m_lan_user = "bblp"; + m_lan_user = wxGetApp().getAgent()->default_lan_username(); } MediaFilePanel::~MediaFilePanel() @@ -465,14 +466,9 @@ void MediaFilePanel::fetchUrl(boost::weak_ptr wfs) BOOST_LOG_TRIVIAL(info) << "MediaFilePanel::fetchUrl: " << m_local_proto << m_remote_proto; m_waiting_support = false; NetworkAgent *agent = wxGetApp().getAgent(); - std::string agent_version = agent ? agent->get_version() : ""; if (agent && (m_lan_mode || !m_remote_proto) && m_local_proto && !m_lan_ip.empty()) { - std::string url = agent->get_local_camera_url(m_lan_ip, m_lan_user, m_lan_passwd); - url += "&device=" + m_machine; - url += "&net_ver=" + agent_version; - url += "&dev_ver=" + m_dev_ver; - url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); - url += "&cli_ver=" + std::string(SLIC3R_VERSION); + std::string url = agent->get_local_camera_url({m_lan_ip, m_lan_user, m_lan_passwd, LVL_None, + m_machine, agent->get_version(), m_dev_ver, "", wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION}); fs->SetUrl(url); return; } @@ -494,33 +490,22 @@ void MediaFilePanel::fetchUrl(boost::weak_ptr wfs) if (agent) { std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; agent->get_camera_url(m_machine + "|" + m_dev_ver + "|" + protocols[m_remote_proto], - [this, wfs, m = m_machine, v = agent->get_version(), dv = m_dev_ver](std::string url) { - if (boost::algorithm::starts_with(url, "bambu:///")) { - url += "&device=" + m; - url += "&net_ver=" + v; - url += "&dev_ver=" + dv; - url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); - url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); - url += "&cli_ver=" + std::string(SLIC3R_VERSION); - } + [this, wfs, m = m_machine](CameraURLResult result) { + std::string url = std::move(result.url); BOOST_LOG_TRIVIAL(info) << "MediaFilePanel::fetchUrl: camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="}); CallAfter([=] { boost::shared_ptr fs(wfs.lock()); if (!fs || fs != m_image_grid->GetFileSystem()) return; - if (boost::algorithm::starts_with(url, "bambu:///")) { + if (result.is_success) { fs->SetUrl(url); } else { m_image_grid->SetStatus(m_bmp_failed, _L("Connection Failed. Please check the network and try again")); - std::string res = "3"; - if (boost::ends_with(url, "]")) { - size_t n = url.find_last_of('['); - if (n != std::string::npos) - res = url.substr(n + 1, url.length() - n - 2); - } + std::string res = result.error_code >= 0 ? std::to_string(result.error_code) : "3"; fs->SetUrl(res); } }); - }, wxGetApp().get_printer_cloud_provider()); + }, wxGetApp().get_printer_cloud_provider(), CameraURLParams{"", "", "", LVL_None, m_machine, agent->get_version(), m_dev_ver, + boost::lexical_cast(&refresh_agora_url), wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION, true}); } } diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 90e956be69..b255bdac32 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #undef pid_t #include #ifdef __WIN32__ @@ -160,12 +161,12 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj) if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) { // Legacy plugin cannot support remote play for H2D, force using local mode - m_remote_proto = MachineObject::LVR_None; + m_remote_proto = LiveviewRemote::LVR_None; } } else { m_camera_exists = false; m_lan_mode = false; - m_lan_proto = MachineObject::LVL_None; + m_lan_proto = LiveviewLocal::LVL_None; m_lan_ip.clear(); m_lan_passwd.clear(); m_dev_ver.clear(); @@ -247,8 +248,8 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan device2 += dev_ver; device2 += "|\"agora\"|"; device2 += channel; - wxGetApp().getAgent()->get_camera_url(device2, [context, callback](std::string url) { - callback(context, url.c_str()); + wxGetApp().getAgent()->get_camera_url(device2, [context, callback](CameraURLResult result) { + callback(context, result.url.c_str()); }, wxGetApp().get_printer_cloud_provider()); } @@ -283,20 +284,20 @@ void MediaPlayCtrl::Play() BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play: " << m_lan_proto << m_remote_proto << m_disable_lan; NetworkAgent *agent = wxGetApp().getAgent(); std::string agent_version = agent ? agent->get_version() : ""; - if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { + if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { m_disable_lan = m_remote_proto && !m_lan_mode; // try remote next time - std::string url; - if (m_lan_proto == MachineObject::LVL_Local) - url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; - else if (m_lan_proto == MachineObject::LVL_Rtsps) - url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps"; - else if (m_lan_proto == MachineObject::LVL_Rtsp) - url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp"; - url += "&device=" + m_machine; - url += "&net_ver=" + agent_version; - url += "&dev_ver=" + m_dev_ver; - url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); - url += "&cli_ver=" + std::string(SLIC3R_VERSION); + std::string url = agent->get_local_camera_url({ + m_lan_ip, + m_lan_user, + m_lan_passwd, + LiveviewLocal(m_lan_proto), + into_u8(m_machine), + agent_version, + m_dev_ver, + "", + wxGetApp().app_config->get("slicer_uuid"), + SLIC3R_VERSION + }); BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(hide_id_middle_string(url, url.find(m_lan_ip), m_lan_ip.length()), {m_lan_passwd}); m_url = url; load(); @@ -312,8 +313,8 @@ void MediaPlayCtrl::Play() // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_Disable (*) // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x) - if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto)) { - Stop(m_lan_proto == MachineObject::LVL_None + if (m_lan_proto <= LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto)) { + Stop(m_lan_proto == LiveviewLocal::LVL_None ? _L("A problem occurred. Please update the printer firmware and try again.") : _L("LAN Only Liveview is off. Please turn on the liveview on printer screen.")); return; @@ -336,46 +337,39 @@ void MediaPlayCtrl::Play() if (agent) { std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; - agent->get_camera_url(m_machine + "|" + m_dev_ver + "|" + protocols[m_remote_proto], - [this, m = m_machine, v = agent_version, dv = m_dev_ver, token = std::weak_ptr(m_token)](std::string url) { - if (token.expired()) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": token has been expired"; - return; - } - - if (boost::algorithm::starts_with(url, "bambu:///")) { - url += "&device=" + into_u8(m); - url += "&net_ver=" + v; - url += "&dev_ver=" + dv; - url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); - url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); - url += "&cli_ver=" + std::string(SLIC3R_VERSION); - } - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url, - {"?uid=", "authkey=", "passwd=", "license=", "token="}); - CallAfter([this, m, url] { - if (m != m_machine) { - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl drop late ttcode for machine: " << m; + agent->get_camera_url( + m_machine + "|" + m_dev_ver + "|" + protocols[m_remote_proto], + [this, m = m_machine, token = std::weak_ptr(m_token)](CameraURLResult result) { + std::string url = std::move(result.url); + const bool success = result.is_success; + const int error_code = result.error_code; + if (token.expired()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": token has been expired"; return; } - if (m_last_state == MEDIASTATE_INITIALIZING) { - if (url.empty() || !boost::algorithm::starts_with(url, "bambu:///")) { - m_failed_code = 3; - if (boost::ends_with(url, "]")) { - size_t n = url.find_last_of('['); - if (n != std::string::npos) - m_failed_code = std::atoi(url.substr(n + 1, url.length() - n - 2).c_str()); - } - Stop(_L("Connection Failed. Please check the network and try again"), from_u8(url)); - } else { - m_url = url; - load(); + + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url, {"?uid=", "authkey=", "passwd=", "license=", "token="}); + CallAfter([this, m, url, success, error_code] { + if (m != m_machine) { + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl drop late ttcode for machine: " << m; + return; } - } else { - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl drop late ttcode for state: " << m_last_state; - } - }); - }, wxGetApp().get_printer_cloud_provider()); + if (m_last_state == MEDIASTATE_INITIALIZING) { + if (!success) { + m_failed_code = error_code >= 0 ? error_code : 3; + Stop(_L("Connection Failed. Please check the network and try again"), from_u8(url)); + } else { + m_url = url; + load(); + } + } else { + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl drop late ttcode for state: " << m_last_state; + } + }); + }, + wxGetApp().get_printer_cloud_provider(), + CameraURLParams{"", "", "", LVL_None, into_u8(m_machine), agent_version, m_dev_ver, + boost::lexical_cast(&refresh_agora_url), wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION, true}); } } @@ -528,16 +522,11 @@ void MediaPlayCtrl::ToggleStream() wxGetApp().app_config->set("not_show_vcamera_stop_prev", "1"); if (res == wxID_CANCEL) return; } - if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { - std::string url; - if (m_lan_proto == MachineObject::LVL_Local) - url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; - else if (m_lan_proto == MachineObject::LVL_Rtsps) - url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps"; - else if (m_lan_proto == MachineObject::LVL_Rtsp) - url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp"; - url += "&device=" + into_u8(m_machine); - url += "&dev_ver=" + m_dev_ver; + if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { + NetworkAgent *agent = wxGetApp().getAgent(); + if (!agent) return; + std::string url = agent->get_local_camera_url({m_lan_ip, m_lan_user, m_lan_passwd, LiveviewLocal(m_lan_proto), + into_u8(m_machine), agent->get_version(), m_dev_ver, "", wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION}); BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(hide_id_middle_string(url, url.find(m_lan_ip), m_lan_ip.length()), {m_lan_passwd}); std::string file_url = data_dir() + "/cameratools/url.txt"; boost::nowide::ofstream file(file_url); @@ -551,20 +540,14 @@ void MediaPlayCtrl::ToggleStream() if (!agent) return; std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; agent->get_camera_url(m_machine + "|" + m_dev_ver + "|" + protocols[m_remote_proto], - [this, m = m_machine, v = agent->get_version(), dv = m_dev_ver](std::string url) { - if (boost::algorithm::starts_with(url, "bambu:///")) { - url += "&device=" + m; - url += "&net_ver=" + v; - url += "&dev_ver=" + dv; - url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); - url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); - url += "&cli_ver=" + std::string(SLIC3R_VERSION); - } + [this, m = m_machine](CameraURLResult result) { + std::string url = std::move(result.url); + const bool success = result.is_success; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url, {"?uid=", "authkey=", "passwd=", "license=", "token="}); - CallAfter([this, m, url] { + CallAfter([this, m, url, success] { if (m != m_machine) return; - if (url.empty() || !boost::algorithm::starts_with(url, "bambu:///")) { + if (!success) { MessageDialog(this->GetParent(), wxString::Format(_L("Virtual camera initialize failed (%s)!"), url.empty() ? _L("Network unreachable") : from_u8(url)), _L("Information"), wxICON_INFORMATION) .ShowModal(); @@ -577,7 +560,8 @@ void MediaPlayCtrl::ToggleStream() file.close(); m_streaming = true; }); - }, wxGetApp().get_printer_cloud_provider()); + }, wxGetApp().get_printer_cloud_provider(), CameraURLParams{"", "", "", LVL_None, into_u8(m_machine), agent->get_version(), m_dev_ver, + boost::lexical_cast(&refresh_agora_url), wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION, true}); } void MediaPlayCtrl::msw_rescale() { diff --git a/src/slic3r/GUI/PartSkipDialog.cpp b/src/slic3r/GUI/PartSkipDialog.cpp index b9dd7d5007..54c66fb80e 100644 --- a/src/slic3r/GUI/PartSkipDialog.cpp +++ b/src/slic3r/GUI/PartSkipDialog.cpp @@ -433,58 +433,38 @@ void PartSkipDialog::fetchUrl(boost::weak_ptr wfs) } std::string dev_ver = obj->get_ota_version(); std::string dev_id = obj->get_dev_id(); - // int remote_proto = obj->get_file_remote(); - NetworkAgent *agent = wxGetApp().getAgent(); - std::string agent_version = agent ? agent->get_version() : ""; + NetworkAgent *agent = wxGetApp().getAgent(); + if (!agent) { + fs->SetUrl("3"); + return; + } auto url_state = m_url_state; if (obj->is_lan_mode_printer()) { url_state = URL_TCP; } - if (agent) { - switch (url_state) { - case URL_TCP: { - std::string devIP = obj->get_dev_ip(); - std::string accessCode = obj->get_access_code(); - std::string tcp_url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode; - CallAfter([=] { + FileTransferURLParams params; + params.url_state = url_state; + params.ip_address = obj->get_dev_ip(); + params.username = agent->default_lan_username(); + params.password = obj->get_access_code(); + params.device_id = dev_id; + params.network_version = agent->get_version(); + params.device_version = dev_ver; + params.refresh_url = boost::lexical_cast(&refresh_agora_url); + params.client_id = wxGetApp().app_config->get("slicer_uuid"); + params.client_version = SLIC3R_VERSION; + + agent->get_file_transfer_url( + dev_id, + [this, wfs](FileTransferURLResult result) { + CallAfter([wfs, result = std::move(result)]() mutable { boost::shared_ptr fs(wfs.lock()); if (!fs) return; - if (boost::algorithm::starts_with(tcp_url, "bambu:///")) { - fs->SetUrl(tcp_url); - } else { - fs->SetUrl("3"); - } + fs->SetUrl(result.is_success ? result.url : "3"); }); - break; - } - case URL_TUTK: { - std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; - agent->get_camera_url(obj->get_dev_id() + "|" + dev_ver + "|" + protocols[3], [this, wfs, m = dev_id, v = agent->get_version(), dv = dev_ver](std::string url) - { - if (boost::algorithm::starts_with(url, "bambu:///")) { - url += "&device=" + m; - url += "&net_ver=" + v; - url += "&dev_ver=" + dv; - url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); - url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); - url += "&cli_ver=" + std::string(SLIC3R_VERSION); - } - CallAfter([=] { - boost::shared_ptr fs(wfs.lock()); - if (!fs) return; - if (boost::algorithm::starts_with(url, "bambu:///")) { - fs->SetUrl(url); - } else { - fs->SetUrl("3"); - } - }); - }); - break; - } - default: break; - } - } + }, + std::move(params)); } // controller void PartSkipDialog::OnFileSystemEvent(wxCommandEvent &e) diff --git a/src/slic3r/GUI/PartSkipDialog.hpp b/src/slic3r/GUI/PartSkipDialog.hpp index 95eba9f24d..d8c7282710 100644 --- a/src/slic3r/GUI/PartSkipDialog.hpp +++ b/src/slic3r/GUI/PartSkipDialog.hpp @@ -29,11 +29,6 @@ namespace Slic3r { namespace GUI { class SkipPartCanvas; -enum URL_STATE { - URL_TCP, - URL_TUTK, -}; - class PartSkipConfirmDialog : public DPIDialog { private: @@ -160,4 +155,4 @@ private: void OnApplyDialog(wxCommandEvent &event); }; -}} // namespace Slic3r::GUI \ No newline at end of file +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/ReleaseNote.cpp b/src/slic3r/GUI/ReleaseNote.cpp index 7b2d091176..ef5f0f5bee 100644 --- a/src/slic3r/GUI/ReleaseNote.cpp +++ b/src/slic3r/GUI/ReleaseNote.cpp @@ -1801,6 +1801,8 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt) m_trouble_shoot->Hide(); std::string str_ip = m_input_ip->GetTextCtrl()->GetValue().ToStdString(); std::string str_access_code = m_input_access_code->GetTextCtrl()->GetValue().ToStdString(); + if (str_access_code.empty()) + str_access_code = "88888888"; std::string str_name = m_input_printer_name->GetTextCtrl()->GetValue().Strip(wxString::both).ToStdString(); // Serial number should not contain lower case letters, and bambu_network plugin crashes // if user entered the wrong serial number, so we call `Upper()` here. @@ -1835,6 +1837,8 @@ void InputIpAddressDialog::on_send_retry() Fit(); wxString ip = m_input_ip->GetTextCtrl()->GetValue(); wxString str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); + if (str_access_code.empty()) + str_access_code = "88888888"; // check support function if (!m_obj) return; @@ -2058,6 +2062,7 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt) if (str_access_code.empty()) { str_access_code = "88888888"; + m_input_access_code->GetTextCtrl()->SetValue(str_access_code); } auto str_name = m_input_printer_name->GetTextCtrl()->GetValue().Strip(wxString::both); diff --git a/src/slic3r/GUI/SendToPrinter.cpp b/src/slic3r/GUI/SendToPrinter.cpp index 17da4b66a0..acbf911660 100644 --- a/src/slic3r/GUI/SendToPrinter.cpp +++ b/src/slic3r/GUI/SendToPrinter.cpp @@ -1,6 +1,7 @@ #include "SendToPrinter.hpp" #include "I18N.hpp" +#include "IPrinterAgent.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Thread.hpp" #include "GUI.hpp" @@ -25,7 +26,6 @@ #include "DeviceCore/DevManager.h" #include "DeviceCore/DevStorage.h" -#include "slic3r/Utils/FileTransferUtils.hpp" namespace Slic3r { @@ -1715,64 +1715,93 @@ void SendToPrinterDialog::GetConnection() if (m_tcp_try_connect) { std::string devIP = obj->get_dev_ip(); std::string accessCode = obj->get_access_code(); - std::string url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tcp"; - m_filetransfer_tunnel = std::make_unique(module(), url); - m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) { - CallAfter([this, is_success, err_code, error_msg]() { - OnConnection(is_success, err_code, error_msg); - }); - }); - m_filetransfer_tunnel->start_connect(); + + if (agent->get_printer_agent()) { + try { + m_filetransfer_tunnel = agent->get_printer_agent()->create_file_transfer_tunnel(devIP, accessCode); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to create TCP file-transfer tunnel: " << e.what(); + } + + if (m_filetransfer_tunnel && m_filetransfer_tunnel->check_valid()) { + m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) { + CallAfter([this, is_success, err_code, error_msg]() { + OnConnection(is_success, err_code, error_msg); + }); + }); + m_filetransfer_tunnel->start_connect(); + } else { + show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard, + _L("The selected printer does not support file transfer.")); + } + } else { + show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard, + _L("The selected printer does not support file transfer.")); + } } else if (m_tutk_try_connect) { std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; - agent->get_camera_url(obj->get_dev_id() + "|" + dev_ver + "|" + protocols[1], [this, m = dev_id, v = agent->get_version(), dv = dev_ver](std::string url) { - if (boost::algorithm::starts_with(url, "bambu:///")) { - url += "&device=" + m; - url += "&net_ver=" + v; - url += "&dev_ver=" + dv; - url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); - url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); - url += "&cli_ver=" + std::string(SLIC3R_VERSION); - } - - if (m_url_timer && m_url_timer->IsRunning()) - { - m_url_timer->Stop(); - } - - #if !BBL_RELEASE_TO_PUBLIC - BOOST_LOG_TRIVIAL(info) << "SendToPrinter::camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="}); - #endif - - - if (boost::algorithm::starts_with(url, "bambu:///")) - { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tutk"; - m_filetransfer_tunnel = std::make_unique(module(), url); - m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) { - CallAfter([this, is_success, err_code, error_msg]() { OnConnection(is_success, err_code, error_msg); }); - }); - m_filetransfer_tunnel->start_connect(); - } - else - { - std::string res = ""; - if (!url.empty() && boost::ends_with(url, "]")) - { - size_t n = url.find_last_of('['); - if (n != std::string::npos) - res = url.substr(n + 1, url.length() - n - 2); + agent->get_camera_url( + obj->get_dev_id() + "|" + dev_ver + "|" + protocols[1], + [this, pa = agent->get_printer_agent()](CameraURLResult result) { + std::string url = std::move(result.url); + if (m_url_timer && m_url_timer->IsRunning()) { + m_url_timer->Stop(); } - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : Tutk url error: ress = " << res; - } - }); + +#if !BBL_RELEASE_TO_PUBLIC + BOOST_LOG_TRIVIAL(info) << "SendToPrinter::camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="}); +#endif + + if (result.is_success) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tutk"; + if (!pa) { + show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard, + _L("The selected printer does not support file transfer.")); + return; + } + + try { + m_filetransfer_tunnel = pa->create_file_transfer_tunnel_from_url(url); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to create TUTK file-transfer tunnel: " << e.what(); + } + if (!m_filetransfer_tunnel || !m_filetransfer_tunnel->check_valid()) { + show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard, + _L("The selected printer does not support file transfer.")); + return; + } + + m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) { + CallAfter([this, is_success, err_code, error_msg]() { OnConnection(is_success, err_code, error_msg); }); + }); + m_filetransfer_tunnel->start_connect(); + } else { + std::string res = result.error_code >= 0 ? std::to_string(result.error_code) : ""; + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : Tutk url error: ress = " << res; + show_file_transfer_error(PrintDialogStatus::PrintStatusPublicInitFailed, + _L("Connection failed. Please check your network and try again.")); + } + }, + wxGetApp().get_printer_cloud_provider(), + CameraURLParams{"", "", "", LVL_None, dev_id, agent->get_version(), dev_ver, + boost::lexical_cast(&refresh_agora_url), wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION, true}); } } } +void SendToPrinterDialog::show_file_transfer_error(PrintDialogStatus status, wxString message) +{ + if (m_url_timer && m_url_timer->IsRunning()) + m_url_timer->Stop(); + m_connection_status = ConnectionStatus::CONNECTION_FAILED; + GetConnection(); + show_status(status); + update_print_status_msg(message, false, true); +} + void SendToPrinterDialog::OnConnection(bool is_success, int error_code, std::string error_msg) { if (is_success) { @@ -1854,8 +1883,24 @@ void SendToPrinterDialog::ResetTunnelAndJob() void SendToPrinterDialog::CreateMediaAbilityJob() { + NetworkAgent *agent = wxGetApp().getAgent(); + if (!agent || !agent->get_printer_agent()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no printer agent available"; + show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard, + _L("The selected printer does not support file transfer.")); + return; + } nlohmann::json media_ability = {{"cmd_type", 7}}; - m_filetransfer_mediability_job = std::make_unique(module(), std::string(media_ability.dump())); + try { + m_filetransfer_mediability_job = agent->get_printer_agent()->create_file_transfer_job(std::string(media_ability.dump())); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to create media-ability job: " << e.what(); + } + if (!m_filetransfer_mediability_job || !m_filetransfer_mediability_job->check_valid()) { + show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard, + _L("The selected printer does not support file transfer.")); + return; + } m_filetransfer_mediability_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector bin_res) { //this pl CallAfter([this, res, resp_ec, json_res] { @@ -1897,10 +1942,11 @@ void SendToPrinterDialog::CreateMediaAbilityJob() }); }); // Guard against a null transfer tunnel before dereferencing. - if (m_filetransfer_tunnel) { + if (m_filetransfer_tunnel && m_filetransfer_tunnel->check_valid()) { m_filetransfer_mediability_job->start_on(*m_filetransfer_tunnel); } else { - BOOST_LOG_TRIVIAL(info) << "CreateMediaAbilityJob: file transfer tunnel is null"; + show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard, + _L("The selected printer does not support file transfer.")); } } @@ -1913,8 +1959,25 @@ void SendToPrinterDialog::CreateUploadFileJob(const std::string &path, const std upload_params["dest_name"] = name; // filenme no path upload_params["file_path"] = path; + NetworkAgent *agent = wxGetApp().getAgent(); + if (!agent || !agent->get_printer_agent()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no printer agent available"; + show_file_transfer_error(PrintDialogStatus::PrintStatusPublicUploadFiled, + _L("The selected printer does not support file transfer.")); + return; + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Begin CreateUploadFileJob"; - m_filetransfer_uploadfile_job = std::make_unique(module(), std::string(upload_params.dump())); + try { + m_filetransfer_uploadfile_job = agent->get_printer_agent()->create_file_transfer_job(std::string(upload_params.dump())); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to create upload job: " << e.what(); + } + if (!m_filetransfer_uploadfile_job || !m_filetransfer_uploadfile_job->check_valid()) { + show_file_transfer_error(PrintDialogStatus::PrintStatusPublicUploadFiled, + _L("The selected printer does not support file transfer.")); + return; + } m_filetransfer_uploadfile_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector bin_res) { // CallAfter([this, res, resp_ec, json_res, bin_res] { UploadFileRessultCallback(res, resp_ec,json_res, bin_res); @@ -1942,10 +2005,11 @@ void SendToPrinterDialog::CreateUploadFileJob(const std::string &path, const std }); }); // Guard against a null transfer tunnel before dereferencing. - if (m_filetransfer_tunnel) { + if (m_filetransfer_tunnel && m_filetransfer_tunnel->check_valid()) { m_filetransfer_uploadfile_job->start_on(*m_filetransfer_tunnel); } else { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": file transfer tunnel is null"; + show_file_transfer_error(PrintDialogStatus::PrintStatusPublicUploadFiled, + _L("The selected printer does not support file transfer.")); } } diff --git a/src/slic3r/GUI/SendToPrinter.hpp b/src/slic3r/GUI/SendToPrinter.hpp index 14493a1f20..717910ae61 100644 --- a/src/slic3r/GUI/SendToPrinter.hpp +++ b/src/slic3r/GUI/SendToPrinter.hpp @@ -24,6 +24,7 @@ #include #include +#include "IPrinterAgent.hpp" #include "SelectMachine.hpp" #include "GUI_Utils.hpp" #include "wxExtensions.hpp" @@ -43,8 +44,6 @@ namespace Slic3r { -class FileTransferTunnel; -class FileTransferJob; namespace GUI { @@ -171,9 +170,9 @@ private: enum ConnectionStatus { NOT_START, CONNECTING, CONNECTED, CONNECTION_FAILED, DISCONNECTED }; ConnectionStatus m_connection_status{ConnectionStatus::NOT_START}; - std::unique_ptr m_filetransfer_tunnel; - std::unique_ptr m_filetransfer_mediability_job; - std::unique_ptr m_filetransfer_uploadfile_job; + std::unique_ptr m_filetransfer_tunnel; + std::unique_ptr m_filetransfer_mediability_job; + std::unique_ptr m_filetransfer_uploadfile_job; wxDateTime m_last_refresh_time; public: @@ -223,6 +222,7 @@ public: private: void ResetConnectMethod(); void ResetTunnelAndJob(); + void show_file_transfer_error(PrintDialogStatus status, wxString message); void OnConnection(bool is_success, int error_code, std::string error_msg); void CreateMediaAbilityJob(); void CreateUploadFileJob(const std::string &path, const std::string &name); diff --git a/src/slic3r/Utils/BBLCloudServiceAgent.cpp b/src/slic3r/Utils/BBLCloudServiceAgent.cpp index 846e4ce509..df0164c52e 100644 --- a/src/slic3r/Utils/BBLCloudServiceAgent.cpp +++ b/src/slic3r/Utils/BBLCloudServiceAgent.cpp @@ -1,5 +1,6 @@ #include "BBLCloudServiceAgent.hpp" #include "BBLNetworkPlugin.hpp" +#include "NetworkAgent.hpp" #include #include "Http.hpp" @@ -606,13 +607,47 @@ int BBLCloudServiceAgent::modify_printer_name(std::string dev_id, std::string de // Model Mall & Publishing // ============================================================================ -int BBLCloudServiceAgent::get_camera_url(std::string dev_id, std::function callback) +int BBLCloudServiceAgent::get_camera_url(std::string dev_id, std::function callback, CameraURLParams params) { auto& plugin = BBLNetworkPlugin::instance(); auto agent = plugin.get_agent(); auto func = plugin.get_get_camera_url(); if (func && agent) { - return func(agent, dev_id, callback); + auto make_result = [](std::string url) { + CameraURLResult result; + result.url = std::move(url); + result.is_success = result.url.rfind("bambu:///", 0) == 0; + if (result.is_success) { + result.error_code = 0; + } else if (!result.url.empty() && result.url.back() == ']') { + const auto start = result.url.rfind('['); + if (start != std::string::npos && start + 1 < result.url.size() - 1) { + try { + result.error_code = std::stoi(result.url.substr(start + 1, result.url.size() - start - 2)); + } catch (...) { + } + } + } + return result; + }; + if (params.apply_meta) { + auto decorated_callback = [callback = std::move(callback), params = std::move(params), make_result](std::string url) { + CameraURLResult result = make_result(std::move(url)); + if (result.is_success) { + result.url += "&device=" + params.device; + result.url += "&net_ver=" + params.network_version; + result.url += "&dev_ver=" + params.device_version; + result.url += "&refresh_url=" + params.refresh_url; + result.url += "&cli_id=" + params.client_id; + result.url += "&cli_ver=" + params.client_version; + } + callback(std::move(result)); + }; + return func(agent, std::move(dev_id), std::move(decorated_callback)); + } + return func(agent, std::move(dev_id), [callback = std::move(callback), make_result](std::string url) { + callback(make_result(std::move(url))); + }); } return -1; } diff --git a/src/slic3r/Utils/BBLCloudServiceAgent.hpp b/src/slic3r/Utils/BBLCloudServiceAgent.hpp index 9f03f0b839..59de3e41d3 100644 --- a/src/slic3r/Utils/BBLCloudServiceAgent.hpp +++ b/src/slic3r/Utils/BBLCloudServiceAgent.hpp @@ -89,7 +89,7 @@ public: int modify_printer_name(std::string dev_id, std::string dev_name) override; // Model Mall & Publishing - int get_camera_url(std::string dev_id, std::function callback) override; + int get_camera_url(std::string dev_id, std::function callback, CameraURLParams params) override; int get_design_staffpick(int offset, int limit, std::function callback) override; int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) override; int get_model_publish_url(std::string* url) override; diff --git a/src/slic3r/Utils/BBLPrinterAgent.cpp b/src/slic3r/Utils/BBLPrinterAgent.cpp index b72ab883bb..591750005d 100644 --- a/src/slic3r/Utils/BBLPrinterAgent.cpp +++ b/src/slic3r/Utils/BBLPrinterAgent.cpp @@ -1,14 +1,208 @@ #include "BBLPrinterAgent.hpp" #include "BBLNetworkPlugin.hpp" +#include "FileTransferUtils.hpp" +#include "IPrinterAgent.hpp" #include "NetworkAgentFactory.hpp" +#include "NetworkAgent.hpp" #include #include +#include #include #include +#include +#include namespace Slic3r { +// ============================================================================ +// File Transfer (Bambu eMMC tunnel ABI) +// ============================================================================ + +BBLFileTransferTunnel::BBLFileTransferTunnel(const std::string &url) : IFileTransferTunnel(url) +{ + FileTransferModule &m = module(); + m_ = &m; + // Guard against missing symbols in older Bambu networking plugins. + // These symbols were added in a newer plugin ABI; if the installed + // plugin predates them, ft_tunnel_create/ft_tunnel_set_status_cb + // will be null and calling them crashes. + if (!m_->ft_tunnel_create || !m_->ft_tunnel_set_status_cb) { + throw std::runtime_error("Bambu networking plugin is too old: missing ft_tunnel_* symbols. " + "Please update the networking plugin."); + } + FT_TunnelHandle *h{}; + if (m_->ft_tunnel_create(url.c_str(), &h) != 0 || !h) { + throw std::runtime_error("ft_tunnel_create failed"); + } + h_ = h; + + // C API: ft_status_cb(void* user, int old_status, int new_status, int err, const char* msg) + auto tramp = [](void *user, int old_status, int new_status, int err_code, const char *msg) noexcept { + auto *self = reinterpret_cast(user); + self->status_ = new_status; + if (!self->status_cb_) return; + try { + self->status_cb_(old_status, new_status, err_code, std::string(msg ? msg : "")); + } catch (...) {} + }; + if (m_->ft_tunnel_set_status_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_set_status_cb failed"); } +} + +void BBLFileTransferTunnel::start_connect() +{ + // C API: ft_conn_cb(void* user, int ok, int err, const char* msg) + auto tramp = [](void *user, int ok, int ec, const char *msg) noexcept { + auto *pcb = reinterpret_cast(user); + if (!pcb) return; + try { + (*pcb)(ok == 0, ec, std::string(msg ? msg : "")); + } catch (...) {} + }; + if (m_->ft_tunnel_start_connect(h_, tramp, &conn_cb_) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_start_connect failed"); } +} + +bool BBLFileTransferTunnel::sync_start_connect() +{ + return m_->ft_tunnel_sync_connect(h_) == FT_OK; +} + +void BBLFileTransferTunnel::shutdown() +{ + if (m_->ft_tunnel_shutdown) (void) m_->ft_tunnel_shutdown(h_); +} + +BBLFileTransferJob::BBLFileTransferJob(const std::string ¶ms_json) : IFileTransferJob(params_json) +{ + m_ = &module(); + FT_JobHandle *h{}; + if (m_->ft_job_create(params_json.c_str(), &h) != 0 || !h) { + throw std::runtime_error("ft_job_create failed"); + } + h_ = h; + + // C API: ft_job_result_cb(void* user, int tunnel_err, ft_job_result result) + auto tramp = [](void *user, ft_job_result r) noexcept { + auto *self = reinterpret_cast(user); + if (!self) return; + + try { + self->finished_ = true; + self->solve_result(r); + if (self->result_cb_) self->result_cb_(self->res_, self->resp_ec_, self->res_json_, self->res_bin_); + } catch (...) { + // swallow + } + + try { + if (auto *mod = self ? self->m_ : nullptr) { + if (mod->ft_job_result_destroy) + mod->ft_job_result_destroy(&r); + else if (mod->ft_free) { + if (r.json) mod->ft_free((void *) r.json); + if (r.bin) mod->ft_free((void *) r.bin); + } + } + } catch (...) {} + }; + + if (m_->ft_job_set_result_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_job_set_result_cb failed"); } +} + +bool BBLFileTransferJob::get_result(int &ec, int &resp_ec, std::string &json, std::vector &bin, uint32_t timeout_ms) +{ + if (!h_) throw std::runtime_error("job handle invalid"); + ft_job_result result; + if (m_->ft_job_get_result(h_, timeout_ms, &result) == ft_err::FT_EXCEPTION) return false; + solve_result(result); + m_->ft_job_result_destroy(&result); + ec = res_; + resp_ec = res_; + json = res_json_; + bin = res_bin_; + return true; +} + +void BBLFileTransferJob::start_on(IFileTransferTunnel &t) +{ + if (!h_) throw std::runtime_error("job handle invalid"); + auto *handle = reinterpret_cast(t.native()); + if (m_->ft_tunnel_start_job(handle, h_) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_start_job failed"); } +} + +void BBLFileTransferJob::on_msg(MsgCb cb) +{ + IFileTransferJob::on_msg(std::move(cb)); + if (!h_) return; + + // C API: ft_job_msg_cb(void* user, ft_job_msg msg) + auto tramp = [](void *user, ft_job_msg m) noexcept { + auto *self = reinterpret_cast(user); + if (!self) return; + try { + if (self->msg_cb_) { self->msg_cb_(m.kind, std::string(m.json ? m.json : "")); } + } catch (...) {} + + try { + if (auto *mod = self->m_) { + if (mod->ft_job_msg_destroy) + mod->ft_job_msg_destroy(&m); + else if (mod->ft_free && m.json) + mod->ft_free((void *) m.json); + } + } catch (...) {} + }; + + if (m_->ft_job_set_msg_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_job_set_msg_cb failed"); } +} + +bool BBLFileTransferJob::try_get_msg(int &kind, std::string &json) +{ + if (!h_) return false; + ft_job_msg m{}; + int rc = m_->ft_job_try_get_msg(h_, &m); + if (rc != 0) return false; + + kind = m.kind; + json.assign(m.json ? m.json : ""); + + if (m_->ft_job_msg_destroy) + m_->ft_job_msg_destroy(&m); + else if (m_->ft_free && m.json) + m_->ft_free((void *) m.json); + + return true; +} + +bool BBLFileTransferJob::get_msg(uint32_t timeout_ms, int &kind, std::string &json) +{ + if (!h_) return false; + ft_job_msg m{}; + int rc = m_->ft_job_get_msg(h_, timeout_ms, &m); + if (rc != 0) return false; + + kind = m.kind; + json.assign(m.json ? m.json : ""); + + if (m_->ft_job_msg_destroy) + m_->ft_job_msg_destroy(&m); + else if (m_->ft_free && m.json) + m_->ft_free((void *) m.json); + + return true; +} + +void BBLFileTransferJob::solve_result(ft_job_result result) +{ + res_ = result.ec; + resp_ec_ = result.resp_ec; + + res_bin_.clear(); + if (result.bin && result.bin_size) res_bin_.assign(reinterpret_cast(result.bin), + reinterpret_cast(result.bin) + result.bin_size); + res_json_.assign(result.json ? result.json : ""); +} + BBLPrinterAgent::BBLPrinterAgent() = default; BBLPrinterAgent::~BBLPrinterAgent() = default; @@ -246,9 +440,70 @@ int BBLPrinterAgent::send_message_to_printer(std::string dev_id, std::string jso return -1; } -std::string BBLPrinterAgent::get_local_camera_url(std::string dev_ip, std::string username, std::string password) +std::string BBLPrinterAgent::get_local_camera_url(CameraURLParams params) { - return "bambu:///local/" + dev_ip + ".?port=6000&user=" + username + "&passwd=" + password; + std::string url; + if (params.protocol == LVL_Local) + url = "bambu:///local/" + params.ip_address + ".?port=6000&user=" + params.user + "&passwd=" + params.password; + else if (params.protocol == LVL_Rtsps) + url = "bambu:///rtsps___" + params.user + ":" + params.password + "@" + params.ip_address + "/streaming/live/1?proto=rtsps"; + else if (params.protocol == LVL_Rtsp) + url = "bambu:///rtsp___" + params.user + ":" + params.password + "@" + params.ip_address + "/streaming/live/1?proto=rtsp"; + else + url = "bambu:///local/" + params.ip_address + ".?port=6000&user=" + params.user + "&passwd=" + params.password; + + url += "&device=" + params.device; + url += "&net_ver=" + params.network_version; + url += "&dev_ver=" + params.device_version; + url += "&cli_id=" + params.client_id; + url += "&cli_ver=" + params.client_version; + return url; +} + +std::string BBLPrinterAgent::get_local_file_transfer_url(const FileTransferURLParams& params) +{ + // Keep the historical PartSkipDialog URL unchanged. It is a file-transfer + // tunnel URL, not a camera URL, so it intentionally has no camera metadata + // suffix and no dot before the query string. + return "bambu:///local/" + params.ip_address + "?port=6000&user=" + params.username + "&passwd=" + params.password; +} + +int BBLPrinterAgent::get_file_transfer_url(std::string dev_id, std::function callback, + FileTransferURLParams params) +{ + if (params.url_state == URL_TCP) { + FileTransferURLResult result; + result.url = get_local_file_transfer_url(params); + result.is_success = !result.url.empty(); + result.error_code = result.is_success ? 0 : -1; + if (callback) + callback(std::move(result)); + return result.is_success ? 0 : -1; + } + + if (!m_cloud_agent) { + if (callback) + callback({}); + return -1; + } + + const std::string protocols = "\"tutk\",\"agora\""; + return m_cloud_agent->get_camera_url( + std::move(dev_id) + "|" + params.device_version + "|" + protocols, + std::move(callback), + CameraURLParams{ + "", + "", + "", + LVL_None, + params.device_id, + params.network_version, + params.device_version, + params.refresh_url, + params.client_id, + params.client_version, + true + }); } // ============================================================================ @@ -512,6 +767,15 @@ int BBLPrinterAgent::start_local_print_with_record(PrintParams params, OnUpdateS BBLNetworkPlugin::instance().get_start_local_print_with_record(), params, update_fn, cancel_fn, wait_fn); } +int BBLPrinterAgent::verify_local_print_access(PrintParams params) +{ + params.project_name = "verify_job"; + params.filename = Slic3r::resources_dir() + "/check_access_code.txt"; + params.try_emmc_print = false; + + return start_send_gcode_to_sdcard(params, nullptr, nullptr, nullptr); +} + int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) { int result = dispatch_start( @@ -527,6 +791,16 @@ int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStat int BBLPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) { + if (params.connection_type == "lan" && params.print_type == "from_normal") { + const int verify_result = verify_local_print_access(params); + if (verify_result != 0) { + BOOST_LOG_TRIVIAL(error) << "LAN connection verification failed: result=" << verify_result + << ", dev_ip=" << params.dev_ip << ", dev_id=" << params.dev_id + << ", password_length=" << params.password.size(); + return ORCA_NETWORK_ERR_ACCESS_VERIFICATION_FAILED; + } + } + return dispatch_start( BBLNetworkPlugin::instance().get_start_local_print(), params, update_fn, cancel_fn); } @@ -639,4 +913,17 @@ FilamentSyncMode BBLPrinterAgent::get_filament_sync_mode() const return FilamentSyncMode::subscription; } +std::unique_ptr BBLPrinterAgent::create_file_transfer_tunnel(std::string& dev_ip, std::string& access_code) { + std::string url = "bambu:///local/" + dev_ip + "?port=6000&user=" + default_lan_username() + "&passwd=" + access_code; + return std::make_unique(url); +} + +std::unique_ptr BBLPrinterAgent::create_file_transfer_tunnel_from_url(std::string url) { + return std::make_unique(url); +} + +std::unique_ptr BBLPrinterAgent::create_file_transfer_job(std::string params_json) { + return std::make_unique(params_json); +} + } // namespace Slic3r diff --git a/src/slic3r/Utils/BBLPrinterAgent.hpp b/src/slic3r/Utils/BBLPrinterAgent.hpp index 37f655c2fe..8d767faa1c 100644 --- a/src/slic3r/Utils/BBLPrinterAgent.hpp +++ b/src/slic3r/Utils/BBLPrinterAgent.hpp @@ -3,12 +3,83 @@ #include "IPrinterAgent.hpp" #include "ICloudServiceAgent.hpp" +#include "FileTransferUtils.hpp" #include #include #include namespace Slic3r { +/** + * BBLFileTransferTunnel - Bambu eMMC tunnel, backed by the Bambu network + * plugin's ft_tunnel_* ABI (see FileTransferUtils.hpp). Only BBLPrinterAgent + * constructs these; callers only ever see them through IFileTransferTunnel. + */ +class BBLFileTransferTunnel : public IFileTransferTunnel +{ +public: + BBLFileTransferTunnel(const std::string &url); + ~BBLFileTransferTunnel() override { reset(); } + + void start_connect() override; + bool sync_start_connect() override; + void shutdown() override; + bool check_valid() const override { return h_ != nullptr; } + void *native() const noexcept override { return h_; } + +private: + void reset() noexcept + { + if (h_) { + m_->ft_tunnel_release(h_); + h_ = nullptr; + } + } + + FileTransferModule *m_{}; + FT_TunnelHandle *h_{}; +}; + +/** + * BBLFileTransferJob - a single ft_job_* operation (media-ability query, + * file upload, ...) run on a BBLFileTransferTunnel. Same ABI-wrapper role + * as BBLFileTransferTunnel; only BBLPrinterAgent constructs these. + */ +class BBLFileTransferJob : public IFileTransferJob +{ +public: + explicit BBLFileTransferJob(const std::string ¶ms_json); + ~BBLFileTransferJob() override { reset(); } + + bool get_result(int &ec, int &resp_ec, std::string &json, std::vector &bin, uint32_t timeout_ms) override; + void start_on(IFileTransferTunnel &t) override; + // why: unlike on_result() (fires from a trampoline registered once in the ctor), + // ft_job_set_msg_cb is only wired up here, lazily, on first real subscriber - + // that ABI call has to happen in the concrete class, not the vendor-neutral base. + void on_msg(MsgCb cb) override; + bool try_get_msg(int &kind, std::string &json) override; + bool get_msg(uint32_t timeout_ms, int &kind, std::string &json) override; + void *native() const noexcept override { return h_; } + bool check_valid() const override { return h_ != nullptr; } + void cancel() override + { + if (m_->ft_job_cancel && h_) m_->ft_job_cancel(h_); + } + +private: + void reset() noexcept + { + if (h_) { + m_->ft_job_release(h_); + h_ = nullptr; + } + } + void solve_result(ft_job_result result); + + FileTransferModule *m_{}; + FT_JobHandle *h_{}; +}; + /** * BBLPrinterAgent - BBL DLL wrapper implementation of IPrinterAgent. * @@ -45,7 +116,10 @@ public: int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override; int disconnect_printer() override; int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override; - std::string get_local_camera_url(std::string dev_ip, std::string username, std::string password) override; + std::string get_local_camera_url(CameraURLParams params) override; + std::string get_local_file_transfer_url(const FileTransferURLParams& params) override; + int get_file_transfer_url(std::string dev_id, std::function callback, + FileTransferURLParams params) override; std::string default_lan_username() const override { return "bblp"; } // Certificates @@ -100,7 +174,13 @@ public: int set_queue_on_main_fn(QueueOnMainFn fn) override; FilamentSyncMode get_filament_sync_mode() const override; + std::unique_ptr create_file_transfer_tunnel(std::string& dev_ip, std::string& access_code) override; + std::unique_ptr create_file_transfer_tunnel_from_url(std::string url) override; + std::unique_ptr create_file_transfer_job(std::string params_json) override; + private: + int verify_local_print_access(PrintParams params); + // why: the lan/cloud DECISION stays machine-side; keep this mechanical branch in sync with publish_json. int publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode); diff --git a/src/slic3r/Utils/FileTransferUtils.cpp b/src/slic3r/Utils/FileTransferUtils.cpp index ebca741af5..8601f6712e 100644 --- a/src/slic3r/Utils/FileTransferUtils.cpp +++ b/src/slic3r/Utils/FileTransferUtils.cpp @@ -1,8 +1,4 @@ -#include -#include #include "FileTransferUtils.hpp" -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/DeviceCore/DevManager.h" namespace Slic3r { @@ -37,191 +33,4 @@ FileTransferModule::FileTransferModule(ModuleHandle networking_module, int requi ft_job_get_msg = sym_lookup(networking_, "ft_job_get_msg"); } -FileTransferTunnel::FileTransferTunnel(FileTransferModule &m, const std::string &url) : m_(&m) -{ - // Guard against missing symbols in older Bambu networking plugins. - // These symbols were added in a newer plugin ABI; if the installed - // plugin predates them, ft_tunnel_create/ft_tunnel_set_status_cb - // will be null and calling them crashes. - if (!m_->ft_tunnel_create || !m_->ft_tunnel_set_status_cb) { - throw std::runtime_error("Bambu networking plugin is too old: missing ft_tunnel_* symbols. " - "Please update the networking plugin."); - } - FT_TunnelHandle *h{}; - if (m_->ft_tunnel_create(url.c_str(), &h) != 0 || !h) { - throw std::runtime_error("ft_tunnel_create failed"); - } - h_ = h; - - // C API: ft_status_cb(void* user, int old_status, int new_status, int err, const char* msg) - auto tramp = [](void *user, int old_status, int new_status, int err_code, const char *msg) noexcept { - auto *self = reinterpret_cast(user); - self->status_ = new_status; - if (!self->status_cb_) return; - try { - self->status_cb_(old_status, new_status, err_code, std::string(msg ? msg : "")); - } catch (...) {} - }; - if (m_->ft_tunnel_set_status_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_set_status_cb failed"); } -} - -void FileTransferTunnel::start_connect() -{ - // C API: ft_conn_cb(void* user, int ok, int err, const char* msg) - auto tramp = [](void *user, int ok, int ec, const char *msg) noexcept { - auto *pcb = reinterpret_cast(user); - if (!pcb) return; - try { - (*pcb)(ok == 0, ec, std::string(msg ? msg : "")); - } catch (...) {} - }; - if (m_->ft_tunnel_start_connect(h_, tramp, &conn_cb_) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_start_connect failed"); } -} - -bool FileTransferTunnel::sync_start_connect() -{ - return m_->ft_tunnel_sync_connect(h_) == FT_OK; -} - -void FileTransferTunnel::on_connection(ConnectionCb cb) { conn_cb_ = std::move(cb); } -void FileTransferTunnel::on_status(TunnelStatusCb cb) { status_cb_ = std::move(cb); } - -void FileTransferTunnel::shutdown() -{ - if (m_->ft_tunnel_shutdown) (void) m_->ft_tunnel_shutdown(h_); -} - -FileTransferJob::FileTransferJob(FileTransferModule &m, const std::string ¶ms_json) : m_(&m) -{ - FT_JobHandle *h{}; - if (m_->ft_job_create(params_json.c_str(), &h) != 0 || !h) { - - } - h_ = h; - - // C API: ft_job_result_cb(void* user, int tunnel_err, ft_job_result result) - auto tramp = [](void *user, ft_job_result r) noexcept { - auto *self = reinterpret_cast(user); - if (!self) return; - - try { - self->finished_ = true; - self->solve_result(r); - - if (self->result_cb_) self->result_cb_(self->res_, self->resp_ec_, self->res_json_, self->res_bin_); - self->m_->ft_job_result_destroy(&r); - } catch (...) { - // swallow - } - - try { - if (auto *mod = self ? self->m_ : nullptr) { - if (mod->ft_job_result_destroy) - mod->ft_job_result_destroy(&r); - else if (mod->ft_free) { - if (r.json) mod->ft_free((void *) r.json); - if (r.bin) mod->ft_free((void *) r.bin); - } - } - } catch (...) {} - }; - - if (m_->ft_job_set_result_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_job_set_result_cb failed"); } -} - -void FileTransferJob::on_result(ResultCb cb) { result_cb_ = std::move(cb); } - -bool FileTransferJob::get_result(int &ec, int &resp_ec, std::string &json, std::vector &bin, uint32_t timeout_ms) -{ - if (!h_) throw std::runtime_error("job handle invalid"); - ft_job_result result; - if (m_->ft_job_get_result(h_, timeout_ms, &result) == ft_err::FT_EXCEPTION) return false; - solve_result(result); - m_->ft_job_result_destroy(&result); - ec = res_; - resp_ec = res_; - json = res_json_; - bin = res_bin_; - return true; -} - -void FileTransferJob::start_on(FileTransferTunnel &t) -{ - if (!h_) throw std::runtime_error("job handle invalid"); - if (m_->ft_tunnel_start_job(t.native(), h_) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_start_job failed"); } -} - -void FileTransferJob::on_msg(MsgCb cb) -{ - msg_cb_ = std::move(cb); - if (!h_) return; - - // C API: ft_job_msg_cb(void* user, ft_job_msg msg) - auto tramp = [](void *user, ft_job_msg m) noexcept { - auto *self = reinterpret_cast(user); - if (!self) return; - try { - if (self->msg_cb_) { self->msg_cb_(m.kind, std::string(m.json ? m.json : "")); } - } catch (...) {} - - try { - if (auto *mod = self->m_) { - if (mod->ft_job_msg_destroy) - mod->ft_job_msg_destroy(&m); - else if (mod->ft_free && m.json) - mod->ft_free((void *) m.json); - } - } catch (...) {} - }; - - if (m_->ft_job_set_msg_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_job_set_msg_cb failed"); } -} - -bool FileTransferJob::try_get_msg(int &kind, std::string &json) -{ - if (!h_) return false; - ft_job_msg m{}; - int rc = m_->ft_job_try_get_msg(h_, &m); - if (rc != 0) return false; - - kind = m.kind; - json.assign(m.json ? m.json : ""); - - if (m_->ft_job_msg_destroy) - m_->ft_job_msg_destroy(&m); - else if (m_->ft_free && m.json) - m_->ft_free((void *) m.json); - - return true; -} - -bool FileTransferJob::get_msg(uint32_t timeout_ms, int &kind, std::string &json) -{ - if (!h_) return false; - ft_job_msg m{}; - int rc = m_->ft_job_get_msg(h_, timeout_ms, &m); - if (rc != 0) return false; - - kind = m.kind; - json.assign(m.json ? m.json : ""); - - if (m_->ft_job_msg_destroy) - m_->ft_job_msg_destroy(&m); - else if (m_->ft_free && m.json) - m_->ft_free((void *) m.json); - - return true; -} - -void FileTransferJob::solve_result(ft_job_result result) -{ - res_ = result.ec; - resp_ec_ = result.resp_ec; - - res_bin_.clear(); - if (result.bin && result.bin_size) res_bin_.assign(reinterpret_cast(result.bin), - reinterpret_cast(result.bin) + result.bin_size); - res_json_.assign(result.json ? result.json : ""); -} - -} // namespace Slic3r \ No newline at end of file +} // namespace Slic3r diff --git a/src/slic3r/Utils/FileTransferUtils.hpp b/src/slic3r/Utils/FileTransferUtils.hpp index 037a1bef0c..2e0d162ddb 100644 --- a/src/slic3r/Utils/FileTransferUtils.hpp +++ b/src/slic3r/Utils/FileTransferUtils.hpp @@ -1,13 +1,7 @@ #pragma once #include -#include -#include -#include #include #include -#include -#include -#include #include #ifdef _WIN32 @@ -134,103 +128,11 @@ struct FileTransferModule FileTransferModule &operator=(const FileTransferModule &) = delete; }; -class FileTransferTunnel -{ -public: - using ConnectionCb = std::function; - using TunnelStatusCb = std::function; - - explicit FileTransferTunnel(FileTransferModule &m, const std::string &url); - ~FileTransferTunnel() { reset(); } - - FileTransferTunnel(const FileTransferTunnel &) = delete; - FileTransferTunnel &operator=(const FileTransferTunnel &) = delete; - FileTransferTunnel(FileTransferTunnel &&) = delete; - FileTransferTunnel &operator=(FileTransferTunnel &&) = delete; - - void start_connect(); - bool sync_start_connect(); - void on_connection(ConnectionCb cb); - void on_status(TunnelStatusCb cb); - - void shutdown(); - - int get_status() const { return status_; } - bool check_valid() const { return h_ != nullptr; } - FT_TunnelHandle *native() const noexcept { return h_; } - -private: - void reset() noexcept - { - if (h_) { - m_->ft_tunnel_release(h_); - h_ = nullptr; - } - } - - int status_{}; - FileTransferModule *m_{}; - FT_TunnelHandle *h_{}; - ConnectionCb conn_cb_{}; - TunnelStatusCb status_cb_{}; -}; - -class FileTransferJob -{ -public: - using ResultCb = std::function bin_res)>; - using MsgCb = std::function; - - explicit FileTransferJob(FileTransferModule &m, const std::string ¶ms_json); - ~FileTransferJob() { reset(); } - - FileTransferJob(const FileTransferJob &) = delete; - FileTransferJob &operator=(const FileTransferJob &) = delete; - FileTransferJob(FileTransferJob &&) = delete; - FileTransferJob &operator=(FileTransferJob &&) = delete; - - void on_result(ResultCb cb); - - bool get_result(int &ec, int &resp_ec, std::string &json, std::vector &bin, uint32_t timeout_ms); - - void start_on(FileTransferTunnel &t); - - void on_msg(MsgCb cb); - - bool try_get_msg(int &kind, std::string &json); - - bool get_msg(uint32_t timeout_ms, int &kind, std::string &json); - - FT_JobHandle *native() const noexcept { return h_; } - bool check_valid() const { return h_ != nullptr; } - bool finished() const { return finished_; } - - void cancel() - { - if (m_->ft_job_cancel && h_) m_->ft_job_cancel(h_); - } - -private: - void reset() noexcept - { - if (h_) { - m_->ft_job_release(h_); - h_ = nullptr; - } - } - - void solve_result(ft_job_result result); - - FileTransferModule *m_{}; - FT_JobHandle *h_{}; - ResultCb result_cb_{}; - MsgCb msg_cb_{}; - bool finished_ = false; - int res_ = 0; - int resp_ec_ = 0; - std::string res_json_; - std::vector res_bin_; -}; +// FileTransferTunnel/FileTransferJob (the OOP wrapper around the ft_tunnel_*/ +// ft_job_* ABI below) live in BBLPrinterAgent.hpp as BBLFileTransferTunnel/ +// BBLFileTransferJob, implementing IFileTransferTunnel/IFileTransferJob +// (IPrinterAgent.hpp) - this header stays the low-level symbol-table layer +// only, same role as bambu_networking.hpp's function pointer typedefs. namespace detail { inline FileTransferModule *g_mod = nullptr; diff --git a/src/slic3r/Utils/ICloudServiceAgent.hpp b/src/slic3r/Utils/ICloudServiceAgent.hpp index 556c253641..e446afab09 100644 --- a/src/slic3r/Utils/ICloudServiceAgent.hpp +++ b/src/slic3r/Utils/ICloudServiceAgent.hpp @@ -47,6 +47,9 @@ struct CloudEvent { using AppOnServerConnectedFn = std::function; using AppOnHttpErrorFn = std::function; +struct CameraURLParams; +struct CameraURLResult; + class ICloudServiceAgent { public: virtual ~ICloudServiceAgent() = default; @@ -328,7 +331,8 @@ public: /** * Request live camera streaming URL. */ - virtual int get_camera_url(std::string dev_id, std::function callback) = 0; + virtual int get_camera_url(std::string dev_id, std::function callback, + CameraURLParams params) = 0; /** * Fetch staff-picked designs from model mall. diff --git a/src/slic3r/Utils/IPrinterAgent.hpp b/src/slic3r/Utils/IPrinterAgent.hpp index 0e88e34d4a..d869e56ba6 100644 --- a/src/slic3r/Utils/IPrinterAgent.hpp +++ b/src/slic3r/Utils/IPrinterAgent.hpp @@ -2,6 +2,7 @@ #define __I_PRINTER_AGENT_HPP__ #include "bambu_networking.hpp" +#include // why: these extend the BAMBU_NETWORK_* return space rather than opening a new one - the value // flows through the same int domain callers already compare against BAMBU_NETWORK_SUCCESS. // They live here and not in bambu_networking.hpp because that file is a vendor header replaced @@ -9,8 +10,14 @@ // -70xx is free: the vendor occupies -1..-25 and -10xx through -60xx. #define ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED -7010 // no translation exists for this command #define ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE -7020 // a translation exists; this printer lacks the capability +#define ORCA_NETWORK_ERR_ACCESS_VERIFICATION_FAILED -7030 // printer access preflight failed before printing #include #include +#include +#include +#include + +#include "NetworkAgent.hpp" namespace Slic3r { @@ -43,6 +50,77 @@ enum class FilamentSyncMode { pull ///< On-demand fetch via REST API (blocking call) }; +class IFileTransferTunnel +{ +public: + using ConnectionCb = std::function; + using TunnelStatusCb = std::function; + + explicit IFileTransferTunnel(const std::string& url) : url_(url) {} + virtual ~IFileTransferTunnel() = default; + + IFileTransferTunnel(const IFileTransferTunnel&) = delete; + IFileTransferTunnel& operator=(const IFileTransferTunnel&) = delete; + IFileTransferTunnel(IFileTransferTunnel&&) = delete; + IFileTransferTunnel& operator=(IFileTransferTunnel&&) = delete; + + virtual void start_connect() = 0; + virtual bool sync_start_connect() = 0; + virtual void on_connection(ConnectionCb cb) { conn_cb_ = std::move(cb); } + virtual void on_status(TunnelStatusCb cb) { status_cb_ = std::move(cb); } + + virtual void shutdown() = 0; + + virtual int get_status() const { return status_; } + virtual bool check_valid() const = 0; + + // why: IFileTransferJob::start_on() only ever sees a tunnel through this interface, + // but needs the concrete backend handle to hand to its own start-job call - native() + // is the type-erased escape hatch, same pattern IFileTransferJob::native() already uses. + virtual void *native() const noexcept { return nullptr; } + +protected: + std::string url_; + int status_{}; + ConnectionCb conn_cb_{}; + TunnelStatusCb status_cb_{}; +}; + +class IFileTransferJob { +public: + using ResultCb = std::function bin_res)>; + using MsgCb = std::function; + + explicit IFileTransferJob(const std::string ¶ms_json) : params_json_(params_json) {} + virtual ~IFileTransferJob() = default; + + IFileTransferJob(const IFileTransferJob &) = delete; + IFileTransferJob &operator=(const IFileTransferJob &) = delete; + IFileTransferJob(IFileTransferJob &&) = delete; + IFileTransferJob &operator=(IFileTransferJob &&) = delete; + + virtual void on_result(ResultCb cb) { result_cb_ = std::move(cb); } + virtual bool get_result(int &ec, int &resp_ec, std::string &json, std::vector &bin, uint32_t timeout_ms) = 0; + virtual void start_on(IFileTransferTunnel &t) = 0; + virtual void on_msg(MsgCb cb) { msg_cb_ = std::move(cb); } + virtual bool try_get_msg(int &kind, std::string &json) = 0; + virtual bool get_msg(uint32_t timeout_ms, int &kind, std::string &json) = 0; + virtual void *native() const noexcept { return nullptr; } + virtual bool check_valid() const = 0; + virtual bool finished() const { return finished_; } + virtual void cancel() = 0; + +protected: + std::string params_json_; + ResultCb result_cb_{}; + MsgCb msg_cb_{}; + bool finished_ = false; + int res_ = 0; + int resp_ec_ = 0; + std::string res_json_; + std::vector res_bin_; +}; + /** * IPrinterAgent - Interface for printer operations. * @@ -111,8 +189,16 @@ public: * Build a ready-to-use local (LAN) camera stream URL for this agent's protocol. * Returns an empty string if the agent has no local camera stream support. */ - virtual std::string get_local_camera_url(std::string dev_ip, std::string username, std::string password) - { return {}; } + virtual std::string get_local_camera_url(CameraURLParams params) { return ""; } + + /** + * Build a ready-to-use local (LAN) file transfer URL for this agent's protocol. + * Returns an empty string if the agent has no local file transfer support. + */ + virtual std::string get_local_file_transfer_url(const FileTransferURLParams& params) { return ""; } + + virtual int get_file_transfer_url(std::string, std::function, FileTransferURLParams) + { return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } /** * Default LAN account username for this agent's protocol, if it has a fixed one. @@ -326,6 +412,33 @@ public: * Populates the MachineObject's DevFilaSystem with fetched filament data. */ virtual bool fetch_filament_info(std::string dev_id) { return false; } + + /** + * Build a local eMMC transfer tunnel for this agent's protocol, if it has one. + * Returns nullptr if the agent has no local file-transfer tunnel concept + * (matches the inert-default pattern used by get_local_camera_url() etc. above - + * this stays non-pure so adding it doesn't force every IPrinterAgent implementation, + * including the Python plugin capability bridge, to override a Bambu-only concept). + */ + virtual std::unique_ptr create_file_transfer_tunnel(std::string& dev_ip, std::string& access_code) + { return nullptr; } + + /** + * Wrap an already-resolved transfer URL (e.g. a cloud-relay/TUTK URL obtained via + * a camera-url lookup) in a local transfer tunnel. Same inert-default reasoning as + * create_file_transfer_tunnel() above; use that one instead when building a tunnel + * straight from dev_ip/access_code. + */ + virtual std::unique_ptr create_file_transfer_tunnel_from_url(std::string url) + { return nullptr; } + + /** + * Build a file-transfer job (media-ability query, upload, ...) to run on a tunnel + * from create_file_transfer_tunnel()/create_file_transfer_tunnel_from_url(). Same + * inert-default reasoning as the tunnel factories above. + */ + virtual std::unique_ptr create_file_transfer_job(std::string params_json) + { return nullptr; } }; } // namespace Slic3r diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index ce1639541f..7598408e25 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -4,6 +4,7 @@ #include #include +#include "IPrinterAgent.hpp" #include "libslic3r/Utils.hpp" #include "NetworkAgent.hpp" #include "BBLNetworkPlugin.hpp" @@ -507,11 +508,12 @@ int NetworkAgent::modify_printer_name(std::string dev_id, std::string dev_name, return -1; } -int NetworkAgent::get_camera_url(std::string dev_id, std::function callback, const std::string& provider) +int NetworkAgent::get_camera_url(std::string dev_id, std::function callback, + const std::string& provider, CameraURLParams params) { const auto cloud_agent = get_cloud_agent(provider); if (cloud_agent) - return cloud_agent->get_camera_url(std::move(dev_id), std::move(callback)); + return cloud_agent->get_camera_url(std::move(dev_id), std::move(callback), std::move(params)); return -1; } @@ -852,13 +854,28 @@ int NetworkAgent::send_message_to_printer(std::string dev_id, std::string json_s return -1; } -std::string NetworkAgent::get_local_camera_url(std::string dev_ip, std::string username, std::string password) +std::string NetworkAgent::get_local_camera_url(CameraURLParams params) { if (m_printer_agent) - return m_printer_agent->get_local_camera_url(dev_ip, username, password); + return m_printer_agent->get_local_camera_url(params); return {}; } +std::string NetworkAgent::get_local_file_transfer_url(const FileTransferURLParams& params) +{ + if (m_printer_agent) + return m_printer_agent->get_local_file_transfer_url(params); + return {}; +} + +int NetworkAgent::get_file_transfer_url(std::string dev_id, std::function callback, + FileTransferURLParams params) +{ + if (m_printer_agent) + return m_printer_agent->get_file_transfer_url(std::move(dev_id), std::move(callback), std::move(params)); + return -1; +} + std::string NetworkAgent::default_lan_username() const { if (m_printer_agent) diff --git a/src/slic3r/Utils/NetworkAgent.hpp b/src/slic3r/Utils/NetworkAgent.hpp index cc03362f88..0f3a055e03 100644 --- a/src/slic3r/Utils/NetworkAgent.hpp +++ b/src/slic3r/Utils/NetworkAgent.hpp @@ -2,9 +2,11 @@ #define __NETWORK_Agent_HPP__ #include "bambu_networking.hpp" + #include "libslic3r/ProjectTask.hpp" #include "ICloudServiceAgent.hpp" -#include "IPrinterAgent.hpp" +#include "slic3r/GUI/DeviceManager.hpp" + #include #include #include @@ -12,6 +14,53 @@ namespace Slic3r { +class IPrinterAgent; +enum class FilamentSyncMode; + +enum URL_STATE { + URL_TCP, + URL_TUTK, +}; + +struct CameraURLParams { + std::string ip_address; + std::string user; + std::string password; + LiveviewLocal protocol; + std::string device; + std::string network_version; + std::string device_version; + std::string refresh_url; + std::string client_id; + std::string client_version; + bool apply_meta{false}; +}; + +struct FileTransferURLParams { + URL_STATE url_state{URL_TCP}; + std::string ip_address; + std::string username; + std::string password; + std::string device_id; + std::string network_version; + std::string device_version; + std::string refresh_url; + std::string client_id; + std::string client_version; +}; + +struct FileTransferURLResult { + bool is_success{false}; + std::string url; + int error_code{-1}; +}; + +struct CameraURLResult { + bool is_success{false}; + std::string url; + int error_code{-1}; +}; + // Forward declaration class BBLNetworkPlugin; @@ -108,7 +157,8 @@ public: int get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json, const std::string& provider = ORCA_CLOUD_PROVIDER); int query_bind_status(std::vector query_list, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER); int modify_printer_name(std::string dev_id, std::string dev_name, const std::string& provider = ORCA_CLOUD_PROVIDER); - int get_camera_url(std::string dev_id, std::function callback, const std::string& provider = ORCA_CLOUD_PROVIDER); + int get_camera_url(std::string dev_id, std::function callback, + const std::string& provider = ORCA_CLOUD_PROVIDER, CameraURLParams params = {}); int get_design_staffpick(int offset, int limit, std::function callback, const std::string& provider = ORCA_CLOUD_PROVIDER); int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out, const std::string& provider = ORCA_CLOUD_PROVIDER); int get_model_publish_url(std::string* url, const std::string& provider = ORCA_CLOUD_PROVIDER); @@ -155,7 +205,10 @@ public: int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl); int disconnect_printer(); int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag); - std::string get_local_camera_url(std::string dev_ip, std::string username, std::string password); + std::string get_local_camera_url(CameraURLParams params); + std::string get_local_file_transfer_url(const FileTransferURLParams& params); + int get_file_transfer_url(std::string dev_id, std::function callback, + FileTransferURLParams params = {}); std::string default_lan_username() const; int check_cert(); void install_device_cert(std::string dev_id, bool lan_only); diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp index a372ab5b7c..4b06c8b0e5 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp @@ -1,4 +1,5 @@ #include "OrcaCloudServiceAgent.hpp" +#include "NetworkAgent.hpp" #include "Http.hpp" #include "libslic3r/Utils.hpp" #include "slic3r/GUI/GUI_App.hpp" @@ -2698,11 +2699,12 @@ int OrcaCloudServiceAgent::modify_printer_name(std::string dev_id, std::string d return BAMBU_NETWORK_SUCCESS; } -int OrcaCloudServiceAgent::get_camera_url(std::string dev_id, std::function callback) +int OrcaCloudServiceAgent::get_camera_url(std::string dev_id, std::function callback, CameraURLParams params) { + (void) params; BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_camera_url (stub)"; if (callback) - callback(""); + callback({}); return BAMBU_NETWORK_SUCCESS; } diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp index 3ae86ec27c..696ba261c6 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp @@ -240,7 +240,7 @@ public: // ======================================================================== // ICloudServiceAgent Interface Implementation - Model Mall & Publishing // ======================================================================== - int get_camera_url(std::string dev_id, std::function callback) override; + int get_camera_url(std::string dev_id, std::function callback, CameraURLParams params) override; int get_design_staffpick(int offset, int limit, std::function callback) override; int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) override; int get_model_publish_url(std::string* url) override;