Creality K1-family CFS support (K1 SE) — follow-up to #13752 (#14089)

This commit is contained in:
Grant Harkness
2026-09-21 11:37:50 +08:00
committed by GitHub
parent b8f4aa782e
commit a5d3aaefd3
14 changed files with 107 additions and 51 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Creality", "name": "Creality",
"version": "02.03.02.81", "version": "02.03.02.82",
"force_update": "0", "force_update": "0",
"description": "Creality configurations", "description": "Creality configurations",
"machine_model_list": [ "machine_model_list": [
@@ -149,7 +149,8 @@
"fan_speedup_overhangs": "1", "fan_speedup_overhangs": "1",
"fan_speedup_time": "0", "fan_speedup_time": "0",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_load_filament_time": "119", "machine_load_filament_time": "119",
@@ -151,7 +151,8 @@
"fan_speedup_overhangs": "1", "fan_speedup_overhangs": "1",
"fan_speedup_time": "0", "fan_speedup_time": "0",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_load_filament_time": "119", "machine_load_filament_time": "119",
@@ -151,7 +151,8 @@
"fan_speedup_overhangs": "1", "fan_speedup_overhangs": "1",
"fan_speedup_time": "0", "fan_speedup_time": "0",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_load_filament_time": "119", "machine_load_filament_time": "119",
@@ -151,7 +151,8 @@
"fan_speedup_overhangs": "1", "fan_speedup_overhangs": "1",
"fan_speedup_time": "0", "fan_speedup_time": "0",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_load_filament_time": "119", "machine_load_filament_time": "119",
@@ -32,7 +32,8 @@
"fan_speedup_time": "0", "fan_speedup_time": "0",
"gcode_flavor": "klipper", "gcode_flavor": "klipper",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_end_gcode": "END_PRINT", "machine_end_gcode": "END_PRINT",
@@ -32,7 +32,8 @@
"fan_speedup_time": "0", "fan_speedup_time": "0",
"gcode_flavor": "klipper", "gcode_flavor": "klipper",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_end_gcode": "END_PRINT", "machine_end_gcode": "END_PRINT",
@@ -150,7 +150,8 @@
"fan_speedup_overhangs": "1", "fan_speedup_overhangs": "1",
"fan_speedup_time": "0", "fan_speedup_time": "0",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_load_filament_time": "119", "machine_load_filament_time": "119",
@@ -152,7 +152,8 @@
"fan_speedup_overhangs": "1", "fan_speedup_overhangs": "1",
"fan_speedup_time": "0", "fan_speedup_time": "0",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_load_filament_time": "119", "machine_load_filament_time": "119",
@@ -152,7 +152,8 @@
"fan_speedup_overhangs": "1", "fan_speedup_overhangs": "1",
"fan_speedup_time": "0", "fan_speedup_time": "0",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_load_filament_time": "119", "machine_load_filament_time": "119",
@@ -31,7 +31,8 @@
"fan_speedup_time": "0", "fan_speedup_time": "0",
"gcode_flavor": "klipper", "gcode_flavor": "klipper",
"high_current_on_filament_swap": "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]", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"machine_LED_light_exist": "1", "machine_LED_light_exist": "1",
"machine_end_gcode": "END_PRINT", "machine_end_gcode": "END_PRINT",
+5 -13
View File
@@ -1,4 +1,5 @@
#include "CrealityHostDiscovery.hpp" #include "CrealityHostDiscovery.hpp"
#include "CrealityPrint.hpp"
#include "cxmdns.h" #include "cxmdns.h"
#include "Http.hpp" #include "Http.hpp"
@@ -11,25 +12,16 @@ namespace Slic3r {
namespace { namespace {
struct ModelEntry { const char* code; const char* name; }; // Model capability/name lookups live in CrealityPrint (one table shared
constexpr ModelEntry kCfsCapableModels[] = { // with the print host) so discovery can't drift out of sync again.
{"F008", "K2 Plus"},
{"F012", "K2 Pro"},
{"F021", "K2"},
};
bool is_cfs_capable(const std::string& code) bool is_cfs_capable(const std::string& code)
{ {
for (const auto& m : kCfsCapableModels) return CrealityPrint::model_supports_multi_color(code);
if (code == m.code) return true;
return false;
} }
std::string model_name_for(const std::string& code) std::string model_name_for(const std::string& code)
{ {
for (const auto& m : kCfsCapableModels) return CrealityPrint::model_display_name(code);
if (code == m.code) return m.name;
return {};
} }
// Extract the device suffix from a service name like // Extract the device suffix from a service name like
+75 -27
View File
@@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <map> #include <map>
#include <unordered_set>
#include <sstream> #include <sstream>
#include <exception> #include <exception>
#include <boost/format.hpp> #include <boost/format.hpp>
@@ -217,29 +218,36 @@ static void ws_connect(net::io_context& ioc, websocket::stream<beast::tcp_stream
std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-coro"); std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-coro");
})); }));
ws.handshake(host, "/"); ws.handshake(host, "/");
// K1-family firmware requires TEXT-mode WebSocket frames; K2-family accepts both.
#ifdef _WIN32 // SO_RCVTIMEO is a no-op with Boost.Asio on macOS/Linux (kqueue/epoll); use
DWORD recv_timeout = 3000; // expires_after() on the tcp_stream layer instead (managed per-call by the caller).
#else ws.text(true);
struct timeval recv_timeout = {3, 0};
#endif
setsockopt(beast::get_lowest_layer(ws).socket().native_handle(),
SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&recv_timeout), sizeof(recv_timeout));
} }
static std::string ws_send_and_read(websocket::stream<beast::tcp_stream>& ws, const json& cmd, const std::string& expected_key, int max_reads = 20) static std::string ws_send_and_read(websocket::stream<beast::tcp_stream>& ws, const json& cmd, const std::string& expected_key, int max_reads = 20)
{ {
ws.write(net::buffer(to_string(cmd))); 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::flat_buffer buf;
beast::error_code ec; beast::error_code ec;
ws.read(buf, 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; break;
if (ec) if (ec)
throw beast::system_error{ec}; throw beast::system_error{ec};
std::string msg = beast::buffers_to_string(buf.data()); 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) if (msg.find(expected_key) != std::string::npos)
return msg; return msg;
} }
@@ -247,6 +255,19 @@ static std::string ws_send_and_read(websocket::stream<beast::tcp_stream>& ws, co
return {}; 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<std::string> k1_models = {
"K1",
"K1 SE",
"K1C",
"K1_CFS-C",
};
return k1_models.count(model) > 0;
}
void CrealityPrint::query_model() const void CrealityPrint::query_model() const
{ {
if (!m_model.empty()) if (!m_model.empty())
@@ -256,29 +277,48 @@ void CrealityPrint::query_model() const
test(msg); 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<std::string, std::string>& cfs_capable_models()
{ {
query_model(); static const std::map<std::string, std::string> models = {
// 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<std::string, std::string> names = {
{"F008", "K2 Plus"}, {"F008", "K2 Plus"},
{"F012", "K2 Pro"}, {"F012", "K2 Pro"},
{"F021", "K2"}, {"F021", "K2"},
{"F022", "SPARKX i7"}, {"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(); query_model();
if (m_model.empty()) if (m_model.empty())
return "unreachable"; return "unreachable";
auto it = names.find(m_model); std::string name = model_display_name(m_model);
return it != names.end() ? it->second : "unknown (" + m_model + ")"; return !name.empty() ? name : "unknown (" + m_model + ")";
} }
std::string CrealityPrint::query_boxes_info() const std::string CrealityPrint::query_boxes_info() const
@@ -288,9 +328,15 @@ std::string CrealityPrint::query_boxes_info() const
websocket::stream<beast::tcp_stream> ws{ioc}; websocket::stream<beast::tcp_stream> ws{ioc};
ws_connect(ioc, ws, m_host, "9999"); 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}}}}; json boxs_query = {{"method", "get"}, {"params", {{"boxsInfo", 1}}}};
std::string result = ws_send_and_read(ws, boxs_query, "boxsInfo"); 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; return result;
} catch (std::exception const& e) { } catch (std::exception const& e) {
BOOST_LOG_TRIVIAL(error) << "CrealityPrint: Failed to query boxsInfo: " << e.what(); 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<std::string, std::string>& extended_info) const bool CrealityPrint::start_print(wxString &msg, const std::string &filename, const std::map<std::string, std::string>& extended_info) const
{ {
try { 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; net::io_context ioc;
websocket::stream<beast::tcp_stream> ws{ioc}; websocket::stream<beast::tcp_stream> ws{ioc};
@@ -379,7 +426,7 @@ bool CrealityPrint::start_print(wxString &msg, const std::string &filename, cons
json cmd = { json cmd = {
{"method", "set"}, {"method", "set"},
{"params", { {"params", {
{"opGcodeFile", "printprt:/usr/data/printer_data/gcodes/" + filename} {"opGcodeFile", "printprt:" + gcode_path}
}} }}
}; };
ws.write(net::buffer(to_string(cmd))); 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 // Same reason: the printer may have already closed the connection. A close
// error here is not a failure — the start command was sent above. // error here is not a failure — the start command was sent above.
beast::error_code close_ec; beast::error_code close_ec;
beast::get_lowest_layer(ws).expires_after(std::chrono::seconds(3));
ws.close(websocket::close_code::normal, close_ec); ws.close(websocket::close_code::normal, close_ec);
return true; return true;
} catch(std::exception const& e) { } catch(std::exception const& e) {
+6
View File
@@ -31,6 +31,12 @@ public:
PrintHostPostUploadActions get_post_upload_actions() const override; PrintHostPostUploadActions get_post_upload_actions() const override;
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override; bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
bool supports_multi_color_print() const; 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 query_boxes_info() const;
std::string model_name() const; std::string model_name() const;