This commit is contained in:
SoftFever
2026-01-26 19:34:14 +08:00
parent 1a696173c0
commit 978e3b79b5
17 changed files with 276 additions and 568 deletions

View File

@@ -518,7 +518,7 @@ namespace Slic3r
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false); it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else #else
it->second->connect(it->second->local_use_ssl_for_mqtt); it->second->connect(it->second->local_use_ssl);
#endif #endif
it->second->set_lan_mode_connection_state(true); it->second->set_lan_mode_connection_state(true);
} }
@@ -542,7 +542,7 @@ namespace Slic3r
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false); it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else #else
it->second->connect(it->second->local_use_ssl_for_mqtt); it->second->connect(it->second->local_use_ssl);
#endif #endif
it->second->set_lan_mode_connection_state(true); it->second->set_lan_mode_connection_state(true);
} }

View File

@@ -363,6 +363,30 @@ std::string MachineObject::get_ftp_folder()
return DevPrinterConfigUtil::get_ftp_folder(printer_type); return DevPrinterConfigUtil::get_ftp_folder(printer_type);
} }
std::string MachineObject::dev_id_from_address(const std::string& host, const std::string& port)
{
std::string result = host;
// Normalize host: strip protocol and path
if (result.find("http://") == 0)
result = result.substr(7);
else if (result.find("https://") == 0)
result = result.substr(8);
auto slash = result.find('/');
if (slash != std::string::npos)
result = result.substr(0, slash);
// Build full address (host:port)
if (!port.empty()) {
// Strip inline port if present (port comes from printhost_port)
auto colon = result.find(':');
if (colon != std::string::npos)
result = result.substr(0, colon);
result += ":" + port;
}
return result;
}
bool MachineObject::HasRecentCloudMessage() bool MachineObject::HasRecentCloudMessage()
{ {
auto curr_time = std::chrono::system_clock::now(); auto curr_time = std::chrono::system_clock::now();

View File

@@ -164,7 +164,11 @@ public:
std::string get_dev_id() const { return dev_id; } std::string get_dev_id() const { return dev_id; }
void set_dev_id(std::string val) { dev_id = val; } void set_dev_id(std::string val) { dev_id = val; }
bool local_use_ssl_for_mqtt { true }; // Generate consistent dev_id from host address and optional port
// Returns "host:port" or "host" if port is empty
static std::string dev_id_from_address(const std::string& host, const std::string& port = "");
bool local_use_ssl { true };
bool local_use_ssl_for_ftp { true }; bool local_use_ssl_for_ftp { true };
std::string get_ftp_folder(); std::string get_ftp_folder();

View File

@@ -3520,32 +3520,6 @@ void GUI_App::switch_printer_agent(const std::string& agent_id)
// Swap the agent // Swap the agent
m_agent->set_printer_agent(new_printer_agent); m_agent->set_printer_agent(new_printer_agent);
// Update dependent managers
if (m_device_manager) {
m_device_manager->set_agent(m_agent);
// If there's a selected machine that was deferred due to no printer agent,
// trigger a connection now that the agent is ready
MachineObject* selected = m_device_manager->get_selected_machine();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": checking for deferred connection - selected="
<< (selected ? selected->get_dev_id() : "null");
if (selected) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": selected machine - is_lan_mode=" << selected->is_lan_mode_printer()
<< " is_connected=" << selected->is_connected();
}
if (selected && selected->is_lan_mode_printer() && !selected->is_connected()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": connecting deferred LAN machine dev_id=" << selected->get_dev_id();
#if !BBL_RELEASE_TO_PUBLIC
selected->connect(app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else
selected->connect(selected->local_use_ssl_for_mqtt);
#endif
selected->set_lan_mode_connection_state(true);
}
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": m_device_manager is null, cannot check for deferred connection";
}
// Auto-switch MachineObject // Auto-switch MachineObject
select_machine(effective_agent_id); select_machine(effective_agent_id);
@@ -3571,41 +3545,12 @@ void GUI_App::select_machine(const std::string& agent_id)
std::string print_host = host_cfg->opt_string("print_host"); std::string print_host = host_cfg->opt_string("print_host");
if (print_host.empty()) { if (print_host.empty()) {
if (auto* physical_cfg = preset_bundle->physical_printers.get_selected_printer_config()) {
if (!physical_cfg->opt_string("print_host").empty()) {
host_cfg = physical_cfg;
print_host = host_cfg->opt_string("print_host");
}
}
}
if (print_host.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no print_host configured, skipping auto-switch";
return; return;
} }
// Normalize host: strip protocol and path
std::string host = print_host;
if (host.find("http://") == 0) host = host.substr(7);
else if (host.find("https://") == 0) host = host.substr(8);
auto slash = host.find('/');
if (slash != std::string::npos) host = host.substr(0, slash);
// Strip inline port if present (port comes from printhost_port)
auto colon = host.find(':');
if (colon != std::string::npos) host = host.substr(0, colon);
// Get port from separate config
std::string port = host_cfg->opt_string("printhost_port"); std::string port = host_cfg->opt_string("printhost_port");
// Build full address (host:port) for dev_ip // Generate dev_id from host and port
std::string full_addr = host; std::string dev_id = MachineObject::dev_id_from_address(print_host, port);
if (!port.empty()) {
full_addr += ":" + port;
}
// Generate dev_id (replace . and : with _)
std::string dev_id = full_addr;
std::replace(dev_id.begin(), dev_id.end(), '.', '_');
std::replace(dev_id.begin(), dev_id.end(), ':', '_');
// Check if already exists by dev_id // Check if already exists by dev_id
MachineObject* existing = m_device_manager->get_local_machine(dev_id); MachineObject* existing = m_device_manager->get_local_machine(dev_id);
@@ -3614,9 +3559,8 @@ void GUI_App::select_machine(const std::string& agent_id)
if (!existing) { if (!existing) {
auto local_machines = m_device_manager->get_local_machinelist(); auto local_machines = m_device_manager->get_local_machinelist();
for (auto& [id, machine] : local_machines) { for (auto& [id, machine] : local_machines) {
if (machine && machine->get_dev_ip() == full_addr) { if (machine && machine->get_dev_ip() == dev_id) {
existing = machine; existing = machine;
dev_id = existing->get_dev_id(); // Use existing dev_id
break; break;
} }
} }
@@ -3626,8 +3570,9 @@ void GUI_App::select_machine(const std::string& agent_id)
if (!existing) { if (!existing) {
BBLocalMachine machine; BBLocalMachine machine;
machine.dev_id = dev_id; machine.dev_id = dev_id;
machine.dev_ip = full_addr; // We use dev_id as dev_ip to store the address (host:port)
machine.dev_name = agent_id + " (" + full_addr + ")"; machine.dev_ip = dev_id;
machine.dev_name = dev_id;
machine.printer_type = preset.config.opt_string("printer_model"); machine.printer_type = preset.config.opt_string("printer_model");
existing = m_device_manager->insert_local_device( existing = m_device_manager->insert_local_device(
@@ -3639,6 +3584,7 @@ void GUI_App::select_machine(const std::string& agent_id)
} }
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": created new machine dev_id=" << dev_id; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": created new machine dev_id=" << dev_id;
} }
existing->local_use_ssl = boost::istarts_with(print_host, "https://");
// Use MonitorPanel::select_machine() to trigger full selection flow // Use MonitorPanel::select_machine() to trigger full selection flow
// This reuses existing logic for machine switching (UI updates, callbacks, etc.) // This reuses existing logic for machine switching (UI updates, callbacks, etc.)

View File

@@ -202,7 +202,7 @@ void PrintJob::process(Ctl &ctl)
// local print access // local print access
params.dev_ip = m_dev_ip; params.dev_ip = m_dev_ip;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt; params.use_ssl_for_mqtt = m_local_use_ssl;
params.username = "bblp"; params.username = "bblp";
params.password = m_access_code; params.password = m_access_code;

View File

@@ -74,7 +74,7 @@ public:
int m_print_from_sdc_plate_idx = 0; int m_print_from_sdc_plate_idx = 0;
bool m_local_use_ssl_for_mqtt { true }; bool m_local_use_ssl { true };
bool m_local_use_ssl_for_ftp { true }; bool m_local_use_ssl_for_ftp { true };
bool task_bed_leveling; bool task_bed_leveling;
bool task_flow_cali; bool task_flow_cali;

View File

@@ -183,7 +183,7 @@ void SendJob::process(Ctl &ctl)
params.username = "bblp"; params.username = "bblp";
params.password = m_access_code; params.password = m_access_code;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt; params.use_ssl_for_mqtt = m_local_use_ssl;
wxString error_text; wxString error_text;
std::string msg_text; std::string msg_text;

View File

@@ -42,7 +42,7 @@ public:
std::string connection_type; std::string connection_type;
bool m_local_use_ssl_for_ftp{true}; bool m_local_use_ssl_for_ftp{true};
bool m_local_use_ssl_for_mqtt{true}; bool m_local_use_ssl{true};
bool cloud_print_only { false }; bool cloud_print_only { false };
bool has_sdcard { false }; bool has_sdcard { false };
bool task_use_ams { true }; bool task_use_ams { true };

View File

@@ -1844,10 +1844,10 @@ void InputIpAddressDialog::on_send_retry()
m_send_job->m_access_code = str_access_code.ToStdString(); m_send_job->m_access_code = str_access_code.ToStdString();
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; m_send_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
#else #else
m_send_job->m_local_use_ssl_for_mqtt = m_obj->local_use_ssl_for_mqtt; m_send_job->m_local_use_ssl = m_obj->local_use_ssl;
m_send_job->m_local_use_ssl_for_ftp = m_obj->local_use_ssl_for_ftp; m_send_job->m_local_use_ssl_for_ftp = m_obj->local_use_ssl_for_ftp;
#endif #endif

View File

@@ -2464,10 +2464,10 @@ void SelectMachineDialog::on_send_print()
m_print_job->m_access_code = obj_->get_access_code(); m_print_job->m_access_code = obj_->get_access_code();
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
m_print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; m_print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
m_print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; m_print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
#else #else
m_print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; m_print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp;
m_print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; m_print_job->m_local_use_ssl = obj_->local_use_ssl;
#endif #endif
m_print_job->connection_type = obj_->connection_type(); m_print_job->connection_type = obj_->connection_type();
m_print_job->cloud_print_only = obj_->is_support_cloud_print_only; m_print_job->cloud_print_only = obj_->is_support_cloud_print_only;

View File

@@ -958,10 +958,10 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event)
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; m_send_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
#else #else
m_send_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; m_send_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp;
m_send_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; m_send_job->m_local_use_ssl = obj_->local_use_ssl;
#endif #endif
m_send_job->connection_type = obj_->connection_type(); m_send_job->connection_type = obj_->connection_type();

View File

@@ -1791,10 +1791,10 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
#else #else
print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp;
print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; print_job->m_local_use_ssl = obj_->local_use_ssl;
#endif #endif
print_job->connection_type = obj_->connection_type(); print_job->connection_type = obj_->connection_type();
@@ -1896,10 +1896,10 @@ void CalibUtils::send_to_print(const std::vector<CalibInfo> &calib_infos, wxStri
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
#else #else
print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp;
print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; print_job->m_local_use_ssl = obj_->local_use_ssl;
#endif #endif
print_job->connection_type = obj_->connection_type(); print_job->connection_type = obj_->connection_type();

View File

@@ -3,6 +3,7 @@
#include "libslic3r/Preset.hpp" #include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@@ -27,90 +28,6 @@ namespace websocket = beast::websocket;
namespace net = boost::asio; namespace net = boost::asio;
using tcp = net::ip::tcp; using tcp = net::ip::tcp;
constexpr const char* k_no_api_key = "__NO_API_KEY__";
bool is_numeric(const std::string& value)
{
return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; });
}
std::string normalize_base_url(std::string host, const std::string& port)
{
boost::trim(host);
if (host.empty()) {
return "";
}
std::string value = host;
if (is_numeric(port) && value.find("://") == std::string::npos && value.find(':') == std::string::npos) {
value += ":" + port;
}
if (!boost::istarts_with(value, "http://") && !boost::istarts_with(value, "https://")) {
value = "http://" + value;
}
if (value.size() > 1 && value.back() == '/') {
value.pop_back();
}
return value;
}
std::string extract_host(const std::string& base_url)
{
std::string host = base_url;
auto pos = host.find("://");
if (pos != std::string::npos) {
host = host.substr(pos + 3);
}
pos = host.find('/');
if (pos != std::string::npos) {
host = host.substr(0, pos);
}
return host;
}
std::string join_url(const std::string& base_url, const std::string& path)
{
if (base_url.empty()) {
return "";
}
if (path.empty()) {
return base_url;
}
if (base_url.back() == '/' && path.front() == '/') {
return base_url.substr(0, base_url.size() - 1) + path;
}
if (base_url.back() != '/' && path.front() != '/') {
return base_url + "/" + path;
}
return base_url + path;
}
std::string normalize_api_key(const std::string& api_key)
{
if (api_key.empty() || api_key == k_no_api_key) {
return "";
}
return api_key;
}
// Sanitize filename to prevent path traversal attacks
// Extracts only the basename, removing any path components
std::string sanitize_filename(const std::string& filename)
{
if (filename.empty()) {
return "print.gcode";
}
namespace fs = boost::filesystem;
fs::path p(filename);
std::string basename = p.filename().string();
if (basename.empty() || basename == "." || basename == "..") {
return "print.gcode";
}
return basename;
}
struct WsEndpoint struct WsEndpoint
{ {
@@ -197,7 +114,6 @@ void MoonrakerPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent>
{ {
std::lock_guard<std::recursive_mutex> lock(state_mutex); std::lock_guard<std::recursive_mutex> lock(state_mutex);
m_cloud_agent = cloud; m_cloud_agent = cloud;
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Cloud agent set";
} }
int MoonrakerPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag) int MoonrakerPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
@@ -216,37 +132,25 @@ int MoonrakerPrinterAgent::send_message_to_printer(std::string dev_id, std::stri
int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
{ {
(void) username;
(void) use_ssl;
std::string base_url = normalize_base_url(dev_ip, ""); if (dev_id.empty() || dev_ip.empty()) {
std::string api_key = normalize_api_key(password); BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing dev_id or dev_ip";
PrinthostConfig config;
if (get_printhost_config(config)) {
if (base_url.empty()) {
base_url = config.base_url;
}
if (api_key.empty()) {
api_key = normalize_api_key(config.api_key);
}
}
if (base_url.empty()) {
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing host";
dispatch_local_connect(ConnectStatusFailed, dev_id, "host_missing");
return BAMBU_NETWORK_ERR_INVALID_HANDLE; return BAMBU_NETWORK_ERR_INVALID_HANDLE;
} }
if (dev_id.empty()) { init_device_info(dev_id, dev_ip, username, password, use_ssl);
dev_id = extract_host(base_url);
if (device_info.dev_id != dev_id) {
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer dev_id mismatch: expected " << device_info.dev_id << ", got " << dev_id;
dispatch_local_connect(ConnectStatusFailed, dev_id, "dev_id_mismatch");
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
} }
// Check if connection already in progress // Check if connection already in progress
{ {
std::lock_guard<std::recursive_mutex> lock(connect_mutex); std::lock_guard<std::recursive_mutex> lock(connect_mutex);
if (connect_in_progress.load()) { if (connect_in_progress.load()) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Connection already in progress, waiting..."; BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Connection already in progress, waiting...";
// Don't reject - wait for previous connection to complete // Don't reject - wait for previous connection to complete
// This can happen if MonitorPanel triggers connect while previous connect is still running // This can happen if MonitorPanel triggers connect while previous connect is still running
} else { } else {
@@ -278,15 +182,12 @@ int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_i
ws_last_dispatch_ms.store(0); ws_last_dispatch_ms.store(0);
last_print_state.clear(); last_print_state.clear();
store_host(dev_id, base_url, api_key);
// Launch connection in background thread // Launch connection in background thread
connect_thread = std::thread([this, dev_id, base_url, api_key]() { connect_thread = std::thread([this, dev_id]() {
perform_connection_async(dev_id, base_url, api_key); perform_connection_async(dev_id, device_info.base_url, device_info.api_key);
}); });
// Return immediately - UI is not blocked BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: connect_printer launched in background - dev_id=" << dev_id;
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: connect_printer launched in background - dev_id=" << dev_id;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
@@ -346,30 +247,18 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link,
return BAMBU_NETWORK_ERR_INVALID_HANDLE; return BAMBU_NETWORK_ERR_INVALID_HANDLE;
} }
PrinthostConfig config;
get_printhost_config(config);
const std::string api_key = normalize_api_key(config.api_key);
MoonrakerDeviceInfo info;
std::string error; std::string error;
if (!fetch_device_info(base_url, api_key, info, error)) { if (!fetch_device_info(base_url, device_info.api_key, device_info, error)) {
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: bind_detect failed: " << error; BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: bind_detect failed: " << error;
return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED; return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
} }
detect.dev_id = info.dev_id.empty() ? dev_ip : info.dev_id; detect.dev_id = device_info.dev_id.empty() ? dev_ip : device_info.dev_id;
if (!info.model_id.empty()) { detect.model_id = device_info.model_id.empty() ? device_info.model_name : device_info.model_id;
detect.model_id = info.model_id;
} else if (!config.model_id.empty()) {
detect.model_id = config.model_id;
} else {
detect.model_id = config.model_name;
}
// Prefer fetched hostname, then preset model name, then generic fallback // Prefer fetched hostname, then preset model name, then generic fallback
std::string fallback_name = config.model_name.empty() ? "Moonraker Printer" : config.model_name; detect.dev_name = device_info.dev_name;
detect.dev_name = info.dev_name.empty() ? fallback_name : info.dev_name; detect.model_id = device_info.model_id;
detect.model_id = "moonraker"; detect.version = device_info.version;
detect.version = info.version;
detect.connect_type = "lan"; detect.connect_type = "lan";
detect.bind_state = "free"; detect.bind_state = "free";
@@ -447,12 +336,6 @@ int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpda
if (update_fn) update_fn(PrintingStageCreate, 0, "Preparing..."); if (update_fn) update_fn(PrintingStageCreate, 0, "Preparing...");
const std::string base_url = resolve_host(params.dev_id);
if (base_url.empty()) {
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
const std::string api_key = resolve_api_key(params.dev_id, params.password);
std::string filename = params.filename; std::string filename = params.filename;
if (filename.empty()) { if (filename.empty()) {
filename = params.task_name; filename = params.task_name;
@@ -465,7 +348,7 @@ int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpda
std::string safe_filename = sanitize_filename(filename); std::string safe_filename = sanitize_filename(filename);
// Upload only, don't start print // Upload only, don't start print
if (!upload_gcode(params.filename, safe_filename, base_url, api_key, update_fn, cancel_fn)) { if (!upload_gcode(params.filename, safe_filename, device_info.base_url, device_info.api_key, update_fn, cancel_fn)) {
return BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED; return BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED;
} }
@@ -481,13 +364,6 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF
if (cancel_fn && cancel_fn()) { if (cancel_fn && cancel_fn()) {
return BAMBU_NETWORK_ERR_CANCELED; return BAMBU_NETWORK_ERR_CANCELED;
} }
const std::string base_url = resolve_host(params.dev_id);
if (base_url.empty()) {
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
const std::string api_key = resolve_api_key(params.dev_id, params.password);
// Determine the G-code file to upload // Determine the G-code file to upload
// params.filename may be .3mf, params.dst_file contains actual G-code // params.filename may be .3mf, params.dst_file contains actual G-code
std::string gcode_path = params.filename; std::string gcode_path = params.filename;
@@ -513,7 +389,7 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF
// Upload file // Upload file
if (update_fn) update_fn(PrintingStageUpload, 0, "Uploading G-code..."); if (update_fn) update_fn(PrintingStageUpload, 0, "Uploading G-code...");
if (!upload_gcode(gcode_path, upload_filename, base_url, api_key, update_fn, cancel_fn)) { if (!upload_gcode(gcode_path, upload_filename, device_info.base_url, device_info.api_key, update_fn, cancel_fn)) {
return BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED; return BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED;
} }
@@ -525,7 +401,7 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF
// Start print via gcode script (simpler than JSON-RPC) // Start print via gcode script (simpler than JSON-RPC)
if (update_fn) update_fn(PrintingStageSending, 0, "Starting print..."); if (update_fn) update_fn(PrintingStageSending, 0, "Starting print...");
std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename; std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename;
if (!send_gcode(params.dev_id, gcode)) { if (!send_gcode(device_info.dev_id, gcode)) {
return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED; return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED;
} }
@@ -548,11 +424,6 @@ int MoonrakerPrinterAgent::set_on_ssdp_msg_fn(OnMsgArrivedFn fn)
std::lock_guard<std::recursive_mutex> lock(state_mutex); std::lock_guard<std::recursive_mutex> lock(state_mutex);
on_ssdp_msg_fn = fn; on_ssdp_msg_fn = fn;
} }
// Call announce_printhost_device() outside the lock to avoid deadlock
// since announce_printhost_device() also acquires state_mutex
if (fn) {
announce_printhost_device();
}
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
@@ -607,16 +478,11 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
void MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) void MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
{ {
// Moonraker doesn't have standard filament tracking like Qidi BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info (base class no-op) called for dev_id=" << dev_id;
// This is a no-op for standard Moonraker installations
// Note: QidiPrinterAgent overrides this method with actual implementation
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info (base class no-op) called for dev_id=" << dev_id
<< " - if you see this for Qidi printer, virtual dispatch is broken!";
} }
int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::string& json_str) int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::string& json_str)
{ {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: handle_request received: " << json_str;
auto json = nlohmann::json::parse(json_str, nullptr, false); auto json = nlohmann::json::parse(json_str, nullptr, false);
if (json.is_discarded()) { if (json.is_discarded()) {
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Invalid JSON request"; BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Invalid JSON request";
@@ -648,7 +514,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
} }
const std::string cmd = command.get<std::string>(); const std::string cmd = command.get<std::string>();
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Received print command: " << cmd; BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Received print command: " << cmd;
// Handle gcode_line command - this is how G-code commands are sent from OrcaSlicer // Handle gcode_line command - this is how G-code commands are sent from OrcaSlicer
if (cmd == "gcode_line") { if (cmd == "gcode_line") {
@@ -681,7 +547,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED; return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
} }
// ===== NEW: Print control commands ===== // Print control commands
if (cmd == "pause") { if (cmd == "pause") {
return pause_print(dev_id); return pause_print(dev_id);
} }
@@ -725,36 +591,27 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
bool MoonrakerPrinterAgent::get_printhost_config(PrinthostConfig& config) const bool MoonrakerPrinterAgent::init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
{ {
device_info = MoonrakerDeviceInfo{};
auto* preset_bundle = GUI::wxGetApp().preset_bundle; auto* preset_bundle = GUI::wxGetApp().preset_bundle;
if (!preset_bundle) { if (!preset_bundle) {
return false; return false;
} }
auto& preset = preset_bundle->printers.get_edited_preset(); auto& preset = preset_bundle->printers.get_edited_preset();
const auto& printer_cfg = preset.config; const auto& printer_cfg = preset.config;
const DynamicPrintConfig* host_cfg = &printer_cfg; device_info.dev_ip = dev_ip;
config.host = host_cfg->opt_string("print_host");
if (config.host.empty()) {
if (auto* physical_cfg = preset_bundle->physical_printers.get_selected_printer_config()) {
if (!physical_cfg->opt_string("print_host").empty()) {
host_cfg = physical_cfg;
config.host = host_cfg->opt_string("print_host");
}
}
}
if (config.host.empty()) {
return false;
}
config.port = host_cfg->opt_string("printhost_port"); device_info.api_key = password;
config.api_key = host_cfg->opt_string("printhost_apikey"); device_info.model_name = printer_cfg.opt_string("printer_model");
config.model_name = printer_cfg.opt_string("printer_model"); device_info.model_id = preset.get_printer_type(preset_bundle);
config.base_url = normalize_base_url(config.host, config.port); device_info.base_url = use_ssl ? "https://" + dev_ip : "http://" + dev_ip;
config.model_id = preset.get_printer_type(preset_bundle); device_info.dev_id = dev_id;
device_info.version = "";
device_info.dev_name = device_info.dev_id;
return !config.base_url.empty(); return true;
} }
bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
@@ -771,8 +628,8 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
if (!api_key.empty()) { if (!api_key.empty()) {
http.header("X-Api-Key", api_key); http.header("X-Api-Key", api_key);
} }
http.timeout_connect(10) http.timeout_connect(5)
.timeout_max(30) .timeout_max(10)
.on_complete([&](std::string body, unsigned status) { .on_complete([&](std::string body, unsigned status) {
if (status == 200) { if (status == 200) {
response_body = body; response_body = body;
@@ -809,12 +666,7 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
} }
nlohmann::json result = json.contains("result") ? json["result"] : json; nlohmann::json result = json.contains("result") ? json["result"] : json;
info.dev_name = result.value("machine_name", result.value("hostname", "")); info.dev_name = result.value("machine_name", result.value("hostname", ""));
info.dev_id = result.value("machine_uuid", "");
if (info.dev_id.empty()) {
info.dev_id = result.value("serial_number", "");
}
info.model_id = result.value("model", "");
info.version = result.value("software_version", result.value("firmware_version", "")); info.version = result.value("software_version", result.value("firmware_version", ""));
return true; return true;
@@ -833,8 +685,8 @@ bool MoonrakerPrinterAgent::fetch_server_info(const std::string& base_url,
if (!api_key.empty()) { if (!api_key.empty()) {
http.header("X-Api-Key", api_key); http.header("X-Api-Key", api_key);
} }
http.timeout_connect(10) http.timeout_connect(5)
.timeout_max(30) .timeout_max(10)
.on_complete([&](std::string body, unsigned status) { .on_complete([&](std::string body, unsigned status) {
if (status == 200) { if (status == 200) {
response_body = body; response_body = body;
@@ -885,8 +737,8 @@ bool MoonrakerPrinterAgent::fetch_server_info_json(const std::string& base_url,
if (!api_key.empty()) { if (!api_key.empty()) {
http.header("X-Api-Key", api_key); http.header("X-Api-Key", api_key);
} }
http.timeout_connect(10) http.timeout_connect(5)
.timeout_max(30) .timeout_max(10)
.on_complete([&](std::string body, unsigned status) { .on_complete([&](std::string body, unsigned status) {
if (status == 200) { if (status == 200) {
response_body = body; response_body = body;
@@ -932,8 +784,8 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url,
if (!api_key.empty()) { if (!api_key.empty()) {
http.header("X-Api-Key", api_key); http.header("X-Api-Key", api_key);
} }
http.timeout_connect(10) http.timeout_connect(5)
.timeout_max(30) .timeout_max(10)
.on_complete([&](std::string body, unsigned status_code) { .on_complete([&](std::string body, unsigned status_code) {
if (status_code == 200) { if (status_code == 200) {
response_body = body; response_body = body;
@@ -972,31 +824,24 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url,
bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode) const bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode) const
{ {
const std::string base_url = resolve_host(dev_id);
if (base_url.empty()) {
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode - empty base_url for dev_id=" << dev_id;
return false;
}
const std::string api_key = resolve_api_key(dev_id, "");
nlohmann::json payload; nlohmann::json payload;
payload["script"] = gcode; payload["script"] = gcode;
std::string payload_str = payload.dump(); std::string payload_str = payload.dump();
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: send_gcode to " << base_url << " with payload: " << payload_str; BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: send_gcode to " << device_info.base_url << " with payload: " << payload_str;
std::string response_body; std::string response_body;
bool success = false; bool success = false;
std::string http_error; std::string http_error;
auto http = Http::post(join_url(base_url, "/printer/gcode/script")); auto http = Http::post(join_url(device_info.base_url, "/printer/gcode/script"));
if (!api_key.empty()) { if (!device_info.api_key.empty()) {
http.header("X-Api-Key", api_key); http.header("X-Api-Key", device_info.api_key);
} }
http.header("Content-Type", "application/json") http.header("Content-Type", "application/json")
.set_post_body(payload_str) .set_post_body(payload_str)
.timeout_connect(10) .timeout_connect(5)
.timeout_max(30) .timeout_max(10)
.on_complete([&](std::string body, unsigned status_code) { .on_complete([&](std::string body, unsigned status_code) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: send_gcode response status=" << status_code << " body=" << body; BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: send_gcode response status=" << status_code << " body=" << body;
if (status_code == 200) { if (status_code == 200) {
@@ -1020,7 +865,7 @@ bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::str
return false; return false;
} }
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: sent gcode successfully: " << gcode; BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: sent gcode successfully: " << gcode;
return true; return true;
} }
@@ -1037,8 +882,8 @@ bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url,
if (!api_key.empty()) { if (!api_key.empty()) {
http.header("X-Api-Key", api_key); http.header("X-Api-Key", api_key);
} }
http.timeout_connect(10) http.timeout_connect(5)
.timeout_max(30) .timeout_max(10)
.on_complete([&](std::string body, unsigned status) { .on_complete([&](std::string body, unsigned status) {
if (status == 200) { if (status == 200) {
response_body = body; response_body = body;
@@ -1084,15 +929,9 @@ bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url,
int MoonrakerPrinterAgent::send_version_info(const std::string& dev_id) int MoonrakerPrinterAgent::send_version_info(const std::string& dev_id)
{ {
const std::string base_url = resolve_host(dev_id);
if (base_url.empty()) {
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
const std::string api_key = resolve_api_key(dev_id, "");
std::string version; std::string version;
std::string error; std::string error;
if (!fetch_server_info(base_url, api_key, version, error)) { if (!fetch_server_info(device_info.base_url, device_info.api_key, version, error)) {
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Failed to fetch server info: " << error; BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Failed to fetch server info: " << error;
} }
if (version.empty()) { if (version.empty()) {
@@ -1118,7 +957,7 @@ int MoonrakerPrinterAgent::send_access_code(const std::string& dev_id)
{ {
nlohmann::json payload; nlohmann::json payload;
payload["system"]["command"] = "get_access_code"; payload["system"]["command"] = "get_access_code";
payload["system"]["access_code"] = resolve_api_key(dev_id, ""); payload["system"]["access_code"] = device_info.api_key;
dispatch_message(dev_id, payload.dump()); dispatch_message(dev_id, payload.dump());
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
@@ -1127,20 +966,6 @@ void MoonrakerPrinterAgent::announce_printhost_device()
{ {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device() called"; BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device() called";
PrinthostConfig config;
if (!get_printhost_config(config)) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - no printhost config";
return;
}
const std::string base_url = config.base_url;
if (base_url.empty()) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - empty base_url";
return;
}
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - base_url=" << base_url;
OnMsgArrivedFn ssdp_fn; OnMsgArrivedFn ssdp_fn;
{ {
std::lock_guard<std::recursive_mutex> lock(state_mutex); std::lock_guard<std::recursive_mutex> lock(state_mutex);
@@ -1149,43 +974,38 @@ void MoonrakerPrinterAgent::announce_printhost_device()
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - no ssdp callback"; BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - no ssdp callback";
return; return;
} }
if (ssdp_announced_host == base_url && !ssdp_announced_id.empty()) { if (ssdp_announced_host == device_info.base_url && !ssdp_announced_id.empty()) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - already announced"; BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - already announced";
return; return;
} }
} }
const std::string dev_id = extract_host(base_url);
const std::string api_key = normalize_api_key(config.api_key);
// Try to fetch actual device name from Moonraker // Try to fetch actual device name from Moonraker
// Priority: 1) Moonraker hostname, 2) Preset model name, 3) Generic fallback // Priority: 1) Moonraker hostname, 2) Preset model name, 3) Generic fallback
std::string dev_name; std::string dev_name;
MoonrakerDeviceInfo info; MoonrakerDeviceInfo info;
std::string fetch_error; std::string fetch_error;
if (fetch_device_info(base_url, api_key, info, fetch_error) && !info.dev_name.empty()) { if (fetch_device_info(device_info.base_url, device_info.api_key, info, fetch_error) && !info.dev_name.empty()) {
dev_name = info.dev_name; dev_name = info.dev_name;
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Got device name from printer: " << dev_name; BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Got device name from printer: " << dev_name;
} else { } else {
dev_name = config.model_name.empty() ? "Moonraker Printer" : config.model_name; dev_name = device_info.model_name.empty() ? "Moonraker Printer" : device_info.model_name;
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Using fallback device name: " << dev_name BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Using fallback device name: " << dev_name
<< " (fetch_error=" << fetch_error << ")"; << " (fetch_error=" << fetch_error << ")";
} }
const std::string model_id = config.model_id; const std::string model_id = device_info.model_id;
if (auto* app_config = GUI::wxGetApp().app_config) { if (auto* app_config = GUI::wxGetApp().app_config) {
const std::string access_code = api_key.empty() ? k_no_api_key : api_key; const std::string access_code = device_info.api_key;
app_config->set_str("access_code", dev_id, access_code); app_config->set_str("access_code", device_info.dev_id, access_code);
app_config->set_str("user_access_code", dev_id, access_code); app_config->set_str("user_access_code", device_info.dev_id, access_code);
} }
store_host(dev_id, base_url, api_key);
nlohmann::json payload; nlohmann::json payload;
payload["dev_name"] = dev_name; payload["dev_name"] = dev_name;
payload["dev_id"] = dev_id; payload["dev_id"] = device_info.dev_id;
payload["dev_ip"] = extract_host(base_url); payload["dev_ip"] = device_info.dev_ip;
payload["dev_type"] = model_id.empty() ? dev_name : model_id; payload["dev_type"] = model_id.empty() ? dev_name : model_id;
payload["dev_signal"] = "0"; payload["dev_signal"] = "0";
payload["connect_type"] = "lan"; payload["connect_type"] = "lan";
@@ -1197,14 +1017,14 @@ void MoonrakerPrinterAgent::announce_printhost_device()
{ {
std::lock_guard<std::recursive_mutex> lock(state_mutex); std::lock_guard<std::recursive_mutex> lock(state_mutex);
ssdp_announced_host = base_url; ssdp_announced_host = device_info.base_url;
ssdp_announced_id = dev_id; ssdp_announced_id = device_info.dev_id;
// Set this as the selected machine if nothing is currently selected // Set this as the selected machine if nothing is currently selected
// This ensures auto-connect works when MonitorPanel opens // This ensures auto-connect works when MonitorPanel opens
if (selected_machine.empty()) { if (selected_machine.empty()) {
selected_machine = dev_id; selected_machine = device_info.dev_id;
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Auto-selected machine: " << dev_id; BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Auto-selected machine: " << device_info.dev_id;
} }
} }
} }
@@ -1560,7 +1380,6 @@ void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const s
message = build_print_payload_locked(); message = build_print_payload_locked();
} }
BOOST_LOG_TRIVIAL(trace) << "MoonrakerPrinterAgent: Dispatching payload (critical=" << is_critical << "): " << message.dump();
dispatch_message(dev_id, message.dump()); dispatch_message(dev_id, message.dump());
ws_last_dispatch_ms.store(now_ms); ws_last_dispatch_ms.store(now_ms);
ws_last_emit_ms.store(now_ms); // Also update heartbeat timer ws_last_emit_ms.store(now_ms); // Also update heartbeat timer
@@ -1609,7 +1428,6 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
} }
payload["print"]["gcode_state"] = state; payload["print"]["gcode_state"] = state;
// ===== NEW: Print Stage =====
// Map Moonraker state to Bambu stage numbers // Map Moonraker state to Bambu stage numbers
int mc_print_stage = 0; int mc_print_stage = 0;
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("state")) { if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("state")) {
@@ -1621,14 +1439,12 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
} }
payload["print"]["mc_print_stage"] = mc_print_stage; payload["print"]["mc_print_stage"] = mc_print_stage;
// ===== NEW: Error Codes =====
// Leave mc_print_error_code and print_error at 0 // Leave mc_print_error_code and print_error at 0
// UI expects numeric HMS codes - setting to 1 shows generic error dialog // UI expects numeric HMS codes - setting to 1 shows generic error dialog
// Only set if real mapping from Moonraker error strings to HMS codes is defined // Only set if real mapping from Moonraker error strings to HMS codes is defined
payload["print"]["mc_print_error_code"] = 0; payload["print"]["mc_print_error_code"] = 0;
payload["print"]["print_error"] = 0; payload["print"]["print_error"] = 0;
// ===== NEW: Home Flag =====
// Map homed axes to bit field: X=bit0, Y=bit1, Z=bit2 // Map homed axes to bit field: X=bit0, Y=bit1, Z=bit2
// WARNING: This only sets bits 0-2, clearing support flags (bit 3+) // WARNING: This only sets bits 0-2, clearing support flags (bit 3+)
// Bit 3 = 220V voltage, bit 4 = auto recovery, etc. // Bit 3 = 220V voltage, bit 4 = auto recovery, etc.
@@ -1642,12 +1458,10 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
} }
payload["print"]["home_flag"] = home_flag; payload["print"]["home_flag"] = home_flag;
// ===== NEW: Temperature Ranges ===== // Moonraker doesn't provide temperature ranges via API - use hardcoded defaults
// Moonraker doesn't provide this via API - use hardcoded defaults
payload["print"]["nozzle_temp_range"] = {100, 370}; // Typical Klipper range payload["print"]["nozzle_temp_range"] = {100, 370}; // Typical Klipper range
payload["print"]["bed_temp_range"] = {0, 120}; // Typical bed range payload["print"]["bed_temp_range"] = {0, 120}; // Typical bed range
// ===== NEW: Feature Flags =====
payload["print"]["support_send_to_sd"] = true; payload["print"]["support_send_to_sd"] = true;
// Detect bed_leveling support from available objects (bed_mesh or probe) // Detect bed_leveling support from available objects (bed_mesh or probe)
// Default to 0 (not supported) if neither object exists // Default to 0 (not supported) if neither object exists
@@ -1713,7 +1527,6 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
payload["print"]["subtask_name"] = status_cache["print_stats"]["filename"].get<std::string>(); payload["print"]["subtask_name"] = status_cache["print_stats"]["filename"].get<std::string>();
} }
// ===== NEW: G-code File Path =====
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("filename")) { if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("filename")) {
payload["print"]["gcode_file"] = status_cache["print_stats"]["filename"]; payload["print"]["gcode_file"] = status_cache["print_stats"]["filename"];
} }
@@ -1749,69 +1562,11 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
std::chrono::system_clock::now().time_since_epoch()).count()); std::chrono::system_clock::now().time_since_epoch()).count());
payload["t_utc"] = now_ms; payload["t_utc"] = now_ms;
BOOST_LOG_TRIVIAL(trace) << "MoonrakerPrinterAgent: Built payload with gcode_state=" << state;
return payload; return payload;
} }
std::string MoonrakerPrinterAgent::resolve_host(const std::string& dev_id) const
{
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
auto it = host_by_device.find(dev_id);
if (it != host_by_device.end()) {
return it->second;
}
}
PrinthostConfig config;
if (get_printhost_config(config)) {
return config.base_url;
}
return "";
}
std::string MoonrakerPrinterAgent::resolve_api_key(const std::string& dev_id, const std::string& fallback) const
{
std::string api_key = normalize_api_key(fallback);
if (!api_key.empty()) {
return api_key;
}
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
auto it = api_key_by_device.find(dev_id);
if (it != api_key_by_device.end() && !it->second.empty()) {
return it->second;
}
}
PrinthostConfig config;
if (get_printhost_config(config)) {
return normalize_api_key(config.api_key);
}
return "";
}
void MoonrakerPrinterAgent::store_host(const std::string& dev_id, const std::string& host, const std::string& api_key)
{
if (host.empty()) {
return;
}
std::lock_guard<std::recursive_mutex> lock(state_mutex);
host_by_device[dev_id] = host;
if (!api_key.empty()) {
api_key_by_device[dev_id] = api_key;
}
}
void MoonrakerPrinterAgent::dispatch_message(const std::string& dev_id, const std::string& payload) void MoonrakerPrinterAgent::dispatch_message(const std::string& dev_id, const std::string& payload)
{ {
BOOST_LOG_TRIVIAL(trace) << "MoonrakerPrinterAgent: dispatch_message dev_id=" << dev_id
<< " payload_size=" << payload.size();
OnMessageFn local_fn; OnMessageFn local_fn;
OnMessageFn cloud_fn; OnMessageFn cloud_fn;
QueueOnMainFn queue_fn; QueueOnMainFn queue_fn;
@@ -1882,7 +1637,7 @@ bool MoonrakerPrinterAgent::upload_gcode(
http.form_add("root", "gcodes") // Upload to gcodes directory http.form_add("root", "gcodes") // Upload to gcodes directory
.form_add("print", "false") // Don't auto-start print .form_add("print", "false") // Don't auto-start print
.form_add_file("file", source_path.string(), safe_filename) .form_add_file("file", source_path.string(), safe_filename)
.timeout_connect(10) .timeout_connect(5)
.timeout_max(300) // 5 minutes for large files .timeout_max(300) // 5 minutes for large files
.on_complete([&](std::string body, unsigned status) { .on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Upload complete: HTTP " << status << " body: " << body; BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Upload complete: HTTP " << status << " body: " << body;
@@ -1917,9 +1672,6 @@ bool MoonrakerPrinterAgent::upload_gcode(
int MoonrakerPrinterAgent::pause_print(const std::string& dev_id) int MoonrakerPrinterAgent::pause_print(const std::string& dev_id)
{ {
const std::string base_url = resolve_host(dev_id);
const std::string api_key = resolve_api_key(dev_id, "");
nlohmann::json request; nlohmann::json request;
request["jsonrpc"] = "2.0"; request["jsonrpc"] = "2.0";
request["method"] = "printer.print.pause"; request["method"] = "printer.print.pause";
@@ -1963,8 +1715,8 @@ bool MoonrakerPrinterAgent::send_jsonrpc_command(
} }
http.header("Content-Type", "application/json") http.header("Content-Type", "application/json")
.set_post_body(request_str) .set_post_body(request_str)
.timeout_connect(10) .timeout_connect(5)
.timeout_max(30) .timeout_max(10)
.on_complete([&](std::string body, unsigned status) { .on_complete([&](std::string body, unsigned status) {
if (status == 200) { if (status == 200) {
response = body; response = body;
@@ -2007,29 +1759,7 @@ void MoonrakerPrinterAgent::perform_connection_async(
? server_info["result"] : server_info; ? server_info["result"] : server_info;
std::string klippy_state = result_json.value("klippy_state", ""); std::string klippy_state = result_json.value("klippy_state", "");
// Poll for Klippy ready state (with stop check) // Check state
if (klippy_state == "startup") {
for (int i = 0; i < 30; i++) { // 30 second max
{
std::unique_lock<std::recursive_mutex> lock(connect_mutex);
if (connect_stop_requested.load()) {
result = BAMBU_NETWORK_ERR_CANCELED;
break;
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
if (fetch_server_info_json(base_url, api_key, server_info, error_msg)) {
result_json = server_info.contains("result")
? server_info["result"] : server_info;
klippy_state = result_json.value("klippy_state", "");
if (klippy_state == "ready") break;
}
}
}
// Check final state
if (klippy_state != "ready" && result == BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED) { if (klippy_state != "ready" && result == BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED) {
std::string state_message = result_json.value("state_message", "Unknown error"); std::string state_message = result_json.value("state_message", "Unknown error");
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Klippy not ready: " << klippy_state BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Klippy not ready: " << klippy_state
@@ -2081,4 +1811,66 @@ void MoonrakerPrinterAgent::finish_connection()
connect_in_progress.store(false); connect_in_progress.store(false);
} }
bool MoonrakerPrinterAgent::is_numeric(const std::string& value)
{
return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; });
}
std::string MoonrakerPrinterAgent::normalize_base_url(std::string host, const std::string& port)
{
boost::trim(host);
if (host.empty()) {
return "";
}
std::string value = host;
if (is_numeric(port) && value.find("://") == std::string::npos && value.find(':') == std::string::npos) {
value += ":" + port;
}
if (!boost::istarts_with(value, "http://") && !boost::istarts_with(value, "https://")) {
value = "http://" + value;
}
if (value.size() > 1 && value.back() == '/') {
value.pop_back();
}
return value;
}
std::string MoonrakerPrinterAgent::join_url(const std::string& base_url, const std::string& path) const
{
if (base_url.empty()) {
return "";
}
if (path.empty()) {
return base_url;
}
if (base_url.back() == '/' && path.front() == '/') {
return base_url.substr(0, base_url.size() - 1) + path;
}
if (base_url.back() != '/' && path.front() != '/') {
return base_url + "/" + path;
}
return base_url + path;
}
// Sanitize filename to prevent path traversal attacks
// Extracts only the basename, removing any path components
std::string MoonrakerPrinterAgent::sanitize_filename(const std::string& filename)
{
if (filename.empty()) {
return "print.gcode";
}
namespace fs = boost::filesystem;
fs::path p(filename);
std::string basename = p.filename().string();
if (basename.empty() || basename == "." || basename == "..") {
return "print.gcode";
}
return basename;
}
} // namespace Slic3r } // namespace Slic3r

View File

@@ -74,39 +74,31 @@ public:
virtual void fetch_filament_info(std::string dev_id) override; virtual void fetch_filament_info(std::string dev_id) override;
protected: protected:
// Types exposed for derived classes struct MoonrakerDeviceInfo
struct PrinthostConfig
{ {
std::string host; std::string dev_id;
std::string port; std::string dev_ip;
std::string api_key; std::string api_key;
std::string base_url; std::string base_url;
std::string model_id; std::string model_id;
std::string model_name; std::string model_name;
};
struct MoonrakerDeviceInfo
{
std::string dev_id;
std::string dev_name; std::string dev_name;
std::string model_id;
std::string version; std::string version;
}; bool use_ssl = false;
} device_info;
// Methods that derived classes may need to override or access // Methods that derived classes may need to override or access
virtual bool get_printhost_config(PrinthostConfig& config) const; virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const; virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const;
virtual std::string get_dev_type() const { return "moonraker"; }
// Host resolution methods
std::string resolve_host(const std::string& dev_id) const;
std::string resolve_api_key(const std::string& dev_id, const std::string& fallback) const;
void store_host(const std::string& dev_id, const std::string& host, const std::string& api_key);
// State access for derived classes // State access for derived classes
mutable std::recursive_mutex state_mutex; mutable std::recursive_mutex state_mutex;
std::map<std::string, std::string> host_by_device;
std::map<std::string, std::string> api_key_by_device; // Helpers
bool is_numeric(const std::string& value);
std::string normalize_base_url(std::string host, const std::string& port);
std::string sanitize_filename(const std::string& filename);
std::string join_url(const std::string& base_url, const std::string& path) const;
private: private:
int handle_request(const std::string& dev_id, const std::string& json_str); int handle_request(const std::string& dev_id, const std::string& json_str);

View File

@@ -1,15 +1,12 @@
#include "OrcaPrinterAgent.hpp" #include "OrcaPrinterAgent.hpp"
#include "NetworkAgentFactory.hpp" #include "NetworkAgentFactory.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace Slic3r {
const std::string OrcaPrinterAgent_VERSION = "0.0.1"; const std::string OrcaPrinterAgent_VERSION = "0.0.1";
OrcaPrinterAgent::OrcaPrinterAgent(std::string log_dir) : log_dir(std::move(log_dir)) OrcaPrinterAgent::OrcaPrinterAgent(std::string log_dir) : log_dir(std::move(log_dir))
{ {
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: Constructor - log_dir=" << this->log_dir;
} }
OrcaPrinterAgent::~OrcaPrinterAgent() = default; OrcaPrinterAgent::~OrcaPrinterAgent() = default;
@@ -18,7 +15,6 @@ void OrcaPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud
{ {
std::lock_guard<std::mutex> lock(state_mutex); std::lock_guard<std::mutex> lock(state_mutex);
m_cloud_agent = cloud; m_cloud_agent = cloud;
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: Cloud agent set";
} }
// ============================================================================ // ============================================================================
@@ -27,25 +23,21 @@ void OrcaPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud
int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag) int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: send_message (stub) - dev_id=" << dev_id;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: connect_printer (stub) - dev_id=" << dev_id << ", dev_ip=" << dev_ip;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::disconnect_printer() int OrcaPrinterAgent::disconnect_printer()
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: disconnect_printer (stub)";
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: send_message_to_printer (stub) - dev_id=" << dev_id;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
@@ -55,13 +47,11 @@ int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string js
int OrcaPrinterAgent::check_cert() int OrcaPrinterAgent::check_cert()
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: check_cert (stub)";
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only) void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: install_device_cert (stub) - dev_id=" << dev_id;
} }
// ============================================================================ // ============================================================================
@@ -70,7 +60,6 @@ void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only)
bool OrcaPrinterAgent::start_discovery(bool start, bool sending) bool OrcaPrinterAgent::start_discovery(bool start, bool sending)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_discovery (stub) - start=" << start << ", sending=" << sending;
return true; return true;
} }
@@ -80,32 +69,27 @@ bool OrcaPrinterAgent::start_discovery(bool start, bool sending)
int OrcaPrinterAgent::ping_bind(std::string ping_code) int OrcaPrinterAgent::ping_bind(std::string ping_code)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: ping_bind (stub) - ping_code=" << ping_code;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) int OrcaPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: bind_detect (stub) - dev_ip=" << dev_ip;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::bind( int OrcaPrinterAgent::bind(
std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: bind (stub) - dev_id=" << dev_id;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::unbind(std::string dev_id) int OrcaPrinterAgent::unbind(std::string dev_id)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: unbind (stub) - dev_id=" << dev_id;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::request_bind_ticket(std::string* ticket) int OrcaPrinterAgent::request_bind_ticket(std::string* ticket)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: request_bind_ticket (stub)";
if (ticket) if (ticket)
*ticket = ""; *ticket = "";
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
@@ -152,7 +136,6 @@ AgentInfo OrcaPrinterAgent::get_agent_info_static()
int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_print (stub) - task_name=" << params.task_name;
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
@@ -161,25 +144,21 @@ int OrcaPrinterAgent::start_local_print_with_record(PrintParams params,
WasCancelledFn cancel_fn, WasCancelledFn cancel_fn,
OnWaitFn wait_fn) OnWaitFn wait_fn)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_local_print_with_record (stub)";
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) int OrcaPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_send_gcode_to_sdcard (stub)";
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) int OrcaPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_local_print (stub)";
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
int OrcaPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) int OrcaPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_sdcard_print (stub)";
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }

View File

@@ -1,7 +1,5 @@
#include "QidiPrinterAgent.hpp" #include "QidiPrinterAgent.hpp"
#include "Http.hpp" #include "Http.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h" #include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevManager.h" #include "slic3r/GUI/DeviceCore/DevManager.h"
@@ -13,103 +11,12 @@
#include <cctype> #include <cctype>
#include <sstream> #include <sstream>
namespace {
bool is_numeric(const std::string& value)
{
return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; });
}
std::string join_url(const std::string& base_url, const std::string& path)
{
if (base_url.empty()) {
return "";
}
if (path.empty()) {
return base_url;
}
if (base_url.back() == '/' && path.front() == '/') {
return base_url.substr(0, base_url.size() - 1) + path;
}
if (base_url.back() != '/' && path.front() != '/') {
return base_url + "/" + path;
}
return base_url + path;
}
std::string normalize_model_key(std::string value)
{
boost::algorithm::to_lower(value);
std::string normalized;
normalized.reserve(value.size());
for (unsigned char c : value) {
if (std::isalnum(c)) {
normalized.push_back(static_cast<char>(c));
}
}
return normalized;
}
std::string infer_series_id(const std::string& model_id, const std::string& dev_name)
{
std::string source = model_id.empty() ? dev_name : model_id;
boost::trim(source);
if (source.empty()) {
return "";
}
if (is_numeric(source)) {
return source;
}
const std::string key = normalize_model_key(source);
if (key.find("q2") != std::string::npos) {
return "1";
}
if (key.find("xmax") != std::string::npos && key.find("4") != std::string::npos) {
return "3";
}
if ((key.find("xplus") != std::string::npos || key.find("plus") != std::string::npos) && key.find("4") != std::string::npos) {
return "0";
}
return "";
}
std::string normalize_filament_type(const std::string& filament_type)
{
std::string trimmed = filament_type;
boost::trim(trimmed);
std::string upper = trimmed;
std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
if (upper.find("PLA") != std::string::npos)
return "PLA";
if (upper.find("ABS") != std::string::npos)
return "ABS";
if (upper.find("PETG") != std::string::npos)
return "PETG";
if (upper.find("TPU") != std::string::npos)
return "TPU";
if (upper.find("ASA") != std::string::npos)
return "ASA";
if (upper.find("PA") != std::string::npos || upper.find("NYLON") != std::string::npos)
return "PA";
if (upper.find("PC") != std::string::npos)
return "PC";
if (upper.find("PVA") != std::string::npos)
return "PVA";
return trimmed;
}
} // namespace
namespace Slic3r { namespace Slic3r {
const std::string QidiPrinterAgent_VERSION = "0.0.1"; const std::string QidiPrinterAgent_VERSION = "0.0.1";
QidiPrinterAgent::QidiPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) QidiPrinterAgent::QidiPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir))
{ {
BOOST_LOG_TRIVIAL(info) << "QidiPrinterAgent: Constructor";
} }
AgentInfo QidiPrinterAgent::get_agent_info_static() AgentInfo QidiPrinterAgent::get_agent_info_static()
@@ -131,23 +38,16 @@ void QidiPrinterAgent::fetch_filament_info(std::string dev_id)
return; return;
} }
const std::string base_url = resolve_host(dev_id);
if (base_url.empty()) {
BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::fetch_filament_info: Missing host for dev_id=" << dev_id;
return;
}
const std::string api_key = resolve_api_key(dev_id, "");
std::vector<QidiSlotInfo> slots; std::vector<QidiSlotInfo> slots;
int box_count = 0; int box_count = 0;
std::string error; std::string error;
if (!fetch_slot_info(base_url, api_key, slots, box_count, error)) { if (!fetch_slot_info(device_info.base_url, device_info.api_key, slots, box_count, error)) {
BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch slot info: " << error; BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch slot info: " << error;
return; return;
} }
QidiFilamentDict dict; QidiFilamentDict dict;
if (!fetch_filament_dict(base_url, api_key, dict, error)) { if (!fetch_filament_dict(device_info.base_url, device_info.api_key, dict, error)) {
BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch filament dict: " << error; BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch filament dict: " << error;
} }
@@ -155,7 +55,7 @@ void QidiPrinterAgent::fetch_filament_info(std::string dev_id)
{ {
MoonrakerDeviceInfo info; MoonrakerDeviceInfo info;
std::string device_error; std::string device_error;
if (fetch_device_info(base_url, api_key, info, device_error)) { if (fetch_device_info(device_info.base_url, device_info.api_key, info, device_error)) {
series_id = infer_series_id(info.model_id, info.dev_name); series_id = infer_series_id(info.model_id, info.dev_name);
} }
} }
@@ -496,4 +396,68 @@ std::string QidiPrinterAgent::map_filament_type_to_setting_id(const std::string&
return ""; return "";
} }
std::string QidiPrinterAgent::normalize_model_key(std::string value)
{
boost::algorithm::to_lower(value);
std::string normalized;
normalized.reserve(value.size());
for (unsigned char c : value) {
if (std::isalnum(c)) {
normalized.push_back(static_cast<char>(c));
}
}
return normalized;
}
std::string QidiPrinterAgent::infer_series_id(const std::string& model_id, const std::string& dev_name)
{
std::string source = model_id.empty() ? dev_name : model_id;
boost::trim(source);
if (source.empty()) {
return "";
}
if (is_numeric(source)) {
return source;
}
const std::string key = normalize_model_key(source);
if (key.find("q2") != std::string::npos) {
return "1";
}
if (key.find("xmax") != std::string::npos && key.find("4") != std::string::npos) {
return "3";
}
if ((key.find("xplus") != std::string::npos || key.find("plus") != std::string::npos) && key.find("4") != std::string::npos) {
return "0";
}
return "";
}
std::string QidiPrinterAgent::normalize_filament_type(const std::string& filament_type)
{
std::string trimmed = filament_type;
boost::trim(trimmed);
std::string upper = trimmed;
std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
if (upper.find("PLA") != std::string::npos)
return "PLA";
if (upper.find("ABS") != std::string::npos)
return "ABS";
if (upper.find("PETG") != std::string::npos)
return "PETG";
if (upper.find("TPU") != std::string::npos)
return "TPU";
if (upper.find("ASA") != std::string::npos)
return "ASA";
if (upper.find("PA") != std::string::npos || upper.find("NYLON") != std::string::npos)
return "PA";
if (upper.find("PC") != std::string::npos)
return "PC";
if (upper.find("PVA") != std::string::npos)
return "PVA";
return trimmed;
}
} // namespace Slic3r } // namespace Slic3r

View File

@@ -39,8 +39,15 @@ private:
}; };
// Qidi-specific methods // Qidi-specific methods
bool fetch_slot_info(const std::string& base_url, const std::string& api_key, std::vector<QidiSlotInfo>& slots, int& box_count, std::string& error) const; bool fetch_slot_info(const std::string& base_url,
const std::string& api_key,
std::vector<QidiSlotInfo>& slots,
int& box_count,
std::string& error) const;
bool fetch_filament_dict(const std::string& base_url, const std::string& api_key, QidiFilamentDict& dict, std::string& error) const; bool fetch_filament_dict(const std::string& base_url, const std::string& api_key, QidiFilamentDict& dict, std::string& error) const;
std::string normalize_filament_type(const std::string& filament_type);
std::string infer_series_id(const std::string& model_id, const std::string& dev_name);
std::string normalize_model_key(std::string value);
// Static helpers // Static helpers
static void parse_ini_section(const std::string& content, const std::string& section_name, std::map<int, std::string>& result); static void parse_ini_section(const std::string& content, const std::string& section_name, std::map<int, std::string>& result);