From a5d3aaefd3c362ca2bef54b1792d2a41fb81d5e0 Mon Sep 17 00:00:00 2001 From: Grant Harkness <64664463+grant0013@users.noreply.github.com> Date: Mon, 21 Sep 2026 04:37:50 +0100 Subject: [PATCH] =?UTF-8?q?Creality=20K1-family=20CFS=20support=20(K1=20SE?= =?UTF-8?q?)=20=E2=80=94=20follow-up=20to=20#13752=20(#14089)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/profiles/Creality.json | 2 +- .../machine/Creality K1 (0.4 nozzle).json | 3 +- .../machine/Creality K1 (0.6 nozzle).json | 3 +- .../machine/Creality K1 (0.8 nozzle).json | 3 +- .../machine/Creality K1 SE 0.4 nozzle.json | 3 +- .../machine/Creality K1 SE 0.6 nozzle.json | 3 +- .../machine/Creality K1 SE 0.8 nozzle.json | 3 +- .../machine/Creality K1C 0.4 nozzle.json | 3 +- .../machine/Creality K1C 0.6 nozzle.json | 3 +- .../machine/Creality K1C 0.8 nozzle.json | 3 +- .../machine/Creality K1_CFS-C 0.4 nozzle.json | 3 +- src/slic3r/Utils/CrealityHostDiscovery.cpp | 18 +--- src/slic3r/Utils/CrealityPrint.cpp | 102 +++++++++++++----- src/slic3r/Utils/CrealityPrint.hpp | 6 ++ 14 files changed, 107 insertions(+), 51 deletions(-) diff --git a/resources/profiles/Creality.json b/resources/profiles/Creality.json index a0a51e8267..2ba6a5aa62 100644 --- a/resources/profiles/Creality.json +++ b/resources/profiles/Creality.json @@ -1,6 +1,6 @@ { "name": "Creality", - "version": "02.03.02.81", + "version": "02.03.02.82", "force_update": "0", "description": "Creality configurations", "machine_model_list": [ diff --git a/resources/profiles/Creality/machine/Creality K1 (0.4 nozzle).json b/resources/profiles/Creality/machine/Creality K1 (0.4 nozzle).json index 01a3bc7d5d..765e3b3125 100644 --- a/resources/profiles/Creality/machine/Creality K1 (0.4 nozzle).json +++ b/resources/profiles/Creality/machine/Creality K1 (0.4 nozzle).json @@ -149,7 +149,8 @@ "fan_speedup_overhangs": "1", "fan_speedup_time": "0", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_load_filament_time": "119", diff --git a/resources/profiles/Creality/machine/Creality K1 (0.6 nozzle).json b/resources/profiles/Creality/machine/Creality K1 (0.6 nozzle).json index fdb83b032e..ac6c71187b 100644 --- a/resources/profiles/Creality/machine/Creality K1 (0.6 nozzle).json +++ b/resources/profiles/Creality/machine/Creality K1 (0.6 nozzle).json @@ -151,7 +151,8 @@ "fan_speedup_overhangs": "1", "fan_speedup_time": "0", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_load_filament_time": "119", diff --git a/resources/profiles/Creality/machine/Creality K1 (0.8 nozzle).json b/resources/profiles/Creality/machine/Creality K1 (0.8 nozzle).json index 8cb8e6f169..e2df1ba34c 100644 --- a/resources/profiles/Creality/machine/Creality K1 (0.8 nozzle).json +++ b/resources/profiles/Creality/machine/Creality K1 (0.8 nozzle).json @@ -151,7 +151,8 @@ "fan_speedup_overhangs": "1", "fan_speedup_time": "0", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_load_filament_time": "119", diff --git a/resources/profiles/Creality/machine/Creality K1 SE 0.4 nozzle.json b/resources/profiles/Creality/machine/Creality K1 SE 0.4 nozzle.json index f8aa68f3c0..a8aaadeadb 100644 --- a/resources/profiles/Creality/machine/Creality K1 SE 0.4 nozzle.json +++ b/resources/profiles/Creality/machine/Creality K1 SE 0.4 nozzle.json @@ -151,7 +151,8 @@ "fan_speedup_overhangs": "1", "fan_speedup_time": "0", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_load_filament_time": "119", diff --git a/resources/profiles/Creality/machine/Creality K1 SE 0.6 nozzle.json b/resources/profiles/Creality/machine/Creality K1 SE 0.6 nozzle.json index bbc97a8f5a..59435515f5 100644 --- a/resources/profiles/Creality/machine/Creality K1 SE 0.6 nozzle.json +++ b/resources/profiles/Creality/machine/Creality K1 SE 0.6 nozzle.json @@ -32,7 +32,8 @@ "fan_speedup_time": "0", "gcode_flavor": "klipper", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_end_gcode": "END_PRINT", diff --git a/resources/profiles/Creality/machine/Creality K1 SE 0.8 nozzle.json b/resources/profiles/Creality/machine/Creality K1 SE 0.8 nozzle.json index 00282be149..0db81bbdec 100644 --- a/resources/profiles/Creality/machine/Creality K1 SE 0.8 nozzle.json +++ b/resources/profiles/Creality/machine/Creality K1 SE 0.8 nozzle.json @@ -32,7 +32,8 @@ "fan_speedup_time": "0", "gcode_flavor": "klipper", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_end_gcode": "END_PRINT", diff --git a/resources/profiles/Creality/machine/Creality K1C 0.4 nozzle.json b/resources/profiles/Creality/machine/Creality K1C 0.4 nozzle.json index ac6fd03393..9f355f391a 100644 --- a/resources/profiles/Creality/machine/Creality K1C 0.4 nozzle.json +++ b/resources/profiles/Creality/machine/Creality K1C 0.4 nozzle.json @@ -150,7 +150,8 @@ "fan_speedup_overhangs": "1", "fan_speedup_time": "0", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_load_filament_time": "119", diff --git a/resources/profiles/Creality/machine/Creality K1C 0.6 nozzle.json b/resources/profiles/Creality/machine/Creality K1C 0.6 nozzle.json index 58e7fa841f..44c5147f58 100644 --- a/resources/profiles/Creality/machine/Creality K1C 0.6 nozzle.json +++ b/resources/profiles/Creality/machine/Creality K1C 0.6 nozzle.json @@ -152,7 +152,8 @@ "fan_speedup_overhangs": "1", "fan_speedup_time": "0", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_load_filament_time": "119", diff --git a/resources/profiles/Creality/machine/Creality K1C 0.8 nozzle.json b/resources/profiles/Creality/machine/Creality K1C 0.8 nozzle.json index 82edb57d82..9a271da17f 100644 --- a/resources/profiles/Creality/machine/Creality K1C 0.8 nozzle.json +++ b/resources/profiles/Creality/machine/Creality K1C 0.8 nozzle.json @@ -152,7 +152,8 @@ "fan_speedup_overhangs": "1", "fan_speedup_time": "0", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_load_filament_time": "119", diff --git a/resources/profiles/Creality/machine/Creality K1_CFS-C 0.4 nozzle.json b/resources/profiles/Creality/machine/Creality K1_CFS-C 0.4 nozzle.json index 4da0be155c..375e0bc71b 100644 --- a/resources/profiles/Creality/machine/Creality K1_CFS-C 0.4 nozzle.json +++ b/resources/profiles/Creality/machine/Creality K1_CFS-C 0.4 nozzle.json @@ -31,7 +31,8 @@ "fan_speedup_time": "0", "gcode_flavor": "klipper", "high_current_on_filament_swap": "0", - "host_type": "octoprint", + "host_type": "crealityprint", + "printer_agent": "crealityprint", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "machine_LED_light_exist": "1", "machine_end_gcode": "END_PRINT", diff --git a/src/slic3r/Utils/CrealityHostDiscovery.cpp b/src/slic3r/Utils/CrealityHostDiscovery.cpp index 8fcf16749d..a3256794e2 100644 --- a/src/slic3r/Utils/CrealityHostDiscovery.cpp +++ b/src/slic3r/Utils/CrealityHostDiscovery.cpp @@ -1,4 +1,5 @@ #include "CrealityHostDiscovery.hpp" +#include "CrealityPrint.hpp" #include "cxmdns.h" #include "Http.hpp" @@ -11,25 +12,16 @@ namespace Slic3r { namespace { -struct ModelEntry { const char* code; const char* name; }; -constexpr ModelEntry kCfsCapableModels[] = { - {"F008", "K2 Plus"}, - {"F012", "K2 Pro"}, - {"F021", "K2"}, -}; - +// Model capability/name lookups live in CrealityPrint (one table shared +// with the print host) so discovery can't drift out of sync again. bool is_cfs_capable(const std::string& code) { - for (const auto& m : kCfsCapableModels) - if (code == m.code) return true; - return false; + return CrealityPrint::model_supports_multi_color(code); } std::string model_name_for(const std::string& code) { - for (const auto& m : kCfsCapableModels) - if (code == m.code) return m.name; - return {}; + return CrealityPrint::model_display_name(code); } // Extract the device suffix from a service name like diff --git a/src/slic3r/Utils/CrealityPrint.cpp b/src/slic3r/Utils/CrealityPrint.cpp index ec980cee98..323db56ff6 100644 --- a/src/slic3r/Utils/CrealityPrint.cpp +++ b/src/slic3r/Utils/CrealityPrint.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -217,29 +218,36 @@ static void ws_connect(net::io_context& ioc, websocket::stream(&recv_timeout), sizeof(recv_timeout)); + // K1-family firmware requires TEXT-mode WebSocket frames; K2-family accepts both. + // SO_RCVTIMEO is a no-op with Boost.Asio on macOS/Linux (kqueue/epoll); use + // expires_after() on the tcp_stream layer instead (managed per-call by the caller). + ws.text(true); } static std::string ws_send_and_read(websocket::stream& ws, const json& cmd, const std::string& expected_key, int max_reads = 20) { ws.write(net::buffer(to_string(cmd))); - for (int i = 0; i < max_reads; i++) { + // total caps the loop if the printer sends a continuous stream of heartbeats + for (int reads = 0, total = 0; reads < max_reads && total < max_reads * 3; ++total) { beast::flat_buffer buf; beast::error_code ec; ws.read(buf, ec); - if (ec == net::error::would_block) + // would_block = SO_RCVTIMEO fired (Windows/raw sockets) + // beast::error::timeout = expires_after() fired (Boost.Beast, macOS/Linux) + if (ec == net::error::would_block || ec == beast::error::timeout) break; if (ec) throw beast::system_error{ec}; std::string msg = beast::buffers_to_string(buf.data()); + // K1-family firmware sends periodic heartbeat pings; ack them so the + // printer does not close the connection, then keep reading. + if (msg.find("heart_beat") != std::string::npos) { + beast::error_code wr_ec; + ws.write(net::buffer(std::string("ok")), wr_ec); + continue; + } + ++reads; if (msg.find(expected_key) != std::string::npos) return msg; } @@ -247,6 +255,19 @@ static std::string ws_send_and_read(websocket::stream& ws, co return {}; } +// K1-family firmware stores gcodes under /usr/data and uses a stricter WebSocket +// protocol (TEXT frames, heartbeat acks). Add verified K1-family model IDs here. +static bool is_k1_family(const std::string& model) +{ + static const std::unordered_set k1_models = { + "K1", + "K1 SE", + "K1C", + "K1_CFS-C", + }; + return k1_models.count(model) > 0; +} + void CrealityPrint::query_model() const { if (!m_model.empty()) @@ -256,29 +277,48 @@ void CrealityPrint::query_model() const test(msg); } -bool CrealityPrint::supports_multi_color_print() const +// CFS-capable models. One table for capability checks, display names and +// LAN-discovery labelling -- keep additions here only. +static const std::map& cfs_capable_models() { - query_model(); - // K2-platform printers with CFS support - return m_model == "F008" // K2 Plus - || m_model == "F012" // K2 Pro - || m_model == "F021" // K2 - || m_model == "F022"; // SPARKX i7 -} - -std::string CrealityPrint::model_name() const -{ - static const std::map names = { + static const std::map models = { {"F008", "K2 Plus"}, {"F012", "K2 Pro"}, {"F021", "K2"}, {"F022", "SPARKX i7"}, + {"K1", "K1"}, + {"K1 SE", "K1 SE"}, + {"K1C", "K1C"}, + {"K1_CFS-C", "K1_CFS-C"}, }; + return models; +} + +bool CrealityPrint::model_supports_multi_color(const std::string& model) +{ + return cfs_capable_models().count(model) > 0; +} + +std::string CrealityPrint::model_display_name(const std::string& model) +{ + auto& names = cfs_capable_models(); + auto it = names.find(model); + return it != names.end() ? it->second : std::string{}; +} + +bool CrealityPrint::supports_multi_color_print() const +{ + query_model(); + return model_supports_multi_color(m_model); +} + +std::string CrealityPrint::model_name() const +{ query_model(); if (m_model.empty()) return "unreachable"; - auto it = names.find(m_model); - return it != names.end() ? it->second : "unknown (" + m_model + ")"; + std::string name = model_display_name(m_model); + return !name.empty() ? name : "unknown (" + m_model + ")"; } std::string CrealityPrint::query_boxes_info() const @@ -288,9 +328,15 @@ std::string CrealityPrint::query_boxes_info() const websocket::stream ws{ioc}; ws_connect(ioc, ws, m_host, "9999"); + beast::get_lowest_layer(ws).expires_after(std::chrono::seconds(10)); json boxs_query = {{"method", "get"}, {"params", {{"boxsInfo", 1}}}}; std::string result = ws_send_and_read(ws, boxs_query, "boxsInfo"); - ws.close(websocket::close_code::normal); + + // K1 SE closes its side after responding; use the error_code overload so + // the resulting EOF does not throw and discard the already-received result. + beast::error_code close_ec; + beast::get_lowest_layer(ws).expires_after(std::chrono::seconds(3)); + ws.close(websocket::close_code::normal, close_ec); return result; } catch (std::exception const& e) { BOOST_LOG_TRIVIAL(error) << "CrealityPrint: Failed to query boxsInfo: " << e.what(); @@ -301,7 +347,8 @@ std::string CrealityPrint::query_boxes_info() const bool CrealityPrint::start_print(wxString &msg, const std::string &filename, const std::map& extended_info) const { try { - const std::string gcode_path = "/mnt/UDISK/printer_data/gcodes/" + filename; + const std::string data_root = is_k1_family(m_model) ? "/usr/data" : "/mnt/UDISK"; + const std::string gcode_path = data_root + "/printer_data/gcodes/" + filename; net::io_context ioc; websocket::stream ws{ioc}; @@ -379,7 +426,7 @@ bool CrealityPrint::start_print(wxString &msg, const std::string &filename, cons json cmd = { {"method", "set"}, {"params", { - {"opGcodeFile", "printprt:/usr/data/printer_data/gcodes/" + filename} + {"opGcodeFile", "printprt:" + gcode_path} }} }; ws.write(net::buffer(to_string(cmd))); @@ -396,6 +443,7 @@ bool CrealityPrint::start_print(wxString &msg, const std::string &filename, cons // Same reason: the printer may have already closed the connection. A close // error here is not a failure — the start command was sent above. beast::error_code close_ec; + beast::get_lowest_layer(ws).expires_after(std::chrono::seconds(3)); ws.close(websocket::close_code::normal, close_ec); return true; } catch(std::exception const& e) { diff --git a/src/slic3r/Utils/CrealityPrint.hpp b/src/slic3r/Utils/CrealityPrint.hpp index 3b5287f382..2c3c554cf4 100644 --- a/src/slic3r/Utils/CrealityPrint.hpp +++ b/src/slic3r/Utils/CrealityPrint.hpp @@ -31,6 +31,12 @@ public: PrintHostPostUploadActions get_post_upload_actions() const override; bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override; bool supports_multi_color_print() const; + + // Single source of truth for the CFS-capable model table, shared with + // LAN discovery (CrealityHostDiscovery). Model is the /info "model" + // value: an F-code on the K2 platform, a literal name on K1-family. + static bool model_supports_multi_color(const std::string& model); + static std::string model_display_name(const std::string& model); std::string query_boxes_info() const; std::string model_name() const;