From c729849843372667fd9c94ab255c5e9a93ebf0a3 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Tue, 25 Aug 2026 16:39:08 +0800 Subject: [PATCH] cleanup moonraker and snapmaker printer agents --- src/slic3r/GUI/DeviceManager.cpp | 2 - src/slic3r/GUI/StatusPanel.cpp | 13 ---- src/slic3r/GUI/StatusPanel.hpp | 2 - src/slic3r/Utils/IPrinterAgent.hpp | 3 - src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 22 +----- src/slic3r/Utils/MoonrakerPrinterAgent.hpp | 8 +-- src/slic3r/Utils/SnapmakerPrinterAgent.cpp | 84 ++++++++-------------- src/slic3r/Utils/SnapmakerPrinterAgent.hpp | 11 ++- 8 files changed, 43 insertions(+), 102 deletions(-) diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 4981d426bf..4bfee32879 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -1793,8 +1793,6 @@ int MachineObject::command_ams_refresh_rfid2(int ams_id, int slot_id) int MachineObject::command_start_camera() { if (!m_agent) return -1; - // why: this fires from the camera view's renew timer, so a refusal must stay silent - - // show_unsupported_dlg() here would pop a dialog every ~5 min on every other printer. return m_agent->command_start_camera(get_dev_id()); } diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index b939897de8..97c4920be7 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -2315,9 +2315,6 @@ void StatusPanel::update_camera_state(MachineObject* obj) const bool has_printer_webcam = !obj->webcam_stream_url.empty(); if (has_printer_webcam) { if (m_printer_webcam_url != obj->webcam_stream_url) { - // why: start timing belongs to the loaded camera. - // carrying it across printers suppresses the new camera's initial start. - m_camera_start_sent = std::chrono::steady_clock::time_point{}; m_custom_camera_view->LoadURL(obj->webcam_stream_url); m_custom_camera_view->Show(); m_media_ctrl->Hide(); @@ -2331,19 +2328,9 @@ void StatusPanel::update_camera_state(MachineObject* obj) m_media_ctrl->Hide(); m_media_play_ctrl->Hide(); } - // why: printers like the U1 capture only while asked and retire the capture task ~362 s - // after each start, so the open camera view has to renew ahead of that. 300 s matches - // Snapmaker's own client. Agents that do not need it refuse the call silently. - const auto now = std::chrono::steady_clock::now(); - if (m_camera_start_sent == std::chrono::steady_clock::time_point{} || - now - m_camera_start_sent >= std::chrono::seconds(300)) { - obj->command_start_camera(); - m_camera_start_sent = now; - } } else if (!m_printer_webcam_url.empty()) { handle_camera_source_change(); m_printer_webcam_url.clear(); - m_camera_start_sent = std::chrono::steady_clock::time_point{}; } //sdcard diff --git a/src/slic3r/GUI/StatusPanel.hpp b/src/slic3r/GUI/StatusPanel.hpp index 4e516695c5..170a9d970d 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -666,8 +666,6 @@ protected: int m_last_extrusion = -1; int m_last_vcamera = -1; std::string m_printer_webcam_url; - // note: zero = not started; see update_camera_state() for the renew interval. - std::chrono::steady_clock::time_point m_camera_start_sent{}; int m_model_mall_request_count = 0; bool m_is_load_with_temp = false; json m_rating_result; diff --git a/src/slic3r/Utils/IPrinterAgent.hpp b/src/slic3r/Utils/IPrinterAgent.hpp index 5853b0e398..69d8d53ce9 100644 --- a/src/slic3r/Utils/IPrinterAgent.hpp +++ b/src/slic3r/Utils/IPrinterAgent.hpp @@ -93,9 +93,6 @@ public: { return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } virtual int command_ams_select_tray(std::string, std::string, int, bool) { return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } - // why: some printers emit camera frames only while explicitly asked, and retire the - // capture task on their own - the camera view starts it and renews it. Printers with an - // always-on stream need nothing here, hence the honest refusal by default. virtual int command_start_camera(std::string) { return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 22ea0d52f1..d898c51e3a 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -1558,9 +1558,6 @@ bool MoonrakerPrinterAgent::send_ws_rpc(const std::string& method, const nlohman request["id"] = next_jsonrpc_id++; const std::string body = request.dump(); - // why: the configured address and Moonraker's own API port are both plausible websocket - // homes - the U1 answers on 7125 while its base_url points at port 80. Try what the user - // configured first, then the default API port. std::vector ports{endpoint.port}; if (endpoint.port != "7125") { ports.emplace_back("7125"); @@ -1590,9 +1587,6 @@ bool MoonrakerPrinterAgent::send_ws_rpc(const std::string& method, const nlohman ws.text(true); ws.write(net::buffer(body)); - // why: some RPCs are answered on another transport entirely, so a reply may never - // come - read once with a short deadline purely to let the printer act on the - // request before we close, then drop the socket. A timeout here is the normal path. ws.next_layer().expires_after(std::chrono::seconds(2)); beast::flat_buffer buffer; beast::error_code read_ec; @@ -1613,18 +1607,6 @@ bool MoonrakerPrinterAgent::send_ws_rpc(const std::string& method, const nlohman bool MoonrakerPrinterAgent::fetch_webcam_info(const std::string& base_url, const std::string& api_key, uint64_t generation) { - if (const std::string override_url = webcam_stream_override(base_url); !override_url.empty()) { - { - std::lock_guard lock(payload_mutex); - if (generation != connect_generation.load()) { - return false; - } - webcam_stream_url = override_url; - } - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: using printer-specific webcam URL " << override_url; - return true; - } - std::string stream_url; std::string webcam_name; std::string error; @@ -2155,6 +2137,8 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba // Read loop while (!ws_stop.load()) { + on_status_loop_tick(dev_id); + ws.next_layer().expires_after(std::chrono::seconds(2)); beast::flat_buffer buffer; beast::error_code ec; @@ -2969,7 +2953,7 @@ std::string MoonrakerPrinterAgent::join_url(const std::string& base_url, const s // Sanitize filename to prevent path traversal attacks // Extracts only the basename, removing any path components -std::string MoonrakerPrinterAgent::sanitize_filename(const std::string& filename) const +std::string MoonrakerPrinterAgent::sanitize_filename(const std::string& filename) { if (filename.empty()) { return "print.gcode"; diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index f1798f097f..19ef02ee22 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -125,7 +125,7 @@ protected: // 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) const; + std::string sanitize_filename(const std::string& filename); std::string join_url(const std::string& base_url, const std::string& path) const; // Trim whitespace and convert to uppercase @@ -142,13 +142,9 @@ protected: bool post_print_action(const std::string& action, const std::string& base_url, const std::string& api_key) const; - // Send one JSON-RPC call over a short-lived Moonraker websocket. Returns true when the - // request was written; it never waits for a reply. bool send_ws_rpc(const std::string& method, const nlohmann::json& params); - // why: a printer with no /server/webcams/list entry can still name its stream directly; - // returning empty (the default) keeps the normal Moonraker discovery path. - virtual std::string webcam_stream_override(const std::string& base_url) const { return {}; } + virtual void on_status_loop_tick(const std::string& dev_id) {} private: int handle_request(const std::string& dev_id, const std::string& json_str); diff --git a/src/slic3r/Utils/SnapmakerPrinterAgent.cpp b/src/slic3r/Utils/SnapmakerPrinterAgent.cpp index f516f30e77..3e465cb855 100644 --- a/src/slic3r/Utils/SnapmakerPrinterAgent.cpp +++ b/src/slic3r/Utils/SnapmakerPrinterAgent.cpp @@ -1,13 +1,11 @@ #include "SnapmakerPrinterAgent.hpp" #include "Http.hpp" #include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Utils.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "nlohmann/json.hpp" -#include #include -#include +#include #include namespace Slic3r { @@ -15,6 +13,13 @@ namespace Slic3r { namespace { constexpr const char* SNAPMAKER_AGENT_VERSION = "0.0.1"; +constexpr int64_t CAMERA_REFRESH_INTERVAL_MS = 300'000; + +int64_t now_ms() +{ + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); +} // Safely access a parallel array by index, returning a fallback if out of bounds. template @@ -71,67 +76,38 @@ std::string find_closest_color_preset_by_vendor_and_type(const PresetCollection& SnapmakerPrinterAgent::SnapmakerPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) {} -int SnapmakerPrinterAgent::command_start_camera(std::string dev_id) +void SnapmakerPrinterAgent::start_camera_monitor() { - (void) dev_id; - // why: the printer executes this over the websocket but answers only over MQTT, and the - // call itself blocks on socket I/O - it fires from the camera view's renew timer on the UI - // thread, so run it detached rather than block the caller on a reply that never comes. - // note: interval is dead time in SECONDS on top of a ~0.455 s capture, so 0 is the 2.15 fps - // ceiling (1 measures 0.63 fps), and it cannot be changed while a capture task is running. std::thread([this] { send_ws_rpc("camera.start_monitor", {{"domain", "lan"}, {"interval", 0}, {"expect_pw", false}}); }).detach(); - return BAMBU_NETWORK_SUCCESS; + m_camera_last_fire_ms.store(now_ms()); } -std::string SnapmakerPrinterAgent::webcam_stream_override(const std::string& base_url) const +void SnapmakerPrinterAgent::on_status_loop_tick(const std::string& dev_id) { - const std::string snapshot_url = join_url(base_url, "/server/files/camera/monitor.jpg"); - - // why: one wrapper file per printer - two U1s would otherwise overwrite each other's URL. - const boost::filesystem::path page = boost::filesystem::path(data_dir()) / "cache" / - ("snapmaker_camera_" + sanitize_filename(device_info.dev_ip) + ".html"); - - // why: the printer writes a still JPEG at ~2 fps, so the page polls it with a cache buster - // instead of consuming a stream. Chaining the next request off onload (never a bare - // setInterval) keeps requests from piling up when the printer is slow to answer. - const std::string html = - "Camera" - "\"\"\n"; - - std::string write_error; - try { - boost::filesystem::create_directories(page.parent_path()); - boost::nowide::ofstream out(page.string().c_str(), std::ios::binary | std::ios::trunc); - out << html; - out.close(); - // note: an ofstream reports a failed write in its state, not by throwing. - if (!out) { - write_error = "write failed"; - } - } catch (const std::exception& e) { - write_error = e.what(); - } - if (!write_error.empty()) { - // why: no wrapper means no camera - a raw monitor.jpg URL would render one frozen frame - // and read as a broken feed, so fall back to showing nothing and say why in the log. - BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent: could not write camera page " << page.string() - << ": " << write_error; - return {}; + (void) dev_id; + const int64_t last = m_camera_last_fire_ms.load(); + if (last == 0 || now_ms() - last >= CAMERA_REFRESH_INTERVAL_MS) { + start_camera_monitor(); } +} - return "file://" + page.generic_string(); +int SnapmakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) +{ + const int rtn = MoonrakerPrinterAgent::connect_printer(dev_id, dev_ip, username, password, use_ssl); + if (rtn == BAMBU_NETWORK_SUCCESS) { + start_camera_monitor(); + } + return rtn; +} + +int SnapmakerPrinterAgent::command_start_camera(std::string dev_id) +{ + (void) dev_id; + start_camera_monitor(); + return BAMBU_NETWORK_SUCCESS; } AgentInfo SnapmakerPrinterAgent::get_agent_info_static() diff --git a/src/slic3r/Utils/SnapmakerPrinterAgent.hpp b/src/slic3r/Utils/SnapmakerPrinterAgent.hpp index 04b67cd573..62cde387c5 100644 --- a/src/slic3r/Utils/SnapmakerPrinterAgent.hpp +++ b/src/slic3r/Utils/SnapmakerPrinterAgent.hpp @@ -2,6 +2,8 @@ #include "MoonrakerPrinterAgent.hpp" +#include +#include #include namespace Slic3r { @@ -16,14 +18,17 @@ public: AgentInfo get_agent_info() override { return get_agent_info_static(); } bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override; + int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override; int command_start_camera(std::string dev_id) override; -protected: - std::string webcam_stream_override(const std::string& base_url) const override; - private: // Combine filament_type + filament_sub_type into a unified type string static std::string combine_filament_type(const std::string& type, const std::string& sub_type); + + void start_camera_monitor(); + void on_status_loop_tick(const std::string& dev_id) override; + + std::atomic m_camera_last_fire_ms{0}; }; } // namespace Slic3r