OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::CrealityPrintAgent Class Referencefinal

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/slic3r/Utils/CrealityPrintAgent.hpp>

Inheritance diagram for Slic3r::CrealityPrintAgent:
Collaboration diagram for Slic3r::CrealityPrintAgent:

Classes

struct  CFSSlot

Public Member Functions

 CrealityPrintAgent (std::string log_dir)
 ~CrealityPrintAgent () override=default
AgentInfo get_agent_info () override
bool fetch_filament_info (std::string dev_id, FilamentSyncMode sync_mode=FilamentSyncMode::pull) override
void set_cloud_agent (std::shared_ptr< ICloudServiceAgent > cloud) override
int send_message (std::string dev_id, std::string json_str, int qos, int flag) override
int connect_printer (const PrinterConnectionParams &params) override
int disconnect_printer () override
int send_message_to_printer (std::string dev_id, std::string json_str, int qos, int flag) override
bool start_discovery (bool start, bool sending) override
int bind_detect (std::string dev_ip, std::string sec_link, detectResult &detect) override
int set_server_callback (OnServerErrFn fn) override
std::string get_user_selected_machine () override
int set_user_selected_machine (std::string dev_id) override
int start_print (PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
int start_local_print_with_record (PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
int start_send_gcode_to_sdcard (PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
int start_local_print (PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override
int start_sdcard_print (PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override
int set_on_ssdp_msg_fn (OnMsgArrivedFn fn) override
int set_on_printer_connected_fn (OnPrinterConnectedFn fn) override
int set_on_subscribe_failure_fn (GetSubscribeFailureFn fn) override
int set_on_message_fn (OnMessageFn fn) override
int set_on_user_message_fn (OnMessageFn fn) override
int set_on_local_connect_fn (OnLocalConnectedFn fn) override
int set_on_local_message_fn (OnMessageFn fn) override
int set_queue_on_main_fn (QueueOnMainFn fn) override
FilamentSyncMode get_filament_sync_mode () const override
virtual int command_ams_refresh_rfid (std::string, int, int, int, bool)
virtual int command_ams_calibrate (std::string, int, int, bool)
virtual int command_ams_select_tray (std::string, std::string, int, bool)
virtual int command_start_camera (std::string)
virtual int command_xyz_abs (std::string dev_id, int sequence_id, bool lan_mode)
virtual int command_auto_leveling (std::string dev_id, int sequence_id, bool lan_mode)
virtual int command_go_home (std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode)
virtual int command_set_bed (std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode)
virtual int command_set_nozzle (std::string dev_id, int temp, int sequence_id, bool lan_mode)
virtual int command_axis_control (std::string dev_id, std::string axis, double unit, double input_val, int speed, bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode)
virtual std::string default_lan_username () const
virtual int check_cert ()
virtual void install_device_cert (std::string dev_id, bool lan_only)
virtual int ping_bind (std::string ping_code)
virtual int bind (std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
virtual int unbind (std::string dev_id)
virtual int request_bind_ticket (std::string *ticket)
virtual int get_hms_snapshot (std::string dev_id, std::string file_name, std::function< void(std::string, int)> callback)
virtual int start_subscribe (std::string module)
virtual int stop_subscribe (std::string module)
virtual int add_subscribe (std::vector< std::string > dev_list)
virtual int del_subscribe (std::vector< std::string > dev_list)
virtual CameraStreamMode get_camera_stream_mode () const
virtual std::string to_orca_filament_id (const std::string &printer_filament_id) const
virtual std::string from_orca_filament_id (const std::string &orca_filament_id) const
virtual std::string get_camera_url () const

Static Public Member Functions

static AgentInfo get_agent_info_static ()
static bool parse_cfs_response (const std::string &response, std::vector< CFSSlot > &slots, int &box_count, std::string &error)
static std::string normalize_filament_type (const std::string &filament_type)
static std::string match_filament_preset (const PresetCollection &filaments, const std::string &vendor, const std::string &brand_name, const std::string &base_type)

Protected Member Functions

void build_ams_payload (int ams_count, int max_lane_index, const std::vector< AmsTrayData > &trays)
virtual bool init_device_info (const std::string &dev_id, const std::string &dev_ip, const std::string &username, const std::string &password, bool use_ssl, const std::string &port)
virtual bool fetch_device_info (const std::string &base_url, const std::string &api_key, MoonrakerDeviceInfo &info, std::string &error, const std::function< bool()> &cancelled) const
void start_status_stream (const std::string &dev_id, const std::string &base_url, const std::string &api_key)
void stop_status_stream ()
bool is_numeric (const std::string &value)
std::string normalize_base_url (bool use_ssl, const std::string &host, const std::string &port)
std::string sanitize_filename (const std::string &filename)
std::string join_url (const std::string &base_url, const std::string &path) const

Static Protected Member Functions

static std::string trim_and_upper (const std::string &input)
static std::string map_filament_type_to_generic_id (const std::string &filament_type)

Protected Attributes

struct Slic3r::MoonrakerPrinterAgent::MoonrakerDeviceInfo device_info
struct Slic3r::MoonrakerPrinterAgent::StatusStreamTimings ws_timings
std::recursive_mutex state_mutex

Private Member Functions

int handle_request (const std::string &dev_id, const std::string &json_str)
int send_version_info (const std::string &dev_id)
int send_access_code (const std::string &dev_id)
bool fetch_object_list (const std::string &base_url, const std::string &api_key, std::set< std::string > &objects, std::string &error) const
bool query_printer_status (const std::string &base_url, const std::string &api_key, nlohmann::json &status, std::string &error, const std::function< bool()> &cancelled) const
bool send_gcode_sync (const std::string &dev_id, const std::string &gcode) const
void send_gcode_async (const std::string &dev_id, const std::string &gcode, std::function< void(bool)> on_result={}) const
void announce_printhost_device ()
void dispatch_local_connect (int state, const std::string &dev_id, const std::string &msg)
void dispatch_printer_connected (const std::string &dev_id)
void dispatch_message (const std::string &dev_id, const std::string &payload)
void join_stopped_status_stream ()
void run_status_stream (std::string dev_id, std::string base_url, std::string api_key, StatusStreamTimings timings)
void handle_ws_message (const std::string &dev_id, const std::string &payload)
void send_heartbeat_if_due (const std::string &dev_id, std::chrono::milliseconds interval)
void update_status_cache (const nlohmann::json &updates)
nlohmann::json build_print_payload_locked () const
int pause_print (const std::string &dev_id)
int resume_print (const std::string &dev_id)
int cancel_print (const std::string &dev_id)
bool upload_gcode (const std::string &local_path, const std::string &filename, const std::string &base_url, const std::string &api_key, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
bool send_jsonrpc_command (const std::string &base_url, const std::string &api_key, const nlohmann::json &request, std::string &response) const
void perform_connection_async (const std::string &dev_id, const std::string &base_url, const std::string &api_key, uint64_t generation)
bool fetch_hh_filament_info (std::vector< AmsTrayData > &trays, int &max_lane_index)
bool fetch_moonraker_filament_data (std::vector< AmsTrayData > &trays, int &max_lane_index)
int publish_command_json (std::string dev_id, const nlohmann::json &j, bool lan_mode)

Static Private Member Functions

static std::string safe_json_string (const nlohmann::json &obj, const char *key)
static int safe_json_int (const nlohmann::json &obj, const char *key)
static std::string safe_array_string (const nlohmann::json &arr, int idx)
static int safe_array_int (const nlohmann::json &arr, int idx)
static std::string normalize_color_value (const std::string &color)

Private Attributes

std::string ssdp_announced_host
std::string ssdp_announced_id
std::shared_ptr< ICloudServiceAgent > m_cloud_agent
std::string selected_machine
OnMsgArrivedFn on_ssdp_msg_fn
OnPrinterConnectedFn on_printer_connected_fn
GetSubscribeFailureFn on_subscribe_failure_fn
OnMessageFn on_message_fn
OnMessageFn on_user_message_fn
OnLocalConnectedFn on_local_connect_fn
OnMessageFn on_local_message_fn
QueueOnMainFn queue_on_main_fn
OnServerErrFn on_server_err_fn
std::recursive_mutex payload_mutex
nlohmann::json status_cache
std::atomic< int > next_jsonrpc_id {1}
std::set< std::string > available_objects
std::atomic< bool > ws_stop {false}
std::atomic< bool > ws_reconnect_requested {false}
std::atomic< uint64_t > ws_last_emit_ms {0}
std::thread ws_thread
std::mutex ws_abort_mutex
std::function< void()> ws_abort
std::atomic< uint64_t > ws_last_dispatch_ms {0}
std::string last_print_state
std::atomic< uint64_t > connect_generation {0}
std::thread connect_thread
std::recursive_mutex connect_mutex

Static Private Attributes

static constexpr uint64_t STATUS_UPDATE_INTERVAL_MS = 1000

Detailed Description


Class Documentation

◆ Slic3r::CrealityPrintAgent::CFSSlot

struct Slic3r::CrealityPrintAgent::CFSSlot
Collaboration diagram for Slic3r::CrealityPrintAgent::CFSSlot:
Class Members
int box_id = 0
string brand_name
string color_hex
string filament_type
int slot_id = 0
string vendor

Constructor & Destructor Documentation

◆ CrealityPrintAgent()

Slic3r::CrealityPrintAgent::CrealityPrintAgent ( std::string log_dir)
explicit
177 : MoonrakerPrinterAgent(std::move(log_dir))
178{
179}
MoonrakerPrinterAgent(std::string log_dir)
Definition MoonrakerPrinterAgent.cpp:150

References Slic3r::MoonrakerPrinterAgent::MoonrakerPrinterAgent().

Here is the call graph for this function:

◆ ~CrealityPrintAgent()

Slic3r::CrealityPrintAgent::~CrealityPrintAgent ( )
overridedefault

Member Function Documentation

◆ add_subscribe()

virtual int Slic3r::IPrinterAgent::add_subscribe ( std::vector< std::string > dev_list)
inlinevirtualinherited

Subscribe to push streams for specific device identifiers.

Reimplemented in Slic3r::BBLPrinterAgent.

353{ (void) dev_list; return BAMBU_NETWORK_SUCCESS; }
#define BAMBU_NETWORK_SUCCESS
Definition bambu_networking.hpp:16

References BAMBU_NETWORK_SUCCESS.

◆ announce_printhost_device()

void Slic3r::MoonrakerPrinterAgent::announce_printhost_device ( )
privateinherited
1405{
1406 OnMsgArrivedFn ssdp_fn;
1407 {
1408 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1409 ssdp_fn = on_ssdp_msg_fn;
1410 if (!ssdp_fn || device_info.dev_id.empty() || device_info.dev_ip.empty() || device_info.base_url.empty()) {
1411 return;
1412 }
1413 if (ssdp_announced_host == device_info.base_url && !ssdp_announced_id.empty()) {
1414 return;
1415 }
1416 }
1417
1418 // Never asks the printer, since discovery runs on the GUI thread and an unreachable printer would freeze it
1419 const std::string dev_name = device_info.model_name.empty() ? "Moonraker Printer" : device_info.model_name;
1420
1421 const std::string model_id = device_info.model_id;
1422
1423 nlohmann::json payload;
1424 payload["dev_name"] = dev_name;
1425 payload["dev_id"] = device_info.dev_id;
1426 payload["dev_ip"] = device_info.dev_ip;
1427 payload["dev_type"] = model_id.empty() ? dev_name : model_id;
1428 payload["dev_signal"] = "0";
1429 payload["connect_type"] = "lan";
1430 payload["bind_state"] = "free";
1431 payload["sec_link"] = "secure";
1432 payload["ssdp_version"] = "v1";
1433
1434 ssdp_fn(payload.dump());
1435
1436 {
1437 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1440
1441 // Set this as the selected machine if nothing is currently selected
1442 if (selected_machine.empty()) {
1444 }
1445 }
1446}
OnMsgArrivedFn on_ssdp_msg_fn
Definition MoonrakerPrinterAgent.hpp:202
std::string ssdp_announced_id
Definition MoonrakerPrinterAgent.hpp:198
std::string ssdp_announced_host
Definition MoonrakerPrinterAgent.hpp:197
std::string selected_machine
Definition MoonrakerPrinterAgent.hpp:200
std::recursive_mutex state_mutex
Definition MoonrakerPrinterAgent.hpp:125
struct Slic3r::MoonrakerPrinterAgent::MoonrakerDeviceInfo device_info
std::function< void(std::string dev_info_json_str)> OnMsgArrivedFn
Definition bambu_networking.hpp:134
@ lock
Definition GLGizmoEmboss.cpp:209

References announce_printhost_device(), device_info, on_ssdp_msg_fn, selected_machine, ssdp_announced_host, ssdp_announced_id, and state_mutex.

Referenced by announce_printhost_device(), and start_discovery().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ bind()

virtual int Slic3r::IPrinterAgent::bind ( std::string dev_ip,
std::string dev_id,
std::string dev_model,
std::string sec_link,
std::string timezone,
bool improved,
OnUpdateStatusFn update_fn )
inlinevirtualinherited

Execute the multi-stage printer binding workflow.

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

276 {
277 (void) dev_ip;
278 (void) dev_id;
279 (void) dev_model;
280 (void) sec_link;
281 (void) timezone;
282 (void) improved;
283 (void) update_fn;
285 }

References BAMBU_NETWORK_SUCCESS.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ bind_detect()

int Slic3r::MoonrakerPrinterAgent::bind_detect ( std::string dev_ip,
std::string sec_link,
detectResult & detect )
overridevirtualinherited

Perform binding detection/handshake on a LAN printer.

Implements Slic3r::IPrinterAgent.

262{
263 (void) sec_link;
264
265 detect.dev_id = device_info.dev_id.empty() ? dev_ip : device_info.dev_id;
266 detect.model_id = device_info.model_id.empty() ? device_info.model_name : device_info.model_id;
267 // Prefer fetched hostname, then preset model name, then generic fallback
268 detect.dev_name = device_info.dev_name.empty() ? dev_ip : device_info.dev_name;
269 detect.model_id = device_info.model_id;
270 detect.version = device_info.version;
271 detect.connect_type = "lan";
272 detect.bind_state = "free";
273
275}

References BAMBU_NETWORK_SUCCESS, bind_detect(), Slic3r::detectResult::bind_state, Slic3r::detectResult::connect_type, Slic3r::detectResult::dev_id, Slic3r::detectResult::dev_name, device_info, Slic3r::detectResult::model_id, and Slic3r::detectResult::version.

Referenced by bind_detect().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ build_ams_payload()

void Slic3r::MoonrakerPrinterAgent::build_ams_payload ( int ams_count,
int max_lane_index,
const std::vector< AmsTrayData > & trays )
protectedinherited
473{
474
475 // Look up MachineObject via DeviceManager
476 auto* dev_manager = GUI::wxGetApp().getDeviceManager();
477 if (!dev_manager) {
478 return;
479 }
480 MachineObject* obj = dev_manager->get_my_machine(device_info.dev_id);
481 if (!obj) {
482 return;
483 }
484
485 // Build BBL-format JSON for DevFilaSystemParser::ParseV1_0
486 nlohmann::json ams_json = nlohmann::json::object();
487 nlohmann::json ams_array = nlohmann::json::array();
488
489 // Calculate ams_exist_bits and tray_exist_bits
490 unsigned long ams_exist_bits = 0;
491 unsigned long tray_exist_bits = 0;
492
493 for (int ams_id = 0; ams_id < ams_count; ++ams_id) {
494 ams_exist_bits |= (1 << ams_id);
495
496 nlohmann::json ams_unit = nlohmann::json::object();
497 ams_unit["id"] = std::to_string(ams_id);
498 ams_unit["info"] = "0002"; // treat as AMS_LITE
499
500 nlohmann::json tray_array = nlohmann::json::array();
501 int max_slot_in_this_ams = std::min(3, max_lane_index - ams_id * 4);
502 for (int slot_id = 0; slot_id <= max_slot_in_this_ams; ++slot_id) {
503 int slot_index = ams_id * 4 + slot_id;
504
505 // Find tray with matching slot_index
506 const AmsTrayData* tray = nullptr;
507 for (const auto& t : trays) {
508 if (t.slot_index == slot_index) {
509 tray = &t;
510 break;
511 }
512 }
513
514 nlohmann::json tray_json = nlohmann::json::object();
515 tray_json["id"] = std::to_string(slot_id);
516 tray_json["tag_uid"] = "0000000000000000";
517
518 if (tray && tray->has_filament) {
519 tray_exist_bits |= (1 << slot_index);
520
521 tray_json["tray_info_idx"] = tray->tray_info_idx;
522 tray_json["tray_type"] = tray->tray_type;
523 tray_json["tray_color"] = normalize_color_value(tray->tray_color);
524
525 // Add temperature data if provided
526 if (tray->bed_temp > 0) {
527 tray_json["bed_temp"] = std::to_string(tray->bed_temp);
528 }
529 if (tray->nozzle_temp > 0) {
530 tray_json["nozzle_temp_max"] = std::to_string(tray->nozzle_temp);
531 }
532 } else {
533 tray_json["tray_info_idx"] = "";
534 tray_json["tray_type"] = "";
535 tray_json["tray_color"] = "00000000";
536 tray_json["tray_slot_placeholder"] = "1";
537 }
538
539 tray_array.push_back(tray_json);
540 }
541 ams_unit["tray"] = tray_array;
542 ams_array.push_back(ams_unit);
543 }
544
545 // Format as hex strings (matching BBL protocol)
546 std::ostringstream ams_exist_ss;
547 ams_exist_ss << std::hex << std::uppercase << ams_exist_bits;
548 std::ostringstream tray_exist_ss;
549 tray_exist_ss << std::hex << std::uppercase << tray_exist_bits;
550
551 ams_json["ams"] = ams_array;
552 ams_json["ams_exist_bits"] = ams_exist_ss.str();
553 ams_json["tray_exist_bits"] = tray_exist_ss.str();
554
555 // Wrap in the expected structure for ParseV1_0
556 nlohmann::json print_json = nlohmann::json::object();
557 print_json["ams"] = ams_json;
558
559 // Call the parser to populate DevFilaSystem
560 DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem().get(), false);
561 BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays";
562
563 // Set printer_type so update_sync_status() can match it against the preset's printer type.
564 // Without this, the comparison fails and all sync badges are cleared.
565 obj->printer_type = device_info.model_id;
566
567 // Set push counters so is_info_ready() returns true for pull-mode agents.
568 if (obj->m_push_count == 0) {
569 obj->m_push_count = 1;
570 }
571 if (obj->m_full_msg_count == 0) {
572 obj->m_full_msg_count = 1;
573 }
574 obj->last_push_time = std::chrono::system_clock::now();
575
576 // Set storage state - Moonraker printers use virtual_sdcard, storage is always available.
577 // This is required for SelectMachineDialog to allow printing (otherwise it blocks with "No SD card").
578 obj->GetStorage()->set_sdcard_state(DevStorage::HAS_SDCARD_NORMAL);
579
580 // Populate module_vers so is_info_ready() passes the version check.
581 // Moonraker printers don't have BBL-style version info, but we need a non-empty map.
582 if (obj->module_vers.empty()) {
583 DevFirmwareVersionInfo ota_info;
584 ota_info.name = "ota";
585 ota_info.sw_ver = "1.0.0"; // Placeholder version for Moonraker printers
586 obj->module_vers.emplace("ota", ota_info);
587 }
588}
static void ParseV1_0(const nlohmann::json &print_json, MachineObject *obj, DevFilaSystem *system, bool key_field_only)
Definition DevFilaSystem.cpp:382
@ HAS_SDCARD_NORMAL
Definition DevStorage.h:19
static std::string normalize_color_value(const std::string &color)
Definition MoonrakerPrinterAgent.cpp:731
Definition MoonrakerPrinterAgent.hpp:100

References Slic3r::MoonrakerPrinterAgent::AmsTrayData::bed_temp, build_ams_payload(), device_info, Slic3r::MachineObject::GetFilaSystem(), Slic3r::MachineObject::GetStorage(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::has_filament, Slic3r::DevStorage::HAS_SDCARD_NORMAL, Slic3r::MachineObject::last_push_time, Slic3r::MachineObject::m_full_msg_count, Slic3r::MachineObject::m_push_count, Slic3r::MachineObject::module_vers, Slic3r::DevFirmwareVersionInfo::name, normalize_color_value(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::nozzle_temp, Slic3r::DevFilaSystemParser::ParseV1_0(), Slic3r::MachineObject::printer_type, Slic3r::DevStorage::set_sdcard_state(), Slic3r::DevFirmwareVersionInfo::sw_ver, Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_color, Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_info_idx, and Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_type.

Referenced by build_ams_payload(), Slic3r::CrealityPrintAgent::fetch_filament_info(), fetch_filament_info(), Slic3r::QidiPrinterAgent::fetch_filament_info(), and Slic3r::SnapmakerPrinterAgent::fetch_filament_info().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ build_print_payload_locked()

nlohmann::json Slic3r::MoonrakerPrinterAgent::build_print_payload_locked ( ) const
privateinherited
1922{
1923 nlohmann::json payload;
1924 payload["print"]["command"] = "push_status";
1925 payload["print"]["msg"] = 0;
1926 payload["print"]["support_mqtt_alive"] = true;
1927
1928 std::string state = "IDLE";
1929 if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("state") &&
1930 status_cache["print_stats"]["state"].is_string()) {
1931 state = map_moonraker_state(status_cache["print_stats"]["state"].get<std::string>());
1932 }
1933 payload["print"]["gcode_state"] = state;
1934
1935 // Map Moonraker state to Bambu stage numbers
1936 int mc_print_stage = 0;
1937 if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("state")) {
1938 std::string mr_state = status_cache["print_stats"]["state"].get<std::string>();
1939 if (mr_state == "printing")
1940 mc_print_stage = 1;
1941 else if (mr_state == "paused")
1942 mc_print_stage = 2;
1943 else if (mr_state == "complete")
1944 mc_print_stage = 3;
1945 else if (mr_state == "error")
1946 mc_print_stage = 4;
1947 }
1948 payload["print"]["mc_print_stage"] = mc_print_stage;
1949
1950 // Leave mc_print_error_code and print_error at 0
1951 // UI expects numeric HMS codes - setting to 1 shows generic error dialog
1952 // Only set if real mapping from Moonraker error strings to HMS codes is defined
1953 payload["print"]["mc_print_error_code"] = 0;
1954 payload["print"]["print_error"] = 0;
1955
1956 // Map homed axes to bit field: X=bit0, Y=bit1, Z=bit2
1957 // WARNING: This only sets bits 0-2, clearing support flags (bit 3+)
1958 // Bit 3 = 220V voltage, bit 4 = auto recovery, etc.
1959 // This is acceptable for Moonraker (no AMS, different feature set)
1960 int home_flag = 0;
1961 if (status_cache.contains("toolhead") && status_cache["toolhead"].contains("homed_axes")) {
1962 std::string homed = status_cache["toolhead"]["homed_axes"].get<std::string>();
1963 if (homed.find('X') != std::string::npos)
1964 home_flag |= 1; // bit 0
1965 if (homed.find('Y') != std::string::npos)
1966 home_flag |= 2; // bit 1
1967 if (homed.find('Z') != std::string::npos)
1968 home_flag |= 4; // bit 2
1969 }
1970 payload["print"]["home_flag"] = home_flag;
1971
1972 // Moonraker doesn't provide temperature ranges via API - use hardcoded defaults
1973 payload["print"]["nozzle_temp_range"] = {100, 370}; // Typical Klipper range
1974 payload["print"]["bed_temp_range"] = {0, 120}; // Typical bed range
1975
1976 payload["print"]["support_send_to_sd"] = true;
1977 // Detect bed_leveling support from available objects (bed_mesh or probe)
1978 // Default to 0 (not supported) if neither object exists
1979 bool has_bed_leveling = (available_objects.count("bed_mesh") != 0 || available_objects.count("probe") != 0);
1980 payload["print"]["support_bed_leveling"] = has_bed_leveling ? 1 : 0;
1981
1982 const nlohmann::json* extruder = nullptr;
1983 if (status_cache.contains("extruder") && status_cache["extruder"].is_object()) {
1984 extruder = &status_cache["extruder"];
1985 } else {
1986 for (const auto& item : status_cache.items()) {
1987 if (item.value().is_object() && item.key().rfind("extruder", 0) == 0) {
1988 extruder = &item.value();
1989 break;
1990 }
1991 }
1992 }
1993
1994 if (extruder) {
1995 if (extruder->contains("temperature") && (*extruder)["temperature"].is_number()) {
1996 payload["print"]["nozzle_temper"] = (*extruder)["temperature"].get<float>();
1997 }
1998 if (extruder->contains("target") && (*extruder)["target"].is_number()) {
1999 payload["print"]["nozzle_target_temper"] = (*extruder)["target"].get<float>();
2000 }
2001 }
2002
2003 if (status_cache.contains("heater_bed") && status_cache["heater_bed"].is_object()) {
2004 const auto& bed = status_cache["heater_bed"];
2005 if (bed.contains("temperature") && bed["temperature"].is_number()) {
2006 payload["print"]["bed_temper"] = bed["temperature"].get<float>();
2007 }
2008 if (bed.contains("target") && bed["target"].is_number()) {
2009 payload["print"]["bed_target_temper"] = bed["target"].get<float>();
2010 }
2011 }
2012
2013 // Handle fan speed - only if Moonraker provides "fan" object (standard API)
2014 if (status_cache.contains("fan") && status_cache["fan"].is_object() && !status_cache["fan"].empty()) {
2015 const auto& fan = status_cache["fan"];
2016 if (fan.contains("speed") && fan["speed"].is_number()) {
2017 double speed = fan["speed"].get<double>();
2018 int pwm = 0;
2019 if (speed <= 1.0) {
2020 pwm = static_cast<int>(speed * 255.0 + 0.5);
2021 } else {
2022 pwm = static_cast<int>(speed + 0.5);
2023 }
2024 pwm = std::clamp(pwm, 0, 255);
2025 payload["print"]["fan_gear"] = pwm;
2026 } else if (fan.contains("power") && fan["power"].is_number()) {
2027 double power = fan["power"].get<double>();
2028 int pwm = static_cast<int>(power * 255.0 + 0.5);
2029 pwm = std::clamp(pwm, 0, 255);
2030 payload["print"]["fan_gear"] = pwm;
2031 }
2032 }
2033 // If "fan" object doesn't exist, don't include fan_gear in payload
2034
2035 if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("filename") &&
2036 status_cache["print_stats"]["filename"].is_string()) {
2037 payload["print"]["subtask_name"] = status_cache["print_stats"]["filename"].get<std::string>();
2038 }
2039
2040 if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("filename")) {
2041 payload["print"]["gcode_file"] = status_cache["print_stats"]["filename"];
2042 }
2043
2044 int mc_percent = -1;
2045 if (status_cache.contains("virtual_sdcard") && status_cache["virtual_sdcard"].contains("progress") &&
2046 status_cache["virtual_sdcard"]["progress"].is_number()) {
2047 const double progress = status_cache["virtual_sdcard"]["progress"].get<double>();
2048 if (progress >= 0.0) {
2049 mc_percent = std::clamp(static_cast<int>(progress * 100.0 + 0.5), 0, 100);
2050 }
2051 }
2052 if (mc_percent >= 0) {
2053 payload["print"]["mc_percent"] = mc_percent;
2054 }
2055
2056 if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("total_duration") &&
2057 status_cache["print_stats"].contains("print_duration") && status_cache["print_stats"]["total_duration"].is_number() &&
2058 status_cache["print_stats"]["print_duration"].is_number()) {
2059 const double total = status_cache["print_stats"]["total_duration"].get<double>();
2060 const double elapsed = status_cache["print_stats"]["print_duration"].get<double>();
2061 if (total > 0.0 && elapsed >= 0.0) {
2062 const auto remaining_minutes = std::max(0, static_cast<int>((total - elapsed) / 60.0));
2063 payload["print"]["mc_remaining_time"] = remaining_minutes;
2064 }
2065 }
2066
2067 const auto now_ms = static_cast<uint64_t>(
2068 std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
2069 payload["t_utc"] = now_ms;
2070
2071 return payload;
2072}
nlohmann::json status_cache
Definition MoonrakerPrinterAgent.hpp:213
std::set< std::string > available_objects
Definition MoonrakerPrinterAgent.hpp:216
std::string map_moonraker_state(std::string state)
Definition MoonrakerPrinterAgent.cpp:112

References available_objects, build_print_payload_locked(), anonymous_namespace{MoonrakerPrinterAgent.cpp}::map_moonraker_state(), and status_cache.

Referenced by build_print_payload_locked(), handle_ws_message(), and send_heartbeat_if_due().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ cancel_print()

int Slic3r::MoonrakerPrinterAgent::cancel_print ( const std::string & dev_id)
privateinherited
2192{
2193 send_gcode_async(dev_id, "CANCEL_PRINT");
2194 return BAMBU_NETWORK_SUCCESS;
2195}
void send_gcode_async(const std::string &dev_id, const std::string &gcode, std::function< void(bool)> on_result={}) const
Definition MoonrakerPrinterAgent.cpp:1238

References BAMBU_NETWORK_SUCCESS, cancel_print(), and send_gcode_async().

Referenced by cancel_print(), and handle_request().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_cert()

virtual int Slic3r::IPrinterAgent::check_cert ( )
inlinevirtualinherited

Validate current user certificates for the printer.

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

235{ return BAMBU_NETWORK_SUCCESS; }

References BAMBU_NETWORK_SUCCESS.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ command_ams_calibrate()

virtual int Slic3r::IPrinterAgent::command_ams_calibrate ( std::string ,
int ,
int ,
bool  )
inlinevirtualinherited

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

#define ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED
Definition IPrinterAgent.hpp:10

References ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ command_ams_refresh_rfid()

virtual int Slic3r::IPrinterAgent::command_ams_refresh_rfid ( std::string ,
int ,
int ,
int ,
bool  )
inlinevirtualinherited

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

References ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ command_ams_select_tray()

virtual int Slic3r::IPrinterAgent::command_ams_select_tray ( std::string ,
std::string ,
int ,
bool  )
inlinevirtualinherited

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

References ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ command_auto_leveling()

virtual int Slic3r::IPrinterAgent::command_auto_leveling ( std::string dev_id,
int sequence_id,
bool lan_mode )
inlinevirtualinherited

Reimplemented in Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

141 {
142 nlohmann::json j;
143 j["print"]["command"] = "gcode_line";
144 j["print"]["param"] = "G29 \n";
145 j["print"]["sequence_id"] = std::to_string(sequence_id);
146 return publish_command_json(dev_id, j, lan_mode);
147 }
int publish_command_json(std::string dev_id, const nlohmann::json &j, bool lan_mode)
Definition IPrinterAgent.hpp:125

References publish_command_json().

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ command_axis_control()

virtual int Slic3r::IPrinterAgent::command_axis_control ( std::string dev_id,
std::string axis,
double unit,
double input_val,
int speed,
bool is_core_xy,
bool supports_mqtt_axis_control,
int sequence_id,
bool lan_mode )
inlinevirtualinherited

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

184 {
185 (void) supports_mqtt_axis_control;
186
187 double value = input_val;
188 if (!is_core_xy && (axis == "Y" || axis == "Z"))
189 value = -input_val;
190
191 std::string value_str = (boost::format("%.1f") % (value * unit)).str();
192 std::string gcode;
193 if (axis == "X" || axis == "Y" || axis == "Z") {
194 gcode = (boost::format("G91 \nG1 %1%%2% F%3%\nG90 \n") % axis % value_str % speed).str();
195 } else if (axis == "E") {
196 gcode = (boost::format("M83 \nG0 %1%%2% F%3%\n") % axis % value_str % speed).str();
197 } else {
198 return -1;
199 }
200
201 nlohmann::json j;
202 j["print"]["command"] = "gcode_line";
203 j["print"]["param"] = gcode;
204 j["print"]["sequence_id"] = std::to_string(sequence_id);
205 return publish_command_json(dev_id, j, lan_mode);
206 }
@ gcode
Definition Time.hpp:14

References publish_command_json().

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ command_go_home()

virtual int Slic3r::IPrinterAgent::command_go_home ( std::string dev_id,
bool is_printing,
bool supports_mqtt_homing,
int sequence_id,
bool lan_mode )
inlinevirtualinherited

Reimplemented in Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

149 {
150 nlohmann::json j;
151 j["print"]["sequence_id"] = std::to_string(sequence_id);
152 if (supports_mqtt_homing) {
153 j["print"]["command"] = "back_to_center";
154 } else {
155 j["print"]["command"] = "gcode_line";
156 j["print"]["param"] = is_printing ? "G28 X\n" : "G28 \n";
157 }
158 return publish_command_json(dev_id, j, lan_mode);
159 }

References publish_command_json().

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ command_set_bed()

virtual int Slic3r::IPrinterAgent::command_set_bed ( std::string dev_id,
int temp,
bool supports_mqtt_bed_ctrl,
int sequence_id,
bool lan_mode )
inlinevirtualinherited

Reimplemented in Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

161 {
162 nlohmann::json j;
163 j["print"]["sequence_id"] = std::to_string(sequence_id);
164 if (supports_mqtt_bed_ctrl) {
165 j["print"]["command"] = "set_bed_temp";
166 j["print"]["temp"] = temp;
167 } else {
168 j["print"]["command"] = "gcode_line";
169 j["print"]["param"] = (boost::format("M140 S%1%\n") % temp).str();
170 }
171 return publish_command_json(dev_id, j, lan_mode);
172 }

References publish_command_json().

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ command_set_nozzle()

virtual int Slic3r::IPrinterAgent::command_set_nozzle ( std::string dev_id,
int temp,
int sequence_id,
bool lan_mode )
inlinevirtualinherited

Reimplemented in Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

174 {
175 nlohmann::json j;
176 j["print"]["command"] = "gcode_line";
177 j["print"]["param"] = (boost::format("M104 S%1%\n") % temp).str();
178 j["print"]["sequence_id"] = std::to_string(sequence_id);
179 return publish_command_json(dev_id, j, lan_mode);
180 }

References publish_command_json().

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ command_start_camera()

virtual int Slic3r::IPrinterAgent::command_start_camera ( std::string )
inlinevirtualinherited

Reimplemented in Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

References ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ command_xyz_abs()

virtual int Slic3r::IPrinterAgent::command_xyz_abs ( std::string dev_id,
int sequence_id,
bool lan_mode )
inlinevirtualinherited

Reimplemented in Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

133 {
134 nlohmann::json j;
135 j["print"]["command"] = "gcode_line";
136 j["print"]["param"] = "G90 \n";
137 j["print"]["sequence_id"] = std::to_string(sequence_id);
138 return publish_command_json(dev_id, j, lan_mode);
139 }

References publish_command_json().

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ connect_printer()

int Slic3r::MoonrakerPrinterAgent::connect_printer ( const PrinterConnectionParams & params)
overridevirtualinherited

Establish a direct LAN connection to a printer.

Implements Slic3r::IPrinterAgent.

191{
192 if (params.dev_id.empty() || params.host.empty()) {
193 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing dev_id or dev_ip";
195 }
196
197 std::string base_url;
198 std::string api_key;
199 uint64_t gen;
200 {
201 std::lock_guard<std::recursive_mutex> lock(connect_mutex);
202 init_device_info(params.dev_id, params.host, params.username, params.password, params.use_ssl, params.port);
203 gen = ++connect_generation;
204 base_url = device_info.base_url;
205 api_key = device_info.api_key;
206 }
207
208 // Stop existing status stream and clear state
210 {
211 std::lock_guard<std::recursive_mutex> lock(payload_mutex);
212 status_cache = nlohmann::json::object();
213 }
214 ws_last_emit_ms.store(0);
215 ws_last_dispatch_ms.store(0);
216 last_print_state.clear();
217
218 // Launch connection in background thread (capture by value to avoid data races)
219 {
220 std::lock_guard<std::recursive_mutex> lock(connect_mutex);
221 // Each connection thread joins the one it supersedes, so the destructor's join waits for all of them
222 // Shared, so a failed thread start does not destroy the superseded thread while it is still joinable
223 auto superseded = std::make_shared<std::thread>(std::move(connect_thread));
224 try {
225 connect_thread = std::thread([this, dev_id = params.dev_id, base_url, api_key, gen, superseded]() {
226 perform_connection_async(dev_id, base_url, api_key, gen);
227 if (superseded->joinable()) {
228 superseded->join();
229 }
230 });
231 } catch (...) {
232 connect_thread = std::move(*superseded);
233 throw;
234 }
235 }
236
238}
#define BAMBU_NETWORK_ERR_INVALID_HANDLE
Definition bambu_networking.hpp:17
std::string last_print_state
Definition MoonrakerPrinterAgent.hpp:229
std::atomic< uint64_t > ws_last_emit_ms
Definition MoonrakerPrinterAgent.hpp:220
std::thread connect_thread
Definition MoonrakerPrinterAgent.hpp:233
virtual bool init_device_info(const std::string &dev_id, const std::string &dev_ip, const std::string &username, const std::string &password, bool use_ssl, const std::string &port)
Definition MoonrakerPrinterAgent.cpp:1099
std::recursive_mutex payload_mutex
Definition MoonrakerPrinterAgent.hpp:212
std::atomic< uint64_t > connect_generation
Definition MoonrakerPrinterAgent.hpp:232
std::recursive_mutex connect_mutex
Definition MoonrakerPrinterAgent.hpp:234
std::atomic< uint64_t > ws_last_dispatch_ms
Definition MoonrakerPrinterAgent.hpp:228
void stop_status_stream()
Definition MoonrakerPrinterAgent.cpp:1503

References BAMBU_NETWORK_ERR_INVALID_HANDLE, connect_generation, connect_mutex, connect_thread, Slic3r::PrinterConnectionParams::dev_id, device_info, Slic3r::PrinterConnectionParams::host, init_device_info(), last_print_state, Slic3r::PrinterConnectionParams::password, payload_mutex, Slic3r::PrinterConnectionParams::port, status_cache, stop_status_stream(), Slic3r::PrinterConnectionParams::use_ssl, Slic3r::PrinterConnectionParams::username, ws_last_dispatch_ms, and ws_last_emit_ms.

Here is the call graph for this function:

◆ default_lan_username()

virtual std::string Slic3r::IPrinterAgent::default_lan_username ( ) const
inlinevirtualinherited

Default LAN account username for this agent's protocol, if it has a fixed one. Returns an empty string if the agent has no fixed default (e.g. caller must supply one).

Reimplemented in Slic3r::BBLPrinterAgent.

212{ return {}; }

◆ del_subscribe()

virtual int Slic3r::IPrinterAgent::del_subscribe ( std::vector< std::string > dev_list)
inlinevirtualinherited

Remove device-level subscriptions.

Reimplemented in Slic3r::BBLPrinterAgent.

358{ (void) dev_list; return BAMBU_NETWORK_SUCCESS; }

References BAMBU_NETWORK_SUCCESS.

◆ disconnect_printer()

int Slic3r::MoonrakerPrinterAgent::disconnect_printer ( )
overridevirtualinherited

Tear down the active LAN printer connection.

Implements Slic3r::IPrinterAgent.

241{
242 {
243 std::lock_guard<std::recursive_mutex> lock(connect_mutex);
245 ++connect_generation; // Invalidate any in-flight connection
246 }
247
250}
Definition MoonrakerPrinterAgent.hpp:76

References BAMBU_NETWORK_SUCCESS, connect_generation, connect_mutex, device_info, disconnect_printer(), and stop_status_stream().

Referenced by disconnect_printer().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ dispatch_local_connect()

void Slic3r::MoonrakerPrinterAgent::dispatch_local_connect ( int state,
const std::string & dev_id,
const std::string & msg )
privateinherited
1449{
1450 OnLocalConnectedFn local_fn;
1451 QueueOnMainFn queue_fn;
1452 {
1453 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1454 local_fn = on_local_connect_fn;
1455 queue_fn = queue_on_main_fn;
1456 }
1457 if (!local_fn) {
1458 return;
1459 }
1460
1461 auto dispatch = [state, dev_id, msg, local_fn]() { local_fn(state, dev_id, msg); };
1462 if (queue_fn) {
1463 queue_fn(dispatch);
1464 } else {
1465 dispatch();
1466 }
1467}
QueueOnMainFn queue_on_main_fn
Definition MoonrakerPrinterAgent.hpp:209
OnLocalConnectedFn on_local_connect_fn
Definition MoonrakerPrinterAgent.hpp:207
std::function< void(int status, std::string dev_id, std::string msg)> OnLocalConnectedFn
Definition bambu_networking.hpp:121
std::function< void(std::function< void()>)> QueueOnMainFn
Definition bambu_networking.hpp:136

References dispatch_local_connect(), on_local_connect_fn, queue_on_main_fn, and state_mutex.

Referenced by dispatch_local_connect(), and perform_connection_async().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ dispatch_message()

void Slic3r::MoonrakerPrinterAgent::dispatch_message ( const std::string & dev_id,
const std::string & payload )
privateinherited
2075{
2076 OnMessageFn local_fn;
2077 OnMessageFn cloud_fn;
2078 QueueOnMainFn queue_fn;
2079 {
2080 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2081 local_fn = on_local_message_fn;
2082 cloud_fn = on_message_fn;
2083 queue_fn = queue_on_main_fn;
2084 }
2085
2086 if (!local_fn && !cloud_fn) {
2087 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: dispatch_message - no message callback registered!";
2088 return;
2089 }
2090
2091 auto dispatch = [dev_id, payload, local_fn, cloud_fn]() {
2092 if (local_fn) {
2093 local_fn(dev_id, payload);
2094 return;
2095 }
2096 if (cloud_fn) {
2097 cloud_fn(dev_id, payload);
2098 }
2099 };
2100
2101 if (queue_fn) {
2102 queue_fn(dispatch);
2103 } else {
2104 dispatch();
2105 }
2106}
OnMessageFn on_message_fn
Definition MoonrakerPrinterAgent.hpp:205
OnMessageFn on_local_message_fn
Definition MoonrakerPrinterAgent.hpp:208
std::function< void(std::string dev_id, std::string msg)> OnMessageFn
Definition bambu_networking.hpp:123

References dispatch_message(), on_local_message_fn, on_message_fn, queue_on_main_fn, and state_mutex.

Referenced by dispatch_message(), handle_request(), handle_ws_message(), send_access_code(), send_heartbeat_if_due(), and send_version_info().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ dispatch_printer_connected()

void Slic3r::MoonrakerPrinterAgent::dispatch_printer_connected ( const std::string & dev_id)
privateinherited
1470{
1471 OnPrinterConnectedFn connected_fn;
1472 QueueOnMainFn queue_fn;
1473 {
1474 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1475 connected_fn = on_printer_connected_fn;
1476 queue_fn = queue_on_main_fn;
1477 }
1478 if (!connected_fn) {
1479 return;
1480 }
1481
1482 auto dispatch = [dev_id, connected_fn]() { connected_fn(dev_id); };
1483 if (queue_fn) {
1484 queue_fn(dispatch);
1485 } else {
1486 dispatch();
1487 }
1488}
OnPrinterConnectedFn on_printer_connected_fn
Definition MoonrakerPrinterAgent.hpp:203
std::function< void(std::string topic_str)> OnPrinterConnectedFn
Definition bambu_networking.hpp:120

References dispatch_printer_connected(), on_printer_connected_fn, queue_on_main_fn, and state_mutex.

Referenced by dispatch_printer_connected(), and perform_connection_async().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fetch_device_info()

bool Slic3r::MoonrakerPrinterAgent::fetch_device_info ( const std::string & base_url,
const std::string & api_key,
MoonrakerDeviceInfo & info,
std::string & error,
const std::function< bool()> & cancelled ) const
protectedvirtualinherited
1127{
1128 auto fetch_json = [&](const std::string& url, nlohmann::json& out) {
1129 std::string response_body;
1130 bool success = false;
1131 std::string http_error;
1132
1133 auto http = Http::get(url);
1134 if (!api_key.empty()) {
1135 http.header("X-Api-Key", api_key);
1136 }
1137 http.timeout_connect(5)
1138 .timeout_max(10)
1139 .on_progress(cancel_when(cancelled))
1140 .on_complete([&](std::string body, unsigned status) {
1141 if (status == 200) {
1142 response_body = body;
1143 success = true;
1144 } else {
1145 http_error = "HTTP error: " + std::to_string(status);
1146 }
1147 })
1148 .on_error([&](std::string body, std::string err, unsigned status) {
1149 http_error = err;
1150 if (status > 0) {
1151 http_error += " (HTTP " + std::to_string(status) + ")";
1152 }
1153 })
1154 .perform_sync();
1155
1156 if (!success) {
1157 error = http_error.empty() ? "Connection failed" : http_error;
1158 return false;
1159 }
1160
1161 out = nlohmann::json::parse(response_body, nullptr, false, true);
1162 if (out.is_discarded()) {
1163 error = "Invalid JSON response";
1164 return false;
1165 }
1166 return true;
1167 };
1168
1169 nlohmann::json json;
1170 std::string url = join_url(base_url, "/server/info");
1171 if (!fetch_json(url, json)) {
1172 return false;
1173 }
1174
1175 nlohmann::json result = json.contains("result") ? json["result"] : json;
1176 info.dev_name = result.value("machine_name", result.value("hostname", ""));
1177 info.version = result.value("moonraker_version", "");
1178 info.klippy_state = result.value("klippy_state", "");
1179
1180 return true;
1181}
static Http get(std::string url)
Definition Http.cpp:886
std::string join_url(const std::string &base_url, const std::string &path) const
Definition MoonrakerPrinterAgent.cpp:2331
nlohmann::json json
Definition AssembleList.cpp:42
@ http
Definition IPrinterAgent.hpp:64
Slic3r::Http::ProgressFn cancel_when(std::function< bool()> cancelled)
Definition MoonrakerPrinterAgent.cpp:130

References anonymous_namespace{MoonrakerPrinterAgent.cpp}::cancel_when(), Slic3r::MoonrakerPrinterAgent::MoonrakerDeviceInfo::dev_name, fetch_device_info(), Slic3r::Http::get(), Slic3r::http, join_url(), Slic3r::MoonrakerPrinterAgent::MoonrakerDeviceInfo::klippy_state, and Slic3r::MoonrakerPrinterAgent::MoonrakerDeviceInfo::version.

Referenced by fetch_device_info(), Slic3r::QidiPrinterAgent::fetch_filament_info(), and perform_connection_async().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fetch_filament_info()

bool Slic3r::CrealityPrintAgent::fetch_filament_info ( std::string dev_id,
FilamentSyncMode sync_mode = FilamentSyncMode::pull )
overridevirtual

Refresh filament info from the printer synchronously. Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull. Populates the MachineObject's DevFilaSystem with fetched filament data.

Reimplemented from Slic3r::IPrinterAgent.

292{
293 if (device_info.dev_ip.empty()) {
294 BOOST_LOG_TRIVIAL(warning)
295 << "CrealityPrintAgent::fetch_filament_info: no device IP, falling back to base agent";
296 return MoonrakerPrinterAgent::fetch_filament_info(std::move(dev_id));
297 }
298
299 // Build a CrealityPrint helper so we can use its model detection + WS helpers
300 // (added in upstream PR #13291).
301 DynamicPrintConfig cfg;
302 cfg.set_key_value("print_host", new ConfigOptionString("http://" + device_info.dev_ip));
303 cfg.set_key_value("print_host_webui", new ConfigOptionString(""));
304 cfg.set_key_value("printhost_cafile", new ConfigOptionString(""));
305 cfg.set_key_value("printhost_port", new ConfigOptionString(""));
306 cfg.set_key_value("printhost_apikey", new ConfigOptionString(device_info.api_key));
307 cfg.set_key_value("printhost_ssl_ignore_revoke", new ConfigOptionBool(false));
308
309 CrealityPrint host(&cfg);
310
311 // Defer to base if this isn't a K-series board with CFS firmware support.
312 if (!host.supports_multi_color_print()) {
313 BOOST_LOG_TRIVIAL(info)
314 << "CrealityPrintAgent: " << host.model_name()
315 << " is not CFS-capable, deferring to base Moonraker agent";
316 return MoonrakerPrinterAgent::fetch_filament_info(std::move(dev_id));
317 }
318
319 BOOST_LOG_TRIVIAL(info)
320 << "CrealityPrintAgent: querying CFS slots on " << host.model_name();
321
322 const std::string response = host.query_boxes_info();
323
324 std::vector<CFSSlot> slots;
325 int box_count = 0;
326 std::string parse_err;
327 if (!parse_cfs_response(response, slots, box_count, parse_err)) {
328 BOOST_LOG_TRIVIAL(warning)
329 << "CrealityPrintAgent: CFS query failed (" << parse_err << "), "
330 << "falling back to base agent";
331 return MoonrakerPrinterAgent::fetch_filament_info(std::move(dev_id));
332 }
333
334 if (box_count == 0) {
335 // No active CFS boxes attached — printer is in direct-spool mode. Let the
336 // base agent take over so the user still gets whatever filament info
337 // Moonraker exposes.
338 BOOST_LOG_TRIVIAL(info)
339 << "CrealityPrintAgent: no active CFS boxes, deferring to base agent";
340 return MoonrakerPrinterAgent::fetch_filament_info(std::move(dev_id));
341 }
342
343 BOOST_LOG_TRIVIAL(info)
344 << "CrealityPrintAgent: " << box_count << " CFS box(es), "
345 << slots.size() << " loaded slot(s)";
346
347 // Index loaded slots by (box, slot) for O(1) lookup as we walk the full
348 // box_count * 4 grid, emitting an AmsTrayData entry for each physical slot.
349 std::map<std::pair<int, int>, const CFSSlot*> by_position;
350 for (const auto& s : slots)
351 by_position[{s.box_id, s.slot_id}] = &s;
352
353 auto* bundle = GUI::wxGetApp().preset_bundle;
354
355 const int max_slots = box_count * 4;
356 std::vector<AmsTrayData> trays;
357 trays.reserve(max_slots);
358
359 for (int box = 0; box < box_count; ++box) {
360 for (int idx = 0; idx < 4; ++idx) {
361 AmsTrayData tray;
362 tray.slot_index = box * 4 + idx;
363
364 auto it = by_position.find({box, idx});
365 if (it == by_position.end()) {
366 tray.has_filament = false;
367 trays.push_back(std::move(tray));
368 continue;
369 }
370
371 const CFSSlot& s = *it->second;
372 tray.has_filament = true;
373 tray.tray_type = normalize_filament_type(s.filament_type);
374 tray.tray_color = s.color_hex;
375
376 if (bundle) {
377 tray.tray_info_idx = match_filament_preset(
378 bundle->filaments, s.vendor, s.brand_name, tray.tray_type);
379 }
380
381 trays.push_back(std::move(tray));
382 }
383 }
384
385 build_ams_payload(box_count, max_slots - 1, trays);
386 return true;
387}
static std::string match_filament_preset(const PresetCollection &filaments, const std::string &vendor, const std::string &brand_name, const std::string &base_type)
Definition CrealityPrintAgent.cpp:91
static std::string normalize_filament_type(const std::string &filament_type)
Definition CrealityPrintAgent.cpp:191
static bool parse_cfs_response(const std::string &response, std::vector< CFSSlot > &slots, int &box_count, std::string &error)
Definition CrealityPrintAgent.cpp:212
Definition CrealityPrintAgent.hpp:30
int slot_index
Definition MoonrakerPrinterAgent.hpp:101
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode=FilamentSyncMode::pull) override
Definition MoonrakerPrinterAgent.cpp:590
void build_ams_payload(int ams_count, int max_lane_index, const std::vector< AmsTrayData > &trays)
Definition MoonrakerPrinterAgent.cpp:472
zaa_minimize_perimeter_height ConfigOptionString
Definition PrintConfig.hpp:1702

References Slic3r::CrealityPrintAgent::CFSSlot::brand_name, Slic3r::MoonrakerPrinterAgent::build_ams_payload(), Slic3r::CrealityPrintAgent::CFSSlot::color_hex, Slic3r::ConfigOptionString, Slic3r::MoonrakerPrinterAgent::device_info, Slic3r::MoonrakerPrinterAgent::fetch_filament_info(), Slic3r::CrealityPrintAgent::CFSSlot::filament_type, Slic3r::MoonrakerPrinterAgent::AmsTrayData::has_filament, match_filament_preset(), Slic3r::CrealityPrint::model_name(), normalize_filament_type(), parse_cfs_response(), Slic3r::CrealityPrint::query_boxes_info(), Slic3r::DynamicConfig::set_key_value(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::slot_index, Slic3r::CrealityPrint::supports_multi_color_print(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_color, Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_info_idx, Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_type, and Slic3r::CrealityPrintAgent::CFSSlot::vendor.

Here is the call graph for this function:

◆ fetch_hh_filament_info()

bool Slic3r::MoonrakerPrinterAgent::fetch_hh_filament_info ( std::vector< AmsTrayData > & trays,
int & max_lane_index )
privateinherited
866{
867 // Query Happy Hare MMU status
868 std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu");
869
870 std::string response_body;
871 bool success = false;
872 std::string http_error;
873
874 auto http = Http::get(url);
875 if (!device_info.api_key.empty()) {
876 http.header("X-Api-Key", device_info.api_key);
877 }
878 http.timeout_connect(5)
879 .timeout_max(10)
880 .on_complete([&](std::string body, unsigned status) {
881 if (status == 200) {
882 response_body = body;
883 success = true;
884 } else {
885 http_error = "HTTP error: " + std::to_string(status);
886 }
887 })
888 .on_error([&](std::string body, std::string err, unsigned status) {
889 http_error = err;
890 if (status > 0) {
891 http_error += " (HTTP " + std::to_string(status) + ")";
892 }
893 })
894 .perform_sync();
895
896 if (!success) {
897 BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: Failed to fetch HH data: " << http_error;
898 return false;
899 }
900
901 auto json = nlohmann::json::parse(response_body, nullptr, false, true);
902 if (json.is_discarded()) {
903 BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: Invalid JSON response";
904 return false;
905 }
906
907 // Expected structure: { "result": { "status": { "mmu": { ... } } } }
908 if (!json.contains("result") || !json["result"].contains("status") ||
909 !json["result"]["status"].contains("mmu") || !json["result"]["status"]["mmu"].is_object()) {
910 BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: No mmu object in response";
911 return false;
912 }
913
914 const auto& mmu = json["result"]["status"]["mmu"];
915
916 // Check if HH is installed (empty mmu object means HH not installed)
917 if (mmu.empty()) {
918 BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: Empty mmu object (HH not installed)";
919 return false;
920 }
921
922 // Get num_gates
923 if (!mmu.contains("num_gates") || !mmu["num_gates"].is_number()) {
924 BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent::fetch_hh_filament_info: No num_gates field";
925 return false;
926 }
927
928 int num_gates = mmu["num_gates"].get<int>();
929 if (num_gates <= 0) {
930 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_hh_filament_info: Invalid num_gates: " << num_gates;
931 return false;
932 }
933
934 // Get arrays
935 const auto& gate_status = mmu.contains("gate_status") ? mmu["gate_status"] : nlohmann::json::array();
936 const auto& gate_material = mmu.contains("gate_material") ? mmu["gate_material"] : nlohmann::json::array();
937 const auto& gate_color = mmu.contains("gate_color") ? mmu["gate_color"] : nlohmann::json::array();
938 const auto& gate_temperature = mmu.contains("gate_temperature") ? mmu["gate_temperature"] : nlohmann::json::array();
939
940 if (!gate_status.is_array() || !gate_material.is_array() ||
941 !gate_color.is_array() || !gate_temperature.is_array()) {
942 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_hh_filament_info: HH arrays not found or invalid type";
943 return false;
944 }
945
946 // Parse gate data
947 trays.clear();
948 max_lane_index = 0;
949
950 for (int gate_idx = 0; gate_idx < num_gates; ++gate_idx) {
951 // Check gate_status: -1 = unknown, 0 = empty, 1 or 2 = available
952 int status = safe_array_int(gate_status, gate_idx);
953 if (status <= 0) {
954 continue; // Skip unknown or empty gates
955 }
956
957 // Extract gate data
958 std::string material = safe_array_string(gate_material, gate_idx);
959 std::string color = safe_array_string(gate_color, gate_idx);
960 int nozzle_temp = safe_array_int(gate_temperature, gate_idx);
961
962 // Skip if no material type (empty gate)
963 if (material.empty()) {
964 continue;
965 }
966
967 AmsTrayData tray;
968 tray.slot_index = gate_idx;
969 tray.tray_type = material;
970 tray.tray_color = color;
971 tray.nozzle_temp = nozzle_temp;
972 tray.bed_temp = 0; // HH doesn't provide bed temp in gate arrays
973 tray.has_filament = true;
974
975 auto* bundle = GUI::wxGetApp().preset_bundle;
976 tray.tray_info_idx = bundle
977 ? bundle->filaments.filament_id_by_type(tray.tray_type)
978 : map_filament_type_to_generic_id(tray.tray_type);
979
980 max_lane_index = std::max(max_lane_index, gate_idx);
981 trays.push_back(tray);
982 }
983
984 if (trays.empty()) {
985 BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_hh_filament_info: No valid HH gates found";
986 return false;
987 }
988
989 return true;
990}
static int safe_array_int(const nlohmann::json &arr, int idx)
Definition MoonrakerPrinterAgent.cpp:724
static std::string map_filament_type_to_generic_id(const std::string &filament_type)
Definition MoonrakerPrinterAgent.cpp:629
static std::string safe_array_string(const nlohmann::json &arr, int idx)
Definition MoonrakerPrinterAgent.cpp:717

References Slic3r::MoonrakerPrinterAgent::AmsTrayData::bed_temp, device_info, fetch_hh_filament_info(), Slic3r::Http::get(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::has_filament, Slic3r::http, join_url(), map_filament_type_to_generic_id(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::nozzle_temp, safe_array_int(), safe_array_string(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::slot_index, Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_color, Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_info_idx, and Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_type.

Referenced by fetch_filament_info(), and fetch_hh_filament_info().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fetch_moonraker_filament_data()

bool Slic3r::MoonrakerPrinterAgent::fetch_moonraker_filament_data ( std::vector< AmsTrayData > & trays,
int & max_lane_index )
privateinherited
768{
769 // Fetch lane data from Moonraker database
770 std::string url = join_url(device_info.base_url, "/server/database/item?namespace=lane_data");
771
772 std::string response_body;
773 bool success = false;
774 std::string http_error;
775
776 auto http = Http::get(url);
777 if (!device_info.api_key.empty()) {
778 http.header("X-Api-Key", device_info.api_key);
779 }
780 http.timeout_connect(5)
781 .timeout_max(10)
782 .on_complete([&](std::string body, unsigned status) {
783 if (status == 200) {
784 response_body = body;
785 success = true;
786 } else {
787 http_error = "HTTP error: " + std::to_string(status);
788 }
789 })
790 .on_error([&](std::string body, std::string err, unsigned status) {
791 http_error = err;
792 if (status > 0) {
793 http_error += " (HTTP " + std::to_string(status) + ")";
794 }
795 })
796 .perform_sync();
797
798 if (!success) {
799 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: Failed to fetch lane data: " << http_error;
800 return false;
801 }
802
803 auto json = nlohmann::json::parse(response_body, nullptr, false, true);
804 if (json.is_discarded()) {
805 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: Invalid JSON response";
806 return false;
807 }
808
809 // Expected structure: { "result": { "namespace": "lane_data", "value": { "lane1": {...}, ... } } }
810 if (!json.contains("result") || !json["result"].contains("value") || !json["result"]["value"].is_object()) {
811 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: Unexpected JSON structure or no lane_data found";
812 return false;
813 }
814
815 // Parse response into AmsTrayData
816 const auto& value = json["result"]["value"];
817 trays.clear();
818 max_lane_index = 0;
819
820 for (const auto& [lane_key, lane_obj] : value.items()) {
821 if (!lane_obj.is_object()) {
822 continue;
823 }
824
825 // Extract lane index from the "lane" field (tool number, 0-based)
826 std::string lane_str = safe_json_string(lane_obj, "lane");
827 int lane_index = -1;
828 if (!lane_str.empty()) {
829 try {
830 lane_index = std::stoi(lane_str);
831 } catch (...) {
832 lane_index = -1;
833 }
834 }
835
836 if (lane_index < 0) {
837 continue;
838 }
839
840 AmsTrayData tray;
841 tray.slot_index = lane_index;
842 tray.tray_color = safe_json_string(lane_obj, "color");
843 tray.tray_type = safe_json_string(lane_obj, "material");
844 tray.bed_temp = safe_json_int(lane_obj, "bed_temp");
845 tray.nozzle_temp = safe_json_int(lane_obj, "nozzle_temp");
846 tray.has_filament = !tray.tray_type.empty();
847 auto* bundle = GUI::wxGetApp().preset_bundle;
848 tray.tray_info_idx = bundle
849 ? bundle->filaments.filament_id_by_type(tray.tray_type)
850 : map_filament_type_to_generic_id(tray.tray_type);
851
852 max_lane_index = std::max(max_lane_index, lane_index);
853 trays.push_back(tray);
854 }
855
856 if (trays.empty()) {
857 BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: No lanes found";
858 return false;
859 }
860
861 return true;
862}
static std::string safe_json_string(const nlohmann::json &obj, const char *key)
Definition MoonrakerPrinterAgent.cpp:701
static int safe_json_int(const nlohmann::json &obj, const char *key)
Definition MoonrakerPrinterAgent.cpp:709

References Slic3r::MoonrakerPrinterAgent::AmsTrayData::bed_temp, device_info, fetch_moonraker_filament_data(), Slic3r::Http::get(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::has_filament, Slic3r::http, join_url(), map_filament_type_to_generic_id(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::nozzle_temp, safe_json_int(), safe_json_string(), Slic3r::MoonrakerPrinterAgent::AmsTrayData::slot_index, Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_color, Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_info_idx, and Slic3r::MoonrakerPrinterAgent::AmsTrayData::tray_type.

Referenced by fetch_filament_info(), and fetch_moonraker_filament_data().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fetch_object_list()

bool Slic3r::MoonrakerPrinterAgent::fetch_object_list ( const std::string & base_url,
const std::string & api_key,
std::set< std::string > & objects,
std::string & error ) const
privateinherited
1322{
1323 std::string response_body;
1324 bool success = false;
1325 std::string http_error;
1326
1327 auto http = Http::get(join_url(base_url, "/printer/objects/list"));
1328 if (!api_key.empty()) {
1329 http.header("X-Api-Key", api_key);
1330 }
1331 http.timeout_connect(5)
1332 .timeout_max(10)
1333 // Gives up once the status stream, the only caller, stops
1334 .on_progress(cancel_when([this] { return ws_stop.load(); }))
1335 .on_complete([&](std::string body, unsigned status) {
1336 if (status == 200) {
1337 response_body = body;
1338 success = true;
1339 } else {
1340 http_error = "HTTP error: " + std::to_string(status);
1341 }
1342 })
1343 .on_error([&](std::string body, std::string err, unsigned status) {
1344 http_error = err;
1345 if (status > 0) {
1346 http_error += " (HTTP " + std::to_string(status) + ")";
1347 }
1348 })
1349 .perform_sync();
1350
1351 if (!success) {
1352 error = http_error.empty() ? "Connection failed" : http_error;
1353 return false;
1354 }
1355
1356 auto json = nlohmann::json::parse(response_body, nullptr, false, true);
1357 if (json.is_discarded()) {
1358 error = "Invalid JSON response";
1359 return false;
1360 }
1361
1362 nlohmann::json result = json.contains("result") ? json["result"] : json;
1363 if (!result.contains("objects") || !result["objects"].is_array()) {
1364 error = "Unexpected JSON structure";
1365 return false;
1366 }
1367
1368 objects.clear();
1369 for (const auto& entry : result["objects"]) {
1370 if (entry.is_string()) {
1371 objects.insert(entry.get<std::string>());
1372 }
1373 }
1374
1375 return !objects.empty();
1376}
std::atomic< bool > ws_stop
Definition MoonrakerPrinterAgent.hpp:218

References anonymous_namespace{MoonrakerPrinterAgent.cpp}::cancel_when(), fetch_object_list(), Slic3r::Http::get(), Slic3r::http, join_url(), and ws_stop.

Referenced by fetch_object_list(), and run_status_stream().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ from_orca_filament_id()

virtual std::string Slic3r::IPrinterAgent::from_orca_filament_id ( const std::string & orca_filament_id) const
inlinevirtualinherited

Reimplemented in Slic3r::BBLPrinterAgent.

472{ return orca_filament_id; }

◆ get_agent_info()

AgentInfo Slic3r::CrealityPrintAgent::get_agent_info ( )
inlineoverridevirtual

Get agent information.

Implements Slic3r::IPrinterAgent.

43{ return get_agent_info_static(); }
static AgentInfo get_agent_info_static()
Definition CrealityPrintAgent.cpp:181

References get_agent_info_static().

Here is the call graph for this function:

◆ get_agent_info_static()

AgentInfo Slic3r::CrealityPrintAgent::get_agent_info_static ( )
static
182{
183 return AgentInfo{
184 "crealityprint",
185 "CrealityPrint",
187 "Creality K-series printer agent (CFS-aware filament sync)"
188 };
189}
constexpr const char * CrealityPrintAgent_VERSION
Definition CrealityPrintAgent.cpp:28

Referenced by get_agent_info().

Here is the caller graph for this function:

◆ get_camera_stream_mode()

virtual CameraStreamMode Slic3r::IPrinterAgent::get_camera_stream_mode ( ) const
inlinevirtualinherited

Get the camera stream mode for this agent. This value can be deterministic and derived at runtime if the printer supports multiple camera stream modes. E.g. LAN => HTTP/HTTPS/RTSP, Cloud => WebRTC.

Returns
CameraStreamMode indicating how the camera stream is obtained or used:

Reimplemented in Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

455{ return CameraStreamMode::none; }
@ none
Definition IPrinterAgent.hpp:63

References Slic3r::none.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ get_camera_url()

virtual std::string Slic3r::IPrinterAgent::get_camera_url ( ) const
inlinevirtualinherited

Get the current camera stream URL for this agent's active machine. Only meaningful when get_camera_stream_mode() returns an HTTP, HTTPS, or RTSP mode.

Reimplemented in Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

478{ return {}; }

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ get_filament_sync_mode()

FilamentSyncMode Slic3r::MoonrakerPrinterAgent::get_filament_sync_mode ( ) const
inlineoverridevirtualinherited

Get the filament synchronization mode for this agent.

Returns
FilamentSyncMode indicating how filament data is obtained:
  • subscription: Real-time push updates via MQTT (no fetch needed)
  • pull: On-demand fetch via REST API (call fetch_filament_info())
  • none: Filament synchronization not supported

Reimplemented from Slic3r::IPrinterAgent.

71{ return FilamentSyncMode::pull; }
@ pull
On-demand fetch via REST API (blocking call).
Definition IPrinterAgent.hpp:59

References Slic3r::pull.

◆ get_hms_snapshot()

virtual int Slic3r::IPrinterAgent::get_hms_snapshot ( std::string dev_id,
std::string file_name,
std::function< void(std::string, int)> callback )
inlinevirtualinherited

Fetch the cloud snapshot image captured at a print failure. Returns 0 if the request was dispatched; the image body arrives via callback(body, http_status).

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

312 {
313 (void) dev_id;
314 (void) file_name;
315 (void) callback;
316 return -1;
317 }

◆ get_user_selected_machine()

std::string Slic3r::MoonrakerPrinterAgent::get_user_selected_machine ( )
overridevirtualinherited

Return the currently selected printer ID.

Implements Slic3r::IPrinterAgent.

285{
286 std::lock_guard<std::recursive_mutex> lock(state_mutex);
287 return selected_machine;
288}

References get_user_selected_machine(), selected_machine, and state_mutex.

Referenced by get_user_selected_machine().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ handle_request()

int Slic3r::MoonrakerPrinterAgent::handle_request ( const std::string & dev_id,
const std::string & json_str )
privateinherited
993{
994 auto json = nlohmann::json::parse(json_str, nullptr, false);
995 if (json.is_discarded()) {
996 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Invalid JSON request";
998 }
999
1000 // Handle info commands
1001 if (json.contains("info") && json["info"].contains("command")) {
1002 const auto& command = json["info"]["command"];
1003 if (command.is_string() && command.get<std::string>() == "get_version") {
1004 return send_version_info(dev_id);
1005 }
1006 }
1007
1008 // Handle system commands
1009 if (json.contains("system") && json["system"].contains("command")) {
1010 const auto& command = json["system"]["command"];
1011 if (command.is_string() && command.get<std::string>() == "get_access_code") {
1012 return send_access_code(dev_id);
1013 }
1014 }
1015
1016 // Handle print commands
1017 if (json.contains("print") && json["print"].contains("command")) {
1018 const auto& command = json["print"]["command"];
1019 if (!command.is_string()) {
1020 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: print command is not a string";
1022 }
1023
1024 const std::string cmd = command.get<std::string>();
1025
1026 // Handle gcode_line command - this is how G-code commands are sent from OrcaSlicer
1027 if (cmd == "gcode_line") {
1028 if (!json["print"].contains("param") || !json["print"]["param"].is_string()) {
1029 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: gcode_line missing param value, full json: " << json_str;
1031 }
1032 std::string gcode = json["print"]["param"].get<std::string>();
1033
1034 // Extract sequence_id from request if present
1035 std::string sequence_id;
1036 if (json["print"].contains("sequence_id") && json["print"]["sequence_id"].is_string()) {
1037 sequence_id = json["print"]["sequence_id"].get<std::string>();
1038 }
1039
1040 nlohmann::json response;
1041 response["print"]["command"] = "gcode_line";
1042 if (!sequence_id.empty()) {
1043 response["print"]["sequence_id"] = sequence_id;
1044 }
1045 response["print"]["param"] = gcode;
1046
1047 send_gcode_async(dev_id, gcode, [this, dev_id, response](bool success) mutable {
1048 response["print"]["result"] = success ? "success" : "failed";
1049 dispatch_message(dev_id, response.dump());
1050 });
1051 return BAMBU_NETWORK_SUCCESS;
1052 }
1053
1054 // Print control commands
1055 if (cmd == "pause") {
1056 return pause_print(dev_id);
1057 }
1058 if (cmd == "resume") {
1059 return resume_print(dev_id);
1060 }
1061 if (cmd == "stop") {
1062 return cancel_print(dev_id);
1063 }
1064
1065 // Bed temperature - UI sends "temp" field
1066 if (cmd == "set_bed_temp") {
1067 if (json["print"].contains("temp") && json["print"]["temp"].is_number()) {
1068 int temp = json["print"]["temp"].get<int>();
1069 std::string gcode = "SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=" + std::to_string(temp);
1070 send_gcode_async(dev_id, gcode);
1071 return BAMBU_NETWORK_SUCCESS;
1072 }
1073 }
1074
1075 // Nozzle temperature - UI sends "target_temp" and "extruder_index" fields
1076 if (cmd == "set_nozzle_temp") {
1077 if (json["print"].contains("target_temp") && json["print"]["target_temp"].is_number()) {
1078 int temp = json["print"]["target_temp"].get<int>();
1079 int extruder_idx = 0; // Default to main extruder
1080 if (json["print"].contains("extruder_index") && json["print"]["extruder_index"].is_number()) {
1081 extruder_idx = json["print"]["extruder_index"].get<int>();
1082 }
1083 std::string heater = (extruder_idx == 0) ? "extruder" : "extruder" + std::to_string(extruder_idx);
1084 std::string gcode = "SET_HEATER_TEMPERATURE HEATER=" + heater + " TARGET=" + std::to_string(temp);
1085 send_gcode_async(dev_id, gcode);
1086 return BAMBU_NETWORK_SUCCESS;
1087 }
1088 }
1089
1090 if (cmd == "home") {
1091 send_gcode_async(dev_id, "G28");
1092 return BAMBU_NETWORK_SUCCESS;
1093 }
1094 }
1095
1096 return BAMBU_NETWORK_SUCCESS;
1097}
#define BAMBU_NETWORK_ERR_INVALID_RESULT
Definition bambu_networking.hpp:35
void dispatch_message(const std::string &dev_id, const std::string &payload)
Definition MoonrakerPrinterAgent.cpp:2074
int resume_print(const std::string &dev_id)
Definition MoonrakerPrinterAgent.cpp:2185
int send_access_code(const std::string &dev_id)
Definition MoonrakerPrinterAgent.cpp:1395
int pause_print(const std::string &dev_id)
Definition MoonrakerPrinterAgent.cpp:2179
int cancel_print(const std::string &dev_id)
Definition MoonrakerPrinterAgent.cpp:2191
int send_version_info(const std::string &dev_id)
Definition MoonrakerPrinterAgent.cpp:1378
bool contains(const std::vector< T > &vector, const Point &point)
Definition Geometry.cpp:55
std::string to_string(const Vec2crd &pt)
Definition Point.hpp:165

References BAMBU_NETWORK_ERR_INVALID_RESULT, BAMBU_NETWORK_SUCCESS, cancel_print(), Slic3r::Geometry::contains(), dispatch_message(), handle_request(), pause_print(), resume_print(), send_access_code(), send_gcode_async(), and send_version_info().

Referenced by handle_request(), send_message(), and send_message_to_printer().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ handle_ws_message()

void Slic3r::MoonrakerPrinterAgent::handle_ws_message ( const std::string & dev_id,
const std::string & payload )
privateinherited
1786{
1787 auto json = nlohmann::json::parse(payload, nullptr, false);
1788 if (json.is_discarded()) {
1789 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Invalid WebSocket message JSON";
1790 return;
1791 }
1792
1793 bool updated = false;
1794 bool is_critical = false; // Track if this is a critical update that bypasses throttle
1795
1796 // Check for subscription response (has "result.status") - initial subscription is critical
1797 if (json.contains("result") && json["result"].contains("status") && json["result"]["status"].is_object()) {
1798 update_status_cache(json["result"]["status"]);
1799 updated = true;
1800 is_critical = true; // Initial subscription response - dispatch immediately
1801 }
1802
1803 // Check for status update notifications
1804 if (json.contains("method") && json["method"].is_string()) {
1805 const std::string method = json["method"].get<std::string>();
1806 if (method == "notify_status_update" && json.contains("params") && json["params"].is_array() && !json["params"].empty() &&
1807 json["params"][0].is_object()) {
1808 update_status_cache(json["params"][0]);
1809 updated = true;
1810 // Note: is_critical stays false for regular status updates (telemetry)
1811 } else if (method == "notify_klippy_ready") {
1812 nlohmann::json updates;
1813 updates["print_stats"]["state"] = "standby";
1814 update_status_cache(updates);
1815 updated = true;
1816 is_critical = true; // Klippy events are critical
1817 } else if (method == "notify_klippy_shutdown") {
1818 nlohmann::json updates;
1819 updates["print_stats"]["state"] = "error";
1820 update_status_cache(updates);
1821 updated = true;
1822 is_critical = true; // Klippy events are critical
1823 }
1824 // Handle Klippy disconnect - update status and trigger reconnection
1825 else if (method == "notify_klippy_disconnected") {
1826 // Klippy disconnected - update status to reflect disconnect state
1827 nlohmann::json updates;
1828 updates["print_stats"]["state"] = "error";
1829 update_status_cache(updates);
1830 updated = true;
1831 is_critical = true; // Klippy events are critical
1832 // Set flag to trigger reconnection after dispatching the status update
1833 ws_reconnect_requested.store(true);
1834 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Klippy disconnected, triggering reconnection";
1835 }
1836 }
1837
1838 // Check for print state changes (critical - always dispatch immediately)
1839 if (updated && !is_critical) {
1840 std::string current_state;
1841 {
1842 std::lock_guard<std::recursive_mutex> lock(payload_mutex);
1843 if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("state") &&
1844 status_cache["print_stats"]["state"].is_string()) {
1845 current_state = status_cache["print_stats"]["state"].get<std::string>();
1846 }
1847 }
1848
1849 if (!current_state.empty() && current_state != last_print_state) {
1850 is_critical = true;
1851 last_print_state = current_state;
1852 }
1853 }
1854
1855 if (updated) {
1856 const auto now_ms = steady_now_ms();
1857 const auto last_dispatch_ms = ws_last_dispatch_ms.load();
1858
1859 // Dispatch if: critical change OR throttle interval elapsed
1860 const bool should_dispatch = is_critical || last_dispatch_ms == 0 || now_ms - last_dispatch_ms >= STATUS_UPDATE_INTERVAL_MS;
1861
1862 if (should_dispatch) {
1863 nlohmann::json message;
1864 {
1865 std::lock_guard<std::recursive_mutex> lock(payload_mutex);
1866 message = build_print_payload_locked();
1867 }
1868
1869 dispatch_message(dev_id, message.dump());
1870 ws_last_dispatch_ms.store(now_ms);
1871 ws_last_emit_ms.store(now_ms); // Also update heartbeat timer
1872 }
1873 // else: skip dispatch, cache is updated for next dispatch cycle
1874 }
1875}
static constexpr uint64_t STATUS_UPDATE_INTERVAL_MS
Definition MoonrakerPrinterAgent.hpp:227
nlohmann::json build_print_payload_locked() const
Definition MoonrakerPrinterAgent.cpp:1921
std::atomic< bool > ws_reconnect_requested
Definition MoonrakerPrinterAgent.hpp:219
void update_status_cache(const nlohmann::json &updates)
Definition MoonrakerPrinterAgent.cpp:1895
uint64_t steady_now_ms()
Definition MoonrakerPrinterAgent.cpp:138

References build_print_payload_locked(), dispatch_message(), handle_ws_message(), last_print_state, payload_mutex, status_cache, STATUS_UPDATE_INTERVAL_MS, anonymous_namespace{MoonrakerPrinterAgent.cpp}::steady_now_ms(), update_status_cache(), ws_last_dispatch_ms, ws_last_emit_ms, and ws_reconnect_requested.

Referenced by handle_ws_message(), and run_status_stream().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ init_device_info()

bool Slic3r::MoonrakerPrinterAgent::init_device_info ( const std::string & dev_id,
const std::string & dev_ip,
const std::string & username,
const std::string & password,
bool use_ssl,
const std::string & port )
protectedvirtualinherited
1100{
1102 auto* preset_bundle = GUI::wxGetApp().preset_bundle;
1103 if (!preset_bundle) {
1104 return false;
1105 }
1106
1107 auto& preset = preset_bundle->printers.get_edited_preset();
1108 const auto& printer_cfg = preset.config;
1109
1110 device_info.dev_ip = dev_ip;
1111 device_info.api_key = password;
1112 device_info.model_name = printer_cfg.opt_string("printer_model");
1113 device_info.model_id = preset.get_printer_type(preset_bundle);
1114 device_info.base_url = normalize_base_url(use_ssl, dev_ip, port);
1115 device_info.dev_id = dev_id;
1116 device_info.version = "";
1117 device_info.dev_name = device_info.dev_id;
1118
1119 return true;
1120}
std::string normalize_base_url(bool use_ssl, const std::string &host, const std::string &port)
Definition MoonrakerPrinterAgent.cpp:2323

References device_info, init_device_info(), and normalize_base_url().

Referenced by connect_printer(), and init_device_info().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ install_device_cert()

virtual void Slic3r::IPrinterAgent::install_device_cert ( std::string dev_id,
bool lan_only )
inlinevirtualinherited

Install or refresh device certificate for LAN TLS.

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

241 {
242 (void) dev_id;
243 (void) lan_only;
244 }

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ is_numeric()

bool Slic3r::MoonrakerPrinterAgent::is_numeric ( const std::string & value)
protectedinherited
2319{
2320 return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; });
2321}

References is_numeric().

Referenced by Slic3r::QidiPrinterAgent::fetch_slot_info(), Slic3r::QidiPrinterAgent::infer_series_id(), and is_numeric().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ join_stopped_status_stream()

void Slic3r::MoonrakerPrinterAgent::join_stopped_status_stream ( )
privateinherited
1523{
1524 std::thread stopped;
1525 {
1526 std::lock_guard<std::mutex> lock(ws_abort_mutex);
1527 // A running stream belongs to a newer connection, so only one that is already stopping is joined
1528 if (ws_stop.load() && ws_thread.get_id() != std::this_thread::get_id()) {
1529 stopped = std::move(ws_thread);
1530 }
1531 }
1532 if (stopped.joinable()) {
1533 stopped.join();
1534 }
1535}
std::thread ws_thread
Definition MoonrakerPrinterAgent.hpp:221
std::mutex ws_abort_mutex
Definition MoonrakerPrinterAgent.hpp:222

References join_stopped_status_stream(), ws_abort_mutex, ws_stop, and ws_thread.

Referenced by join_stopped_status_stream(), and perform_connection_async().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ join_url()

std::string Slic3r::MoonrakerPrinterAgent::join_url ( const std::string & base_url,
const std::string & path ) const
protectedinherited
2332{
2333 if (base_url.empty()) {
2334 return "";
2335 }
2336 if (path.empty()) {
2337 return base_url;
2338 }
2339 if (base_url.back() == '/' && path.front() == '/') {
2340 return base_url.substr(0, base_url.size() - 1) + path;
2341 }
2342 if (base_url.back() != '/' && path.front() != '/') {
2343 return base_url + "/" + path;
2344 }
2345 return base_url + path;
2346}

References join_url().

Referenced by fetch_device_info(), Slic3r::QidiPrinterAgent::fetch_filament_dict(), Slic3r::SnapmakerPrinterAgent::fetch_filament_info(), fetch_hh_filament_info(), fetch_moonraker_filament_data(), fetch_object_list(), Slic3r::QidiPrinterAgent::fetch_slot_info(), join_url(), query_printer_status(), send_gcode_async(), send_gcode_sync(), send_jsonrpc_command(), and upload_gcode().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ map_filament_type_to_generic_id()

std::string Slic3r::MoonrakerPrinterAgent::map_filament_type_to_generic_id ( const std::string & filament_type)
staticprotectedinherited
630{
631 const std::string upper = trim_and_upper(filament_type);
632
633 // Normalize reported material names (trimmed, uppercased) to an OrcaFilamentLibrary
634 // generic family. The family's filament_id is resolved from the loaded system presets
635 // below rather than hardcoded, so profile id re-mints never require touching this table.
636 // scripts/test_moonraker_lane_data.py parses this initializer; keep the {"A", "B"} format.
637 static const std::map<std::string, std::string> type_to_ofl_family = {
638 // PLA variants
639 {"PLA", "PLA"},
640 {"PLA-CF", "PLA-CF"},
641 {"PLA SILK", "PLA Silk"},
642 {"PLA-SILK", "PLA Silk"},
643 {"PLA HIGH SPEED", "PLA High Speed"},
644 {"PLA-HS", "PLA High Speed"},
645 {"PLA HS", "PLA High Speed"},
646
647 // ABS/ASA variants
648 {"ABS", "ABS"},
649 {"ASA", "ASA"},
650
651 // PETG/PET variants
652 {"PETG", "PETG"},
653 {"PET", "PETG"},
654 {"PCTG", "PCTG"},
655
656 // PA/Nylon variants
657 {"PA", "PA"},
658 {"NYLON", "PA"},
659 {"PA-CF", "PA-CF"},
660 {"PPA", "PPA-CF"},
661 {"PPA-CF", "PPA-CF"},
662 {"PPA-GF", "PPA-GF"},
663
664 // PC variants
665 {"PC", "PC"},
666
667 // PP/PE variants
668 {"PE", "PE"},
669 {"PP", "PP"},
670
671 // Support materials
672 {"PVA", "PVA"},
673 {"HIPS", "HIPS"},
674 {"BVOH", "BVOH"},
675
676 // TPU variants
677 {"TPU", "TPU"},
678
679 // Other materials
680 {"EVA", "EVA"},
681 {"PHA", "PHA"},
682 {"COPE", "CoPE"},
683 {"SBS", "SBS"},
684 };
685
686 auto it = type_to_ofl_family.find(upper);
687 if (it == type_to_ofl_family.end())
688 return UNKNOWN_FILAMENT_ID;
689
690 if (auto* bundle = GUI::wxGetApp().preset_bundle) {
691 const Preset* preset = bundle->filaments.find_preset("Generic " + it->second + " @System");
692 if (preset != nullptr && preset->is_system && !preset->filament_id.empty())
693 return preset->filament_id;
694 }
695
696 // Unknown material, or no loaded preset data to resolve against
697 return UNKNOWN_FILAMENT_ID;
698}
#define UNKNOWN_FILAMENT_ID
Definition Preset.hpp:72
static std::string trim_and_upper(const std::string &input)
Definition MoonrakerPrinterAgent.cpp:620
zaa_minimize_perimeter_height filament_type((ConfigOptionBools, filament_soluble))((ConfigOptionStrings
@ Preset
Definition calib.hpp:39

References Slic3r::Preset::filament_id, Slic3r::filament_type(), Slic3r::Preset::is_system, map_filament_type_to_generic_id(), trim_and_upper(), and UNKNOWN_FILAMENT_ID.

Referenced by Slic3r::SnapmakerPrinterAgent::fetch_filament_info(), fetch_hh_filament_info(), fetch_moonraker_filament_data(), and map_filament_type_to_generic_id().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ match_filament_preset()

std::string Slic3r::CrealityPrintAgent::match_filament_preset ( const PresetCollection & filaments,
const std::string & vendor,
const std::string & brand_name,
const std::string & base_type )
static
95{
96 const std::string vendor_lower = to_lower(vendor);
97 const std::string brand_lower = to_lower(brand_name);
98 const std::string type_lower = to_lower(base_type);
99
100 // Everything the spool told us about itself, as words. A word in a preset's name
101 // that is not in here is a qualifier the spool never claimed.
102 std::set<std::string> spool_words{"generic"};
103 for (const std::string& src : {brand_lower, vendor_lower, type_lower})
104 for (auto& w : name_words(src))
105 spool_words.insert(std::move(w));
106
107 struct Match {
108 const Preset* preset;
109 int score;
110 bool is_user;
111 };
112 std::vector<Match> matches;
113
114 int considered = 0;
115 for (const auto& p : filaments.get_presets()) {
116 if (!p.is_visible || !p.is_compatible) continue;
117 // Note: we deliberately do NOT filter on get_preset_base(p) == &p.
118 // K2 owners frequently keep tweaked copies of system presets
119 // (e.g. "Creality Hyper PLA @K2 (Harky)" with their per-spool PA),
120 // which are derived presets — filtering to bases-only would skip
121 // exactly the presets users care about most.
122 ++considered;
123
124 std::string preset_type;
125 if (const auto* ft = p.config.option<ConfigOptionStrings>("filament_type"))
126 if (!ft->values.empty()) preset_type = ft->values.front();
127 if (to_lower(preset_type) != type_lower) continue;
128
129 const std::string name_lower = to_lower(p.name);
130 int score = 0;
131 if (!brand_lower.empty() && name_lower.find(brand_lower) != std::string::npos)
132 score += 20;
133 // Profile OR name - neither alone covers both the vendor's own generics and the
134 // third party filaments shipped inside its bundle. See the header comment.
135 if (!vendor_lower.empty()
136 && ((p.vendor != nullptr && to_lower(p.vendor->name) == vendor_lower)
137 || name_lower.find(vendor_lower) != std::string::npos))
138 score += 10;
139
140 if (score == 0) continue;
141
142 for (const auto& w : name_words(p.name))
143 if (spool_words.count(w) == 0)
144 score -= 5;
145
146 matches.push_back({&p, score, !p.is_system && !p.is_default});
147 }
148
149 if (matches.empty()) {
150 const std::string fallback = filaments.filament_id_by_type(base_type);
151 const bool fallback_ok = has_visible_base_preset(filaments, fallback);
152 BOOST_LOG_TRIVIAL(info)
153 << "CrealityPrintAgent: no preset scored for spool {" << vendor << " "
154 << brand_name << " (" << base_type << ")} after considering " << considered
155 << " presets; falling back to generic preset id \"" << fallback << "\""
156 << (fallback_ok ? "" : " (NOT visible — returning empty)");
157 return fallback_ok ? fallback : std::string();
158 }
159
160 std::sort(matches.begin(), matches.end(),
161 [](const Match& a, const Match& b) {
162 if (a.score != b.score) return a.score > b.score;
163 if (a.is_user != b.is_user) return !a.is_user; // prefer system over user
164 return a.preset->name < b.preset->name; // keep the winner deterministic
165 });
166
167 BOOST_LOG_TRIVIAL(info)
168 << "CrealityPrintAgent: matched spool {" << vendor << " " << brand_name
169 << " (" << base_type << ")} -> preset \"" << matches.front().preset->name
170 << "\" (score=" << matches.front().score
171 << ", " << matches.size() << " candidate(s) of " << considered << " considered)";
172
173 return matches.front().preset->filament_id;
174}
std::vector< std::string > name_words(const std::string &name)
Definition CrealityPrintAgent.cpp:49
bool has_visible_base_preset(const PresetCollection &filaments, const std::string &filament_id)
Definition CrealityPrintAgent.cpp:36
std::string to_lower(std::string s)
Definition CrealityPrintAgent.cpp:30

References Slic3r::PresetCollection::filament_id_by_type(), and Slic3r::PresetCollection::get_presets().

Referenced by fetch_filament_info().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ normalize_base_url()

std::string Slic3r::MoonrakerPrinterAgent::normalize_base_url ( bool use_ssl,
const std::string & host,
const std::string & port )
protectedinherited
2324{
2325 std::string value = use_ssl ? "https://" : "http://";
2326 value += host;
2327 value += port.empty() ? "" : (":" + port);
2328 return value;
2329}

References normalize_base_url().

Referenced by init_device_info(), and normalize_base_url().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ normalize_color_value()

std::string Slic3r::MoonrakerPrinterAgent::normalize_color_value ( const std::string & color)
staticprivateinherited
732{
733 std::string value = color;
734 boost::trim(value);
735
736 // Remove 0x or 0X prefix if present
737 if (value.size() >= 2 && (value.rfind("0x", 0) == 0 || value.rfind("0X", 0) == 0)) {
738 value = value.substr(2);
739 }
740 // Remove # prefix if present
741 if (!value.empty() && value[0] == '#') {
742 value = value.substr(1);
743 }
744
745 // Extract only hex digits
746 std::string normalized;
747 for (char c : value) {
748 if (std::isxdigit(static_cast<unsigned char>(c))) {
749 normalized.push_back(static_cast<char>(std::toupper(static_cast<unsigned char>(c))));
750 }
751 }
752
753 // If 6 hex digits, add FF alpha
754 if (normalized.size() == 6) {
755 normalized += "FF";
756 }
757
758 // Validate length - return default if invalid
759 if (normalized.size() != 8) {
760 return "00000000";
761 }
762
763 return normalized;
764}

References normalize_color_value().

Referenced by build_ams_payload(), and normalize_color_value().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ normalize_filament_type()

std::string Slic3r::CrealityPrintAgent::normalize_filament_type ( const std::string & filament_type)
static
192{
193 static const std::vector<std::string> bases = {
194 "PETG", "PET", "PLA", "ABS", "ASA", "TPU", "PC", "PA", "PVA", "HIPS"
195 };
196 for (const auto& base : bases) {
197 if (filament_type.rfind(base, 0) == 0) return base;
198 }
199 return filament_type;
200}

References Slic3r::filament_type().

Referenced by fetch_filament_info().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parse_cfs_response()

bool Slic3r::CrealityPrintAgent::parse_cfs_response ( const std::string & response,
std::vector< CFSSlot > & slots,
int & box_count,
std::string & error )
static
216{
217
218 slots.clear();
219 box_count = 0;
220
221 if (response.empty()) {
222 error = "empty response";
223 return false;
224 }
225
226 json resp;
227 try {
228 resp = json::parse(response);
229 } catch (const std::exception& e) {
230 error = std::string("JSON parse error: ") + e.what();
231 return false;
232 }
233
234 if (!resp.contains("boxsInfo") || !resp["boxsInfo"].contains("materialBoxs")) {
235 error = "invalid schema (missing boxsInfo.materialBoxs)";
236 return false;
237 }
238
239 // Sequential AMS-style index for accepted CFS boxes. The K2's raw box.id has
240 // gaps (id 0 is the external spool holder, type=1, skipped) — using the raw id
241 // would publish phantom slots for the gap. Renumber accepted boxes 0,1,2,...
242 int cfs_count = 0;
243 for (const auto& box : resp["boxsInfo"]["materialBoxs"]) {
244 const int box_st = box.value("state", 0);
245 const int box_type = box.value("type", 0);
246 if (box_st != 1) continue; // inactive boxes
247 if (box_type != 0) continue; // non-CFS (external spool holder, handled separately by upload dialog)
248
249 const int cfs_index = cfs_count++;
250
251 if (!box.contains("materials") || !box["materials"].is_array())
252 continue;
253
254 for (const auto& mat : box["materials"]) {
255 // CFS slot state encoding observed across K2 family firmwares:
256 // * K2 (base) / K2 Pro : 0 = empty, 1 = loaded.
257 // * K2 Plus (1.1.5.5/CFS 1.4.2 onwards): 0 = empty,
258 // 1 = loaded AND currently
259 // selected as the active
260 // spool for printing,
261 // 2 = loaded but not selected.
262 // We treat anything non-zero as loaded. Belt-and-braces: also skip
263 // entries that look blank (no vendor and no type) regardless of state.
264 const int s_state = mat.value("state", 0);
265 const std::string s_vendor = mat.value("vendor", std::string());
266 const std::string s_type = mat.value("type", std::string());
267 if (s_state == 0) continue; // explicitly empty
268 if (s_vendor.empty() && s_type.empty()) continue; // blank entry — likely empty under a different state encoding
269
270 CFSSlot s;
271 s.box_id = cfs_index;
272 s.slot_id = mat.value("id", 0);
273 s.vendor = s_vendor;
274 s.brand_name = mat.value("name", "");
275 s.filament_type = s_type;
276 s.color_hex = mat.value("color", "#FFFFFF");
277
278 // Creality reports colour as "#0RRGGBB" (8 chars with a leading zero
279 // after '#'). Normalise to standard "#RRGGBB".
280 if (s.color_hex.size() == 8 && s.color_hex[0] == '#')
281 s.color_hex = "#" + s.color_hex.substr(2);
282
283 slots.push_back(std::move(s));
284 }
285 }
286
287 box_count = cfs_count;
288 return true;
289}
int box_id
Definition CrealityPrintAgent.hpp:31

References Slic3r::CrealityPrintAgent::CFSSlot::box_id, Slic3r::CrealityPrintAgent::CFSSlot::brand_name, Slic3r::CrealityPrintAgent::CFSSlot::color_hex, Slic3r::CrealityPrintAgent::CFSSlot::filament_type, Slic3r::CrealityPrintAgent::CFSSlot::slot_id, and Slic3r::CrealityPrintAgent::CFSSlot::vendor.

Referenced by fetch_filament_info().

Here is the caller graph for this function:

◆ pause_print()

int Slic3r::MoonrakerPrinterAgent::pause_print ( const std::string & dev_id)
privateinherited
2180{
2181 send_gcode_async(dev_id, "PAUSE");
2182 return BAMBU_NETWORK_SUCCESS;
2183}

References BAMBU_NETWORK_SUCCESS, pause_print(), and send_gcode_async().

Referenced by handle_request(), and pause_print().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ perform_connection_async()

void Slic3r::MoonrakerPrinterAgent::perform_connection_async ( const std::string & dev_id,
const std::string & base_url,
const std::string & api_key,
uint64_t generation )
privateinherited
2235{
2236 auto is_stale = [&]() { return generation != connect_generation.load(); };
2237
2239 std::string error_msg;
2240
2241 // Early exit if a newer connection was started before we begin
2242 if (is_stale()) {
2243 return;
2244 }
2245
2246 try {
2247 MoonrakerDeviceInfo fetched_info;
2248 if (!fetch_device_info(base_url, api_key, fetched_info, error_msg, is_stale)) {
2249 if (!is_stale()) {
2250 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Failed to fetch server info: " << error_msg;
2251 }
2252 // Orca todo: revist here, for now don't send error, this is set current MachineObject to null
2253 // dispatch_local_connect(ConnectStatusFailed, dev_id, "server_info_failed");
2254 return;
2255 }
2256
2257 // Commit fetched info back to device_info under lock, only if still current
2258 {
2259 std::lock_guard<std::recursive_mutex> lock(connect_mutex);
2260 if (is_stale()) {
2261 return;
2262 }
2263 device_info.dev_name = fetched_info.dev_name.empty() ? dev_id : fetched_info.dev_name;
2264 device_info.version = fetched_info.version;
2265 device_info.klippy_state = fetched_info.klippy_state;
2266 }
2267
2268// Orca todo: disable websocket for now, as we don't use MonitorPanel for Moonraker printers yet
2269#if 1
2270 // Query initial status
2271 nlohmann::json initial_status;
2272 if (query_printer_status(base_url, api_key, initial_status, error_msg, is_stale)) {
2273 // Under connect_mutex, so a superseded connection never writes into the next connection's cache
2274 std::lock_guard<std::recursive_mutex> lock(connect_mutex);
2275 if (is_stale()) {
2276 return;
2277 }
2278 update_status_cache(initial_status);
2279 BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Initial status queried successfully";
2280 } else if (!is_stale()) {
2281 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Initial status query failed: " << error_msg;
2282 }
2283
2285 // Holds connect_mutex across the staleness check and the start, so a disconnect during the queries above prevents the start
2286 {
2287 std::lock_guard<std::recursive_mutex> lock(connect_mutex);
2288 if (is_stale()) {
2289 return;
2290 }
2291 start_status_stream(dev_id, base_url, api_key);
2292 }
2293#endif
2294
2295 // Success!
2296 result = BAMBU_NETWORK_SUCCESS;
2297
2298 } catch (const std::exception& e) {
2299 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Connection exception: " << e.what();
2300 error_msg = std::string("exception: ") + e.what();
2302 }
2303
2304 if (result == BAMBU_NETWORK_SUCCESS) {
2305 // Under connect_mutex, so a superseded connection never reports itself connected
2306 std::lock_guard<std::recursive_mutex> lock(connect_mutex);
2307 if (!is_stale()) {
2310 BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: connect_printer completed - dev_id=" << dev_id;
2311 }
2312 } else if (result != BAMBU_NETWORK_ERR_CANCELED) {
2313 // Orca todo: revist here, for now don't send error, this is set current MachineObject to null
2314 // dispatch_local_connect(ConnectStatusFailed, dev_id, error_msg);
2315 }
2316}
#define BAMBU_NETWORK_ERR_CANCELED
Definition bambu_networking.hpp:34
#define BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED
Definition bambu_networking.hpp:96
void start_status_stream(const std::string &dev_id, const std::string &base_url, const std::string &api_key)
Definition MoonrakerPrinterAgent.cpp:1490
void join_stopped_status_stream()
Definition MoonrakerPrinterAgent.cpp:1522
virtual bool fetch_device_info(const std::string &base_url, const std::string &api_key, MoonrakerDeviceInfo &info, std::string &error, const std::function< bool()> &cancelled) const
Definition MoonrakerPrinterAgent.cpp:1122
void dispatch_printer_connected(const std::string &dev_id)
Definition MoonrakerPrinterAgent.cpp:1469
void dispatch_local_connect(int state, const std::string &dev_id, const std::string &msg)
Definition MoonrakerPrinterAgent.cpp:1448
bool query_printer_status(const std::string &base_url, const std::string &api_key, nlohmann::json &status, std::string &error, const std::function< bool()> &cancelled) const
Definition MoonrakerPrinterAgent.cpp:1183
@ ConnectStatusOk
Definition bambu_networking.hpp:176

References BAMBU_NETWORK_ERR_CANCELED, BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED, BAMBU_NETWORK_SUCCESS, connect_generation, connect_mutex, Slic3r::ConnectStatusOk, Slic3r::MoonrakerPrinterAgent::MoonrakerDeviceInfo::dev_name, device_info, dispatch_local_connect(), dispatch_printer_connected(), fetch_device_info(), join_stopped_status_stream(), Slic3r::MoonrakerPrinterAgent::MoonrakerDeviceInfo::klippy_state, perform_connection_async(), query_printer_status(), start_status_stream(), update_status_cache(), and Slic3r::MoonrakerPrinterAgent::MoonrakerDeviceInfo::version.

Referenced by perform_connection_async().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ping_bind()

virtual int Slic3r::IPrinterAgent::ping_bind ( std::string ping_code)
inlinevirtualinherited

Ping the binding endpoint to check printer readiness.

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

261 {
262 (void) ping_code;
264 }

References BAMBU_NETWORK_SUCCESS.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ publish_command_json()

int Slic3r::IPrinterAgent::publish_command_json ( std::string dev_id,
const nlohmann::json & j,
bool lan_mode )
inlineprivateinherited
126 {
127 return lan_mode ? send_message_to_printer(dev_id, j.dump(), 0, 0)
128 : send_message(dev_id, j.dump(), 0, 0);
129 }
virtual int send_message(std::string dev_id, std::string json_str, int qos, int flag)=0
virtual int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)=0
size_t dump(const PresetBundle &bundle, const Sections &sections, std::ostream &out)
Definition profile_include_dump.cpp:96

References send_message(), and send_message_to_printer().

Referenced by command_auto_leveling(), command_axis_control(), command_go_home(), command_set_bed(), command_set_nozzle(), and command_xyz_abs().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ query_printer_status()

bool Slic3r::MoonrakerPrinterAgent::query_printer_status ( const std::string & base_url,
const std::string & api_key,
nlohmann::json & status,
std::string & error,
const std::function< bool()> & cancelled ) const
privateinherited
1188{
1189 std::string url = join_url(base_url, "/printer/objects/query?print_stats&virtual_sdcard&extruder&heater_bed&fan");
1190
1191 std::string response_body;
1192 bool success = false;
1193 std::string http_error;
1194
1195 auto http = Http::get(url);
1196 if (!api_key.empty()) {
1197 http.header("X-Api-Key", api_key);
1198 }
1199 http.timeout_connect(5)
1200 .timeout_max(10)
1201 .on_progress(cancel_when(cancelled))
1202 .on_complete([&](std::string body, unsigned status_code) {
1203 if (status_code == 200) {
1204 response_body = body;
1205 success = true;
1206 } else {
1207 http_error = "HTTP error: " + std::to_string(status_code);
1208 }
1209 })
1210 .on_error([&](std::string body, std::string err, unsigned status_code) {
1211 http_error = err;
1212 if (status_code > 0) {
1213 http_error += " (HTTP " + std::to_string(status_code) + ")";
1214 }
1215 })
1216 .perform_sync();
1217
1218 if (!success) {
1219 error = http_error.empty() ? "Connection failed" : http_error;
1220 return false;
1221 }
1222
1223 auto json = nlohmann::json::parse(response_body, nullptr, false, true);
1224 if (json.is_discarded()) {
1225 error = "Invalid JSON response";
1226 return false;
1227 }
1228
1229 if (!json.contains("result") || !json["result"].contains("status")) {
1230 error = "Unexpected JSON structure";
1231 return false;
1232 }
1233
1234 status = json["result"]["status"];
1235 return true;
1236}

References anonymous_namespace{MoonrakerPrinterAgent.cpp}::cancel_when(), Slic3r::Http::get(), Slic3r::http, join_url(), and query_printer_status().

Referenced by perform_connection_async(), and query_printer_status().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ request_bind_ticket()

virtual int Slic3r::IPrinterAgent::request_bind_ticket ( std::string * ticket)
inlinevirtualinherited

Request a one-time bind ticket from the server.

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

300 {
301 if (ticket)
302 *ticket = {};
304 }

References BAMBU_NETWORK_SUCCESS.

◆ resume_print()

int Slic3r::MoonrakerPrinterAgent::resume_print ( const std::string & dev_id)
privateinherited
2186{
2187 send_gcode_async(dev_id, "RESUME");
2188 return BAMBU_NETWORK_SUCCESS;
2189}

References BAMBU_NETWORK_SUCCESS, resume_print(), and send_gcode_async().

Referenced by handle_request(), and resume_print().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ run_status_stream()

void Slic3r::MoonrakerPrinterAgent::run_status_stream ( std::string dev_id,
std::string base_url,
std::string api_key,
StatusStreamTimings timings )
privateinherited
1538{
1539 WsEndpoint endpoint;
1540 if (!parse_ws_endpoint(base_url, endpoint)) {
1541 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: websocket endpoint invalid for base_url=" << base_url;
1542 return;
1543 }
1544 if (endpoint.secure) {
1545 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: websocket wss not supported for base_url=" << base_url;
1546 return;
1547 }
1548
1549 net::io_context ioc;
1550 net::steady_timer backoff{ioc};
1551 std::function<void()> abort_connection; // Set while a connection attempt runs
1552 const auto run = [&](auto&& initiate) { return run_until_done(ioc, ws_stop, initiate); };
1553
1554 // Registered under the lock stop_status_stream() takes, so a stop either finds this abort or has already set ws_stop
1555 {
1556 std::lock_guard<std::mutex> lock(ws_abort_mutex);
1557 if (ws_stop.load()) {
1558 return;
1559 }
1560 ws_abort = [&] {
1561 net::post(ioc, [&] {
1562 backoff.cancel();
1563 if (abort_connection) {
1564 abort_connection();
1565 }
1566 });
1567 };
1568 }
1569 const ScopeGuard clear_abort([this] {
1570 std::lock_guard<std::mutex> lock(ws_abort_mutex);
1571 ws_abort = nullptr;
1572 });
1573
1574 // Reconnection logic
1575 ws_reconnect_requested.store(false); // Reset reconnect flag
1576 int retry_count = 0;
1577
1578 while (!ws_stop.load()) {
1579 bool connection_lost = false; // Flag to distinguish clean shutdown from unexpected disconnect
1580
1581 tcp::resolver resolver{ioc};
1582 websocket::stream<beast::tcp_stream> ws{ioc};
1583 beast::tcp_stream& stream = beast::get_lowest_layer(ws);
1584 net::steady_timer close_deadline{ioc};
1585 bool closing = false;
1586 bool deadline_pending = false;
1587 auto last_message = std::chrono::steady_clock::now();
1588 // Sends a close frame if the websocket is open, and drops the socket when the printer has not answered within timings.close
1589 const auto close_politely = [&](websocket::close_code code) {
1590 if (!ws.is_open() || closing) {
1591 return;
1592 }
1593 closing = true;
1594 deadline_pending = true;
1595 close_deadline.expires_after(timings.close);
1596 close_deadline.async_wait([&](beast::error_code ec) {
1597 deadline_pending = false;
1598 if (!ec) {
1599 beast::error_code ignored;
1600 stream.socket().close(ignored);
1601 }
1602 });
1603 ws.async_close(code, [&](beast::error_code) {
1604 closing = false;
1605 close_deadline.cancel();
1606 });
1607 };
1608 abort_connection = [&] {
1609 // Beast reports a closing websocket as not open, and dropping its socket would cut off the close frame
1610 if (closing) {
1611 return;
1612 }
1613 if (ws.is_open()) {
1614 close_politely(websocket::close_code::going_away);
1615 } else {
1616 beast::error_code ignored;
1617 stream.socket().close(ignored);
1618 }
1619 };
1620 const ScopeGuard clear_connection_abort([&] { abort_connection = nullptr; });
1621
1622 try {
1623 const beast::error_code connect_ec =
1624 connect_until_done(ioc, stream, resolver.resolve(endpoint.host, endpoint.port), std::chrono::seconds(5), ws_stop);
1625 if (connect_ec) {
1626 throw beast::system_error{connect_ec};
1627 }
1628 // Clears the connect deadline, which would otherwise close the socket 5 s after connecting
1629 stream.expires_never();
1630
1631 websocket::stream_base::timeout timeouts{};
1632 timeouts.handshake_timeout = timings.handshake;
1633 timeouts.idle_timeout = timings.idle;
1634 timeouts.keep_alive_pings = true;
1635 ws.set_option(timeouts);
1636 // Beast pings on a fixed schedule, so the heartbeat also checks how long the printer has sent no message
1637 ws.control_callback([&](websocket::frame_type kind, beast::string_view) {
1638 if (kind == websocket::frame_type::pong && !closing &&
1639 std::chrono::steady_clock::now() - last_message >= timings.heartbeat) {
1640 send_heartbeat_if_due(dev_id, timings.heartbeat);
1641 }
1642 });
1643 ws.set_option(websocket::stream_base::decorator([&](websocket::request_type& req) {
1644 req.set(http::field::user_agent, "OrcaSlicer");
1645 if (!api_key.empty()) {
1646 req.set("X-Api-Key", api_key);
1647 }
1648 }));
1649
1650 std::string host_header = endpoint.host;
1651 if (!endpoint.port.empty() && endpoint.port != "80") {
1652 host_header += ":" + endpoint.port;
1653 }
1654 if (const auto ec = run([&](auto&& handler) { ws.async_handshake(host_header, endpoint.target, handler); })) {
1655 throw beast::system_error{ec};
1656 }
1657 ws.text(true);
1658
1659 // Send client identification
1660 nlohmann::json identify;
1661 identify["jsonrpc"] = "2.0";
1662 identify["method"] = "server.connection.identify";
1663 identify["params"]["client_name"] = "OrcaSlicer";
1664 identify["params"]["version"] = MoonrakerPrinterAgent_VERSION;
1665 identify["params"]["type"] = "agent";
1666 identify["params"]["url"] = "https://github.com/SoftFever/OrcaSlicer";
1667 identify["id"] = 0;
1668 const std::string identify_payload = identify.dump();
1669 if (const auto ec = run([&](auto&& handler) { ws.async_write(net::buffer(identify_payload), handler); })) {
1670 throw beast::system_error{ec};
1671 }
1672
1673 std::set<std::string> subscribe_objects = {"print_stats", "virtual_sdcard"};
1674 std::set<std::string> available_objects;
1675 std::string list_error;
1676 if (fetch_object_list(base_url, api_key, available_objects, list_error)) {
1677 {
1678 std::lock_guard<std::recursive_mutex> lock(payload_mutex);
1679 this->available_objects = std::move(available_objects);
1680 }
1681
1682 if (this->available_objects.count("heater_bed") != 0) {
1683 subscribe_objects.insert("heater_bed");
1684 }
1685 if (this->available_objects.count("fan") != 0) {
1686 subscribe_objects.insert("fan");
1687 }
1688
1689 // Add toolhead for homing status
1690 if (this->available_objects.count("toolhead") != 0) {
1691 subscribe_objects.insert("toolhead");
1692 }
1693
1694 // Add display_status for layer info (if available)
1695 if (this->available_objects.count("display_status") != 0) {
1696 subscribe_objects.insert("display_status");
1697 }
1698
1699 for (const auto& name : this->available_objects) {
1700 if (name == "extruder" || name.rfind("extruder", 0) == 0) {
1701 subscribe_objects.insert(name);
1702 if (name == "extruder") {
1703 break;
1704 }
1705 }
1706 }
1707 } else {
1708 subscribe_objects.insert("extruder");
1709 subscribe_objects.insert("heater_bed");
1710 subscribe_objects.insert("toolhead"); // Add toolhead as fallback
1711 subscribe_objects.insert("fan"); // Try to subscribe to fan as fallback
1712 }
1713
1714 nlohmann::json subscribe;
1715 subscribe["jsonrpc"] = "2.0";
1716 subscribe["method"] = "printer.objects.subscribe";
1717 nlohmann::json objects = nlohmann::json::object();
1718 for (const auto& name : subscribe_objects) {
1719 objects[name] = nullptr;
1720 }
1721 subscribe["params"]["objects"] = std::move(objects);
1722 subscribe["id"] = 1;
1723 const std::string subscribe_payload = subscribe.dump();
1724 if (const auto ec = run([&](auto&& handler) { ws.async_write(net::buffer(subscribe_payload), handler); })) {
1725 throw beast::system_error{ec};
1726 }
1727
1728 // Read loop
1729 while (!ws_stop.load()) {
1730 beast::flat_buffer buffer;
1731 const auto ec = run([&](auto&& handler) { ws.async_read(buffer, handler); });
1732 if (ws_stop.load()) {
1733 break;
1734 }
1735 if (ec == websocket::error::closed) {
1736 connection_lost = true;
1737 break;
1738 }
1739 if (ec) {
1740 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: websocket read error: " << ec.message();
1741 connection_lost = true;
1742 break;
1743 }
1744 last_message = std::chrono::steady_clock::now();
1745 // A message shows the printer is answering, so the next drop starts the backoff from reconnect_delay
1746 retry_count = 0;
1747 handle_ws_message(dev_id, beast::buffers_to_string(buffer.data()));
1748 // Check if handle_ws_message triggered reconnection request
1749 if (ws_reconnect_requested.exchange(false)) {
1750 connection_lost = true;
1751 break;
1752 }
1753 }
1754 } catch (const std::exception& e) {
1755 if (!ws_stop.load()) {
1756 BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: websocket disconnected: " << e.what();
1757 }
1758 connection_lost = true;
1759 }
1760
1761 close_politely(ws_stop.load() ? websocket::close_code::going_away : websocket::close_code::normal);
1762 ioc.restart();
1763 // Both handlers refer to this connection, so neither may be left queued for the next one
1764 while ((closing || deadline_pending) && ioc.run_one() != 0) {}
1765
1766 // Exit loop on clean shutdown
1767 if (!connection_lost) {
1768 break;
1769 }
1770
1771 // Check if we should stop reconnection attempts
1772 if (ws_stop.load()) {
1773 break;
1774 }
1775
1776 // Exponential backoff before reconnection, capped at 32 times reconnect_delay so a printer that comes back is found again
1777 const auto delay = timings.reconnect_delay * (1 << std::min(retry_count, 5));
1778 BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Reconnecting in " << delay.count() << "ms (attempt " << (retry_count + 1) << ")";
1779 backoff.expires_after(delay);
1780 run([&](auto&& handler) { backoff.async_wait(handler); });
1781 retry_count++;
1782 }
1783}
std::function< void()> ws_abort
Definition MoonrakerPrinterAgent.hpp:223
bool fetch_object_list(const std::string &base_url, const std::string &api_key, std::set< std::string > &objects, std::string &error) const
Definition MoonrakerPrinterAgent.cpp:1318
void handle_ws_message(const std::string &dev_id, const std::string &payload)
Definition MoonrakerPrinterAgent.cpp:1785
void send_heartbeat_if_due(const std::string &dev_id, std::chrono::milliseconds interval)
Definition MoonrakerPrinterAgent.cpp:1877
AppActionRunResult run(const std::string &key, const std::string &param={})
Definition NativeCommands.cpp:668
const std::string MoonrakerPrinterAgent_VERSION
Definition MoonrakerPrinterAgent.cpp:148
boost::beast::error_code connect_until_done(boost::asio::io_context &ioc, boost::beast::tcp_stream &stream, const Endpoints &endpoints, std::chrono::milliseconds timeout, const std::atomic< bool > &stop)
Definition AsioUtils.hpp:43
boost::beast::error_code run_until_done(boost::asio::io_context &ioc, const std::atomic< bool > &stop, Initiate &&initiate)
Definition AsioUtils.hpp:16
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, JoinType joinType, double miterLimit)
Definition ClipperUtils.cpp:670
bool parse_ws_endpoint(const std::string &base_url, WsEndpoint &endpoint)
Definition MoonrakerPrinterAgent.cpp:79

References available_objects, Slic3r::MoonrakerPrinterAgent::StatusStreamTimings::close, Slic3r::closing(), Slic3r::connect_until_done(), fetch_object_list(), handle_ws_message(), Slic3r::MoonrakerPrinterAgent::StatusStreamTimings::handshake, Slic3r::MoonrakerPrinterAgent::StatusStreamTimings::heartbeat, Slic3r::MoonrakerPrinterAgent::StatusStreamTimings::idle, Slic3r::MoonrakerPrinterAgent_VERSION, anonymous_namespace{MoonrakerPrinterAgent.cpp}::parse_ws_endpoint(), payload_mutex, Slic3r::MoonrakerPrinterAgent::StatusStreamTimings::reconnect_delay, run_status_stream(), Slic3r::run_until_done(), send_heartbeat_if_due(), ws_abort, ws_abort_mutex, ws_reconnect_requested, and ws_stop.

Referenced by run_status_stream(), and start_status_stream().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ safe_array_int()

int Slic3r::MoonrakerPrinterAgent::safe_array_int ( const nlohmann::json & arr,
int idx )
staticprivateinherited
725{
726 if (arr.is_array() && idx >= 0 && idx < static_cast<int>(arr.size()) && arr[idx].is_number())
727 return arr[idx].get<int>();
728 return 0;
729}

References safe_array_int().

Referenced by fetch_hh_filament_info(), and safe_array_int().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ safe_array_string()

std::string Slic3r::MoonrakerPrinterAgent::safe_array_string ( const nlohmann::json & arr,
int idx )
staticprivateinherited
718{
719 if (arr.is_array() && idx >= 0 && idx < static_cast<int>(arr.size()) && arr[idx].is_string())
720 return arr[idx].get<std::string>();
721 return "";
722}

References safe_array_string().

Referenced by fetch_hh_filament_info(), and safe_array_string().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ safe_json_int()

int Slic3r::MoonrakerPrinterAgent::safe_json_int ( const nlohmann::json & obj,
const char * key )
staticprivateinherited
710{
711 auto it = obj.find(key);
712 if (it != obj.end() && it->is_number())
713 return it->get<int>();
714 return 0;
715}

References safe_json_int().

Referenced by fetch_moonraker_filament_data(), and safe_json_int().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ safe_json_string()

std::string Slic3r::MoonrakerPrinterAgent::safe_json_string ( const nlohmann::json & obj,
const char * key )
staticprivateinherited
702{
703 auto it = obj.find(key);
704 if (it != obj.end() && it->is_string())
705 return it->get<std::string>();
706 return "";
707}

References safe_json_string().

Referenced by fetch_moonraker_filament_data(), and safe_json_string().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sanitize_filename()

std::string Slic3r::MoonrakerPrinterAgent::sanitize_filename ( const std::string & filename)
protectedinherited
2351{
2352 if (filename.empty()) {
2353 return "print.gcode";
2354 }
2355 namespace fs = boost::filesystem;
2356 fs::path p(filename);
2357 std::string basename = p.filename().string();
2358 if (basename.empty() || basename == "." || basename == "..") {
2359 return "print.gcode";
2360 }
2361 return basename;
2362}

References sanitize_filename().

Referenced by sanitize_filename(), start_local_print(), start_send_gcode_to_sdcard(), and upload_gcode().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ send_access_code()

int Slic3r::MoonrakerPrinterAgent::send_access_code ( const std::string & dev_id)
privateinherited
1396{
1397 nlohmann::json payload;
1398 payload["system"]["command"] = "get_access_code";
1399 payload["system"]["access_code"] = device_info.api_key;
1400 dispatch_message(dev_id, payload.dump());
1401 return BAMBU_NETWORK_SUCCESS;
1402}

References BAMBU_NETWORK_SUCCESS, device_info, dispatch_message(), and send_access_code().

Referenced by handle_request(), and send_access_code().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ send_gcode_async()

void Slic3r::MoonrakerPrinterAgent::send_gcode_async ( const std::string & dev_id,
const std::string & gcode,
std::function< void(bool)> on_result = {} ) const
privateinherited
1240{
1241 (void) dev_id;
1242 const std::string base_url = device_info.base_url;
1243 const std::string api_key = device_info.api_key;
1244 auto http = Http::post(join_url(base_url, "/printer/gcode/script"));
1245 if (!api_key.empty()) {
1246 http.header("X-Api-Key", api_key);
1247 }
1248 http.header("Content-Type", "application/json")
1249 .set_post_body(nlohmann::json{{"script", gcode}}.dump())
1250 .timeout_connect(5)
1251 .timeout_max(10)
1252 .on_complete([on_result](std::string body, unsigned status_code) {
1253 (void) body;
1254 const bool success = status_code == 200;
1255 if (!success) {
1256 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: HTTP error " << status_code;
1257 }
1258 if (on_result) {
1259 on_result(success);
1260 }
1261 })
1262 .on_error([on_result](std::string body, std::string err, unsigned status_code) {
1263 (void) body;
1264 std::string error = err;
1265 if (status_code > 0) {
1266 error += " (HTTP " + std::to_string(status_code) + ")";
1267 }
1268 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: " << error;
1269 if (on_result) {
1270 on_result(false);
1271 }
1272 })
1273 .perform();
1274}
static Http post(std::string url)
Definition Http.cpp:891

References device_info, Slic3r::http, join_url(), Slic3r::Http::post(), and send_gcode_async().

Referenced by cancel_print(), handle_request(), pause_print(), resume_print(), and send_gcode_async().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ send_gcode_sync()

bool Slic3r::MoonrakerPrinterAgent::send_gcode_sync ( const std::string & dev_id,
const std::string & gcode ) const
privateinherited
1277{
1278 nlohmann::json payload;
1279 payload["script"] = gcode;
1280 std::string payload_str = payload.dump();
1281
1282 std::string response_body;
1283 bool success = false;
1284 std::string http_error;
1285
1286 auto http = Http::post(join_url(device_info.base_url, "/printer/gcode/script"));
1287 if (!device_info.api_key.empty()) {
1288 http.header("X-Api-Key", device_info.api_key);
1289 }
1290 http.header("Content-Type", "application/json")
1291 .set_post_body(payload_str)
1292 .timeout_connect(5)
1293 .timeout_max(10)
1294 .on_complete([&](std::string body, unsigned status_code) {
1295 if (status_code == 200) {
1296 response_body = body;
1297 success = true;
1298 } else {
1299 http_error = "HTTP error: " + std::to_string(status_code);
1300 }
1301 })
1302 .on_error([&](std::string body, std::string err, unsigned status_code) {
1303 http_error = err;
1304 if (status_code > 0) {
1305 http_error += " (HTTP " + std::to_string(status_code) + ")";
1306 }
1307 })
1308 .perform_sync();
1309
1310 if (!success) {
1311 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: " << http_error;
1312 return false;
1313 }
1314
1315 return true;
1316}

References device_info, Slic3r::http, join_url(), Slic3r::Http::post(), and send_gcode_sync().

Referenced by send_gcode_sync(), and start_local_print().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ send_heartbeat_if_due()

void Slic3r::MoonrakerPrinterAgent::send_heartbeat_if_due ( const std::string & dev_id,
std::chrono::milliseconds interval )
privateinherited
1878{
1879 const auto now_ms = steady_now_ms();
1880 const auto last_ms = ws_last_emit_ms.load();
1881 // Re-sends only a status the printer has reported at least once
1882 if (last_ms == 0 || now_ms - last_ms < static_cast<uint64_t>(interval.count())) {
1883 return;
1884 }
1885
1886 nlohmann::json message;
1887 {
1888 std::lock_guard<std::recursive_mutex> lock(payload_mutex);
1889 message = build_print_payload_locked();
1890 }
1891 dispatch_message(dev_id, message.dump());
1892 ws_last_emit_ms.store(now_ms);
1893}

References build_print_payload_locked(), dispatch_message(), payload_mutex, send_heartbeat_if_due(), anonymous_namespace{MoonrakerPrinterAgent.cpp}::steady_now_ms(), and ws_last_emit_ms.

Referenced by run_status_stream(), and send_heartbeat_if_due().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ send_jsonrpc_command()

bool Slic3r::MoonrakerPrinterAgent::send_jsonrpc_command ( const std::string & base_url,
const std::string & api_key,
const nlohmann::json & request,
std::string & response ) const
privateinherited
2201{
2202 std::string request_str = request.dump();
2203 std::string url = join_url(base_url, "/printer/print/start");
2204
2205 bool success = false;
2206 std::string http_error;
2207
2208 auto http = Http::post(url);
2209 if (!api_key.empty()) {
2210 http.header("X-Api-Key", api_key);
2211 }
2212 http.header("Content-Type", "application/json")
2213 .set_post_body(request_str)
2214 .timeout_connect(5)
2215 .timeout_max(10)
2216 .on_complete([&](std::string body, unsigned status) {
2217 if (status == 200) {
2218 response = body;
2219 success = true;
2220 } else {
2221 http_error = "HTTP " + std::to_string(status);
2222 }
2223 })
2224 .on_error([&](std::string body, std::string err, unsigned status) { http_error = err; })
2225 .perform_sync();
2226
2227 if (!success) {
2228 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: JSON-RPC command failed: " << http_error;
2229 }
2230
2231 return success;
2232}

References Slic3r::http, join_url(), Slic3r::Http::post(), and send_jsonrpc_command().

Referenced by send_jsonrpc_command().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ send_message()

int Slic3r::MoonrakerPrinterAgent::send_message ( std::string dev_id,
std::string json_str,
int qos,
int flag )
overridevirtualinherited

Publish a JSON command to a printer through cloud relay.

Implements Slic3r::IPrinterAgent.

177{
178 (void) qos;
179 (void) flag;
180 return handle_request(dev_id, json_str);
181}
int handle_request(const std::string &dev_id, const std::string &json_str)
Definition MoonrakerPrinterAgent.cpp:992

References handle_request().

Here is the call graph for this function:

◆ send_message_to_printer()

int Slic3r::MoonrakerPrinterAgent::send_message_to_printer ( std::string dev_id,
std::string json_str,
int qos,
int flag )
overridevirtualinherited

Send a JSON command to a LAN printer (bypassing cloud).

Implements Slic3r::IPrinterAgent.

184{
185 (void) qos;
186 (void) flag;
187 return handle_request(dev_id, json_str);
188}

References handle_request().

Here is the call graph for this function:

◆ send_version_info()

int Slic3r::MoonrakerPrinterAgent::send_version_info ( const std::string & dev_id)
privateinherited
1379{
1380 nlohmann::json payload;
1381 payload["info"]["command"] = "get_version";
1382 payload["info"]["result"] = "success";
1383 payload["info"]["module"] = nlohmann::json::array();
1384
1385 nlohmann::json module;
1386 module["name"] = "ota";
1387 module["sw_ver"] = device_info.version;
1388 module["product_name"] = "Moonraker";
1389 payload["info"]["module"].push_back(module);
1390
1391 dispatch_message(dev_id, payload.dump());
1392 return BAMBU_NETWORK_SUCCESS;
1393}
FileTransferModule & module()
Definition FileTransferUtils.hpp:248

References BAMBU_NETWORK_SUCCESS, dispatch_message(), Slic3r::module(), and send_version_info().

Referenced by handle_request(), and send_version_info().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_cloud_agent()

void Slic3r::MoonrakerPrinterAgent::set_cloud_agent ( std::shared_ptr< ICloudServiceAgent > cloud)
overridevirtualinherited

Set the cloud agent used for token access. Must be called before any cloud-relay operations.

Implements Slic3r::IPrinterAgent.

171{
172 std::lock_guard<std::recursive_mutex> lock(state_mutex);
173 m_cloud_agent = cloud;
174}
std::shared_ptr< ICloudServiceAgent > m_cloud_agent
Definition MoonrakerPrinterAgent.hpp:199

References m_cloud_agent, and state_mutex.

◆ set_on_local_connect_fn()

int Slic3r::MoonrakerPrinterAgent::set_on_local_connect_fn ( OnLocalConnectedFn fn)
overridevirtualinherited

Register LAN connection status callback.

Implements Slic3r::IPrinterAgent.

452{
453 std::lock_guard<std::recursive_mutex> lock(state_mutex);
456}

References BAMBU_NETWORK_SUCCESS, on_local_connect_fn, set_on_local_connect_fn(), and state_mutex.

Referenced by set_on_local_connect_fn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_on_local_message_fn()

int Slic3r::MoonrakerPrinterAgent::set_on_local_message_fn ( OnMessageFn fn)
overridevirtualinherited

Register LAN message callback.

Implements Slic3r::IPrinterAgent.

459{
460 std::lock_guard<std::recursive_mutex> lock(state_mutex);
463}

References BAMBU_NETWORK_SUCCESS, on_local_message_fn, set_on_local_message_fn(), and state_mutex.

Referenced by set_on_local_message_fn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_on_message_fn()

int Slic3r::MoonrakerPrinterAgent::set_on_message_fn ( OnMessageFn fn)
overridevirtualinherited

Register cloud device message callback.

Implements Slic3r::IPrinterAgent.

438{
439 std::lock_guard<std::recursive_mutex> lock(state_mutex);
440 on_message_fn = fn;
442}

References BAMBU_NETWORK_SUCCESS, on_message_fn, set_on_message_fn(), and state_mutex.

Referenced by set_on_message_fn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_on_printer_connected_fn()

int Slic3r::MoonrakerPrinterAgent::set_on_printer_connected_fn ( OnPrinterConnectedFn fn)
overridevirtualinherited

Register printer MQTT connection callback.

Implements Slic3r::IPrinterAgent.

424{
425 std::lock_guard<std::recursive_mutex> lock(state_mutex);
428}

References BAMBU_NETWORK_SUCCESS, on_printer_connected_fn, set_on_printer_connected_fn(), and state_mutex.

Referenced by set_on_printer_connected_fn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_on_ssdp_msg_fn()

int Slic3r::MoonrakerPrinterAgent::set_on_ssdp_msg_fn ( OnMsgArrivedFn fn)
overridevirtualinherited

Register SSDP discovery callback.

Implements Slic3r::IPrinterAgent.

415{
416 {
417 std::lock_guard<std::recursive_mutex> lock(state_mutex);
418 on_ssdp_msg_fn = fn;
419 }
421}

References BAMBU_NETWORK_SUCCESS, on_ssdp_msg_fn, set_on_ssdp_msg_fn(), and state_mutex.

Referenced by set_on_ssdp_msg_fn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_on_subscribe_failure_fn()

int Slic3r::MoonrakerPrinterAgent::set_on_subscribe_failure_fn ( GetSubscribeFailureFn fn)
overridevirtualinherited

Register subscription failure callback.

Implements Slic3r::IPrinterAgent.

431{
432 std::lock_guard<std::recursive_mutex> lock(state_mutex);
435}
GetSubscribeFailureFn on_subscribe_failure_fn
Definition MoonrakerPrinterAgent.hpp:204

References BAMBU_NETWORK_SUCCESS, on_subscribe_failure_fn, set_on_subscribe_failure_fn(), and state_mutex.

Referenced by set_on_subscribe_failure_fn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_on_user_message_fn()

int Slic3r::MoonrakerPrinterAgent::set_on_user_message_fn ( OnMessageFn fn)
overridevirtualinherited

Register user-scoped message callback.

Implements Slic3r::IPrinterAgent.

445{
446 std::lock_guard<std::recursive_mutex> lock(state_mutex);
449}
OnMessageFn on_user_message_fn
Definition MoonrakerPrinterAgent.hpp:206

References BAMBU_NETWORK_SUCCESS, on_user_message_fn, set_on_user_message_fn(), and state_mutex.

Referenced by set_on_user_message_fn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_queue_on_main_fn()

int Slic3r::MoonrakerPrinterAgent::set_queue_on_main_fn ( QueueOnMainFn fn)
overridevirtualinherited

Provide main thread queue callback.

Implements Slic3r::IPrinterAgent.

466{
467 std::lock_guard<std::recursive_mutex> lock(state_mutex);
468 queue_on_main_fn = fn;
470}

References BAMBU_NETWORK_SUCCESS, queue_on_main_fn, set_queue_on_main_fn(), and state_mutex.

Referenced by set_queue_on_main_fn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_server_callback()

int Slic3r::MoonrakerPrinterAgent::set_server_callback ( OnServerErrFn fn)
overridevirtualinherited

Register callback for fatal HTTP errors.

Implements Slic3r::IPrinterAgent.

278{
279 std::lock_guard<std::recursive_mutex> lock(state_mutex);
280 on_server_err_fn = fn;
282}
OnServerErrFn on_server_err_fn
Definition MoonrakerPrinterAgent.hpp:210

References BAMBU_NETWORK_SUCCESS, on_server_err_fn, set_server_callback(), and state_mutex.

Referenced by set_server_callback().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_user_selected_machine()

int Slic3r::MoonrakerPrinterAgent::set_user_selected_machine ( std::string dev_id)
overridevirtualinherited

Update the selected cloud machine preference.

Implements Slic3r::IPrinterAgent.

291{
292 std::lock_guard<std::recursive_mutex> lock(state_mutex);
293 selected_machine = dev_id;
295}

References BAMBU_NETWORK_SUCCESS, selected_machine, set_user_selected_machine(), and state_mutex.

Referenced by set_user_selected_machine().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ start_discovery()

bool Slic3r::MoonrakerPrinterAgent::start_discovery ( bool start,
bool sending )
overridevirtualinherited

Start or stop SSDP discovery.

Implements Slic3r::IPrinterAgent.

253{
254 (void) sending;
255 if (start) {
257 }
258 return true;
259}
void announce_printhost_device()
Definition MoonrakerPrinterAgent.cpp:1404

References announce_printhost_device(), and start_discovery().

Referenced by start_discovery().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ start_local_print()

int Slic3r::MoonrakerPrinterAgent::start_local_print ( PrintParams params,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn )
overridevirtualinherited

Start a LAN-only print.

Implements Slic3r::IPrinterAgent.

350{
351 if (update_fn)
352 update_fn(PrintingStageCreate, 0, _u8L("Preparing..."));
353
354 // Check cancellation
355 if (cancel_fn && cancel_fn()) {
357 }
358 // Determine the G-code file to upload
359 // params.filename may be .3mf, params.dst_file contains actual G-code
360 std::string gcode_path = params.filename;
361 if (!params.dst_file.empty()) {
362 gcode_path = params.dst_file;
363 }
364
365 // Check if file exists and has .gcode extension
366 namespace fs = boost::filesystem;
367 fs::path source_path(gcode_path);
368 if (!fs::exists(source_path)) {
369 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: G-code file does not exist: " << gcode_path;
371 }
372
373 // Extract filename for upload (relative to gcodes root)
374 std::string upload_filename = source_path.filename().string();
375 if (!boost::iends_with(upload_filename, ".gcode")) {
376 upload_filename += ".gcode";
377 }
378 // Sanitize filename to prevent path traversal attacks (extra safety)
379 upload_filename = sanitize_filename(upload_filename);
380
381 // Upload file
382 if (update_fn)
383 update_fn(PrintingStageUpload, 0, _u8L("Uploading G-code..."));
384 if (!upload_gcode(gcode_path, upload_filename, device_info.base_url, device_info.api_key, update_fn, cancel_fn)) {
386 }
387
388 // Check cancellation
389 if (cancel_fn && cancel_fn()) {
391 }
392
393 // Start print via gcode script (simpler than JSON-RPC)
394 if (update_fn)
395 update_fn(PrintingStageSending, 0, _u8L("Starting print..."));
396 std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename;
397 if (!send_gcode_sync(device_info.dev_id, gcode)) {
399 }
400
401 if (update_fn)
402 update_fn(PrintingStageFinished, 100, _u8L("Print started"));
404}
#define BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED
Definition bambu_networking.hpp:89
#define BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED
Definition bambu_networking.hpp:90
#define BAMBU_NETWORK_ERR_FILE_NOT_EXIST
Definition bambu_networking.hpp:30
std::string sanitize_filename(const std::string &filename)
Definition MoonrakerPrinterAgent.cpp:2350
bool send_gcode_sync(const std::string &dev_id, const std::string &gcode) const
Definition MoonrakerPrinterAgent.cpp:1276
bool upload_gcode(const std::string &local_path, const std::string &filename, const std::string &base_url, const std::string &api_key, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
Definition MoonrakerPrinterAgent.cpp:2108
@ PrintingStageSending
Definition bambu_networking.hpp:151
@ PrintingStageCreate
Definition bambu_networking.hpp:148
@ PrintingStageFinished
Definition bambu_networking.hpp:154
@ PrintingStageUpload
Definition bambu_networking.hpp:149
#define _u8L(s)
Definition I18N.hpp:11

References _u8L, BAMBU_NETWORK_ERR_CANCELED, BAMBU_NETWORK_ERR_FILE_NOT_EXIST, BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED, BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED, BAMBU_NETWORK_SUCCESS, device_info, Slic3r::PrintParams::dst_file, Slic3r::PrintParams::filename, Slic3r::PrintingStageCreate, Slic3r::PrintingStageFinished, Slic3r::PrintingStageSending, Slic3r::PrintingStageUpload, sanitize_filename(), send_gcode_sync(), start_local_print(), and upload_gcode().

Referenced by start_local_print().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ start_local_print_with_record()

int Slic3r::MoonrakerPrinterAgent::start_local_print_with_record ( PrintParams params,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn,
OnWaitFn wait_fn )
overridevirtualinherited

Start a local print with cloud record.

Implements Slic3r::IPrinterAgent.

310{
311 (void) params;
312 (void) update_fn;
313 (void) cancel_fn;
314 (void) wait_fn;
316}

References BAMBU_NETWORK_SUCCESS, and start_local_print_with_record().

Referenced by start_local_print_with_record().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ start_print()

int Slic3r::MoonrakerPrinterAgent::start_print ( PrintParams params,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn,
OnWaitFn wait_fn )
overridevirtualinherited

Start a fully managed cloud print.

Implements Slic3r::IPrinterAgent.

298{
299 (void) params;
300 (void) update_fn;
301 (void) cancel_fn;
302 (void) wait_fn;
304}

References BAMBU_NETWORK_SUCCESS, and start_print().

Referenced by start_print().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ start_sdcard_print()

int Slic3r::MoonrakerPrinterAgent::start_sdcard_print ( PrintParams params,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn )
overridevirtualinherited

Start a print from printer's SD card.

Implements Slic3r::IPrinterAgent.

407{
408 (void) params;
409 (void) update_fn;
410 (void) cancel_fn;
412}

References BAMBU_NETWORK_SUCCESS, and start_sdcard_print().

Referenced by start_sdcard_print().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ start_send_gcode_to_sdcard()

int Slic3r::MoonrakerPrinterAgent::start_send_gcode_to_sdcard ( PrintParams params,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn,
OnWaitFn wait_fn )
overridevirtualinherited

Upload gcode to printer's SD card without starting.

Implements Slic3r::IPrinterAgent.

322{
323 (void) wait_fn;
324
325 if (update_fn)
326 update_fn(PrintingStageCreate, 0, _u8L("Preparing..."));
327
328 std::string filename = params.filename;
329 if (filename.empty()) {
330 filename = params.task_name;
331 }
332 if (!boost::iends_with(filename, ".gcode")) {
333 filename += ".gcode";
334 }
335
336 // Sanitize filename to prevent path traversal attacks
337 std::string safe_filename = sanitize_filename(filename);
338
339 // Upload only, don't start print
340 if (!upload_gcode(params.filename, safe_filename, device_info.base_url, device_info.api_key, update_fn, cancel_fn)) {
342 }
343
344 if (update_fn)
345 update_fn(PrintingStageFinished, 100, _u8L("File uploaded"));
347}
#define BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED
Definition bambu_networking.hpp:93

References _u8L, BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED, BAMBU_NETWORK_SUCCESS, device_info, Slic3r::PrintParams::filename, Slic3r::PrintingStageCreate, Slic3r::PrintingStageFinished, sanitize_filename(), start_send_gcode_to_sdcard(), Slic3r::PrintParams::task_name, and upload_gcode().

Referenced by start_send_gcode_to_sdcard().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ start_status_stream()

void Slic3r::MoonrakerPrinterAgent::start_status_stream ( const std::string & dev_id,
const std::string & base_url,
const std::string & api_key )
protectedinherited
1491{
1493 if (base_url.empty()) {
1494 return;
1495 }
1496
1497 std::lock_guard<std::mutex> lock(ws_abort_mutex);
1498 ws_stop.store(false);
1499 ws_thread = std::thread(
1500 [this, dev_id, base_url, api_key, timings = ws_timings]() { run_status_stream(dev_id, base_url, api_key, timings); });
1501}
void run_status_stream(std::string dev_id, std::string base_url, std::string api_key, StatusStreamTimings timings)
Definition MoonrakerPrinterAgent.cpp:1537
struct Slic3r::MoonrakerPrinterAgent::StatusStreamTimings ws_timings

References run_status_stream(), start_status_stream(), stop_status_stream(), ws_abort_mutex, ws_stop, ws_thread, and ws_timings.

Referenced by perform_connection_async(), and start_status_stream().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ start_subscribe()

virtual int Slic3r::IPrinterAgent::start_subscribe ( std::string module)
inlinevirtualinherited

Subscribe to a logical module (for example app- or tunnel-scoped streams).

Reimplemented in Slic3r::BBLPrinterAgent.

343{ (void) module; return BAMBU_NETWORK_SUCCESS; }

References BAMBU_NETWORK_SUCCESS, and Slic3r::module().

Here is the call graph for this function:

◆ stop_status_stream()

void Slic3r::MoonrakerPrinterAgent::stop_status_stream ( )
protectedinherited
1504{
1505 std::thread stopped;
1506 {
1507 std::lock_guard<std::mutex> lock(ws_abort_mutex);
1508 ws_stop.store(true);
1509 if (ws_abort) {
1510 ws_abort();
1511 }
1512 // A callback on the stream thread can stop the stream, which then ends on its own and is joined later, outside connect_mutex
1513 if (ws_thread.get_id() != std::this_thread::get_id()) {
1514 stopped = std::move(ws_thread);
1515 }
1516 }
1517 if (stopped.joinable()) {
1518 stopped.join();
1519 }
1520}

References stop_status_stream(), ws_abort, ws_abort_mutex, ws_stop, and ws_thread.

Referenced by ~MoonrakerPrinterAgent(), connect_printer(), disconnect_printer(), start_status_stream(), and stop_status_stream().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ stop_subscribe()

virtual int Slic3r::IPrinterAgent::stop_subscribe ( std::string module)
inlinevirtualinherited

Stop listening to a formerly subscribed module.

Reimplemented in Slic3r::BBLPrinterAgent.

348{ (void) module; return BAMBU_NETWORK_SUCCESS; }

References BAMBU_NETWORK_SUCCESS, and Slic3r::module().

Here is the call graph for this function:

◆ to_orca_filament_id()

virtual std::string Slic3r::IPrinterAgent::to_orca_filament_id ( const std::string & printer_filament_id) const
inlinevirtualinherited

Translate one filament id across the printer boundary.

Orca content-addresses every system filament; a printer, its AMS and its vendor cloud know only that vendor's own catalog ids. An agent whose printers already speak Orca's ids leaves them alone, and so does an id with no mapping.

Reimplemented in Slic3r::BBLPrinterAgent.

471{ return printer_filament_id; }

◆ trim_and_upper()

std::string Slic3r::MoonrakerPrinterAgent::trim_and_upper ( const std::string & input)
staticprotectedinherited
621{
622 std::string result = input;
623 boost::trim(result);
624 std::transform(result.begin(), result.end(), result.begin(),
625 [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
626 return result;
627}

References trim_and_upper().

Referenced by Slic3r::SnapmakerPrinterAgent::combine_filament_type(), map_filament_type_to_generic_id(), Slic3r::QidiPrinterAgent::map_filament_type_to_setting_id(), Slic3r::QidiPrinterAgent::normalize_filament_type(), and trim_and_upper().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ unbind()

virtual int Slic3r::IPrinterAgent::unbind ( std::string dev_id)
inlinevirtualinherited

Remove the association between account and printer.

Reimplemented in Slic3r::BBLPrinterAgent, and Slic3r::PyPrinterAgentPluginCapabilityTrampoline.

291 {
292 (void) dev_id;
294 }

References BAMBU_NETWORK_SUCCESS.

Referenced by Slic3r::PrinterAgentPluginCapability::RegisterBindings().

Here is the caller graph for this function:

◆ update_status_cache()

void Slic3r::MoonrakerPrinterAgent::update_status_cache ( const nlohmann::json & updates)
privateinherited
1896{
1897 if (!updates.is_object()) {
1898 return;
1899 }
1900
1901 std::lock_guard<std::recursive_mutex> lock(payload_mutex);
1902 if (!status_cache.is_object()) {
1903 status_cache = nlohmann::json::object();
1904 }
1905
1906 for (const auto& item : updates.items()) {
1907 if (item.value().is_object()) {
1908 nlohmann::json& target = status_cache[item.key()];
1909 if (!target.is_object()) {
1910 target = nlohmann::json::object();
1911 }
1912 for (const auto& field : item.value().items()) {
1913 target[field.key()] = field.value();
1914 }
1915 } else {
1916 status_cache[item.key()] = item.value();
1917 }
1918 }
1919}

References payload_mutex, status_cache, and update_status_cache().

Referenced by handle_ws_message(), perform_connection_async(), and update_status_cache().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ upload_gcode()

bool Slic3r::MoonrakerPrinterAgent::upload_gcode ( const std::string & local_path,
const std::string & filename,
const std::string & base_url,
const std::string & api_key,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn )
privateinherited
2114{
2115 namespace fs = boost::filesystem;
2116
2117 // Validate file exists
2118 fs::path source_path(local_path);
2119 if (!fs::exists(source_path)) {
2120 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: File does not exist: " << local_path;
2121 return false;
2122 }
2123
2124 // Check file size
2125 std::uintmax_t file_size = fs::file_size(source_path);
2126 if (file_size > 1024 * 1024 * 1024) { // 1GB limit
2127 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: File too large: " << file_size << " bytes";
2128 return false;
2129 }
2130
2131 // Sanitize filename to prevent path traversal attacks
2132 std::string safe_filename = sanitize_filename(filename);
2133
2134 bool result = true;
2135 std::string http_error;
2136
2137 // Use Http::form_add and Http::form_add_file
2138 auto http = Http::post(join_url(base_url, "/server/files/upload"));
2139 if (!api_key.empty()) {
2140 http.header("X-Api-Key", api_key);
2141 }
2142 http.form_add("root", "gcodes") // Upload to gcodes directory
2143 .form_add("print", "false") // Don't auto-start print
2144 .form_add_file("file", source_path.string(), safe_filename)
2145 .timeout_connect(5)
2146 .timeout_max(300) // 5 minutes for large files
2147 .on_complete([&](std::string body, unsigned status) {
2148 (void) body;
2149 (void) status;
2150 })
2151 .on_error([&](std::string body, std::string err, unsigned status) {
2152 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Upload error: " << err << " HTTP " << status;
2153 http_error = err;
2154 result = false;
2155 })
2156 .on_progress([&](Http::Progress progress, bool& cancel) {
2157 // Check for cancellation via WasCancelledFn
2158 if (cancel_fn && cancel_fn()) {
2159 cancel = true;
2160 result = false;
2161 return;
2162 }
2163 // Report progress via OnUpdateStatusFn
2164 if (update_fn && progress.ultotal > 0) {
2165 int percent = static_cast<int>((progress.ulnow * 100) / progress.ultotal);
2166 update_fn(PrintingStageUpload, percent, _u8L("Uploading..."));
2167 }
2168 })
2169 .perform_sync();
2170
2171 if (!result) {
2172 BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Upload failed: " << http_error;
2173 return false;
2174 }
2175
2176 return true;
2177}

References _u8L, Slic3r::http, join_url(), Slic3r::Http::post(), Slic3r::PrintingStageUpload, sanitize_filename(), Slic3r::Http::Progress::ulnow, Slic3r::Http::Progress::ultotal, and upload_gcode().

Referenced by start_local_print(), start_send_gcode_to_sdcard(), and upload_gcode().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ available_objects

std::set<std::string> Slic3r::MoonrakerPrinterAgent::available_objects
privateinherited

◆ connect_generation

std::atomic<uint64_t> Slic3r::MoonrakerPrinterAgent::connect_generation {0}
privateinherited

◆ connect_mutex

std::recursive_mutex Slic3r::MoonrakerPrinterAgent::connect_mutex
privateinherited

◆ connect_thread

std::thread Slic3r::MoonrakerPrinterAgent::connect_thread
privateinherited

◆ device_info

◆ last_print_state

std::string Slic3r::MoonrakerPrinterAgent::last_print_state
privateinherited

◆ m_cloud_agent

std::shared_ptr<ICloudServiceAgent> Slic3r::MoonrakerPrinterAgent::m_cloud_agent
privateinherited

◆ next_jsonrpc_id

std::atomic<int> Slic3r::MoonrakerPrinterAgent::next_jsonrpc_id {1}
privateinherited
215{1};

◆ on_local_connect_fn

OnLocalConnectedFn Slic3r::MoonrakerPrinterAgent::on_local_connect_fn
privateinherited

◆ on_local_message_fn

OnMessageFn Slic3r::MoonrakerPrinterAgent::on_local_message_fn
privateinherited

◆ on_message_fn

OnMessageFn Slic3r::MoonrakerPrinterAgent::on_message_fn
privateinherited

◆ on_printer_connected_fn

OnPrinterConnectedFn Slic3r::MoonrakerPrinterAgent::on_printer_connected_fn
privateinherited

◆ on_server_err_fn

OnServerErrFn Slic3r::MoonrakerPrinterAgent::on_server_err_fn
privateinherited

Referenced by set_server_callback().

◆ on_ssdp_msg_fn

OnMsgArrivedFn Slic3r::MoonrakerPrinterAgent::on_ssdp_msg_fn
privateinherited

◆ on_subscribe_failure_fn

GetSubscribeFailureFn Slic3r::MoonrakerPrinterAgent::on_subscribe_failure_fn
privateinherited

◆ on_user_message_fn

OnMessageFn Slic3r::MoonrakerPrinterAgent::on_user_message_fn
privateinherited

Referenced by set_on_user_message_fn().

◆ payload_mutex

std::recursive_mutex Slic3r::MoonrakerPrinterAgent::payload_mutex
mutableprivateinherited

◆ queue_on_main_fn

QueueOnMainFn Slic3r::MoonrakerPrinterAgent::queue_on_main_fn
privateinherited

◆ selected_machine

std::string Slic3r::MoonrakerPrinterAgent::selected_machine
privateinherited

◆ ssdp_announced_host

std::string Slic3r::MoonrakerPrinterAgent::ssdp_announced_host
privateinherited

◆ ssdp_announced_id

std::string Slic3r::MoonrakerPrinterAgent::ssdp_announced_id
privateinherited

◆ state_mutex

◆ status_cache

nlohmann::json Slic3r::MoonrakerPrinterAgent::status_cache
privateinherited

◆ STATUS_UPDATE_INTERVAL_MS

uint64_t Slic3r::MoonrakerPrinterAgent::STATUS_UPDATE_INTERVAL_MS = 1000
staticconstexprprivateinherited

Referenced by handle_ws_message().

◆ ws_abort

std::function<void()> Slic3r::MoonrakerPrinterAgent::ws_abort
privateinherited

◆ ws_abort_mutex

std::mutex Slic3r::MoonrakerPrinterAgent::ws_abort_mutex
privateinherited

◆ ws_last_dispatch_ms

std::atomic<uint64_t> Slic3r::MoonrakerPrinterAgent::ws_last_dispatch_ms {0}
privateinherited
228{0};

Referenced by connect_printer(), and handle_ws_message().

◆ ws_last_emit_ms

std::atomic<uint64_t> Slic3r::MoonrakerPrinterAgent::ws_last_emit_ms {0}
privateinherited

◆ ws_reconnect_requested

std::atomic<bool> Slic3r::MoonrakerPrinterAgent::ws_reconnect_requested {false}
privateinherited
219{false}; // Flag to trigger reconnection

Referenced by handle_ws_message(), and run_status_stream().

◆ ws_stop

std::atomic<bool> Slic3r::MoonrakerPrinterAgent::ws_stop {false}
privateinherited

◆ ws_thread

std::thread Slic3r::MoonrakerPrinterAgent::ws_thread
privateinherited

◆ ws_timings

struct Slic3r::MoonrakerPrinterAgent::StatusStreamTimings Slic3r::MoonrakerPrinterAgent::ws_timings
protectedinherited

Referenced by start_status_stream().


The documentation for this class was generated from the following files: