mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-01 04:41:32 +00:00
Removal of specific ORCA_PRINTER_AGENT_ID usage in DeviceManager/SelectMachine/SendMultiMachinePage. Generalized the capabilities under IPrinterAgent
This commit is contained in:
@@ -516,7 +516,8 @@ void AMSSetting::on_insert_material_read(wxCommandEvent &event)
|
||||
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
|
||||
bool remain_opt = m_checkbox_remain->GetValue();
|
||||
|
||||
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
|
||||
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
|
||||
UpdateByObj(m_obj);
|
||||
|
||||
m_sizer_Insert_material_tip_inline->Layout();
|
||||
Layout();
|
||||
@@ -542,7 +543,8 @@ void AMSSetting::on_starting_read(wxCommandEvent &event)
|
||||
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
|
||||
bool remain_opt = m_checkbox_remain->GetValue();
|
||||
|
||||
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
|
||||
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
|
||||
UpdateByObj(m_obj);
|
||||
|
||||
m_sizer_starting_tip_inline->Layout();
|
||||
Layout();
|
||||
@@ -556,7 +558,8 @@ void AMSSetting::on_remain(wxCommandEvent& event)
|
||||
bool start_read_opt = m_checkbox_starting_auto_read->GetValue();
|
||||
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
|
||||
bool remain_opt = m_checkbox_remain->GetValue();
|
||||
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
|
||||
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
|
||||
UpdateByObj(m_obj);
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
@@ -792,4 +795,4 @@ void AMSSettingArrangeAMSOrder::OnBtnRearrangeClicked(wxCommandEvent& event)
|
||||
}
|
||||
#endif
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -1702,8 +1702,8 @@ int MachineObject::check_resume_condition()
|
||||
}
|
||||
int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp, std::optional<int> extruder_id)
|
||||
{
|
||||
if (!orca_ams_command_supported("print.ams_change_filament")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_change_filament: connector does not advertise the command";
|
||||
if (!printer_supports_command("print.ams_change_filament")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_change_filament: printer agent does not support the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
json j;
|
||||
@@ -1749,8 +1749,8 @@ int MachineObject::command_ams_change_filament(bool load, std::string ams_id, st
|
||||
|
||||
int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read_opt, bool remain_flag)
|
||||
{
|
||||
if (!orca_ams_command_supported("print.ams_user_setting")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_user_settings: connector does not advertise the command";
|
||||
if (!printer_supports_command("print.ams_user_setting")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_user_settings: printer agent does not support the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
json j;
|
||||
@@ -1761,28 +1761,44 @@ int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read
|
||||
j["print"]["tray_read_option"] = tray_read_opt;
|
||||
j["print"]["calibrate_remain_flag"] = remain_flag;
|
||||
|
||||
m_fila_system->GetAmsSystemSetting().SetDetectOnInsertEnabled(tray_read_opt);
|
||||
m_fila_system->GetAmsSystemSetting().SetDetectOnPowerupEnabled(start_read_opt);
|
||||
m_fila_system->GetAmsSystemSetting().SetDetectRemainEnabled(remain_flag);
|
||||
ams_user_setting_start = time(nullptr);
|
||||
|
||||
return this->publish_json(j);
|
||||
const int rc = this->publish_json(j);
|
||||
if (rc == 0) {
|
||||
m_fila_system->GetAmsSystemSetting().SetDetectOnInsertEnabled(tray_read_opt);
|
||||
m_fila_system->GetAmsSystemSetting().SetDetectOnPowerupEnabled(start_read_opt);
|
||||
m_fila_system->GetAmsSystemSetting().SetDetectRemainEnabled(remain_flag);
|
||||
ams_user_setting_start = time(nullptr);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_calibrate(int ams_id)
|
||||
{
|
||||
if (!m_agent) return -1;
|
||||
if (!m_agent->owns_agent(printer_agent_id))
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
bool MachineObject::orca_ams_command_supported(const char* command) const
|
||||
bool MachineObject::printer_supports_command(const char* command) const
|
||||
{
|
||||
if (printer_agent_id != ORCA_PRINTER_AGENT_ID)
|
||||
return true;
|
||||
// fms is the AMS axis; the advertised command set is the per-command gate.
|
||||
if (!is_support_fms)
|
||||
if (!command || !m_agent)
|
||||
return false;
|
||||
return command != nullptr && supported_commands.count(command) != 0;
|
||||
return m_agent->supports_command(printer_agent_id, get_dev_id(), command);
|
||||
}
|
||||
|
||||
bool MachineObject::printer_uses_filament_mapping() const
|
||||
{
|
||||
if (!m_agent)
|
||||
return false;
|
||||
// An unrecorded owner goes through owns_agent() like any other, because
|
||||
// whichever agent is active is also the one formatting the payload. Only a
|
||||
// recorded owner that differs from the active agent is a mismatch.
|
||||
return m_agent->uses_filament_mapping(printer_agent_id);
|
||||
}
|
||||
|
||||
bool MachineObject::printer_supports_feature(const char* feature) const
|
||||
{
|
||||
return feature && m_agent && m_agent->supports_feature(printer_agent_id, get_dev_id(), feature);
|
||||
}
|
||||
|
||||
bool MachineObject::supports_extrusion_cali() const
|
||||
@@ -1793,10 +1809,8 @@ bool MachineObject::supports_extrusion_cali() const
|
||||
|
||||
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
|
||||
{
|
||||
// OrcaSonar: writing slot metadata is gated on the filament_slots capability.
|
||||
// Absent/false means unsupported; Bambu keeps the legacy behaviour.
|
||||
if (printer_agent_id == ORCA_PRINTER_AGENT_ID && !is_support_filament_slots) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_filament_settings: printer does not advertise filament_slots";
|
||||
if (!printer_supports_command("print.ams_filament_setting")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_filament_settings: printer agent does not support slot metadata updates";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
@@ -1834,11 +1848,8 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
|
||||
int MachineObject::command_ams_refresh_rfid(int ams_id, int slot_id)
|
||||
{
|
||||
if (!m_agent) return -1;
|
||||
// OrcaSonar: RFID read requires the connector to advertise the
|
||||
// `print.ams_get_rfid` command (which already implies fms plus the macro).
|
||||
// Unknown capability is not support; Bambu is untouched.
|
||||
if (!orca_ams_command_supported("print.ams_get_rfid")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_refresh_rfid: connector does not advertise the command";
|
||||
if (!printer_supports_command("print.ams_get_rfid")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_refresh_rfid: printer agent does not support the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), ams_id, slot_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
@@ -1854,10 +1865,8 @@ int MachineObject::command_start_camera()
|
||||
int MachineObject::command_ams_select_tray(std::string tray_id)
|
||||
{
|
||||
if (!m_agent) return -1;
|
||||
// OrcaSonar: this publishes the same macro-backed print.ams_change_filament
|
||||
// as command_ams_change_filament, so it takes the same capability gate.
|
||||
if (!orca_ams_command_supported("print.ams_change_filament")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_select_tray: connector does not advertise the command";
|
||||
if (!printer_supports_command("print.ams_change_filament")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_select_tray: printer agent does not support the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
return command_with_dialog(m_agent->command_ams_select_tray(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
@@ -1865,8 +1874,8 @@ int MachineObject::command_ams_select_tray(std::string tray_id)
|
||||
|
||||
int MachineObject::command_ams_control(std::string action)
|
||||
{
|
||||
if (!orca_ams_command_supported("print.ams_control")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_control: connector does not advertise the command";
|
||||
if (!printer_supports_command("print.ams_control")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_control: printer agent does not support the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
if (action == "resume" && check_resume_condition()) return 0;
|
||||
@@ -1884,8 +1893,8 @@ int MachineObject::command_ams_control(std::string action)
|
||||
|
||||
int MachineObject::command_ams_drying_stop()
|
||||
{
|
||||
if (!orca_ams_command_supported("print.auto_stop_ams_dry")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_drying_stop: connector does not advertise the command";
|
||||
if (!printer_supports_command("print.auto_stop_ams_dry")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "command_ams_drying_stop: printer agent does not support the command";
|
||||
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
json j;
|
||||
@@ -2838,7 +2847,9 @@ bool MachineObject::is_camera_busy_off()
|
||||
int MachineObject::publish_json(const json& json_item, int qos, int flag)
|
||||
{
|
||||
int rtn = 0;
|
||||
if (is_lan_mode_printer()) {
|
||||
if (m_agent && !m_agent->owns_agent(printer_agent_id)) {
|
||||
rtn = ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
|
||||
} else if (is_lan_mode_printer()) {
|
||||
rtn = local_publish_json(json_item.dump(), qos, flag);
|
||||
} else {
|
||||
rtn = cloud_publish_json(json_item.dump(), qos, flag);
|
||||
@@ -5577,14 +5588,10 @@ void MachineObject::parse_new_info2(const json& info)
|
||||
return;
|
||||
const auto flags_it = capabilities_it->find("flags");
|
||||
const bool has_flags = flags_it != capabilities_it->end() && flags_it->is_object();
|
||||
// Bambu keeps the legacy behavior: a reply without flags is ignored. Orca
|
||||
// connector-scope features/commands must still be parsed, so fall through
|
||||
// with an empty flags object (every parse_bool below then no-ops).
|
||||
if (!has_flags && printer_agent_id != ORCA_PRINTER_AGENT_ID)
|
||||
if (!has_flags)
|
||||
return;
|
||||
static const json empty_flags = json::object();
|
||||
const json& flags = has_flags ? *flags_it : empty_flags;
|
||||
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: OrcaSonar capability flags=" << flags.dump();
|
||||
const json& flags = *flags_it;
|
||||
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: capability flags=" << flags.dump();
|
||||
|
||||
auto parse_bool = [&flags](const char* name, bool& target) {
|
||||
const auto it = flags.find(name);
|
||||
@@ -5649,64 +5656,6 @@ void MachineObject::parse_new_info2(const json& info)
|
||||
|
||||
m_config->ParseConfig(device_config);
|
||||
m_fan->ParseV2_0(fan_config);
|
||||
|
||||
// OrcaSonar connector-scope capabilities. Absent or non-boolean means
|
||||
// unsupported. A reply without `flags` still parses them (the fall-through
|
||||
// above); Bambu (bbl) devices never enter this branch and keep the
|
||||
// flags-only path.
|
||||
if (printer_agent_id == ORCA_PRINTER_AGENT_ID) {
|
||||
auto parse_features = [this](const json& features) {
|
||||
if (!features.is_object())
|
||||
return;
|
||||
auto set_flag = [&features](const char* name, bool& target) {
|
||||
const auto it = features.find(name);
|
||||
if (it != features.end() && it->is_boolean())
|
||||
target = it->get<bool>();
|
||||
};
|
||||
set_flag("fms", is_support_fms);
|
||||
set_flag("filament_slots", is_support_filament_slots);
|
||||
set_flag("filament_mapping", is_support_filament_mapping);
|
||||
};
|
||||
|
||||
auto parse_commands = [this](const json& commands) {
|
||||
if (!commands.is_array())
|
||||
return;
|
||||
for (const auto& command : commands) {
|
||||
if (command.is_string())
|
||||
supported_commands.insert(command.get<std::string>());
|
||||
}
|
||||
};
|
||||
|
||||
// Clear before filling so a reply without commands does not retain stale values.
|
||||
supported_commands.clear();
|
||||
is_support_fms = false;
|
||||
is_support_filament_slots = false;
|
||||
is_support_filament_mapping = false;
|
||||
|
||||
const auto supported_features_it = info.find("supported_features");
|
||||
if (supported_features_it != info.end())
|
||||
parse_features(*supported_features_it);
|
||||
|
||||
const auto supported_commands_it = info.find("supported_commands");
|
||||
if (supported_commands_it != info.end())
|
||||
parse_commands(*supported_commands_it);
|
||||
|
||||
const auto protocol_it = capabilities_it->find("protocol");
|
||||
if (protocol_it != capabilities_it->end() && protocol_it->is_object()) {
|
||||
const auto protocol_features_it = protocol_it->find("features");
|
||||
if (protocol_features_it != protocol_it->end())
|
||||
parse_features(*protocol_features_it);
|
||||
|
||||
const auto protocol_commands_it = protocol_it->find("supported_commands");
|
||||
if (protocol_commands_it != protocol_it->end())
|
||||
parse_commands(*protocol_commands_it);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: fms=" << is_support_fms
|
||||
<< " filament_slots=" << is_support_filament_slots
|
||||
<< " filament_mapping=" << is_support_filament_mapping
|
||||
<< " supported_commands=" << supported_commands.size();
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_hex_digit(char c) {
|
||||
|
||||
@@ -653,13 +653,6 @@ public:
|
||||
bool is_support_partskip{false};
|
||||
bool is_support_refresh_nozzle{false};
|
||||
|
||||
// OrcaSonar connector-scope capabilities (printer_agent_id == "orca"). Parsed from the
|
||||
// get_capabilities reply; never consulted on Bambu paths.
|
||||
bool is_support_fms{false};
|
||||
bool is_support_filament_slots{false};
|
||||
bool is_support_filament_mapping{false};
|
||||
std::set<std::string> supported_commands;
|
||||
|
||||
// refine printer function options
|
||||
bool is_support_spaghetti_detection{false};
|
||||
bool is_support_purgechutepileup_detection{false};
|
||||
@@ -801,10 +794,9 @@ public:
|
||||
int command_refresh_nozzle();
|
||||
int command_set_chamber(int temp);
|
||||
int check_resume_condition();
|
||||
// OrcaSonar: true when a macro-backed AMS command may be sent. Bambu paths
|
||||
// always allow; an Orca device must have advertised the command (unknown
|
||||
// capability is not support).
|
||||
bool orca_ams_command_supported(const char* command) const;
|
||||
bool printer_supports_command(const char* command) const;
|
||||
bool printer_supports_feature(const char* feature) const;
|
||||
bool printer_uses_filament_mapping() const;
|
||||
// The per-tray flow-dynamics K/N records are a Bambu firmware feature. Other
|
||||
// agents have no printer-side calibration to read or write, so the AMS UI must
|
||||
// not offer K/N for them.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include "DeviceManager.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
@@ -70,5 +71,35 @@ inline bool has_any_mapped_target(const std::vector<FilamentInfo>& result)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Refusal reason for a printer agent that serializes lane selection into
|
||||
// print.gcode_file's per-print filament_mapping field.
|
||||
enum class MappingSendError {
|
||||
none, // no refusal; ams_mapping2 is normalized for send
|
||||
unsupported, // a mapping is engaged but the connector did not advertise it
|
||||
incomplete, // a used filament has no target while others do
|
||||
};
|
||||
|
||||
// Applies the send-time mapping policy and normalizes ams_mapping2 in place.
|
||||
// Agents that do not speak filament_mapping keep their legacy payload untouched.
|
||||
inline MappingSendError prepare_filament_mapping_for_send(MachineObject* obj,
|
||||
std::string& ams_mapping2,
|
||||
const std::vector<FilamentInfo>& mapping_result)
|
||||
{
|
||||
if (!obj || !obj->printer_uses_filament_mapping())
|
||||
return MappingSendError::none;
|
||||
|
||||
// A device with no AMS units has one source, the external spool:
|
||||
// auto-mapping force-selects it, which is not a lane choice. Drop it before
|
||||
// the capability gate so it cannot refuse a print nobody mapped.
|
||||
drop_forced_external_selection(obj->HasAms(), ams_mapping2);
|
||||
|
||||
if (!obj->printer_supports_feature("filament_mapping") && has_engaged_filament_mapping(ams_mapping2))
|
||||
return MappingSendError::unsupported;
|
||||
if (has_any_mapped_target(mapping_result) && has_used_filament_without_target(mapping_result))
|
||||
return MappingSendError::incomplete;
|
||||
return MappingSendError::none;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -3537,27 +3537,26 @@ void SelectMachineDialog::on_send_print()
|
||||
|
||||
get_ams_mapping_result(ams_mapping_array,ams_mapping_array2, ams_mapping_info);
|
||||
|
||||
// A device with no AMS unit has one source, the external spool: OrcaSlicer's
|
||||
// auto-mapping force-selects it, which is not a lane choice. Bambu unchanged.
|
||||
if (obj_->printer_agent_id == ORCA_PRINTER_AGENT_ID)
|
||||
drop_forced_external_selection(obj_->HasAms(), ams_mapping_array2);
|
||||
|
||||
// OrcaSonar: a mapped print requires the connector to advertise
|
||||
// filament_mapping. Refuse rather than start with the map silently dropped;
|
||||
// Bambu keeps its behavior.
|
||||
if (obj_->printer_agent_id == ORCA_PRINTER_AGENT_ID) {
|
||||
if (!obj_->is_support_filament_mapping && has_engaged_filament_mapping(ams_mapping_array2)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "print_job: connector does not advertise filament_mapping; refusing mapped print";
|
||||
m_status_bar->set_status_text(_L("AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."));
|
||||
Enable_Send_Button(true);
|
||||
return;
|
||||
}
|
||||
if (has_any_mapped_target(m_ams_mapping_result) && has_used_filament_without_target(m_ams_mapping_result)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "print_job: a used filament has no AMS target; refusing print";
|
||||
m_status_bar->set_status_text(_L("A filament used by this print has no AMS mapping. Assign it before printing."));
|
||||
Enable_Send_Button(true);
|
||||
return;
|
||||
}
|
||||
// The policy lives in FilamentMappingUtils.hpp; this dialog only differs
|
||||
// from the multi-device page in how it reports a refusal.
|
||||
switch (prepare_filament_mapping_for_send(obj_, ams_mapping_array2, m_ams_mapping_result)) {
|
||||
case MappingSendError::unsupported:
|
||||
BOOST_LOG_TRIVIAL(warning) << "print_job: connector does not advertise filament_mapping; refusing mapped print";
|
||||
m_status_bar->set_status_text(_L("AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."));
|
||||
Enable_Send_Button(true);
|
||||
return;
|
||||
case MappingSendError::incomplete:
|
||||
BOOST_LOG_TRIVIAL(warning) << "print_job: a used filament has no AMS target; refusing print";
|
||||
m_status_bar->set_status_text(_L("A filament used by this print has no AMS mapping. Assign it before printing."));
|
||||
Enable_Send_Button(true);
|
||||
return;
|
||||
case MappingSendError::none:
|
||||
break;
|
||||
default:
|
||||
// A refusal added without a handler here must not silently send.
|
||||
BOOST_LOG_TRIVIAL(warning) << "print_job: unrecognized mapping refusal; refusing print";
|
||||
Enable_Send_Button(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_print_type == PrintFromType::FROM_NORMAL) {
|
||||
|
||||
@@ -744,30 +744,29 @@ void SendMultiMachinePage::on_send(wxCommandEvent& event)
|
||||
|
||||
if (!wxGetApp().is_blocking_printing(obj)) {
|
||||
PrintParams params = request_params(obj);
|
||||
// A device with no AMS unit has one source, the external spool: OrcaSlicer's
|
||||
// auto-mapping force-selects it, which is not a lane choice.
|
||||
if (obj->printer_agent_id == ORCA_PRINTER_AGENT_ID)
|
||||
drop_forced_external_selection(obj->HasAms(), params.ams_mapping2);
|
||||
// OrcaSonar: a mapped print requires the connector to advertise
|
||||
// filament_mapping, and a partially mapped print must not silently drop a
|
||||
// used filament. Any entry the serializer would put on the wire engages the
|
||||
// gate, external slots ({255,0}/{254,0}) included; the extra-spool branch
|
||||
// rewrites to exactly those. Bambu is unchanged.
|
||||
if (obj->printer_agent_id == ORCA_PRINTER_AGENT_ID) {
|
||||
if (!obj->is_support_filament_mapping && has_engaged_filament_mapping(params.ams_mapping2)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SendMultiMachinePage: connector does not advertise filament_mapping; refusing mapped print for "
|
||||
<< obj->get_dev_id();
|
||||
MessageDialog msg_wingow(nullptr, _L("AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."), "", wxICON_WARNING | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
return;
|
||||
}
|
||||
if (params.task_use_ams && has_any_mapped_target(m_ams_mapping_result) &&
|
||||
has_used_filament_without_target(m_ams_mapping_result)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SendMultiMachinePage: a used filament has no target; refusing print for " << obj->get_dev_id();
|
||||
MessageDialog msg_wingow(nullptr, _L("A filament used by this print has no AMS mapping. Assign it before printing."), "", wxICON_WARNING | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
return;
|
||||
}
|
||||
switch (prepare_filament_mapping_for_send(obj, params.ams_mapping2, m_ams_mapping_result)) {
|
||||
case MappingSendError::unsupported:
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "SendMultiMachinePage: connector does not advertise filament_mapping; refusing mapped print for "
|
||||
<< obj->get_dev_id();
|
||||
MessageDialog unavailable_msg(nullptr, _L("AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."), "", wxICON_WARNING | wxOK);
|
||||
unavailable_msg.ShowModal();
|
||||
return;
|
||||
}
|
||||
case MappingSendError::incomplete:
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "SendMultiMachinePage: a used filament has no target; refusing print for " << obj->get_dev_id();
|
||||
MessageDialog incomplete_msg(nullptr, _L("A filament used by this print has no AMS mapping. Assign it before printing."), "", wxICON_WARNING | wxOK);
|
||||
incomplete_msg.ShowModal();
|
||||
return;
|
||||
}
|
||||
case MappingSendError::none:
|
||||
break;
|
||||
default:
|
||||
// A refusal added without a handler here must not silently send.
|
||||
BOOST_LOG_TRIVIAL(warning) << "SendMultiMachinePage: unrecognized mapping refusal for "
|
||||
<< obj->get_dev_id();
|
||||
return;
|
||||
}
|
||||
print_params.push_back(params);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,16 @@ public:
|
||||
*/
|
||||
virtual int send_message(std::string dev_id, std::string json_str, int qos, int flag) = 0;
|
||||
|
||||
// Capability queries are per-device because one agent may own many printers.
|
||||
// Legacy agents keep their existing behavior unless they override these.
|
||||
virtual bool supports_command(const std::string& /*dev_id*/, const std::string& /*command*/) const { return true; }
|
||||
virtual bool supports_feature(const std::string& /*dev_id*/, const std::string& /*feature*/) const { return false; }
|
||||
|
||||
// Whether this agent serializes AMS lane selection into print.gcode_file's
|
||||
// per-print filament_mapping field. A dialect question, not a capability one:
|
||||
// the no-AMS external-spool normalization runs before capabilities are known.
|
||||
virtual bool uses_filament_mapping() const { return false; }
|
||||
|
||||
// why: gcode is firmware dialect, not a waist concept - commands whose body is Bambu-dialect
|
||||
// gcode live on the agent that speaks it; the default is an honest refusal that MachineObject's
|
||||
// publish funnel turns into a dialog.
|
||||
|
||||
@@ -793,6 +793,36 @@ int NetworkAgent::send_message(std::string dev_id, std::string json_str, int qos
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool NetworkAgent::owns_agent(const std::string& expected_agent_id) const
|
||||
{
|
||||
if (!m_printer_agent)
|
||||
return false;
|
||||
return expected_agent_id.empty() || m_printer_agent->get_agent_info().id == expected_agent_id;
|
||||
}
|
||||
|
||||
bool NetworkAgent::supports_command(const std::string& expected_agent_id, const std::string& dev_id,
|
||||
const std::string& command) const
|
||||
{
|
||||
if (!owns_agent(expected_agent_id))
|
||||
return false;
|
||||
return m_printer_agent->supports_command(dev_id, command);
|
||||
}
|
||||
|
||||
bool NetworkAgent::supports_feature(const std::string& expected_agent_id, const std::string& dev_id,
|
||||
const std::string& feature) const
|
||||
{
|
||||
if (!owns_agent(expected_agent_id))
|
||||
return false;
|
||||
return m_printer_agent->supports_feature(dev_id, feature);
|
||||
}
|
||||
|
||||
bool NetworkAgent::uses_filament_mapping(const std::string& expected_agent_id) const
|
||||
{
|
||||
if (!owns_agent(expected_agent_id))
|
||||
return false;
|
||||
return m_printer_agent->uses_filament_mapping();
|
||||
}
|
||||
|
||||
int NetworkAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
|
||||
@@ -150,6 +150,10 @@ public:
|
||||
int set_on_local_message_fn(OnMessageFn fn);
|
||||
int set_server_callback(OnServerErrFn fn);
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag);
|
||||
bool owns_agent(const std::string& expected_agent_id) const;
|
||||
bool supports_command(const std::string& expected_agent_id, const std::string& dev_id, const std::string& command) const;
|
||||
bool supports_feature(const std::string& expected_agent_id, const std::string& dev_id, const std::string& feature) const;
|
||||
bool uses_filament_mapping(const std::string& expected_agent_id) const;
|
||||
int command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode);
|
||||
int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode);
|
||||
int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode);
|
||||
|
||||
@@ -43,19 +43,43 @@ namespace {
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Per-device filament_mapping capability, mirrored from the get_capabilities
|
||||
// reply in merge_capabilities and read by start_sdcard_print so the field is
|
||||
// refused defensively when the connector never advertised it.
|
||||
std::mutex g_filament_mapping_mutex;
|
||||
std::unordered_map<std::string, bool> g_filament_mapping_cache;
|
||||
|
||||
// True only when the connector's get_capabilities reply advertised
|
||||
// filament_mapping for this device. Unknown is not support.
|
||||
bool filament_mapping_advertised(const std::string& dev_id)
|
||||
struct OrcaDeviceCapabilities
|
||||
{
|
||||
std::lock_guard<std::mutex> l(g_filament_mapping_mutex);
|
||||
const auto it = g_filament_mapping_cache.find(dev_id);
|
||||
return it != g_filament_mapping_cache.end() && it->second;
|
||||
bool fms = false;
|
||||
bool filament_slots = false;
|
||||
bool filament_mapping = false;
|
||||
std::set<std::string> supported_commands;
|
||||
};
|
||||
|
||||
std::mutex g_capabilities_mutex;
|
||||
std::unordered_map<std::string, OrcaDeviceCapabilities> g_capabilities;
|
||||
|
||||
OrcaDeviceCapabilities capabilities_for(const std::string& dev_id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_capabilities_mutex);
|
||||
const auto it = g_capabilities.find(dev_id);
|
||||
return it == g_capabilities.end() ? OrcaDeviceCapabilities{} : it->second;
|
||||
}
|
||||
|
||||
bool command_supported(const OrcaDeviceCapabilities& capabilities, const std::string& command)
|
||||
{
|
||||
if (command == "print.ams_filament_setting")
|
||||
return capabilities.filament_slots;
|
||||
|
||||
static const std::set<std::string> macro_backed_commands = {
|
||||
"print.ams_change_filament",
|
||||
"print.ams_control",
|
||||
"print.ams_user_setting",
|
||||
"print.ams_get_rfid",
|
||||
"print.auto_stop_ams_dry",
|
||||
};
|
||||
if (macro_backed_commands.count(command) != 0)
|
||||
return capabilities.fms && capabilities.supported_commands.count(command) != 0;
|
||||
|
||||
if (command.rfind("print.ams_", 0) == 0)
|
||||
return capabilities.supported_commands.count(command) != 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// params.filename is normally the exported .3mf archive; the sliced G-code sits
|
||||
@@ -462,6 +486,14 @@ OrcaPrinterAgent::~OrcaPrinterAgent()
|
||||
++m_lan_generation; // fence any late worker callback
|
||||
++m_cloud_generation;
|
||||
|
||||
std::string lan_dev_id;
|
||||
std::string cloud_dev_id;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
lan_dev_id = m_lan_dev_id;
|
||||
cloud_dev_id = selected_machine;
|
||||
}
|
||||
|
||||
// Drop the cloud status callback before anything else: it holds `this`, and the
|
||||
// cloud agent outlives the printer agent (NetworkAgent::set_printer_agent swaps
|
||||
// the printer agent while m_cloud_agents persist).
|
||||
@@ -497,6 +529,8 @@ OrcaPrinterAgent::~OrcaPrinterAgent()
|
||||
std::lock_guard<std::mutex> l(state_mutex);
|
||||
lan_mqtt_connection.reset();
|
||||
}
|
||||
forget_device_capabilities(lan_dev_id);
|
||||
forget_device_capabilities(cloud_dev_id);
|
||||
}
|
||||
|
||||
OrcaCloudServiceAgent* OrcaPrinterAgent::get_orca_cloud_agent()
|
||||
@@ -604,31 +638,50 @@ std::string OrcaPrinterAgent::merge_capabilities(const std::string& dev_id, cons
|
||||
nozzle_diameter_cache[dev_id] = nozzle_dia;
|
||||
}
|
||||
|
||||
// Per-device filament_mapping capability. Both feature maps carry it;
|
||||
// either being true means the connector advertised it.
|
||||
bool mapping_advertised = false;
|
||||
{
|
||||
const auto top_features = info_it->find("supported_features");
|
||||
if (top_features != info_it->end() && top_features->is_object()) {
|
||||
const auto it = top_features->find("filament_mapping");
|
||||
if (it != top_features->end() && it->is_boolean())
|
||||
mapping_advertised = it->get<bool>();
|
||||
// Connector capabilities are retained per device for command dispatch
|
||||
// and GUI capability queries. A fresh reply replaces stale support.
|
||||
OrcaDeviceCapabilities device_capabilities;
|
||||
auto parse_features = [&device_capabilities](const nlohmann::json& features) {
|
||||
if (!features.is_object())
|
||||
return;
|
||||
auto read_bool = [&features](const char* key, bool& output) {
|
||||
const auto it = features.find(key);
|
||||
if (it != features.end() && it->is_boolean())
|
||||
output = it->get<bool>();
|
||||
};
|
||||
read_bool("fms", device_capabilities.fms);
|
||||
read_bool("filament_slots", device_capabilities.filament_slots);
|
||||
read_bool("filament_mapping", device_capabilities.filament_mapping);
|
||||
};
|
||||
auto parse_commands = [&device_capabilities](const nlohmann::json& commands) {
|
||||
if (!commands.is_array())
|
||||
return;
|
||||
for (const auto& item : commands) {
|
||||
if (item.is_string())
|
||||
device_capabilities.supported_commands.insert(item.get<std::string>());
|
||||
}
|
||||
if (!mapping_advertised && caps_it != info_it->end() && caps_it->is_object()) {
|
||||
const auto protocol_it = caps_it->find("protocol");
|
||||
if (protocol_it != caps_it->end() && protocol_it->is_object()) {
|
||||
const auto features_it = protocol_it->find("features");
|
||||
if (features_it != protocol_it->end() && features_it->is_object()) {
|
||||
const auto it = features_it->find("filament_mapping");
|
||||
if (it != features_it->end() && it->is_boolean())
|
||||
mapping_advertised = it->get<bool>();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const auto top_features = info_it->find("supported_features");
|
||||
if (top_features != info_it->end())
|
||||
parse_features(*top_features);
|
||||
const auto top_commands = info_it->find("supported_commands");
|
||||
if (top_commands != info_it->end())
|
||||
parse_commands(*top_commands);
|
||||
if (caps_it != info_it->end() && caps_it->is_object()) {
|
||||
const auto protocol_it = caps_it->find("protocol");
|
||||
if (protocol_it != caps_it->end() && protocol_it->is_object()) {
|
||||
const auto protocol_features = protocol_it->find("features");
|
||||
if (protocol_features != protocol_it->end())
|
||||
parse_features(*protocol_features);
|
||||
const auto protocol_commands = protocol_it->find("supported_commands");
|
||||
if (protocol_commands != protocol_it->end())
|
||||
parse_commands(*protocol_commands);
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> l(g_filament_mapping_mutex);
|
||||
g_filament_mapping_cache[dev_id] = mapping_advertised;
|
||||
std::lock_guard<std::mutex> lock(g_capabilities_mutex);
|
||||
g_capabilities[dev_id] = std::move(device_capabilities);
|
||||
}
|
||||
// The capabilities reply itself is forwarded unchanged.
|
||||
}
|
||||
@@ -656,6 +709,14 @@ std::string OrcaPrinterAgent::merge_capabilities(const std::string& dev_id, cons
|
||||
return modified ? envelope.dump() : payload;
|
||||
}
|
||||
|
||||
void OrcaPrinterAgent::forget_device_capabilities(const std::string& dev_id)
|
||||
{
|
||||
if (dev_id.empty())
|
||||
return;
|
||||
std::lock_guard<std::mutex> lock(g_capabilities_mutex);
|
||||
g_capabilities.erase(dev_id);
|
||||
}
|
||||
|
||||
void OrcaPrinterAgent::deliver_to_sink(const std::string& dev_id, const std::string& payload, bool local)
|
||||
{
|
||||
parse_ipcam_info(dev_id, payload);
|
||||
@@ -743,6 +804,56 @@ void OrcaPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud
|
||||
int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int /*qos*/, int /*flag*/)
|
||||
{ return route_send(/*is_lan=*/false, dev_id, json_str); }
|
||||
|
||||
bool OrcaPrinterAgent::supports_command(const std::string& dev_id, const std::string& command) const
|
||||
{
|
||||
return command_supported(capabilities_for(dev_id), command);
|
||||
}
|
||||
|
||||
bool OrcaPrinterAgent::supports_feature(const std::string& dev_id, const std::string& feature) const
|
||||
{
|
||||
const OrcaDeviceCapabilities capabilities = capabilities_for(dev_id);
|
||||
if (feature == "fms")
|
||||
return capabilities.fms;
|
||||
if (feature == "filament_slots")
|
||||
return capabilities.filament_slots;
|
||||
if (feature == "filament_mapping")
|
||||
return capabilities.filament_mapping;
|
||||
return false;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::prepare_outgoing_request(const std::string& dev_id, const std::string& payload,
|
||||
std::string& command, std::string& prepared) const
|
||||
{
|
||||
command = "<unparsed>";
|
||||
prepared = payload;
|
||||
nlohmann::json envelope = nlohmann::json::parse(payload, nullptr, false);
|
||||
if (envelope.is_discarded() || !envelope.is_object())
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
|
||||
nlohmann::json* print = nullptr;
|
||||
for (const char* namespace_name : {"pushing", "info", "print", "system", "camera", "xcam", "upgrade", "event", "files"}) {
|
||||
const auto namespace_it = envelope.find(namespace_name);
|
||||
if (namespace_it == envelope.end() || !namespace_it->is_object())
|
||||
continue;
|
||||
const auto command_it = namespace_it->find("command");
|
||||
if (command_it == namespace_it->end() || !command_it->is_string())
|
||||
continue;
|
||||
command = std::string(namespace_name) + "." + command_it->get<std::string>();
|
||||
if (std::string(namespace_name) == "print")
|
||||
print = &*namespace_it;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!supports_command(dev_id, command))
|
||||
return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
|
||||
|
||||
if (command == "print.ams_filament_setting" && print && print->contains("setting_id")) {
|
||||
print->erase("setting_id");
|
||||
prepared = envelope.dump();
|
||||
}
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int tray_id, int sequence_id, bool lan_mode)
|
||||
{
|
||||
(void) ams_id;
|
||||
@@ -1053,6 +1164,7 @@ int OrcaPrinterAgent::connect_printer(const PrinterConnectionParams& params)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: connect_printer rejected unparsable LAN endpoint dev_ip=" << params.host;
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
}
|
||||
forget_device_capabilities(params.dev_id);
|
||||
disconnect_printer();
|
||||
const uint64_t gen = ++m_lan_generation;
|
||||
|
||||
@@ -1109,6 +1221,7 @@ int OrcaPrinterAgent::connect_printer(const PrinterConnectionParams& params)
|
||||
on_connected(params.dev_id, conn, gen);
|
||||
dispatch_local_connect(ConnectStatusOk, params.dev_id, "0");
|
||||
} else if (!connected && !initial) {
|
||||
forget_device_capabilities(params.dev_id);
|
||||
dispatch_local_connect(ConnectStatusLost, params.dev_id, "connection_lost");
|
||||
}
|
||||
});
|
||||
@@ -1160,6 +1273,7 @@ int OrcaPrinterAgent::disconnect_printer()
|
||||
}
|
||||
current_connection = m_current_connection;
|
||||
}
|
||||
forget_device_capabilities(prev_dev);
|
||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN disconnect generation=" << m_lan_generation.load() << " previous_dev_id=" << prev_dev
|
||||
<< " had_connection=" << (doomed ? "yes" : "no")
|
||||
<< " connected=" << (doomed && doomed->is_connected() ? "yes" : "no")
|
||||
@@ -1184,31 +1298,21 @@ int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string js
|
||||
|
||||
int OrcaPrinterAgent::route_send(bool is_lan, const std::string& dev_id, const std::string& json_str)
|
||||
{
|
||||
std::string command = "<unparsed>";
|
||||
try {
|
||||
const nlohmann::json envelope = nlohmann::json::parse(json_str);
|
||||
for (const char* namespace_name : {"pushing", "info", "print", "system", "camera", "xcam", "upgrade", "event", "files"}) {
|
||||
const auto namespace_it = envelope.find(namespace_name);
|
||||
if (namespace_it != envelope.end() && namespace_it->is_object()) {
|
||||
const auto command_it = namespace_it->find("command");
|
||||
if (command_it != namespace_it->end() && command_it->is_string()) {
|
||||
command = std::string(namespace_name) + "." + command_it->get<std::string>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
// Preserve the transport's existing behavior for malformed payloads;
|
||||
// the printer will report the protocol error asynchronously.
|
||||
}
|
||||
std::string command;
|
||||
std::string prepared;
|
||||
const int prepare_rc = prepare_outgoing_request(dev_id, json_str, command, prepared);
|
||||
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::route_send is_lan=" << is_lan << " dev_id=" << dev_id << " command=" << command
|
||||
<< " payload_bytes=" << json_str.size();
|
||||
if (dev_id.empty())
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
if (prepare_rc != BAMBU_NETWORK_SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: refusing unsupported command " << command << " for dev_id=" << dev_id;
|
||||
return prepare_rc;
|
||||
}
|
||||
OrcaMqttConnection* conn = get_appropriate_mqtt_connection(is_lan);
|
||||
if (!conn)
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
const bool queued = conn->send_request(dev_id, json_str);
|
||||
const bool queued = conn->send_request(dev_id, prepared);
|
||||
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::route_send command=" << command << " queued=" << queued << " is_lan=" << is_lan
|
||||
<< " dev_id=" << dev_id;
|
||||
return queued ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED;
|
||||
@@ -1338,19 +1442,20 @@ int OrcaPrinterAgent::set_user_selected_machine(std::string dev_id)
|
||||
}
|
||||
current_connection = m_current_connection;
|
||||
}
|
||||
// Fence in-flight cloud callbacks before discarding their capability state.
|
||||
const uint64_t gen = ++m_cloud_generation;
|
||||
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::set_user_selected_machine: previous=" << previous << " new=" << dev_id
|
||||
<< " cloud=" << (cloud ? "set" : "<null>") << " transport=" << connection_type_name(previous_connection) << "->"
|
||||
<< connection_type_name(current_connection);
|
||||
if (previous != dev_id || previous_connection != current_connection) {
|
||||
forget_device_capabilities(previous);
|
||||
forget_device_capabilities(dev_id);
|
||||
}
|
||||
if (!cloud) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent::set_user_selected_machine: no Orca cloud agent";
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
// Bump ONCE at the top for any change (select or deselect) so a deselect also
|
||||
// fences an in-flight configure thread started by the previous selection. This is
|
||||
// the CLOUD epoch only — a cloud selection must not fence a live LAN session.
|
||||
const uint64_t gen = ++m_cloud_generation;
|
||||
|
||||
auto* conn = cloud->get_mqtt_connection();
|
||||
if (!previous.empty()) {
|
||||
cloud->del_subscribe({previous});
|
||||
@@ -1380,8 +1485,12 @@ int OrcaPrinterAgent::set_user_selected_machine(std::string dev_id)
|
||||
return; // superseded before we ran: do not raise a socket nobody owns
|
||||
|
||||
auto state_handler = [this](bool connected, bool initial) {
|
||||
if (!connected || initial)
|
||||
if (initial)
|
||||
return;
|
||||
if (!connected) {
|
||||
forget_device_capabilities(get_user_selected_machine());
|
||||
return;
|
||||
}
|
||||
auto* current_cloud = get_orca_cloud_agent();
|
||||
OrcaMqttConnection* current_conn = current_cloud ? current_cloud->get_mqtt_connection() : nullptr;
|
||||
const std::string selected = get_user_selected_machine();
|
||||
@@ -1397,6 +1506,12 @@ int OrcaPrinterAgent::set_user_selected_machine(std::string dev_id)
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::unbind(std::string dev_id)
|
||||
{
|
||||
forget_device_capabilities(dev_id);
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Agent Information
|
||||
// ============================================================================
|
||||
@@ -1711,7 +1826,7 @@ int OrcaPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn up
|
||||
// plugin). Never start a mapped print with the map silently dropped.
|
||||
const nlohmann::json filament_mapping = build_filament_mapping(params.ams_mapping2);
|
||||
if (!filament_mapping.empty()) {
|
||||
if (!filament_mapping_advertised(params.dev_id)) {
|
||||
if (!supports_feature(params.dev_id, "filament_mapping")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: refusing mapped print, connector does not advertise filament_mapping";
|
||||
return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ public:
|
||||
|
||||
// Communication
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
bool supports_command(const std::string& dev_id, const std::string& command) const override;
|
||||
bool supports_feature(const std::string& dev_id, const std::string& feature) const override;
|
||||
bool uses_filament_mapping() const override { return true; }
|
||||
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;
|
||||
@@ -53,6 +56,7 @@ public:
|
||||
// Machine Selection
|
||||
std::string get_user_selected_machine() override;
|
||||
int set_user_selected_machine(std::string dev_id) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
|
||||
/**
|
||||
* Get agent information.
|
||||
@@ -129,6 +133,9 @@ protected:
|
||||
// once parse_json reads the Orca dialect natively. See the definition for the
|
||||
// per-rule detail. Returns the payload unchanged when no rule applies.
|
||||
std::string merge_capabilities(const std::string& dev_id, const std::string& payload);
|
||||
void forget_device_capabilities(const std::string& dev_id);
|
||||
int prepare_outgoing_request(const std::string& dev_id, const std::string& payload,
|
||||
std::string& command, std::string& prepared) const;
|
||||
|
||||
// Report the asynchronous LAN connection state using the same callback contract as
|
||||
// the other printer agents. The transport result cannot be returned by
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <slic3r/GUI/DeviceCore/DevManager.h>
|
||||
#include <slic3r/GUI/DeviceManager.hpp>
|
||||
#include <slic3r/GUI/DeviceCore/DevFilaSystem.h>
|
||||
#include <slic3r/GUI/FilamentMappingUtils.hpp>
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
#include <libslic3r/PresetBundle.hpp>
|
||||
#include <libslic3r/PrintConfig.hpp>
|
||||
@@ -17,6 +19,8 @@
|
||||
#include <utility>
|
||||
|
||||
using namespace Slic3r;
|
||||
using Slic3r::GUI::MappingSendError;
|
||||
using Slic3r::GUI::prepare_filament_mapping_for_send;
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace {
|
||||
@@ -48,6 +52,30 @@ public:
|
||||
|
||||
AgentInfo get_agent_info() override { return m_info; }
|
||||
|
||||
// This test double represents a legacy agent, not OrcaPrinterAgent behavior.
|
||||
bool supports_command(const std::string&, const std::string&) const override { return true; }
|
||||
|
||||
// It models whichever agent is registered under its id: only the Orca agent
|
||||
// serializes per-print filament_mapping.
|
||||
bool uses_filament_mapping() const override { return m_info.id == "orca"; }
|
||||
|
||||
using OrcaPrinterAgent::deliver_to_sink;
|
||||
|
||||
int send_message(std::string, std::string json_str, int, int) override
|
||||
{
|
||||
last_message = std::move(json_str);
|
||||
return send_result;
|
||||
}
|
||||
|
||||
int send_message_to_printer(std::string, std::string json_str, int, int) override
|
||||
{
|
||||
last_message = std::move(json_str);
|
||||
return send_result;
|
||||
}
|
||||
|
||||
std::string last_message;
|
||||
int send_result = 0;
|
||||
|
||||
private:
|
||||
AgentInfo m_info;
|
||||
};
|
||||
@@ -160,120 +188,135 @@ TEST_CASE("Device manager filters and rehomes devices by printer-agent ownership
|
||||
CHECK(manager.get_my_machine_list("integration-agent-b").count(machine.dev_id) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Orca capability reply populates connector-scope features and commands", "[DeviceManager][integration]")
|
||||
TEST_CASE("Network agent rejects capability queries for a different device owner", "[DeviceManager][integration]")
|
||||
{
|
||||
ScopedAppConfig app_config;
|
||||
NetworkAgent network(nullptr, std::make_shared<TestPrinterAgent>("orca"));
|
||||
DeviceManager manager(&network, false, &app_config.config);
|
||||
NetworkAgent network(nullptr, std::make_shared<TestPrinterAgent>("bbl"));
|
||||
|
||||
BBLocalMachine orca_machine;
|
||||
orca_machine.dev_id = "orca-device";
|
||||
orca_machine.dev_name = "Orca device";
|
||||
orca_machine.dev_ip = "192.0.2.21";
|
||||
orca_machine.printer_type = "C11";
|
||||
MachineObject* orca_obj = manager.insert_local_device(orca_machine, "lan", "free", "", "access-code");
|
||||
REQUIRE(orca_obj != nullptr);
|
||||
orca_obj->printer_agent_id = "orca";
|
||||
CHECK(network.owns_agent("bbl"));
|
||||
CHECK_FALSE(network.owns_agent("orca"));
|
||||
CHECK(network.supports_command("bbl", "device", "print.ams_control"));
|
||||
CHECK_FALSE(network.supports_command("orca", "device", "print.ams_control"));
|
||||
CHECK_FALSE(network.supports_feature("orca", "device", "filament_mapping"));
|
||||
|
||||
orca_obj->parse_new_info2(json::parse(R"({
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": true, "filament_slots": true, "filament_mapping": true},
|
||||
"supported_commands": ["print.push_status", "print.ams_get_rfid"],
|
||||
"capabilities": {
|
||||
"flags": {},
|
||||
"protocol": {
|
||||
"features": {"filament_mapping": true},
|
||||
"supported_commands": ["print.ams_change_filament"]
|
||||
}
|
||||
}
|
||||
})"));
|
||||
CHECK(orca_obj->is_support_fms);
|
||||
CHECK(orca_obj->is_support_filament_slots);
|
||||
CHECK(orca_obj->is_support_filament_mapping);
|
||||
CHECK(orca_obj->supported_commands.count("print.push_status") == 1);
|
||||
CHECK(orca_obj->supported_commands.count("print.ams_get_rfid") == 1);
|
||||
CHECK(orca_obj->supported_commands.count("print.ams_change_filament") == 1);
|
||||
// The mapping dialect is queried the same way: a legacy owner never inherits
|
||||
// Orca's, and a non-owner is never queried at all.
|
||||
CHECK_FALSE(network.uses_filament_mapping("bbl"));
|
||||
CHECK_FALSE(network.uses_filament_mapping("orca"));
|
||||
|
||||
// Absent or false reads as unsupported, and a later reply without commands clears the set.
|
||||
orca_obj->parse_new_info2(json::parse(R"({
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": false, "filament_slots": false, "filament_mapping": false},
|
||||
"capabilities": {"flags": {}}
|
||||
})"));
|
||||
CHECK_FALSE(orca_obj->is_support_fms);
|
||||
CHECK_FALSE(orca_obj->is_support_filament_slots);
|
||||
CHECK_FALSE(orca_obj->is_support_filament_mapping);
|
||||
CHECK(orca_obj->supported_commands.empty());
|
||||
|
||||
// A reply without capabilities.flags must still parse the Orca features and
|
||||
// commands (fail-closed: don't retain stale "supported" values).
|
||||
orca_obj->parse_new_info2(json::parse(R"({
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"filament_mapping": true},
|
||||
"capabilities": {
|
||||
"protocol": {"supported_commands": ["print.ams_get_rfid"]}
|
||||
}
|
||||
})"));
|
||||
CHECK(orca_obj->is_support_filament_mapping);
|
||||
CHECK_FALSE(orca_obj->is_support_filament_slots);
|
||||
CHECK(orca_obj->supported_commands.count("print.ams_get_rfid") == 1);
|
||||
|
||||
// A non-Orca agent id leaves the connector-scope fields untouched.
|
||||
BBLocalMachine bbl_machine;
|
||||
bbl_machine.dev_id = "bbl-device";
|
||||
bbl_machine.dev_name = "Bambu device";
|
||||
bbl_machine.dev_ip = "192.0.2.22";
|
||||
bbl_machine.printer_type = "C11";
|
||||
MachineObject* bbl_obj = manager.insert_local_device(bbl_machine, "lan", "free", "", "access-code");
|
||||
REQUIRE(bbl_obj != nullptr);
|
||||
bbl_obj->printer_agent_id = "bbl";
|
||||
|
||||
bbl_obj->parse_new_info2(json::parse(R"({
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": true, "filament_slots": true, "filament_mapping": true},
|
||||
"supported_commands": ["print.push_status"],
|
||||
"capabilities": {
|
||||
"flags": {},
|
||||
"protocol": {
|
||||
"features": {"fms": true, "filament_slots": true, "filament_mapping": true},
|
||||
"supported_commands": ["print.ams_get_rfid"]
|
||||
}
|
||||
}
|
||||
})"));
|
||||
CHECK_FALSE(bbl_obj->is_support_fms);
|
||||
CHECK_FALSE(bbl_obj->is_support_filament_slots);
|
||||
CHECK_FALSE(bbl_obj->is_support_filament_mapping);
|
||||
CHECK(bbl_obj->supported_commands.empty());
|
||||
NetworkAgent orca_network(nullptr, std::make_shared<TestPrinterAgent>("orca"));
|
||||
CHECK(orca_network.uses_filament_mapping("orca"));
|
||||
CHECK_FALSE(orca_network.uses_filament_mapping("bbl"));
|
||||
}
|
||||
|
||||
TEST_CASE("Orca per-command AMS gate requires fms and the advertised command", "[DeviceManager][integration]")
|
||||
// Send-time mapping policy belongs to the printer agent's dialect, not to an
|
||||
// agent id: the Orca dialect gets the no-AMS normalization plus both refusals.
|
||||
TEST_CASE("Per-print mapping send policy follows the owning agent's dialect", "[DeviceManager][integration]")
|
||||
{
|
||||
auto orca_agent = std::make_shared<TestPrinterAgent>("orca");
|
||||
NetworkAgent network(nullptr, orca_agent);
|
||||
MachineObject obj(nullptr, &network, "test", "orca-mapping-policy", "127.0.0.1");
|
||||
obj.printer_agent_id = "orca";
|
||||
|
||||
// No AMS: the auto-selected external spool is dropped before the capability
|
||||
// gate, so it cannot refuse a print nobody mapped.
|
||||
std::string external_only = R"([{"ams_id":255,"slot_id":0}])";
|
||||
CHECK(prepare_filament_mapping_for_send(&obj, external_only, {}) == MappingSendError::none);
|
||||
CHECK(external_only.empty());
|
||||
|
||||
// An AMS makes that a real target, but nothing advertised the capability yet.
|
||||
obj.GetFilaSystem()->GetAmsList()["0"] = new DevAms("0", 0, DevAms::AMS);
|
||||
std::string mapped = R"([{"ams_id":0,"slot_id":0}])";
|
||||
CHECK(prepare_filament_mapping_for_send(&obj, mapped, {}) == MappingSendError::unsupported);
|
||||
CHECK(mapped == R"([{"ams_id":0,"slot_id":0}])"); // the refusal leaves it intact
|
||||
|
||||
// Once the connector advertises the capability the mapping passes the gate.
|
||||
orca_agent->deliver_to_sink(obj.get_dev_id(),
|
||||
R"({"info":{"command":"get_capabilities","supported_features":{"filament_mapping":true}}})",
|
||||
false);
|
||||
CHECK(obj.printer_supports_feature("filament_mapping"));
|
||||
CHECK(prepare_filament_mapping_for_send(&obj, mapped, {}) == MappingSendError::none);
|
||||
|
||||
// An unrecorded owner still sends through the active agent, so it takes
|
||||
// that agent's dialect too rather than falling back to the legacy payload.
|
||||
MachineObject unowned(nullptr, &network, "test", "orca-unowned", "127.0.0.1");
|
||||
CHECK(unowned.printer_agent_id.empty());
|
||||
CHECK(unowned.printer_uses_filament_mapping());
|
||||
std::string unowned_external = R"([{"ams_id":255,"slot_id":0}])";
|
||||
CHECK(prepare_filament_mapping_for_send(&unowned, unowned_external, {}) == MappingSendError::none);
|
||||
CHECK(unowned_external.empty()); // the Orca normalization ran
|
||||
|
||||
// A partially mapped print is refused even while the capability holds.
|
||||
FilamentInfo mapped_entry;
|
||||
mapped_entry.ams_id = "0";
|
||||
mapped_entry.slot_id = "0";
|
||||
FilamentInfo unmapped_entry;
|
||||
CHECK(prepare_filament_mapping_for_send(&obj, mapped, {mapped_entry, unmapped_entry}) ==
|
||||
MappingSendError::incomplete);
|
||||
}
|
||||
|
||||
// The legacy payload is the thing being protected: no normalization, no refusal.
|
||||
TEST_CASE("A legacy printer agent keeps its mapping payload untouched", "[DeviceManager][integration]")
|
||||
{
|
||||
NetworkAgent network(nullptr, std::make_shared<TestPrinterAgent>("bbl"));
|
||||
MachineObject obj(nullptr, &network, "test", "bbl-mapping-policy", "127.0.0.1");
|
||||
obj.printer_agent_id = "bbl";
|
||||
|
||||
FilamentInfo mapped_entry;
|
||||
mapped_entry.ams_id = "0";
|
||||
mapped_entry.slot_id = "0";
|
||||
FilamentInfo unmapped_entry;
|
||||
|
||||
std::string external_only = R"([{"ams_id":255,"slot_id":0}])";
|
||||
CHECK(prepare_filament_mapping_for_send(&obj, external_only, {mapped_entry, unmapped_entry}) ==
|
||||
MappingSendError::none);
|
||||
CHECK(external_only == R"([{"ams_id":255,"slot_id":0}])");
|
||||
}
|
||||
|
||||
TEST_CASE("AMS metadata retains setting_id for legacy printer agents", "[DeviceManager][integration]")
|
||||
{
|
||||
ScopedAppConfig app_config;
|
||||
NetworkAgent network(nullptr, std::make_shared<TestPrinterAgent>("orca"));
|
||||
auto printer_agent = std::make_shared<TestPrinterAgent>("bbl");
|
||||
NetworkAgent network(nullptr, printer_agent);
|
||||
DeviceManager manager(&network, false, &app_config.config);
|
||||
|
||||
BBLocalMachine machine;
|
||||
machine.dev_id = "orca-gate";
|
||||
machine.dev_name = "Orca gate";
|
||||
machine.dev_ip = "192.0.2.30";
|
||||
machine.dev_id = "bbl-setting-id";
|
||||
machine.dev_name = "Bambu setting id";
|
||||
machine.dev_ip = "192.0.2.32";
|
||||
machine.printer_type = "C11";
|
||||
MachineObject* obj = manager.insert_local_device(machine, "lan", "free", "", "access-code");
|
||||
REQUIRE(obj != nullptr);
|
||||
obj->printer_agent_id = "orca";
|
||||
|
||||
// fms off: no macro-backed AMS command is allowed even if listed.
|
||||
obj->is_support_fms = false;
|
||||
obj->supported_commands.insert("print.ams_control");
|
||||
CHECK_FALSE(obj->orca_ams_command_supported("print.ams_control"));
|
||||
REQUIRE(obj->command_ams_filament_settings(0, 1, "GFL99", "preset-setting", "00FF00FF", "PLA", 190, 220) == 0);
|
||||
const json payload = json::parse(printer_agent->last_message);
|
||||
CHECK(payload["print"]["setting_id"] == "preset-setting");
|
||||
}
|
||||
|
||||
// fms on: only the commands actually advertised are allowed.
|
||||
obj->is_support_fms = true;
|
||||
CHECK(obj->orca_ams_command_supported("print.ams_control"));
|
||||
CHECK_FALSE(obj->orca_ams_command_supported("print.ams_get_rfid"));
|
||||
TEST_CASE("AMS user settings do not update local state when publishing fails", "[DeviceManager][integration]")
|
||||
{
|
||||
ScopedAppConfig app_config;
|
||||
auto printer_agent = std::make_shared<TestPrinterAgent>("bbl");
|
||||
NetworkAgent network(nullptr, printer_agent);
|
||||
DeviceManager manager(&network, false, &app_config.config);
|
||||
|
||||
// Bambu keeps the legacy permissive path.
|
||||
obj->printer_agent_id = "bbl";
|
||||
CHECK(obj->orca_ams_command_supported("print.anything"));
|
||||
BBLocalMachine machine;
|
||||
machine.dev_id = "bbl-ams-setting-failure";
|
||||
machine.dev_name = "Bambu AMS settings";
|
||||
machine.dev_ip = "192.0.2.33";
|
||||
machine.printer_type = "C11";
|
||||
MachineObject* obj = manager.insert_local_device(machine, "lan", "free", "", "access-code");
|
||||
REQUIRE(obj != nullptr);
|
||||
|
||||
auto& settings = obj->GetFilaSystem()->GetAmsSystemSetting();
|
||||
settings.SetDetectOnInsertEnabled(false);
|
||||
settings.SetDetectOnPowerupEnabled(false);
|
||||
settings.SetDetectRemainEnabled(false);
|
||||
printer_agent->send_result = -1;
|
||||
|
||||
CHECK(obj->command_ams_user_settings(true, true, true) != 0);
|
||||
CHECK(settings.IsDetectOnInsertEnabled() == false);
|
||||
CHECK(settings.IsDetectOnPowerupEnabled() == false);
|
||||
CHECK(settings.IsDetectRemainEnabled() == false);
|
||||
}
|
||||
|
||||
// The AMS dialogs resolve their filament list from the connected device's model. OrcaSonar's
|
||||
|
||||
@@ -25,6 +25,7 @@ struct Probe : OrcaPrinterAgent {
|
||||
using OrcaPrinterAgent::lan_connection_target;
|
||||
using OrcaPrinterAgent::build_filament_mapping;
|
||||
using OrcaPrinterAgent::build_gcode_file_payload;
|
||||
using OrcaPrinterAgent::prepare_outgoing_request;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,6 +72,91 @@ TEST_CASE("OrcaPrinterAgent stamps the get_capabilities nozzle diameter onto pus
|
||||
CHECK(last_payload.find("N/A") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("OrcaPrinterAgent owns connector capabilities and fails closed for AMS commands", "[OrcaPrinterAgent]") {
|
||||
Probe agent("/tmp");
|
||||
agent.deliver_to_sink("orca-caps", R"({
|
||||
"info": {
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": true, "filament_slots": true, "filament_mapping": true},
|
||||
"supported_commands": ["print.ams_get_rfid"],
|
||||
"capabilities": {
|
||||
"protocol": {
|
||||
"features": {"filament_mapping": true},
|
||||
"supported_commands": ["print.ams_change_filament"]
|
||||
}
|
||||
}
|
||||
}
|
||||
})", false);
|
||||
|
||||
CHECK(agent.supports_feature("orca-caps", "filament_mapping"));
|
||||
CHECK(agent.supports_command("orca-caps", "print.ams_filament_setting"));
|
||||
CHECK(agent.supports_command("orca-caps", "print.ams_change_filament"));
|
||||
CHECK(agent.supports_command("orca-caps", "print.ams_get_rfid"));
|
||||
CHECK_FALSE(agent.supports_command("orca-caps", "print.ams_control"));
|
||||
|
||||
agent.deliver_to_sink("orca-caps", R"({
|
||||
"info": {
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": false, "filament_slots": false, "filament_mapping": false},
|
||||
"supported_commands": ["print.ams_control"],
|
||||
"capabilities": {"protocol": {"features": {}, "supported_commands": []}}
|
||||
}
|
||||
})", false);
|
||||
|
||||
CHECK_FALSE(agent.supports_feature("orca-caps", "filament_mapping"));
|
||||
CHECK_FALSE(agent.supports_command("orca-caps", "print.ams_filament_setting"));
|
||||
CHECK_FALSE(agent.supports_command("orca-caps", "print.ams_change_filament"));
|
||||
CHECK_FALSE(agent.supports_command("orca-caps", "print.ams_control"));
|
||||
CHECK_FALSE(agent.supports_command("orca-caps", "print.ams_calibrate"));
|
||||
CHECK(agent.supports_command("orca-caps", "print.gcode_file"));
|
||||
}
|
||||
|
||||
TEST_CASE("OrcaPrinterAgent clears cached capabilities when a device is unbound", "[OrcaPrinterAgent]") {
|
||||
Probe agent("/tmp");
|
||||
agent.deliver_to_sink("orca-forget", R"({
|
||||
"info": {
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"filament_slots": true},
|
||||
"supported_commands": [],
|
||||
"capabilities": {"protocol": {"features": {}, "supported_commands": []}}
|
||||
}
|
||||
})", false);
|
||||
REQUIRE(agent.supports_command("orca-forget", "print.ams_filament_setting"));
|
||||
|
||||
agent.unbind("orca-forget");
|
||||
CHECK_FALSE(agent.supports_command("orca-forget", "print.ams_filament_setting"));
|
||||
}
|
||||
|
||||
TEST_CASE("OrcaPrinterAgent removes setting_id from AMS metadata and gates the request", "[OrcaPrinterAgent]") {
|
||||
Probe agent("/tmp");
|
||||
agent.deliver_to_sink("orca-ams-write", R"({
|
||||
"info": {
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"fms": false, "filament_slots": true},
|
||||
"supported_commands": [],
|
||||
"capabilities": {"protocol": {"features": {"filament_slots": true}, "supported_commands": []}}
|
||||
}
|
||||
})", false);
|
||||
|
||||
const std::string request = R"({"print":{"command":"ams_filament_setting","sequence_id":"9","tray_info_idx":"GFL99","setting_id":"preset-setting"}})";
|
||||
std::string command;
|
||||
std::string prepared;
|
||||
CHECK(agent.prepare_outgoing_request("orca-ams-write", request, command, prepared) == BAMBU_NETWORK_SUCCESS);
|
||||
CHECK(command == "print.ams_filament_setting");
|
||||
const nlohmann::json parsed = nlohmann::json::parse(prepared);
|
||||
CHECK_FALSE(parsed["print"].contains("setting_id"));
|
||||
CHECK(parsed["print"]["tray_info_idx"] == "GFL99");
|
||||
|
||||
agent.deliver_to_sink("orca-ams-write", R"({
|
||||
"info": {
|
||||
"command": "get_capabilities",
|
||||
"supported_features": {"filament_slots": false},
|
||||
"capabilities": {"protocol": {"features": {"filament_slots": false}}}
|
||||
}
|
||||
})", false);
|
||||
CHECK(agent.prepare_outgoing_request("orca-ams-write", request, command, prepared) == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
TEST_CASE("OrcaPrinterAgent::parse_lan_endpoint", "[OrcaPrinterAgent]") {
|
||||
std::string h, p;
|
||||
REQUIRE(Probe::parse_lan_endpoint("192.168.1.9", h, p));
|
||||
|
||||
Reference in New Issue
Block a user