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",
"version": "02.03.02.81",
"version": "02.03.02.82",
"force_update": "0",
"description": "Creality configurations",
"machine_model_list": [
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
+5 -13
View File
@@ -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
+75 -27
View File
@@ -2,6 +2,7 @@
#include <algorithm>
#include <map>
#include <unordered_set>
#include <sstream>
#include <exception>
#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");
}));
ws.handshake(host, "/");
#ifdef _WIN32
DWORD recv_timeout = 3000;
#else
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));
// 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<beast::tcp_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<beast::tcp_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<std::string> 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<std::string, std::string>& 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<std::string, std::string> names = {
static const std::map<std::string, std::string> 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<beast::tcp_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<std::string, std::string>& 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<beast::tcp_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) {
+6
View File
@@ -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;