diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 90c2e9cf06..0e4932ce45 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -533,6 +533,7 @@ set(SLIC3R_GUI_SOURCES Utils/NetworkAgent.hpp Utils/MoonRaker.cpp Utils/MoonRaker.hpp + Utils/TimeoutMap.hpp Utils/PrintHost.cpp Utils/PrintHost.hpp Utils/Serial.cpp diff --git a/src/slic3r/GUI/SSWCP.cpp b/src/slic3r/GUI/SSWCP.cpp index 5d664aeb42..95954455d3 100644 --- a/src/slic3r/GUI/SSWCP.cpp +++ b/src/slic3r/GUI/SSWCP.cpp @@ -1,3 +1,4 @@ +// Implementation of web communication protocol for Slicer Studio #include "SSWCP.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" @@ -28,7 +29,7 @@ using namespace nlohmann; namespace Slic3r { namespace GUI { -// SSWCP_Instance +// Base SSWCP_Instance implementation void SSWCP_Instance::process() { if (m_cmd == "test") { sync_test(); @@ -37,6 +38,7 @@ void SSWCP_Instance::process() { } } +// Mark instance as invalid void SSWCP_Instance::set_Instance_illegal() { m_illegal_mtx.lock(); @@ -44,6 +46,7 @@ void SSWCP_Instance::set_Instance_illegal() m_illegal_mtx.unlock(); } +// Check if instance is invalid bool SSWCP_Instance::is_Instance_illegal() { m_illegal_mtx.lock(); bool res = m_illegal; @@ -52,10 +55,12 @@ bool SSWCP_Instance::is_Instance_illegal() { return res; } +// Get associated webview wxWebView* SSWCP_Instance::get_web_view() const { return m_webview; } +// Send response to JavaScript void SSWCP_Instance::send_to_js() { try { if (is_Instance_illegal()) { @@ -72,25 +77,24 @@ void SSWCP_Instance::send_to_js() { std::string str_res = "window.postMessage(JSON.stringify(" + response.dump() + "), '*');"; - // std::string str_res = "test(" + response.dump() + ");"; - - if (m_webview) { - wxGetApp().CallAfter([this, str_res]() { + if (m_webview && m_webview->GetRefData()) { + auto self = shared_from_this(); + wxGetApp().CallAfter([self, str_res]() { try { - WebView::RunScript(this->m_webview, str_res); + WebView::RunScript(self->m_webview, str_res); } catch (std::exception& e) {} }); } } catch (std::exception& e) {} } +// Clean up instance void SSWCP_Instance::finish_job() { SSWCP::delete_target(this); } +// Asynchronous test implementation void SSWCP_Instance::async_test() { - // 业务逻辑 - auto http = Http::get("http://172.18.1.69/"); http.on_error([&](std::string body, std::string error, unsigned status) { @@ -105,18 +109,24 @@ void SSWCP_Instance::async_test() { .perform(); } +// Synchronous test implementation void SSWCP_Instance::sync_test() { - // 业务逻辑 m_res_data = m_param_data; - - // send_to_js send_to_js(); + finish_job(); +} +// Handle timeout event +void SSWCP_Instance::on_timeout() { + m_status = -2; + m_msg = "timeout"; + send_to_js(); finish_job(); } -// SSWCP_MachineFind_Instance +// SSWCP_MachineFind_Instance implementation +// Process machine find commands void SSWCP_MachineFind_Instance::process() { if (m_event_id != "") { json header; @@ -135,6 +145,7 @@ void SSWCP_MachineFind_Instance::process() { } } +// Set stop flag for machine discovery void SSWCP_MachineFind_Instance::set_stop(bool stop) { m_stop_mtx.lock(); @@ -142,6 +153,7 @@ void SSWCP_MachineFind_Instance::set_stop(bool stop) m_stop_mtx.unlock(); } +// Get machine discovery support info void SSWCP_MachineFind_Instance::sw_GetMachineFindSupportInfo() { // 2.0.0 只支持 mdns - snapmaker @@ -160,6 +172,7 @@ void SSWCP_MachineFind_Instance::sw_GetMachineFindSupportInfo() send_to_js(); } +// Start machine discovery void SSWCP_MachineFind_Instance::sw_StartMachineFind() { try { @@ -192,12 +205,16 @@ void SSWCP_MachineFind_Instance::sw_StartMachineFind() std::string unique_key = "sn"; for (size_t i = 0; i < m_engines.size(); ++i) { + auto self = std::dynamic_pointer_cast(shared_from_this()); + if(!self){ + continue; + } m_engines[i] = Bonjour(mdns_service_names[i]) .set_txt_keys(std::move(txt_keys)) .set_retries(3) .set_timeout(last_time >= 0.0 ? last_time/1000 : 20) - .on_reply([this, unique_key](BonjourReply&& reply) { - if(is_stop()){ + .on_reply([self, unique_key](BonjourReply&& reply) { + if(self->is_stop()){ return; } json machine_data; @@ -236,17 +253,17 @@ void SSWCP_MachineFind_Instance::sw_StartMachineFind() } json machine_object; if (machine_data.count("unique_value")) { - this->add_machine_to_list(machine_object); + self->add_machine_to_list(machine_object); machine_object[reply.txt_data[unique_key]] = machine_data; } else { machine_object[reply.ip.to_string()] = machine_data; } - this->add_machine_to_list(machine_object); + self->add_machine_to_list(machine_object); }) - .on_complete([this]() { - wxGetApp().CallAfter([this]() { - this->onOneEngineEnd(); + .on_complete([self]() { + wxGetApp().CallAfter([self]() { + self->onOneEngineEnd(); }); }) .lookup(); @@ -257,8 +274,7 @@ void SSWCP_MachineFind_Instance::sw_StartMachineFind() } } - } - catch (std::exception& e) {} + } catch (std::exception& e) {} } void SSWCP_MachineFind_Instance::sw_StopMachineFind() @@ -342,17 +358,29 @@ void SSWCP_MachineOption_Instance::sw_UnSubscribeMachineState() { finish_job(); } - host->async_unsubscribe_machine_info([this](const json& response) { + auto self = shared_from_this(); + host->async_unsubscribe_machine_info([self](const json& response) { if (response.is_null()) { - m_status = -1; - m_msg = "failure"; - send_to_js(); - } else { - m_res_data = response; - send_to_js(); + self->m_status = -1; + self->m_msg = "failure"; + self->send_to_js(); + }else if(response.count("error")){ + if("error" == response["error"].get()){ + self->m_status = -2; + self->m_msg = "timeout"; + self->send_to_js(); + } + } + else { + self->m_res_data = response; + self->send_to_js(); } + self->finish_job(); }); + SSWCP::stop_subscribe_machine(); + + } catch (std::exception& e) {} } @@ -365,16 +393,25 @@ void SSWCP_MachineOption_Instance::sw_SubscribeMachineState() { m_msg = "failure"; send_to_js(); finish_job(); + return; } - host->async_subscribe_machine_info([this](const json& response) { + auto self = shared_from_this(); + host->async_subscribe_machine_info([self](const json& response) { if (response.is_null()) { - m_status = -1; - m_msg = "failure"; - send_to_js(); - } else { - m_res_data = response; - send_to_js(); + self->m_status = -1; + self->m_msg = "failure"; + self->send_to_js(); + }else if(response.count("error")){ + if("error" == response["error"].get()){ + self->m_status = -2; + self->m_msg = "timeout"; + self->send_to_js(); + } + } + else { + self->m_res_data = response; + self->send_to_js(); } }); @@ -405,36 +442,32 @@ void SSWCP_MachineOption_Instance::sw_GetMachineState() { } if (!host) { - // 错误处理 m_status = -1; m_msg = "failure"; send_to_js(); finish_job(); - } else { - //m_work_thread = std::thread([this, targets, host]() { - // json response; - // bool res = host->get_machine_info(targets, response); - // if (res) { - // m_res_data = response; - // send_to_js(); - // } else { - // // 错误处理 - // } - //}); - - host->async_get_machine_info(targets, [this](const json& response) { - if (response.is_null() || response.empty()) { - m_status = -1; - m_msg = "failure"; - send_to_js(); - finish_job(); - } else { - m_res_data = response; - send_to_js(); - finish_job(); - } - }); + return; } + + auto self = shared_from_this(); + host->async_get_machine_info(targets, [self](const json& response) { + if (response.is_null()) { + self->m_status = -1; + self->m_msg = "failure"; + self->send_to_js(); + }else if(response.count("error")){ + if("error" == response["error"].get()){ + self->m_status = -2; + self->m_msg = "timeout"; + self->send_to_js(); + } + } + else { + self->m_res_data = response; + self->send_to_js(); + } + self->finish_job(); + }); } else { finish_job(); } @@ -459,58 +492,32 @@ void SSWCP_MachineOption_Instance::sw_SendGCodes() { } if (!host) { - // 错误处理 m_status = -1; m_msg = "failure"; send_to_js(); finish_job(); - } else { - host->async_send_gcodes(str_codes, [this](const json& response) { - if (response.is_null() || response.empty()) { - m_status = -1; - m_msg = "failure"; - send_to_js(); - finish_job(); - } else { - m_res_data = response; - send_to_js(); - finish_job(); - } - }); + return; } - /*http 同步做法 - if (m_param_data["codes"].is_array()) { - json codes = m_param_data["codes"]; - for (size_t i = 0; i < codes.size(); ++i) { - str_codes.push_back(codes[i].get()); + auto self = shared_from_this(); + host->async_send_gcodes(str_codes, [self](const json& response) { + if (response.is_null()) { + self->m_status = -1; + self->m_msg = "failure"; + self->send_to_js(); + }else if(response.count("error")){ + if("error" == response["error"].get()){ + self->m_status = -2; + self->m_msg = "timeout"; + self->send_to_js(); + } + } + else { + self->m_res_data = response; + self->send_to_js(); } - } else if (m_param_data["codes"].is_string()) { - str_codes.push_back(m_param_data["codes"].get()); - } - - - if (!host) { - // 错误处理 - finish_job(); - } else { - m_work_thread = - std::thread([this, str_codes, host]() { - std::string extraInfo = ""; - bool res = host->send_gcodes(str_codes, extraInfo); - if (res) { - send_to_js(); - } else { - // 错误处理 - } - - finish_job(); - }); - } - } else { - // 错误处理 - finish_job(); - */ + self->finish_job(); + }); } } catch (const std::exception&) {} @@ -547,17 +554,24 @@ void SSWCP_MachineOption_Instance::sw_SetMachineSubscribeFilter() send_to_js(); finish_job(); } else { - host->async_set_machine_subscribe_filter(targets, [this](const json& response) { - if (response.is_null() || response.empty()) { - m_status = -1; - m_msg = "failure"; - send_to_js(); - finish_job(); - } else { - m_res_data = response; - send_to_js(); - finish_job(); + auto self = shared_from_this(); + host->async_set_machine_subscribe_filter(targets, [self](const json& response) { + if (response.is_null()) { + self->m_status = -1; + self->m_msg = "failure"; + self->send_to_js(); + }else if(response.count("error")){ + if("error" == response["error"].get()){ + self->m_status = -2; + self->m_msg = "timeout"; + self->send_to_js(); + } + } + else { + self->m_res_data = response; + self->send_to_js(); } + self->finish_job(); }); } } else { @@ -579,17 +593,24 @@ void SSWCP_MachineOption_Instance::sw_GetMachineObjects() return; } - host->async_get_machine_objects([this](const json& response) { - if (response.is_null() || response.empty()) { - m_status = -1; - m_msg = "failure"; - send_to_js(); - finish_job(); - } else { - m_res_data = response; - send_to_js(); - finish_job(); + auto self = shared_from_this(); + host->async_get_machine_objects([self](const json& response) { + if (response.is_null()) { + self->m_status = -1; + self->m_msg = "failure"; + self->send_to_js(); + }else if(response.count("error")){ + if("error" == response["error"].get()){ + self->m_status = -2; + self->m_msg = "timeout"; + self->send_to_js(); + } + } + else { + self->m_res_data = response; + self->send_to_js(); } + self->finish_job(); }); } catch (std::exception& e) {} @@ -695,7 +716,8 @@ void SSWCP_MachineConnect_Instance::sw_connect() { // 错误处理 finish_job(); } else { - m_work_thread = std::thread([this, host, connect_params] { + auto self = shared_from_this(); + m_work_thread = std::thread([self, host, connect_params] { // moonraker_mqtt 如果没有sn码,需要http请求获取sn码 //wxString msg = ""; @@ -723,26 +745,31 @@ void SSWCP_MachineConnect_Instance::sw_connect() { } wxGetApp().mainframe->load_printer_url("http://" + ip_port); - MessageDialog msg_window(nullptr, + wxGetApp().CallAfter([ip_port](){ + MessageDialog msg_window(nullptr, ip_port + _L(" connected sucessfully !\n"), L("Machine Connected"), wxICON_QUESTION | wxOK); - msg_window.ShowModal(); - - auto dialog = wxGetApp().get_web_device_dialog(); - if (dialog) { - dialog->Hide(); - } + msg_window.ShowModal(); + + auto dialog = wxGetApp().get_web_device_dialog(); + if (dialog) { + dialog->EndModal(1); + } + }); + } else { - MessageDialog msg_window(nullptr, ip_port + _L(" connected unseccessfully !\n"), L("Failed"), + wxGetApp().CallAfter([ip_port](){ + MessageDialog msg_window(nullptr, ip_port + _L(" connected unseccessfully !\n"), L("Failed"), wxICON_QUESTION | wxOK); - msg_window.ShowModal(); + msg_window.ShowModal(); + }); - m_status = 1; - m_msg = msg.c_str(); + self->m_status = 1; + self->m_msg = msg.c_str(); } - send_to_js(); - finish_job(); + self->send_to_js(); + self->finish_job(); }); } @@ -755,7 +782,8 @@ void SSWCP_MachineConnect_Instance::sw_connect() { } void SSWCP_MachineConnect_Instance::sw_disconnect() { - m_work_thread = std::thread([this](){ + auto self = shared_from_this(); + m_work_thread = std::thread([self](){ std::shared_ptr host(PrintHost::get_print_host(&wxGetApp().preset_bundle->printers.get_edited_preset().config)); wxString msg = ""; @@ -764,18 +792,18 @@ void SSWCP_MachineConnect_Instance::sw_disconnect() { if (res) { // todo: 交互页面删除 } else { - m_status = 1; - m_msg = msg.c_str(); + self->m_status = 1; + self->m_msg = msg.c_str(); } - send_to_js(); - finish_job(); + self->send_to_js(); + self->finish_job(); }); } -std::unordered_map> SSWCP::m_instance_list; - +TimeoutMap> SSWCP::m_instance_list; +constexpr std::chrono::milliseconds SSWCP::DEFAULT_INSTANCE_TIMEOUT; std::unordered_set SSWCP::m_machine_find_cmd_list = { "sw_GetMachineFindSupportInfo", @@ -798,26 +826,27 @@ std::unordered_set SSWCP::m_machine_connect_cmd_list = { "sw_DisConnect", }; -std::shared_ptr SSWCP::create_sswcp_instance( - std::string cmd, const json& header, const json& data, std::string event_id, wxWebView* webview) +std::shared_ptr SSWCP::create_sswcp_instance(std::string cmd, const json& header, const json& data, std::string event_id, wxWebView* webview) { - if (m_machine_find_cmd_list.count(cmd)) { - return std::shared_ptr(new SSWCP_MachineFind_Instance(cmd, header, data, event_id, webview)); - } - else if (m_machine_option_cmd_list.count(cmd)) { - return std::shared_ptr(new SSWCP_MachineOption_Instance(cmd, header, data, event_id, webview)); - } else if (m_machine_connect_cmd_list.count(cmd)) { - return std::shared_ptr(new SSWCP_MachineConnect_Instance(cmd, header, data, event_id, webview)); - } - else { - return std::shared_ptr(new SSWCP_Instance(cmd, header, data, event_id, webview)); + std::shared_ptr instance; + + if (m_machine_find_cmd_list.find(cmd) != m_machine_find_cmd_list.end()) { + instance = std::make_shared(cmd, header, data, event_id, webview); + } else if (m_machine_connect_cmd_list.find(cmd) != m_machine_connect_cmd_list.end()) { + instance = std::make_shared(cmd, header, data, event_id, webview); + } else if (m_machine_option_cmd_list.find(cmd) != m_machine_option_cmd_list.end()) { + instance = std::make_shared(cmd, header, data, event_id, webview); + } else { + instance = std::make_shared(cmd, header, data, event_id, webview); } + + return instance; } +// Handle incoming web messages void SSWCP::handle_web_message(std::string message, wxWebView* webview) { try { if (!webview) { - // todo: 返回错误处理 return; } @@ -827,12 +856,12 @@ void SSWCP::handle_web_message(std::string message, wxWebView* webview) { return; } - json header = j_message["header"]; - json payload = j_message["payload"]; + json header = j_message["header"]; + json payload = j_message["payload"]; std::string cmd = ""; std::string event_id = ""; - json params; + json params; if (payload.count("cmd")) { cmd = payload["cmd"].get(); @@ -845,9 +874,13 @@ void SSWCP::handle_web_message(std::string message, wxWebView* webview) { event_id = payload["event_id"].get(); } - std::shared_ptr instance = create_sswcp_instance(cmd, header, params, event_id, webview); + std::shared_ptr instance = create_sswcp_instance(cmd, header, params, event_id, webview); if (instance) { - m_instance_list[instance.get()] = instance; + if (event_id != "") { + m_instance_list.add_infinite(instance.get(), instance); + } else { + m_instance_list.add(instance.get(), instance, DEFAULT_INSTANCE_TIMEOUT); + } instance->process(); } //if (!m_func_map.count(cmd)) { @@ -862,31 +895,80 @@ void SSWCP::handle_web_message(std::string message, wxWebView* webview) { } } - +// Delete instance from list void SSWCP::delete_target(SSWCP_Instance* target) { wxGetApp().CallAfter([target]() { - if (m_instance_list.count(target)) { - m_instance_list.erase(target); - } + m_instance_list.remove(target); }); } +// Stop all machine subscriptions +void SSWCP::stop_subscribe_machine() +{ + wxGetApp().CallAfter([]() { + std::vector instances_to_stop; + + auto snapshot = m_instance_list.get_snapshot(); + + // Get all subscription instances to stop + for (const auto& instance : snapshot) { + if (instance.second->getType() == SSWCP_MachineFind_Instance::MACHINE_OPTION && instance.second->m_cmd == "sw_SubscribeMachineState") { + instances_to_stop.push_back(instance.first); + } + } + + // Stop each instance + for (auto* instance : instances_to_stop) { + auto instance_ptr = m_instance_list.get(instance); + if (instance_ptr) { + (*instance_ptr)->finish_job(); + } + } + }); +} +// Stop all machine discovery instances void SSWCP::stop_machine_find() { wxGetApp().CallAfter([]() { - for (auto& instance : m_instance_list) { + std::vector instances_to_stop; + + auto snapshot = m_instance_list.get_snapshot(); + + // Get all discovery instances to stop + for (const auto& instance : snapshot) { if (instance.second->getType() == SSWCP_MachineFind_Instance::MACHINE_FIND) { - instance.second->set_stop(true); + instances_to_stop.push_back(instance.first); + } + } + + // Set stop flag for each instance + for (auto* instance : instances_to_stop) { + auto instance_ptr = m_instance_list.get(instance); + if (instance_ptr) { + (*instance_ptr)->set_stop(true); } } }); } +// Handle webview deletion void SSWCP::on_webview_delete(wxWebView* view) { - for (auto& instance : m_instance_list) { - if (instance.second->get_web_view() == view) { - instance.second->set_Instance_illegal(); + // Mark all instances associated with this webview as invalid + std::vector instances_to_invalidate; + + // Get all instances using this webview + for (const auto& instance : m_instance_list) { + if (instance.second->value->get_web_view() == view) { + instances_to_invalidate.push_back(instance.first); + } + } + + // Mark each instance as invalid + for (auto* instance : instances_to_invalidate) { + auto instance_ptr = m_instance_list.get(instance); + if (instance_ptr) { + (*instance_ptr)->set_Instance_illegal(); } } } diff --git a/src/slic3r/GUI/SSWCP.hpp b/src/slic3r/GUI/SSWCP.hpp index ff342d4e0d..8ba8af26f4 100644 --- a/src/slic3r/GUI/SSWCP.hpp +++ b/src/slic3r/GUI/SSWCP.hpp @@ -1,3 +1,4 @@ +// Web communication protocol implementation for Slicer Studio #ifndef SSWCP_HPP #define SSWCP_HPP @@ -10,69 +11,84 @@ #include #include "nlohmann/json.hpp" #include "Bonjour.hpp" - +#include "slic3r/Utils/TimeoutMap.hpp" using namespace nlohmann; namespace Slic3r { namespace GUI { -class SSWCP_Instance +// Base class for handling web communication instances +class SSWCP_Instance : public std::enable_shared_from_this { public: + // Types of communication instances enum INSTANCE_TYPE { - COMMON, - MACHINE_FIND, - MACHINE_CONNECT, - MACHINE_OPTION, + COMMON, // Common instance type + MACHINE_FIND, // For machine discovery + MACHINE_CONNECT, // For machine connection + MACHINE_OPTION, // For machine options/settings }; public: + // Constructor initializes instance with command and parameters SSWCP_Instance(std::string cmd, const json& header, const json& data, std::string event_id, wxWebView* webview) : m_cmd(cmd), m_header(header), m_webview(webview), m_event_id(event_id), m_param_data(data) {} virtual ~SSWCP_Instance() {} + // Process the command virtual void process(); + // Send response back to JavaScript virtual void send_to_js(); + // Clean up after job completion virtual void finish_job(); + // Get instance type virtual INSTANCE_TYPE getType() { return m_type; } + // Check if instance is stopped virtual bool is_stop() { return false; } virtual void set_stop(bool stop) {} - + // Mark instance as illegal (invalid) virtual void set_Instance_illegal(); virtual bool is_Instance_illegal(); + // Get associated webview wxWebView* get_web_view() const; +public: + // Handle timeout event + virtual void on_timeout(); + private: + // Test methods void sync_test(); void async_test(); public: - std::string m_cmd = ""; - json m_header; - wxWebView* m_webview = nullptr; - std::string m_event_id = ""; - json m_param_data; + std::string m_cmd; // Command to execute + json m_header; // Request header + wxWebView* m_webview; // Associated webview + std::string m_event_id; // Event identifier + json m_param_data; // Command parameters - json m_res_data; - int m_status = 200; - std::string m_msg = "OK"; + json m_res_data; // Response data + int m_status = 200; // Response status code + std::string m_msg = "OK"; // Response message protected: - INSTANCE_TYPE m_type = COMMON; + INSTANCE_TYPE m_type = COMMON; // Instance type private: - bool m_illegal = false; - std::mutex m_illegal_mtx; + bool m_illegal = false; // Invalid flag + std::mutex m_illegal_mtx; // Mutex for illegal flag }; +// Instance class for handling machine connection class SSWCP_MachineConnect_Instance : public SSWCP_Instance { public: @@ -91,16 +107,16 @@ class SSWCP_MachineConnect_Instance : public SSWCP_Instance void process() override; private: + // Connection test methods void sw_test_connect(); - void sw_connect(); - void sw_disconnect(); private: - std::thread m_work_thread; + std::thread m_work_thread; // Worker thread }; +// Instance class for handling machine discovery class SSWCP_MachineFind_Instance : public SSWCP_Instance { public: @@ -116,6 +132,7 @@ class SSWCP_MachineFind_Instance : public SSWCP_Instance void set_stop(bool stop); private: + // Machine discovery methods void sw_GetMachineFindSupportInfo(); void sw_StartMachineFind(); void sw_StopMachineFind(); @@ -125,18 +142,19 @@ class SSWCP_MachineFind_Instance : public SSWCP_Instance void onOneEngineEnd(); private: - std::mutex m_machine_list_mtx; - std::mutex m_stop_mtx; + std::mutex m_machine_list_mtx; // Mutex for machine list + std::mutex m_stop_mtx; // Mutex for stop flag private: - std::unordered_map m_machine_list; + std::unordered_map m_machine_list; // Found machines private: - int m_engine_end_count = 0; - std::vector> m_engines; - bool m_stop = false; + int m_engine_end_count = 0; // Counter for finished discovery engines + std::vector> m_engines; // Discovery engines + bool m_stop = false; // Stop flag }; +// Instance class for handling machine options class SSWCP_MachineOption_Instance : public SSWCP_Instance { public: @@ -155,6 +173,7 @@ class SSWCP_MachineOption_Instance : public SSWCP_Instance void process() override; private: + // Machine option methods void sw_SendGCodes(); void sw_GetMachineState(); void sw_SubscribeMachineState(); @@ -163,33 +182,40 @@ class SSWCP_MachineOption_Instance : public SSWCP_Instance void sw_SetMachineSubscribeFilter(); private: - std::thread m_work_thread; + std::thread m_work_thread; // Worker thread }; +// Main SSWCP class for managing communication instances class SSWCP { public: + // Handle incoming web messages static void handle_web_message(std::string message, wxWebView* webview); + // Create new SSWCP instance static std::shared_ptr create_sswcp_instance( std::string cmd, const json& header, const json& data, std::string event_id, wxWebView* webview); + // Delete instance static void delete_target(SSWCP_Instance* target); + // Stop machine discovery static void stop_machine_find(); + // Stop machine subscription + static void stop_subscribe_machine(); + + // Handle webview deletion static void on_webview_delete(wxWebView* webview); private: - - static std::unordered_set m_machine_find_cmd_list; - static std::unordered_set m_machine_option_cmd_list; - static std::unordered_set m_machine_connect_cmd_list; - static std::unordered_map> m_instance_list; + static std::unordered_set m_machine_find_cmd_list; // Machine find commands + static std::unordered_set m_machine_option_cmd_list; // Machine option commands + static std::unordered_set m_machine_connect_cmd_list; // Machine connect commands + static TimeoutMap> m_instance_list; // Active instances + static constexpr std::chrono::milliseconds DEFAULT_INSTANCE_TIMEOUT{80000}; // Default timeout (8s) }; - - }}; #endif diff --git a/src/slic3r/Utils/MQTT.cpp b/src/slic3r/Utils/MQTT.cpp index f16dd9f2cb..2818d4b890 100644 --- a/src/slic3r/Utils/MQTT.cpp +++ b/src/slic3r/Utils/MQTT.cpp @@ -1,68 +1,69 @@ #include "MQTT.hpp" -#include #include +#include -// 构造函数,初始化MQTT客户端 +// Constructor: Initialize MQTT client with server address and client ID +// @param server_address: Address of the MQTT broker +// @param client_id: Unique identifier for this client +// @param clean_session: Whether to start with a clean session MqttClient::MqttClient(const std::string& server_address, const std::string& client_id, bool clean_session) - : server_address_(server_address), client_id_(client_id), client_(server_address_, client_id_), connOpts_(), subListener_("Subscription"), connected_(false) + : server_address_(server_address) + , client_id_(client_id) + , client_(server_address_, client_id_) + , connOpts_() + , subListener_("Subscription") + , connected_(false) { - // 设置连接选项 + // Configure connection options connOpts_.set_clean_session(clean_session); - connOpts_.set_keep_alive_interval(20); - - // 设置回调 + connOpts_.set_keep_alive_interval(20); // Keep-alive ping every 20 seconds client_.set_callback(*this); } -// 连接到MQTT服务器 +// Establish connection to the MQTT broker +// @return: true if connection successful, false otherwise bool MqttClient::Connect() { try { - std::cout << "Connecting to the MQTT server..." << std::flush; + BOOST_LOG_TRIVIAL(info) << "Connecting to the MQTT server: " << server_address_; mqtt::token_ptr conntok = client_.connect(connOpts_, nullptr, *this); - conntok->wait(); + conntok->wait(); // Wait for connection completion connected_ = true; - std::cout << "Connected" << std::endl; + BOOST_LOG_TRIVIAL(info) << "Successfully connected to MQTT server"; return true; } catch (const mqtt::exception& exc) { connected_ = false; - std::cerr << "\nERROR: Unable to connect to MQTT server: '" << server_address_ << "'\n" << exc.what() << std::endl; + BOOST_LOG_TRIVIAL(error) << "Failed to connect to MQTT server '" << server_address_ << "': " << exc.what(); return false; } } -// 断开与MQTT服务器的连接 +// Disconnect from the MQTT broker +// @return: true if disconnection successful, false otherwise bool MqttClient::Disconnect() { if (!connected_) { - std::cerr << "Already disconnected" << std::endl; + BOOST_LOG_TRIVIAL(warning) << "MQTT client already disconnected"; return false; } try { - std::cout << "\nDisconnecting from the MQTT server..." << std::flush; + BOOST_LOG_TRIVIAL(info) << "Disconnecting from MQTT server..."; mqtt::token_ptr disctok = client_.disconnect(); disctok->wait(); connected_ = false; - std::cout << "OK" << std::endl; + BOOST_LOG_TRIVIAL(info) << "Successfully disconnected from MQTT server"; return true; } catch (const mqtt::exception& exc) { - std::cerr << "Error on disconnect: " << exc.what() << std::endl; + BOOST_LOG_TRIVIAL(error) << "Error disconnecting from MQTT server: " << exc.what(); return false; } } -// 检查客户端是否已连接 -bool MqttClient::CheckConnected() -{ - if (!connected_) { - std::cerr << "Error: Client is not connected to the MQTT server" << std::endl; - return false; - } - return true; -} - -// 订阅某个话题 +// Subscribe to a specific MQTT topic +// @param topic: The topic to subscribe to +// @param qos: Quality of Service level (0, 1, or 2) +// @return: true if subscription successful, false otherwise bool MqttClient::Subscribe(const std::string& topic, int qos) { if (!CheckConnected()) { @@ -70,17 +71,19 @@ bool MqttClient::Subscribe(const std::string& topic, int qos) } try { - std::cout << "Subscribing to topic '" << topic << "' with QoS " << qos << std::endl; + BOOST_LOG_TRIVIAL(info) << "Subscribing to MQTT topic '" << topic << "' with QoS " << qos; mqtt::token_ptr subtok = client_.subscribe(topic, qos, nullptr, subListener_); subtok->wait(); return true; } catch (const mqtt::exception& exc) { - std::cerr << "Error on subscribe: " << exc.what() << std::endl; + BOOST_LOG_TRIVIAL(error) << "Error subscribing to topic '" << topic << "': " << exc.what(); return false; } } -// 解除对某个话题的订阅 +// Unsubscribe from a specific MQTT topic +// @param topic: The topic to unsubscribe from +// @return: true if unsubscription successful, false otherwise bool MqttClient::Unsubscribe(const std::string& topic) { if (!CheckConnected()) { @@ -88,17 +91,21 @@ bool MqttClient::Unsubscribe(const std::string& topic) } try { - std::cout << "Unsubscribing from topic '" << topic << "'" << std::endl; + BOOST_LOG_TRIVIAL(info) << "Unsubscribing from MQTT topic '" << topic << "'"; mqtt::token_ptr unsubtok = client_.unsubscribe(topic); unsubtok->wait(); return true; } catch (const mqtt::exception& exc) { - std::cerr << "Error on unsubscribe: " << exc.what() << std::endl; + BOOST_LOG_TRIVIAL(error) << "Error unsubscribing from topic '" << topic << "': " << exc.what(); return false; } } -// 发布消息到某个话题 +// Publish a message to a specific MQTT topic +// @param topic: The topic to publish to +// @param payload: The message content +// @param qos: Quality of Service level (0, 1, or 2) +// @return: true if publish successful, false otherwise bool MqttClient::Publish(const std::string& topic, const std::string& payload, int qos) { if (!CheckConnected()) { @@ -109,33 +116,50 @@ bool MqttClient::Publish(const std::string& topic, const std::string& payload, i pubmsg->set_qos(qos); try { - std::cout << "Publishing message '" << payload << "' to topic '" << topic << "' with QoS " << qos << std::endl; + BOOST_LOG_TRIVIAL(debug) << "Publishing message to topic '" << topic << "' with QoS " << qos << ": " << payload; mqtt::token_ptr pubtok = client_.publish(pubmsg); pubtok->wait(); return true; } catch (const mqtt::exception& exc) { - std::cerr << "Error on publish: " << exc.what() << std::endl; + BOOST_LOG_TRIVIAL(error) << "Error publishing to topic '" << topic << "': " << exc.what(); return false; } } -// 设置消息到达的回调 +// Set callback function for handling incoming messages +// @param callback: Function to be called when a message arrives void MqttClient::SetMessageCallback(std::function callback) { message_callback_ = callback; } -// 实现mqtt::callback接口的方法 +// Check if the client is currently connected +// @return: true if connected, false otherwise +bool MqttClient::CheckConnected() +{ + if (!connected_) { + BOOST_LOG_TRIVIAL(error) << "MQTT client is not connected to server"; + return false; + } + return true; +} + +// Callback when connection is lost +// Implements automatic reconnection with retry logic +// @param cause: Reason for connection loss void MqttClient::connection_lost(const std::string& cause) { connected_ = false; - std::cout << "\nConnection lost" << std::endl; - if (!cause.empty()) - std::cout << "\tcause: " << cause << std::endl; + BOOST_LOG_TRIVIAL(warning) << "MQTT connection lost"; + if (!cause.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Cause: " << cause; + } - Connect(); + Disconnect(); } +// Callback when a message arrives +// @param msg: Pointer to the received message void MqttClient::message_arrived(mqtt::const_message_ptr msg) { if (message_callback_) { @@ -143,26 +167,30 @@ void MqttClient::message_arrived(mqtt::const_message_ptr msg) } } +// Callback when message delivery is complete +// @param token: Delivery token containing message details void MqttClient::delivery_complete(mqtt::delivery_token_ptr token) { - std::cout << "Delivery complete for token: " << (token ? token->get_message_id() : -1) << std::endl; + BOOST_LOG_TRIVIAL(debug) << "Message delivery complete for token: " << (token ? token->get_message_id() : -1); } -// 实现mqtt::iaction_listener接口的方法 +// Callback for operation failure +// @param tok: Token containing operation details void MqttClient::on_failure(const mqtt::token& tok) { - std::cout << "Failure in token: " << tok.get_message_id() << std::endl; - if (tok.get_reason_code() != 0) - std::cout << "\treason_code: " << tok.get_reason_code() << std::endl; - - // connected_ = false; + BOOST_LOG_TRIVIAL(error) << "Operation failed for token: " << tok.get_message_id(); + if (tok.get_reason_code() != 0) { + BOOST_LOG_TRIVIAL(error) << "Reason code: " << tok.get_reason_code(); + } } +// Callback for operation success +// @param tok: Token containing operation details void MqttClient::on_success(const mqtt::token& tok) { - std::cout << "Success in token: " << tok.get_message_id() << std::endl; + BOOST_LOG_TRIVIAL(debug) << "Operation successful for token: " << tok.get_message_id(); auto top = tok.get_topics(); - if (top && !top->empty()) - std::cout << "\ttoken topic: '" << (*top)[0] << "', ..." << std::endl; - std::cout << std::endl; + if (top && !top->empty()) { + BOOST_LOG_TRIVIAL(debug) << "Token topic: '" << (*top)[0] << "'"; + } } diff --git a/src/slic3r/Utils/MQTT.hpp b/src/slic3r/Utils/MQTT.hpp index 21822541d1..4f066b045d 100644 --- a/src/slic3r/Utils/MQTT.hpp +++ b/src/slic3r/Utils/MQTT.hpp @@ -5,88 +5,93 @@ #include #include #include +#include +// Number of retries for connection and subscription attempts #define CONNECT_RETRY_TIME 3 #define SUBSCRIBE_RETRY_TIME 3 -// 定义 action_listener 类 +// Action listener class to handle MQTT operation callbacks class action_listener : public virtual mqtt::iaction_listener { private: - std::string name_; + std::string name_; // Name identifier for the listener public: + // Constructor with a name for identification action_listener(const std::string& name) : name_(name) {} + // Called when an MQTT operation fails void on_failure(const mqtt::token& tok) override { - std::cout << name_ << " failure"; - if (tok.get_message_id() != 0) - std::cout << " for token: [" << tok.get_message_id() << "]" << std::endl; - std::cout << std::endl; + BOOST_LOG_TRIVIAL(error) << name_ << " operation failed"; + if (tok.get_message_id() != 0) { + BOOST_LOG_TRIVIAL(error) << "Token: [" << tok.get_message_id() << "]"; + } } + // Called when an MQTT operation succeeds void on_success(const mqtt::token& tok) override { - std::cout << name_ << " success"; - if (tok.get_message_id() != 0) - std::cout << " for token: [" << tok.get_message_id() << "]" << std::endl; + BOOST_LOG_TRIVIAL(debug) << name_ << " operation successful"; + if (tok.get_message_id() != 0) { + BOOST_LOG_TRIVIAL(debug) << "Token: [" << tok.get_message_id() << "]"; + } auto top = tok.get_topics(); - if (top && !top->empty()) - std::cout << "\ttoken topic: '" << (*top)[0] << "', ..." << std::endl; - std::cout << std::endl; + if (top && !top->empty()) { + BOOST_LOG_TRIVIAL(debug) << "Token topic: '" << (*top)[0] << "'"; + } } }; +// Main MQTT client class implementing both callback and action listener interfaces class MqttClient : public virtual mqtt::callback, public virtual mqtt::iaction_listener { public: - // 构造函数,初始化MQTT客户端 + // Constructor initializes the MQTT client with server details MqttClient(const std::string& server_address, const std::string& client_id, bool clean_session); - // 连接到MQTT服务器 + // Connect to the MQTT broker bool Connect(); - // 断开与MQTT服务器的连接 + // Disconnect from the MQTT broker bool Disconnect(); - // 订阅某个话题 + // Subscribe to a specific topic with given QoS bool Subscribe(const std::string& topic, int qos); - // 解除对某个话题的订阅 + // Unsubscribe from a specific topic bool Unsubscribe(const std::string& topic); - // 发布消息到某个话题 + // Publish a message to a specific topic with given QoS bool Publish(const std::string& topic, const std::string& payload, int qos); - // 设置消息到达的回调 + // Set callback for handling incoming messages void SetMessageCallback(std::function callback); - // 实现mqtt::callback接口的方法 + // Callback interface implementations void connection_lost(const std::string& cause) override; void message_arrived(mqtt::const_message_ptr msg) override; void delivery_complete(mqtt::delivery_token_ptr token) override; - // 实现mqtt::iaction_listener接口的方法 + // Action listener interface implementations void on_failure(const mqtt::token& tok) override; void on_success(const mqtt::token& tok) override; - // 检查客户端是否已连接 + // Check if client is currently connected bool CheckConnected(); private: - std::string server_address_; - std::string client_id_; - mqtt::async_client client_; - std::function message_callback_; - mqtt::connect_options connOpts_; - bool connected_ = false; - std::map topics_to_resubscribe_; - action_listener subListener_; - int connect_retry_time_; - int subscribe_retry_time_; - - + std::string server_address_; // MQTT broker address + std::string client_id_; // Unique client identifier + mqtt::async_client client_; // Async MQTT client instance + std::function message_callback_; // Message handler + mqtt::connect_options connOpts_; // Connection options + bool connected_; // Connection status flag + std::map topics_to_resubscribe_; // Topics to resubscribe after reconnection + action_listener subListener_; // Subscription listener + int connect_retry_time_; // Connection retry counter + int subscribe_retry_time_; // Subscription retry counter }; #endif // MQTT_H diff --git a/src/slic3r/Utils/MoonRaker.cpp b/src/slic3r/Utils/MoonRaker.cpp index f37357defd..4cec4cc73e 100644 --- a/src/slic3r/Utils/MoonRaker.cpp +++ b/src/slic3r/Utils/MoonRaker.cpp @@ -1,3 +1,4 @@ +// Implementation of Moonraker printer host communication #include "MoonRaker.hpp" #include "MQTT.hpp" @@ -32,21 +33,23 @@ namespace pt = boost::property_tree; namespace Slic3r { namespace { + #ifdef WIN32 +// Helper function to extract host and port from URL std::string get_host_from_url(const std::string& url_in) { std::string url = url_in; - // add http:// if there is no scheme + // Add http:// if there is no scheme size_t double_slash = url.find("//"); if (double_slash == std::string::npos) url = "http://" + url; std::string out = url; CURLU* hurl = curl_url(); if (hurl) { - // Parse the input URL. + // Parse the input URL CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0); if (rc == CURLUE_OK) { - // Replace the address. + // Extract host and port char* host; rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0); if (rc == CURLUE_OK) { @@ -133,6 +136,8 @@ std::string substitute_host(const std::string& orig_addr, std::string sub_addr) #endif } #endif // WIN32 + +// Helper function to URL encode a string std::string escape_string(const std::string& unescaped) { std::string ret_val; @@ -147,6 +152,8 @@ std::string escape_string(const std::string& unescaped) } return ret_val; } + +// Helper function to URL encode each element of a filesystem path std::string escape_path_by_element(const boost::filesystem::path& path) { std::string ret_val = escape_string(path.filename().string()); @@ -168,8 +175,8 @@ Moonraker::Moonraker(DynamicPrintConfig* config) , m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke")) {} +// Return the name of this print host type const char* Moonraker::get_name() const { return "Moonraker"; } - #ifdef WIN32 bool Moonraker::test_with_resolved_ip(wxString& msg) const { @@ -354,14 +361,17 @@ bool Moonraker::test(wxString& msg) const return res; } +// Return success message for connection test wxString Moonraker::get_test_ok_msg() const { return _(L("Connection to Moonraker works correctly.")); } +// Return formatted error message for failed connection test wxString Moonraker::get_test_failed_msg(wxString& msg) const { return GUI::format_wxstr("%s: %s\n\n%s", _L("Could not connect to Moonraker"), msg, _L("Note: Moonraker version at least 1.1.0 is required.")); } +// Upload a file to the printer bool Moonraker::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const { #ifndef WIN32 @@ -389,6 +399,8 @@ bool Moonraker::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro }) .resolve_sync(); } + + // Handle different resolution scenarios if (resolved_addr.empty()) { // no resolved addresses - try system resolving BOOST_LOG_TRIVIAL(error) << "PrusaSlicer failed to resolve hostname " << m_host @@ -426,12 +438,10 @@ bool Moonraker::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro return false; #endif // WIN32 } + #ifdef WIN32 -bool Moonraker::upload_inner_with_resolved_ip(PrintHostUpload upload_data, - ProgressFn prorgess_fn, - ErrorFn error_fn, - InfoFn info_fn, - const boost::asio::ip::address& resolved_addr) const +// Upload file using resolved IP address +bool Moonraker::upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const { info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string())); @@ -493,6 +503,7 @@ bool Moonraker::upload_inner_with_resolved_ip(PrintHostUpload up } #endif // WIN32 +// Upload file using hostname bool Moonraker::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const { const char* name = get_name(); @@ -577,11 +588,13 @@ bool Moonraker::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p return res; } +// Validate version text to confirm printer type bool Moonraker::validate_version_text(const boost::optional& version_text) const { return version_text ? boost::starts_with(*version_text, "Moonraker") : true; } +// Set authentication headers for HTTP requests void Moonraker::set_auth(Http& http) const { http.header("X-Api-Key", m_apikey); @@ -591,6 +604,7 @@ void Moonraker::set_auth(Http& http) const } } +// Construct full URL for API endpoint std::string Moonraker::make_url(const std::string& path) const { if (m_host.find("http://") == 0 || m_host.find("https://") == 0) { @@ -604,7 +618,7 @@ std::string Moonraker::make_url(const std::string& path) const } } - +// Basic connect implementation bool Moonraker::connect(wxString& msg, const nlohmann::json& params) { return test(msg); } @@ -619,27 +633,22 @@ bool Moonraker::connect(wxString& msg, const nlohmann::json& params) { // Moonraker_mqtt MqttClient* Moonraker_Mqtt::m_mqtt_client = nullptr; - -std::string Moonraker_Mqtt::m_request_topic = "/request"; +TimeoutMap Moonraker_Mqtt::m_request_cb_map; +std::function Moonraker_Mqtt::m_status_cb = nullptr; std::string Moonraker_Mqtt::m_response_topic = "/response"; +std::string Moonraker_Mqtt::m_status_topic = "/status"; std::string Moonraker_Mqtt::m_notification_topic = "/notification"; -std::string Moonraker_Mqtt::m_status_topic = "/status"; - -std::unordered_map> Moonraker_Mqtt::m_request_cb_map; -std::timed_mutex Moonraker_Mqtt::m_cb_map_mtx; - -std::function Moonraker_Mqtt::m_status_cb = nullptr; +std::string Moonraker_Mqtt::m_request_topic = "/request"; +std::string Moonraker_Mqtt::m_sn = ""; -std::string Moonraker_Mqtt::m_sn = ""; Moonraker_Mqtt::Moonraker_Mqtt(DynamicPrintConfig* config) : Moonraker(config) { std::string host_info = config->option("print_host")->value; if (!m_mqtt_client) - m_mqtt_client = new MqttClient("mqtt://" + host_info, "orca", false); - - + m_mqtt_client = new MqttClient("mqtt://" + host_info, "orca", true); } +// Connect to MQTT broker bool Moonraker_Mqtt::connect(wxString& msg, const nlohmann::json& params) { if (m_mqtt_client->CheckConnected()) { disconnect(msg, params); @@ -648,7 +657,7 @@ bool Moonraker_Mqtt::connect(wxString& msg, const nlohmann::json& params) { std::string sn = ""; if (!params.count("sn")) { - // 如果没有sn码,需要发http请求获取 + // Need to request SN via HTTP if not provided } else { sn = params["sn"].get(); } @@ -666,14 +675,14 @@ bool Moonraker_Mqtt::connect(wxString& msg, const nlohmann::json& params) { }); return is_connect && response_subscribed; - } - +// Disconnect from MQTT broker bool Moonraker_Mqtt::disconnect(wxString& msg, const nlohmann::json& params) { return m_mqtt_client->Disconnect(); } +// Subscribe to printer status updates void Moonraker_Mqtt::async_subscribe_machine_info(std::function callback) { bool res = m_mqtt_client->Subscribe(m_sn + m_status_topic, 0); @@ -686,10 +695,10 @@ void Moonraker_Mqtt::async_subscribe_machine_info(std::function& scripts, std::function callback) { std::string method = "printer.gcode.script"; @@ -704,29 +713,36 @@ void Moonraker_Mqtt::async_send_gcodes(const std::vector& scripts, json params; params["script"] = str_scripts; - - if (!send_to_request(method, params, true, callback) && callback) { + if (!send_to_request(method, params, true, callback, [callback](){ + json res; + res["error"] = "timeout"; + callback(res); + }) && callback) { callback(json::value_t::null); } } +// Unsubscribe from printer status updates void Moonraker_Mqtt::async_unsubscribe_machine_info(std::function callback) { bool res = m_mqtt_client->Unsubscribe(m_sn + m_status_topic); if (!res) { - if (m_status_cb) { + if (callback) { callback(json::value_t::null); } return; } + m_status_cb = nullptr; callback(json::object()); } -void Moonraker_Mqtt::async_set_machine_subscribe_filter(const std::vector>>& targets, - std::function callback) +// Set filters for printer status subscription +void Moonraker_Mqtt::async_set_machine_subscribe_filter( + const std::vector>>& targets, + std::function callback) { std::string method = "printer.objects.subscribe"; @@ -745,14 +761,19 @@ void Moonraker_Mqtt::async_set_machine_subscribe_filter(const std::vector>>& targets, - std::function callback) +// Query printer information +void Moonraker_Mqtt::async_get_machine_info( + const std::vector>>& targets, + std::function callback) { std::string method = "printer.objects.query"; @@ -771,38 +792,51 @@ void Moonraker_Mqtt::async_get_machine_info(const std::vector callback) { std::string method = "printer.objects.list"; - json params = json::object(); // 空参数对象 + json params = json::object(); - if (!send_to_request(method, params, true, callback) && callback) { + if (!send_to_request(method, params, true, callback, [callback](){ + json res; + res["error"] = "timeout"; + callback(res); + }) && callback) { callback(json::value_t::null); } } -bool Moonraker_Mqtt::send_to_request(const std::string& method, const json& params, bool need_response, std::function callback) +// Send request to printer via MQTT +bool Moonraker_Mqtt::send_to_request( + const std::string& method, + const json& params, + bool need_response, + std::function callback, + std::function timeout_callback) { json body; - body["jsonrpc"] = "2.0"; - body["method"] = method; - body["params"] = params; + body["method"] = method; + body["params"] = params; boost::uuids::uuid uuid = m_generator(); - std::string str_uuid = boost::uuids::to_string(uuid); if (need_response) { - if (!add_response_target(str_uuid, callback)) { + if (!add_response_target(str_uuid, callback, timeout_callback)) { return false; } - body["id"] = str_uuid; + body["id"] = str_uuid; } if (m_mqtt_client) { @@ -811,48 +845,37 @@ bool Moonraker_Mqtt::send_to_request(const std::string& method, const json& para delete_response_target(str_uuid); } return res; - } else { - return false; - } -} - -bool Moonraker_Mqtt::add_response_target(const std::string& id, std::function callback) { - if (m_cb_map_mtx.try_lock_for(std::chrono::seconds(5))) { - m_request_cb_map[id] = callback; - - m_cb_map_mtx.unlock(); - return true; } return false; +} +// Register callback for response to a request +bool Moonraker_Mqtt::add_response_target( + const std::string& id, + std::function callback, + std::function timeout_callback, + std::chrono::milliseconds timeout) +{ + return m_request_cb_map.add( + id, + RequestCallback(std::move(callback), std::move(timeout_callback)), + timeout + ); } +// Remove registered callback void Moonraker_Mqtt::delete_response_target(const std::string& id) { - if (m_cb_map_mtx.try_lock_for(std::chrono::seconds(5))) { - if (m_request_cb_map.count(id)) { - m_request_cb_map.erase(id); - } - m_cb_map_mtx.unlock(); - } - + m_request_cb_map.remove(id); } +// Get and remove callback for a request std::function Moonraker_Mqtt::get_request_callback(const std::string& id) { - if (m_cb_map_mtx.try_lock_for(std::chrono::seconds(5))) { - if (m_request_cb_map.count(id)) { - auto result = m_request_cb_map[id]; - m_cb_map_mtx.unlock(); - return result; - } else { - m_cb_map_mtx.unlock(); - return nullptr; - } - } - - return nullptr; + auto request_cb = m_request_cb_map.get_and_remove(id); + return request_cb ? request_cb->success_cb : nullptr; } +// Handle incoming MQTT messages void Moonraker_Mqtt::on_mqtt_message_arrived(const std::string& topic, const std::string& payload) { try { @@ -862,15 +885,14 @@ void Moonraker_Mqtt::on_mqtt_message_arrived(const std::string& topic, const std on_status_arrived(payload); } else if (topic.find(m_notification_topic) != std::string::npos) { on_notification_arrived(payload); - } + } else { return; } - } catch (std::exception& e) {} } - +// Handle response messages void Moonraker_Mqtt::on_response_arrived(const std::string& payload) { try { @@ -898,6 +920,7 @@ void Moonraker_Mqtt::on_response_arrived(const std::string& payload) } catch (std::exception& e) {} } +// Handle status update messages void Moonraker_Mqtt::on_status_arrived(const std::string& payload) { try { @@ -910,7 +933,6 @@ void Moonraker_Mqtt::on_status_arrived(const std::string& payload) return; } - // 待修改 if (!m_status_cb) { return; } @@ -920,6 +942,7 @@ void Moonraker_Mqtt::on_status_arrived(const std::string& payload) } catch (std::exception& e) {} } +// Handle notification messages void Moonraker_Mqtt::on_notification_arrived(const std::string& payload) { try { diff --git a/src/slic3r/Utils/MoonRaker.hpp b/src/slic3r/Utils/MoonRaker.hpp index d8797cbfc9..0210c3e7b1 100644 --- a/src/slic3r/Utils/MoonRaker.hpp +++ b/src/slic3r/Utils/MoonRaker.hpp @@ -11,6 +11,7 @@ #include "PrintHost.hpp" #include "libslic3r/PrintConfig.hpp" +#include "slic3r/Utils/TimeoutMap.hpp" class MqttClient; @@ -18,21 +19,32 @@ namespace Slic3r { class DynamicPrintConfig; class Http; - +// Base class for communicating with Moonraker API class Moonraker : public PrintHost { public: + // Constructor takes printer configuration Moonraker(DynamicPrintConfig *config); ~Moonraker() override = default; + // Get name of this print host type const char* get_name() const override; + // Test connection to printer virtual bool test(wxString &curl_msg) const override; wxString get_test_ok_msg () const override; wxString get_test_failed_msg (wxString &msg) const override; + + // Upload file to printer bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override; - bool send_gcodes(const std::vector& codes, std::string& extraInfo) override; - bool get_machine_info(const std::vector>>& targets, nlohmann::json& response) override; + + // Send G-code commands to printer + bool send_gcodes(const std::vector& codes, std::string& extraInfo) override; + + // Get printer information + bool get_machine_info(const std::vector>>& targets, nlohmann::json& response) override; + + // Configuration getters bool has_auto_discovery() const override { return true; } bool can_test() const override { return true; } PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } @@ -40,8 +52,11 @@ class Moonraker : public PrintHost const std::string& get_apikey() const { return m_apikey; } const std::string& get_cafile() const { return m_cafile; } + // Connect/disconnect from printer virtual bool connect(wxString& msg, const nlohmann::json& params) override; virtual bool disconnect(wxString& msg, const nlohmann::json& params) override { return true; } + + // Async printer information methods virtual void async_get_machine_info(const std::vector>>& targets, std::function) override {} virtual void async_subscribe_machine_info(std::function) override {} virtual void async_get_machine_objects(std::function)override {} @@ -51,17 +66,20 @@ class Moonraker : public PrintHost virtual void async_send_gcodes(const std::vector& scripts, std::function) override{} protected: + // Internal upload implementations #ifdef WIN32 virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const; #endif virtual bool validate_version_text(const boost::optional &version_text) const; virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const; + // Connection parameters std::string m_host; std::string m_apikey; std::string m_cafile; bool m_ssl_revoke_best_effort; + // Helper methods virtual void set_auth(Http &http) const; std::string make_url(const std::string &path) const; @@ -71,66 +89,82 @@ class Moonraker : public PrintHost #endif }; - - +// Extended Moonraker class that uses MQTT for communication class Moonraker_Mqtt : public Moonraker { public: + // Constructor Moonraker_Mqtt(DynamicPrintConfig* config); + // Override connection methods virtual bool connect(wxString& msg, const nlohmann::json& params) override; - virtual bool disconnect(wxString& msg, const nlohmann::json& params) override; + // Override async information methods virtual void async_get_machine_info(const std::vector>>& targets, std::function callback) override; - virtual void async_subscribe_machine_info(std::function) override; - virtual void async_get_machine_objects(std::function callback) override; - virtual void async_set_machine_subscribe_filter(const std::vector>>& targets, std::function callback) override; - virtual void async_unsubscribe_machine_info(std::function) override; - virtual void async_send_gcodes(const std::vector& scripts, std::function) override; public: + // MQTT message handler void on_mqtt_message_arrived(const std::string& topic, const std::string& payload); private: - bool send_to_request(const std::string& method, const nlohmann::json& params, bool need_response = false, std::function callback = nullptr); - - bool add_response_target(const std::string& id, std::function callback); + // Helper methods for MQTT communication + bool send_to_request(const std::string& method, + const nlohmann::json& params, + bool need_response, + std::function callback, + std::function timeout_callback); + + bool add_response_target(const std::string& id, + std::function callback, + std::function timeout_callback = nullptr, + std::chrono::milliseconds timeout = std::chrono::milliseconds(80000)); std::function get_request_callback(const std::string& id); - void delete_response_target(const std::string& id); + // MQTT message handlers void on_response_arrived(const std::string& payload); - void on_status_arrived(const std::string& payload); - void on_notification_arrived(const std::string& payload); +public: + // Callback structure for MQTT requests + struct RequestCallback { + std::function success_cb; // Success callback + std::function timeout_cb; // Timeout callback + + RequestCallback( + std::function success, + std::function timeout = nullptr) + : success_cb(std::move(success)) + , timeout_cb(std::move(timeout)) + {} + }; private: + // Static MQTT client and related variables static MqttClient* m_mqtt_client; + static TimeoutMap m_request_cb_map; + static std::function m_status_cb; - static std::unordered_map> m_request_cb_map; - static std::timed_mutex m_cb_map_mtx; - static std::function m_status_cb; - - + // MQTT topics static std::string m_response_topic; static std::string m_status_topic; static std::string m_notification_topic; static std::string m_request_topic; + // Printer serial number static std::string m_sn; private: - boost::uuids::random_generator m_generator; // 用于生成唯一id + boost::uuids::random_generator m_generator; // UUID generator for request IDs }; } diff --git a/src/slic3r/Utils/TimeoutMap.hpp b/src/slic3r/Utils/TimeoutMap.hpp new file mode 100644 index 0000000000..7d3e1944e6 --- /dev/null +++ b/src/slic3r/Utils/TimeoutMap.hpp @@ -0,0 +1,246 @@ +#ifndef slic3r_TimeoutMap_hpp_ +#define slic3r_TimeoutMap_hpp_ + +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { + +// Check if type is a shared_ptr +template +struct is_shared_ptr : std::false_type {}; + +template +struct is_shared_ptr> : std::true_type {}; + +// Check if type has an on_timeout method +template +struct has_on_timeout : std::false_type {}; + +template +struct has_on_timeout().on_timeout())>> : std::true_type {}; + +// A thread-safe map container that automatically removes entries after a timeout period +template +class TimeoutMap { +public: + // Clock type used for timing + using clock_type = std::chrono::steady_clock; + using time_point = std::chrono::time_point; + + // Special timeout value indicating item should never expire + static constexpr std::chrono::milliseconds INFINITE_TIMEOUT{std::chrono::milliseconds::max()}; + + // Container for map items with timeout information + struct Item { + V value; // The stored value + time_point expire_time; // When this item expires + bool never_expire; // Flag indicating if item should never expire + + // Constructor initializes item with value and timeout duration + Item(V v, std::chrono::milliseconds timeout) + : value(std::move(v)) + , expire_time(clock_type::now() + timeout) + , never_expire(timeout == INFINITE_TIMEOUT) + {} + }; + + // Constructor starts background thread for checking timeouts + TimeoutMap(std::chrono::milliseconds default_timeout = std::chrono::milliseconds(30000)) + : m_default_timeout(default_timeout) + , m_running(true) { + m_check_thread = std::thread([this]() { check_timeouts(); }); + } + + // Destructor ensures background thread is stopped and joined + ~TimeoutMap() { + m_running = false; + if (m_check_thread.joinable()) { + m_check_thread.join(); + } + } + + // Add an item that never expires + bool add_infinite(const K& key, V value) { + std::lock_guard lock(m_mutex); + m_items[key] = std::make_shared(std::move(value), INFINITE_TIMEOUT); + return true; + } + + // Add an item with optional custom timeout + bool add(const K& key, V value, std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) { + std::lock_guard lock(m_mutex); + if (timeout.count() == 0) timeout = m_default_timeout; + m_items[key] = std::make_shared(std::move(value), timeout); + return true; + } + + // Remove an item by key + void remove(const K& key) { + std::lock_guard lock(m_mutex); + m_items.erase(key); + } + + // Get value by key, returns nullptr if expired or not found + std::shared_ptr get(const K& key) { + std::lock_guard lock(m_mutex); + auto it = m_items.find(key); + if (it != m_items.end()) { + if (it->second->expire_time > std::chrono::steady_clock::now() || it->second->never_expire) { + return std::make_shared(it->second->value); + } else { + m_items.erase(it); + } + } + return nullptr; + } + + // Get and remove value, useful for one-time callbacks + std::shared_ptr get_and_remove(const K& key) { + std::lock_guard lock(m_mutex); + auto it = m_items.find(key); + if (it != m_items.end()) { + if (it->second->expire_time > std::chrono::steady_clock::now()) { + auto value = std::make_shared(it->second->value); + m_items.erase(it); + return value; + } else { + m_items.erase(it); + } + } + return nullptr; + } + + // Check if key exists and has not expired + bool exists(const K& key) { + std::lock_guard lock(m_mutex); + auto it = m_items.find(key); + if (it != m_items.end()) { + if (it->second->expire_time > std::chrono::steady_clock::now()) { + return true; + } else { + m_items.erase(it); + } + } + return false; + } + + // Update timeout for an existing item + bool update_timeout(const K& key, std::chrono::milliseconds timeout) { + std::lock_guard lock(m_mutex); + auto it = m_items.find(key); + if (it != m_items.end()) { + it->second->expire_time = std::chrono::steady_clock::now() + timeout; + return true; + } + return false; + } + + // Clear all items from the map + void clear() { + std::lock_guard lock(m_mutex); + m_items.clear(); + } + + // Get current number of items + size_t size() { + std::lock_guard lock(m_mutex); + return m_items.size(); + } + + // Iterator support + using iterator = typename std::map>::iterator; + using const_iterator = typename std::map>::const_iterator; + + // Iterator access methods + iterator begin() { + std::lock_guard lock(m_mutex); + return m_items.begin(); + } + + iterator end() { + std::lock_guard lock(m_mutex); + return m_items.end(); + } + + const_iterator begin() const { + std::lock_guard lock(m_mutex); + return m_items.begin(); + } + + const_iterator end() const { + std::lock_guard lock(m_mutex); + return m_items.end(); + } + + const_iterator cbegin() const { + std::lock_guard lock(m_mutex); + return m_items.cbegin(); + } + + const_iterator cend() const { + std::lock_guard lock(m_mutex); + return m_items.cend(); + } + + // Get a snapshot of current state + std::vector> get_snapshot() { + std::lock_guard lock(m_mutex); + std::vector> snapshot; + snapshot.reserve(m_items.size()); + for (const auto& [key, item] : m_items) { + snapshot.emplace_back(key, item->value); + } + return snapshot; + } + +private: + // Background thread function to check for expired items + void check_timeouts() { + while (m_running) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + std::lock_guard lock(m_mutex); + auto now = clock_type::now(); + + for (auto it = m_items.begin(); it != m_items.end();) { + if (!it->second->never_expire && it->second->expire_time <= now) { + // Handle timeout callbacks for different types + if constexpr (std::is_same_v) { + if (it->second->value.timeout_cb) { + it->second->value.timeout_cb(); + } + } + else if constexpr (has_on_timeout::value) { + it->second->value.on_timeout(); + } + else if constexpr (is_shared_ptr::value && + has_on_timeout::value) { + if (it->second->value) { + it->second->value->on_timeout(); + } + } + + it = m_items.erase(it); + } else { + ++it; + } + } + } + } + + // Member variables + std::map> m_items; // Storage for timed items + mutable std::mutex m_mutex; // Mutex for thread safety + std::chrono::milliseconds m_default_timeout; // Default timeout duration + std::thread m_check_thread; // Background check thread + bool m_running; // Thread control flag +}; + +} +#endif \ No newline at end of file