refactor: push bbl workflows to bbl printer agent

This commit is contained in:
Ian Chua
2026-08-12 13:50:47 +08:00
parent d5c528b7c7
commit 7cb1805272
23 changed files with 967 additions and 585 deletions
+2
View File
@@ -13,6 +13,8 @@
#include "libslic3r/Time.hpp"
#include "IPrinterAgent.hpp"
using namespace nlohmann;
namespace {
+73
View File
@@ -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<std::string>{});
// 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);
+30 -23
View File
@@ -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
{
+20 -35
View File
@@ -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
+10 -25
View File
@@ -11,6 +11,7 @@
#include "Widgets/ProgressDialog.hpp"
#include <libslic3r/Model.hpp>
#include <libslic3r/Format/bbs_3mf.hpp>
#include <slic3r/GUI/DeviceManager.hpp>
#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<PrinterFileSystem> 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<PrinterFileSystem> 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<std::string>(&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<std::string>(&refresh_agora_url), wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION, true});
}
}
+62 -78
View File
@@ -14,6 +14,7 @@
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/utf8_codecvt.hpp>
#include <slic3r/GUI/DeviceManager.hpp>
#undef pid_t
#include <boost/process.hpp>
#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<std::string>(&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<std::string>(&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<std::string>(&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<std::string>(&refresh_agora_url), wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION, true});
}
void MediaPlayCtrl::msw_rescale() {
+24 -44
View File
@@ -433,58 +433,38 @@ void PartSkipDialog::fetchUrl(boost::weak_ptr<PrinterFileSystem> 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<std::string>(&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<std::string>(&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)
+1 -6
View File
@@ -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
}} // namespace Slic3r::GUI
+5
View File
@@ -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);
+119 -55
View File
@@ -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<FileTransferTunnel>(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<std::string>(&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<FileTransferTunnel>(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<std::string>(&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<FileTransferJob>(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<std::byte> 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<FileTransferJob>(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<std::byte> 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."));
}
}
+5 -5
View File
@@ -24,6 +24,7 @@
#include <wx/wrapsizer.h>
#include <wx/srchctrl.h>
#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<FileTransferTunnel> m_filetransfer_tunnel;
std::unique_ptr<FileTransferJob> m_filetransfer_mediability_job;
std::unique_ptr<FileTransferJob> m_filetransfer_uploadfile_job;
std::unique_ptr<IFileTransferTunnel> m_filetransfer_tunnel;
std::unique_ptr<IFileTransferJob> m_filetransfer_mediability_job;
std::unique_ptr<IFileTransferJob> 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);
+37 -2
View File
@@ -1,5 +1,6 @@
#include "BBLCloudServiceAgent.hpp"
#include "BBLNetworkPlugin.hpp"
#include "NetworkAgent.hpp"
#include <boost/log/trivial.hpp>
#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<void(std::string)> callback)
int BBLCloudServiceAgent::get_camera_url(std::string dev_id, std::function<void(CameraURLResult)> 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;
}
+1 -1
View File
@@ -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<void(std::string)> callback) override;
int get_camera_url(std::string dev_id, std::function<void(CameraURLResult)> callback, CameraURLParams params) override;
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> 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;
+289 -2
View File
@@ -1,14 +1,208 @@
#include "BBLPrinterAgent.hpp"
#include "BBLNetworkPlugin.hpp"
#include "FileTransferUtils.hpp"
#include "IPrinterAgent.hpp"
#include "NetworkAgentFactory.hpp"
#include "NetworkAgent.hpp"
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <memory>
#include <nlohmann/json.hpp>
#include <cmath>
#include <slic3r/GUI/DeviceManager.hpp>
#include <libslic3r/Utils.hpp>
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<BBLFileTransferTunnel *>(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<ConnectionCb *>(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 &params_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<BBLFileTransferJob *>(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<std::byte> &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<FT_TunnelHandle *>(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<BBLFileTransferJob *>(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<const std::byte *>(result.bin),
reinterpret_cast<const std::byte *>(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<void(FileTransferURLResult)> 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<func_start_send_gcode_to_sdcard_legacy, func_start_send_gcode_to_sdcard_0203>(
@@ -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<func_start_local_print_legacy, func_start_local_print_0203>(
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<IFileTransferTunnel> 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<BBLFileTransferTunnel>(url);
}
std::unique_ptr<IFileTransferTunnel> BBLPrinterAgent::create_file_transfer_tunnel_from_url(std::string url) {
return std::make_unique<BBLFileTransferTunnel>(url);
}
std::unique_ptr<IFileTransferJob> BBLPrinterAgent::create_file_transfer_job(std::string params_json) {
return std::make_unique<BBLFileTransferJob>(params_json);
}
} // namespace Slic3r
+81 -1
View File
@@ -3,12 +3,83 @@
#include "IPrinterAgent.hpp"
#include "ICloudServiceAgent.hpp"
#include "FileTransferUtils.hpp"
#include <string>
#include <memory>
#include <nlohmann/json.hpp>
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 &params_json);
~BBLFileTransferJob() override { reset(); }
bool get_result(int &ec, int &resp_ec, std::string &json, std::vector<std::byte> &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<void(FileTransferURLResult)> 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<IFileTransferTunnel> create_file_transfer_tunnel(std::string& dev_ip, std::string& access_code) override;
std::unique_ptr<IFileTransferTunnel> create_file_transfer_tunnel_from_url(std::string url) override;
std::unique_ptr<IFileTransferJob> 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);
+1 -192
View File
@@ -1,8 +1,4 @@
#include <wx/wx.h>
#include <type_traits>
#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<fn_ft_job_get_msg>(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<FileTransferTunnel *>(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<ConnectionCb *>(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 &params_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<FileTransferJob *>(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<std::byte> &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<FileTransferJob *>(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<const std::byte *>(result.bin),
reinterpret_cast<const std::byte *>(result.bin) + result.bin_size);
res_json_.assign(result.json ? result.json : "");
}
} // namespace Slic3r
} // namespace Slic3r
+5 -103
View File
@@ -1,13 +1,7 @@
#pragma once
#include <string>
#include <functional>
#include <vector>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <utility>
#include <cstring>
#include <memory>
#include <boost/log/trivial.hpp>
#ifdef _WIN32
@@ -134,103 +128,11 @@ struct FileTransferModule
FileTransferModule &operator=(const FileTransferModule &) = delete;
};
class FileTransferTunnel
{
public:
using ConnectionCb = std::function<void(bool is_success, int err_code, std::string error_msg)>;
using TunnelStatusCb = std::function<void(int old_status, int new_status, int err_code, std::string error_msg)>;
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<void(int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res)>;
using MsgCb = std::function<void(int kind, std::string json)>;
explicit FileTransferJob(FileTransferModule &m, const std::string &params_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<std::byte> &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<std::byte> 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;
+5 -1
View File
@@ -47,6 +47,9 @@ struct CloudEvent {
using AppOnServerConnectedFn = std::function<void(CloudEvent event, int return_code, int reason_code)>;
using AppOnHttpErrorFn = std::function<void(CloudEvent event, unsigned http_code, std::string http_body)>;
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<void(std::string)> callback) = 0;
virtual int get_camera_url(std::string dev_id, std::function<void(CameraURLResult)> callback,
CameraURLParams params) = 0;
/**
* Fetch staff-picked designs from model mall.
+115 -2
View File
@@ -2,6 +2,7 @@
#define __I_PRINTER_AGENT_HPP__
#include "bambu_networking.hpp"
#include <slic3r/GUI/DeviceManager.hpp>
// 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 <string>
#include <memory>
#include <vector>
#include <functional>
#include <cstdint>
#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<void(bool is_success, int err_code, std::string error_msg)>;
using TunnelStatusCb = std::function<void(int old_status, int new_status, int err_code, std::string error_msg)>;
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<void(int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res)>;
using MsgCb = std::function<void(int kind, std::string json)>;
explicit IFileTransferJob(const std::string &params_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<std::byte> &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<std::byte> 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<void(FileTransferURLResult)>, 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<IFileTransferTunnel> 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<IFileTransferTunnel> 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<IFileTransferJob> create_file_transfer_job(std::string params_json)
{ return nullptr; }
};
} // namespace Slic3r
+21 -4
View File
@@ -4,6 +4,7 @@
#include <algorithm>
#include <boost/log/trivial.hpp>
#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<void(std::string)> callback, const std::string& provider)
int NetworkAgent::get_camera_url(std::string dev_id, std::function<void(CameraURLResult)> 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<void(FileTransferURLResult)> 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)
+56 -3
View File
@@ -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 <map>
#include <memory>
#include <string>
@@ -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<std::string> 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<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
int get_camera_url(std::string dev_id, std::function<void(CameraURLResult)> callback,
const std::string& provider = ORCA_CLOUD_PROVIDER, CameraURLParams params = {});
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> 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<void(FileTransferURLResult)> callback,
FileTransferURLParams params = {});
std::string default_lan_username() const;
int check_cert();
void install_device_cert(std::string dev_id, bool lan_only);
+4 -2
View File
@@ -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<void(std::string)> callback)
int OrcaCloudServiceAgent::get_camera_url(std::string dev_id, std::function<void(CameraURLResult)> callback, CameraURLParams params)
{
(void) params;
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_camera_url (stub)";
if (callback)
callback("");
callback({});
return BAMBU_NETWORK_SUCCESS;
}
+1 -1
View File
@@ -240,7 +240,7 @@ public:
// ========================================================================
// ICloudServiceAgent Interface Implementation - Model Mall & Publishing
// ========================================================================
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) override;
int get_camera_url(std::string dev_id, std::function<void(CameraURLResult)> callback, CameraURLParams params) override;
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> 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;