mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 02:11:18 +00:00
fix(orca-agent): retain virtual trays and preserve external AMS ids
External spool ids 254/255 keep their identity through filament changes and RFID refresh Orca virtual trays persist across push_status snapshots and reset on reconnect AMS writes gate on the device's declared FMS and filament-slot capabilities
This commit is contained in:
@@ -26,9 +26,8 @@
|
||||
using json = nlohmann::json;
|
||||
using namespace Slic3r;
|
||||
|
||||
// DeviceManager's push_status contract for the OrcaSonar virtual tray: an
|
||||
// authoritative empty clears, a populated key re-enables, and a frame that
|
||||
// omits the key leaves both the trays and the support flag alone.
|
||||
// Bambu virtual trays follow full-snapshot removals; Orca virtual trays are
|
||||
// retained after first observation until reconnect.
|
||||
|
||||
// Contract: an authoritative empty vir_slot ([] = "known, no virtual slots")
|
||||
// clears the seeded virtual trays. The consumers were guarded so an empty
|
||||
@@ -36,6 +35,7 @@ using namespace Slic3r;
|
||||
TEST_CASE("An empty vir_slot clears the virtual trays", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "bbl";
|
||||
REQUIRE(machine.vt_slot.size() == 1);
|
||||
REQUIRE(machine.vt_slot[0].id == "255");
|
||||
|
||||
@@ -49,6 +49,7 @@ TEST_CASE("An empty vir_slot clears the virtual trays", "[DeviceManager]")
|
||||
TEST_CASE("A missing vir_slot keeps the virtual trays", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "bbl";
|
||||
REQUIRE(machine.vt_slot.size() == 1);
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status"}})", false);
|
||||
@@ -62,6 +63,7 @@ TEST_CASE("A missing vir_slot keeps the virtual trays", "[DeviceManager]")
|
||||
TEST_CASE("Virtual trays repopulate after an authoritative clear", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "bbl";
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","vir_slot":[]}})", false);
|
||||
REQUIRE(machine.vt_slot.empty());
|
||||
@@ -79,6 +81,7 @@ TEST_CASE("Virtual trays repopulate after an authoritative clear", "[DeviceManag
|
||||
TEST_CASE("An orphan deputy virtual tray is dropped, not indexed", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "bbl";
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","vir_slot":[]}})", false);
|
||||
REQUIRE(machine.vt_slot.empty());
|
||||
@@ -93,6 +96,7 @@ TEST_CASE("An orphan deputy virtual tray is dropped, not indexed", "[DeviceManag
|
||||
TEST_CASE("A configured vir_slot is present even with no material", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "orca";
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","vir_slot":[{"id":"255"},{"id":"254"}]}})", false);
|
||||
|
||||
@@ -106,6 +110,7 @@ TEST_CASE("A configured vir_slot is present even with no material", "[DeviceMana
|
||||
TEST_CASE("A populated vir_slot prunes virtual trays it omits", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "bbl";
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","vir_slot":[{"id":"255"},{"id":"254"}]}})", false);
|
||||
REQUIRE(machine.vt_slot.size() == 2);
|
||||
@@ -121,6 +126,7 @@ TEST_CASE("A populated vir_slot prunes virtual trays it omits", "[DeviceManager]
|
||||
TEST_CASE("A delta vir_slot does not prune virtual trays it omits", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "bbl";
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"vir_slot":[{"id":"255"},{"id":"254"}]}})", false);
|
||||
REQUIRE(machine.vt_slot.size() == 2);
|
||||
@@ -138,6 +144,7 @@ TEST_CASE("A delta vir_slot does not prune virtual trays it omits", "[DeviceMana
|
||||
TEST_CASE("An empty delta vir_slot keeps the virtual trays", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "bbl";
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"vir_slot":[{"id":"255"},{"id":"254"}]}})", false);
|
||||
REQUIRE(machine.vt_slot.size() == 2);
|
||||
@@ -150,6 +157,59 @@ TEST_CASE("An empty delta vir_slot keeps the virtual trays", "[DeviceManager]")
|
||||
CHECK(machine.ams_support_virtual_tray);
|
||||
}
|
||||
|
||||
TEST_CASE("Orca retains seen virtual trays across full snapshots", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "orca";
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"vir_slot":[{"id":"255"},{"id":"254"}]}})", false);
|
||||
REQUIRE(machine.vt_slot.size() == 2);
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"vir_slot":[{"id":"255","tag_uid":"0123456789ABCDEF"}]}})", false);
|
||||
REQUIRE(machine.vt_slot.size() == 2);
|
||||
CHECK(machine.vt_slot[0].tag_uid == "0123456789ABCDEF");
|
||||
CHECK(machine.vt_slot[1].id == "254");
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"vir_slot":[]}})", false);
|
||||
REQUIRE(machine.vt_slot.size() == 2);
|
||||
CHECK(machine.vt_slot[0].id == "255");
|
||||
CHECK(machine.vt_slot[1].id == "254");
|
||||
CHECK(machine.ams_support_virtual_tray);
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0}})", false);
|
||||
CHECK(machine.vt_slot.size() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Orca forgets retained virtual trays at reconnect", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "orca";
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"vir_slot":[{"id":"255"},{"id":"254"}]}})", false);
|
||||
REQUIRE(machine.vt_slot.size() == 2);
|
||||
|
||||
machine.reset_orca_virtual_trays_for_reconnect();
|
||||
REQUIRE(machine.vt_slot.size() == 1);
|
||||
CHECK(machine.vt_slot[0].id == "255");
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"vir_slot":[]}})", false);
|
||||
CHECK(machine.vt_slot.empty());
|
||||
CHECK_FALSE(machine.ams_support_virtual_tray);
|
||||
}
|
||||
|
||||
TEST_CASE("Orca stores a deputy virtual tray at its stable index", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
machine.printer_agent_id = "orca";
|
||||
|
||||
machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"vir_slot":[{"id":"254"}]}})", false);
|
||||
|
||||
REQUIRE(machine.vt_slot.size() == 2);
|
||||
CHECK(machine.vt_slot[0].id == "255");
|
||||
CHECK_FALSE(machine.vt_slot[0].is_exists);
|
||||
CHECK(machine.vt_slot[1].id == "254");
|
||||
CHECK(machine.vt_slot[1].is_exists);
|
||||
}
|
||||
|
||||
TEST_CASE("Capability flags parse without a DeviceManager", "[DeviceManager]")
|
||||
{
|
||||
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
|
||||
@@ -196,9 +256,11 @@ TEST_CASE("Only the BBL agent is RFID-locking", "[DeviceManager]")
|
||||
|
||||
machine.printer_agent_id = "bbl";
|
||||
CHECK(machine.is_bbl_agent());
|
||||
CHECK_FALSE(machine.is_orca_agent());
|
||||
|
||||
machine.printer_agent_id = "orca";
|
||||
CHECK_FALSE(machine.is_bbl_agent());
|
||||
CHECK(machine.is_orca_agent());
|
||||
|
||||
machine.printer_agent_id = "";
|
||||
CHECK(machine.is_bbl_agent());
|
||||
|
||||
@@ -324,22 +324,23 @@ TEST_CASE("OrcaPrinterAgent rewrites Bambu ams_* payloads onto the canonical Orc
|
||||
CHECK(!out["print"].contains("target"));
|
||||
CHECK(out["print"]["tar_temp"] == 220);
|
||||
|
||||
// External-spool selection ("254" arrives hacked to 255 with slot_id=0):
|
||||
// must become the selector op, never a lane.
|
||||
// External slots retain their tool identity through the coordinate address.
|
||||
out = nlohmann::json::parse(canon("dev-c1", R"({"print":{"command":"ams_change_filament","ams_id":255,"target":255,"slot_id":0}})"));
|
||||
CHECK(out["print"]["selector"] == "external");
|
||||
CHECK(!out["print"].contains("lane"));
|
||||
CHECK(!out["print"].contains("ams_id"));
|
||||
|
||||
out = nlohmann::json::parse(canon("dev-c1", R"({"print":{"command":"ams_change_filament","ams_id":0,"target":255,"slot_id":255}})"));
|
||||
CHECK(out["print"]["selector"] == "unload");
|
||||
|
||||
// A coordinate-less body must not fabricate a flat lane from a BBL tray id;
|
||||
// the server validates the address and answers -19.
|
||||
out = nlohmann::json::parse(canon("dev-c1", R"({"print":{"command":"ams_change_filament","target":6}})"));
|
||||
CHECK(out["print"]["selector"] == "lane");
|
||||
CHECK(out["print"]["ams_id"] == 255);
|
||||
CHECK(out["print"]["slot_id"] == 0);
|
||||
CHECK(!out["print"].contains("lane"));
|
||||
CHECK(!out["print"].contains("target"));
|
||||
out = nlohmann::json::parse(canon("dev-c1", R"({"print":{"command":"ams_change_filament","ams_id":254,"target":254,"slot_id":0}})"));
|
||||
CHECK(out["print"]["selector"] == "lane");
|
||||
CHECK(out["print"]["ams_id"] == 254);
|
||||
|
||||
const std::string legacy_unload = R"({"print":{"command":"ams_change_filament","ams_id":0,"target":255,"slot_id":255}})";
|
||||
CHECK(canon("dev-c1", legacy_unload) == legacy_unload);
|
||||
|
||||
// Target-only requests keep the legacy macro path.
|
||||
const std::string target_only = R"({"print":{"command":"ams_change_filament","target":6}})";
|
||||
CHECK(canon("dev-c1", target_only) == target_only);
|
||||
|
||||
// Box coordinates are forwarded unchanged: a fabricated flat lane would be
|
||||
// the BBL tray id, which is not the layout lane for wide/sparse boxes.
|
||||
@@ -378,14 +379,18 @@ TEST_CASE("OrcaPrinterAgent rewrites Bambu ams_* payloads onto the canonical Orc
|
||||
|
||||
const std::string canonical = R"({"print":{"command":"ams_change_filament","selector":"lane","lane":3}})";
|
||||
CHECK(canon("dev-c1", canonical) == canonical);
|
||||
const std::string combined = R"({"print":{"command":"ams_change_filament","target":3,"selector":"lane","lane":3}})";
|
||||
CHECK(canon("dev-c1", combined) == combined);
|
||||
CHECK(canon("dev-c1", R"({"print":{"command":"pause"}})") == R"({"print":{"command":"pause"}})");
|
||||
CHECK(canon("dev-c1", "not json at all") == "not json at all");
|
||||
|
||||
// Declared ams_ops gate at the client: only change_filament is supported.
|
||||
// A legacy unload still uses the legacy macro path, which is covered by
|
||||
// change_filament; selector unload requires its own token.
|
||||
Slic3r::register_ams_ops("dev-c2", {"change_filament"});
|
||||
Slic3r::register_ams_capability("dev-c2", true);
|
||||
bool unsupported = false;
|
||||
canon("dev-c2", R"({"print":{"command":"ams_change_filament","target":255,"slot_id":255}})", &unsupported);
|
||||
CHECK(unsupported);
|
||||
CHECK_FALSE(unsupported);
|
||||
unsupported = false;
|
||||
canon("dev-c2", R"({"print":{"command":"ams_control","param":"pause"}})", &unsupported);
|
||||
CHECK(unsupported);
|
||||
@@ -399,14 +404,24 @@ TEST_CASE("OrcaPrinterAgent rewrites Bambu ams_* payloads onto the canonical Orc
|
||||
unsupported = false;
|
||||
canon("dev-c2", R"({"print":{"command":"ams_change_filament","selector":"external"}})", &unsupported);
|
||||
CHECK(unsupported);
|
||||
unsupported = false;
|
||||
canon("dev-c2", R"({"print":{"command":"ams_change_filament","target":254,"ams_id":254,"slot_id":0}})", &unsupported);
|
||||
CHECK(unsupported);
|
||||
|
||||
// A lane can resolve to an external slot server-side (§7.8), so a device
|
||||
// that declares only external still admits a canonical lane write.
|
||||
Slic3r::register_ams_ops("dev-c4", {"external"});
|
||||
Slic3r::register_ams_capability("dev-c4", true);
|
||||
unsupported = false;
|
||||
canon("dev-c4", R"({"print":{"command":"ams_change_filament","selector":"lane","lane":1}})", &unsupported);
|
||||
CHECK(!unsupported);
|
||||
unsupported = false;
|
||||
canon("dev-c4", R"({"print":{"command":"ams_change_filament","selector":"lane","ams_id":0,"slot_id":0}})", &unsupported);
|
||||
CHECK(unsupported);
|
||||
unsupported = false;
|
||||
canon("dev-c4", R"({"print":{"command":"ams_change_filament","selector":"lane","ams_id":254,"slot_id":0}})", &unsupported);
|
||||
CHECK_FALSE(unsupported);
|
||||
unsupported = false;
|
||||
canon("dev-c4", R"({"print":{"command":"ams_change_filament","selector":"external"}})", &unsupported);
|
||||
CHECK(!unsupported);
|
||||
// Other selectors still gate on their own token.
|
||||
@@ -420,10 +435,9 @@ TEST_CASE("OrcaPrinterAgent rewrites Bambu ams_* payloads onto the canonical Orc
|
||||
CHECK(!unsupported);
|
||||
}
|
||||
|
||||
// filament_setting is advertised by filament_slots alone (OPCP §7.8), so a
|
||||
// standalone printer with no ams_ops can still write slots, while the material
|
||||
// writes stay gated.
|
||||
TEST_CASE("a filament_slots reply admits ams_filament_setting without ams_ops", "[OrcaPrinterAgent]") {
|
||||
// filament_setting is advertised by filament_slots alone, independently of fms
|
||||
// and ams_ops (OPCP §7.8).
|
||||
TEST_CASE("filament slot writes remain independent of FMS", "[OrcaPrinterAgent]") {
|
||||
Probe agent("/tmp");
|
||||
agent.deliver_to_sink("dev-slots",
|
||||
R"({"info":{"command":"get_capabilities","capabilities":{"protocol":{"features":{"fms":false,"filament_slots":true}}}}})",
|
||||
@@ -436,12 +450,25 @@ TEST_CASE("a filament_slots reply admits ams_filament_setting without ams_ops",
|
||||
&unsupported);
|
||||
CHECK_FALSE(unsupported);
|
||||
|
||||
agent.deliver_to_sink("dev-slots",
|
||||
R"({"info":{"command":"get_capabilities","capabilities":{"protocol":{"features":{"fms":false,"filament_slots":true},"ams_ops":["change_filament"]}}}}})",
|
||||
/*local=*/true);
|
||||
unsupported = false;
|
||||
OrcaPrinterAgent::canonicalize_ams_payload(
|
||||
"dev-slots",
|
||||
R"({"print":{"command":"ams_change_filament","target":1,"slot_id":1,"ams_id":0}})",
|
||||
&unsupported);
|
||||
CHECK(unsupported);
|
||||
|
||||
agent.deliver_to_sink("dev-no-slots",
|
||||
R"({"info":{"command":"get_capabilities","capabilities":{"protocol":{"features":{"fms":true,"filament_slots":false},"ams_ops":["filament_setting"]}}}})",
|
||||
/*local=*/true);
|
||||
unsupported = false;
|
||||
OrcaPrinterAgent::canonicalize_ams_payload(
|
||||
"dev-no-slots",
|
||||
R"({"print":{"command":"ams_filament_setting","ams_id":0,"slot_id":0,"tray_type":"PLA"}})",
|
||||
&unsupported);
|
||||
CHECK(unsupported);
|
||||
}
|
||||
|
||||
// A box wider than 4 slots is shown as several 4-tray units, so the BBL tray id
|
||||
@@ -460,6 +487,24 @@ TEST_CASE("an AMS tray selection sends the tray's ams_id and slot_id", "[OrcaPri
|
||||
body = nlohmann::json::parse(OrcaPrinterAgent::build_ams_change_filament_body(3, 124));
|
||||
CHECK(body["print"]["ams_id"] == 0);
|
||||
CHECK(body["print"]["slot_id"] == 3);
|
||||
|
||||
body = nlohmann::json::parse(OrcaPrinterAgent::build_ams_change_filament_body(254, 125));
|
||||
CHECK(body["print"]["ams_id"] == 254);
|
||||
CHECK(body["print"]["slot_id"] == 0);
|
||||
body = nlohmann::json::parse(OrcaPrinterAgent::build_ams_change_filament_body(255, 126));
|
||||
CHECK(body["print"]["ams_id"] == 255);
|
||||
CHECK(body["print"]["slot_id"] == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("RFID refresh maps coordinates and preserves flat tray ids", "[OrcaPrinterAgent]") {
|
||||
auto body = nlohmann::json::parse(OrcaPrinterAgent::build_ams_refresh_rfid_body(1, 2, 127));
|
||||
CHECK(body["print"]["tray_id"] == 6);
|
||||
|
||||
body = nlohmann::json::parse(OrcaPrinterAgent::build_ams_refresh_rfid_body(-1, 9, 128));
|
||||
CHECK(body["print"]["tray_id"] == 9);
|
||||
|
||||
body = nlohmann::json::parse(OrcaPrinterAgent::build_ams_refresh_rfid_body(254, 0, 129));
|
||||
CHECK(body["print"]["tray_id"] == 254);
|
||||
}
|
||||
|
||||
// A filament frame can arrive after the get_capabilities reply was missed (the
|
||||
|
||||
@@ -232,7 +232,7 @@ TEST_CASE("unit: default AMS commands report not supported", "[unit][moonraker]"
|
||||
{
|
||||
MoonrakerPrinterAgent agent("");
|
||||
|
||||
CHECK(agent.command_ams_refresh_rfid("dev", "123", 1, false) == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
CHECK(agent.command_ams_refresh_rfid("dev", -1, 123, 1, false) == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
CHECK(agent.command_ams_calibrate("dev", 1, 2, false) == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
CHECK(agent.command_ams_select_tray("dev", "123", 3, false) == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user