From e089be4ff81c4e0dfe5813f30004cb66b2839cad Mon Sep 17 00:00:00 2001 From: "D.R.racer" Date: Wed, 19 Nov 2025 14:42:54 +0100 Subject: [PATCH] UnloadToFinda: update unit tests --- src/logic/unload_to_finda.cpp | 5 +- tests/unit/logic/stubs/main_loop_stub.cpp | 22 ++- tests/unit/logic/stubs/main_loop_stub.h | 19 +++ .../unload_to_finda/test_unload_to_finda.cpp | 158 ++++++------------ 4 files changed, 89 insertions(+), 115 deletions(-) diff --git a/src/logic/unload_to_finda.cpp b/src/logic/unload_to_finda.cpp index f52532d..b0734e2 100644 --- a/src/logic/unload_to_finda.cpp +++ b/src/logic/unload_to_finda.cpp @@ -87,7 +87,10 @@ bool UnloadToFinda::Step() { // we reached the end of move queue, but the FINDA didn't switch off // two possible causes - grinded filament or malfunctioning FINDA if (--maxTries) { - Reset(maxTries); // try again + // Ideally, the Idler shall rehome and then try again. + // That would auto-resolve errors caused by slipped or misaligned Idler + mi::idler.InvalidateHoming(); + Reset(maxTries); } else { state = FailedFINDA; } diff --git a/tests/unit/logic/stubs/main_loop_stub.cpp b/tests/unit/logic/stubs/main_loop_stub.cpp index 4b70ca7..c241d75 100644 --- a/tests/unit/logic/stubs/main_loop_stub.cpp +++ b/tests/unit/logic/stubs/main_loop_stub.cpp @@ -79,19 +79,27 @@ void ForceReinitAllAutomata() { mg::globals.Init(); mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::AtPulley); } +void HomeIdler() { + logic::NoCommand nc; // just a dummy instance which has an empty Step() + SimulateIdlerHoming(nc); + SimulateIdlerWaitForHomingValid(nc); + SimulateIdlerMoveToParkingPosition(nc); +} + +void HomeSelector() { + logic::NoCommand nc; // just a dummy instance which has an empty Step() + SimulateSelectorHoming(nc); + SimulateSelectorWaitForHomingValid(nc); + SimulateSelectorWaitForReadyState(nc); +} void HomeIdlerAndSelector() { mi::idler.InvalidateHoming(); ms::selector.InvalidateHoming(); - logic::NoCommand nc; // just a dummy instance which has an empty Step() - SimulateIdlerHoming(nc); - SimulateIdlerWaitForHomingValid(nc); - SimulateIdlerMoveToParkingPosition(nc); + HomeIdler(); - SimulateSelectorHoming(nc); - SimulateSelectorWaitForHomingValid(nc); - SimulateSelectorWaitForReadyState(nc); + HomeSelector(); } bool EnsureActiveSlotIndex(uint8_t slot, mg::FilamentLoadState loadState) { diff --git a/tests/unit/logic/stubs/main_loop_stub.h b/tests/unit/logic/stubs/main_loop_stub.h index 0d5d492..7af9798 100644 --- a/tests/unit/logic/stubs/main_loop_stub.h +++ b/tests/unit/logic/stubs/main_loop_stub.h @@ -1,6 +1,7 @@ #pragma once #include "../../../../src/logic/command_base.h" #include "../../../../src/modules/globals.h" +#include "../../../../src/modules/idler.h" extern void main_loop(); extern void ForceReinitAllAutomata(); @@ -44,3 +45,21 @@ static constexpr uint32_t selectorMoveMaxSteps = 40000UL; void HomeIdlerAndSelector(); void SimulateErrDisengagingIdler(logic::CommandBase &cb, ErrorCode deferredEC); + +template +bool SimulateEngageIdlerFully(T &cb) { + return WhileCondition( + cb, + [&](uint32_t) { return !mi::idler.Engaged(); }, + 5000); +} + +template +bool SimulateEngageIdlerPartially(T &cb) { + return WhileCondition( + cb, + [&](uint32_t) { return !mi::idler.PartiallyDisengaged(); }, + 5000); +} + +void HomeIdler(); diff --git a/tests/unit/logic/unload_to_finda/test_unload_to_finda.cpp b/tests/unit/logic/unload_to_finda/test_unload_to_finda.cpp index 2f2ddad..1b9f14a 100644 --- a/tests/unit/logic/unload_to_finda/test_unload_to_finda.cpp +++ b/tests/unit/logic/unload_to_finda/test_unload_to_finda.cpp @@ -12,10 +12,12 @@ #include "../../../../src/modules/motion.h" #include "../../../../src/modules/permanent_storage.h" #include "../../../../src/modules/selector.h" +#include "../../../../src/modules/timebase.h" #include "../../../../src/logic/unload_to_finda.h" #include "../../modules/stubs/stub_adc.h" +#include "../../modules/stubs/stub_timebase.h" #include "../stubs/main_loop_stub.h" #include "../stubs/stub_motion.h" @@ -24,7 +26,7 @@ using namespace std::placeholders; namespace ha = hal::adc; -TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") { +void UnloadToFindaCommonSetup(logic::UnloadToFinda &ff, uint8_t retryAttempts) { ForceReinitAllAutomata(); REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley)); @@ -35,10 +37,8 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") { // and MMU "thinks" it has the filament loaded mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle); - logic::UnloadToFinda ff; - // restart the automaton - just 1 attempt - ff.Reset(1); + ff.Reset(retryAttempts); REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler); @@ -47,12 +47,10 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") { CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::IntermediateSlotPosition(0).v); CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v); - // engaging idler partially - REQUIRE(WhileCondition( - ff, - [&](uint32_t) { return !mi::idler.PartiallyDisengaged(); }, - 5000)); + REQUIRE(SimulateEngageIdlerPartially(ff)); +} +void UnloadToFindaCommonTurnOffFSensor(logic::UnloadToFinda &ff) { // turn off fsensor - the printer freed the filament from the gears SetFSensorStateAndDebounce(false); @@ -63,14 +61,17 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") { CHECK(mm::axes[mm::Pulley].enabled == true); CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v); - // engaging idler fully - REQUIRE(WhileCondition( - ff, - [&](uint32_t) { return !mi::idler.Engaged(); }, - 5000)); + REQUIRE(SimulateEngageIdlerFully(ff)); // now pulling the filament until finda triggers REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA); +} + +TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") { + logic::UnloadToFinda ff; + UnloadToFindaCommonSetup(ff, 1); + UnloadToFindaCommonTurnOffFSensor(ff); + REQUIRE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 10, 1000), 1100)); REQUIRE(ff.State() == logic::UnloadToFinda::OK); @@ -92,37 +93,9 @@ TEST_CASE("unload_to_finda::no_sense_FINDA_upon_start", "[unload_to_finda]") { } TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]") { - ForceReinitAllAutomata(); - REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley)); - - // we need finda ON - SetFINDAStateAndDebounce(true); - // fsensor should be ON - SetFSensorStateAndDebounce(true); - // and MMU "thinks" it has the filament loaded - mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle); - logic::UnloadToFinda ff; - - // restart the automaton - just 1 attempt - ff.Reset(1); - - REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler); - - // it should have instructed the selector and idler to move to slot 1 - // check if the idler and selector have the right command - CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v); - CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v); - CHECK(mm::axes[mm::Idler].enabled == true); - - // engaging idler - REQUIRE(WhileCondition( - ff, - [&](uint32_t) { return !mi::idler.Engaged(); }, - 5000)); - - // now pulling the filament until finda triggers - REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA); + UnloadToFindaCommonSetup(ff, 1); + UnloadToFindaCommonTurnOffFSensor(ff); // no changes to FINDA during unload - we'll pretend it never triggers // but set FSensor correctly @@ -134,96 +107,67 @@ TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]") } TEST_CASE("unload_to_finda::unload_without_FSensor_trigger", "[unload_to_finda]") { - ForceReinitAllAutomata(); - REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley)); - - // we need finda ON - SetFINDAStateAndDebounce(true); - // fsensor should be ON - SetFSensorStateAndDebounce(true); - // and MMU "thinks" it has the filament loaded - mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle); - logic::UnloadToFinda ff; - - // restart the automaton - just 1 attempt - ff.Reset(1); - - REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler); - - // it should have instructed the selector and idler to move to slot 1 - // check if the idler and selector have the right command - CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v); - CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v); - CHECK(mm::axes[mm::Idler].enabled == true); - - // engaging idler - REQUIRE(WhileCondition( - ff, - [&](uint32_t) { return !mi::idler.Engaged(); }, - 5000)); - - // now pulling the filament until finda triggers - REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA); + UnloadToFindaCommonSetup(ff, 1); // no changes to FSensor during unload - we'll pretend it never triggers - // but set FINDA correctly - REQUIRE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 150000, 10000), 50000)); + // time-out in 4 seconds + mt::IncMillis(4000); + main_loop(); + ff.Step(); + + // no pulling actually starts, because the fsensor didn't turn off and the time-out elapsed REQUIRE(ff.State() == logic::UnloadToFinda::FailedFSensor); - REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector); + REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InNozzle); } TEST_CASE("unload_to_finda::unload_repeated", "[unload_to_finda]") { - ForceReinitAllAutomata(); - REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley)); - - // we need finda ON - SetFINDAStateAndDebounce(true); - // fsensor should be ON - SetFSensorStateAndDebounce(true); - // and MMU "thinks" it has the filament loaded - mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle); - logic::UnloadToFinda ff; + UnloadToFindaCommonSetup(ff, 2); - // restart the automaton - 2 attempts - ff.Reset(2); + UnloadToFindaCommonTurnOffFSensor(ff); - REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler); - - // it should have instructed the selector and idler to move to slot 1 - // check if the idler and selector have the right command - CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v); - CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v); - CHECK(mm::axes[mm::Idler].enabled == true); - - // engaging idler - REQUIRE(WhileCondition( - ff, - [&](uint32_t) { return !mi::idler.Engaged(); }, - 5000)); - - // now pulling the filament until finda triggers - REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA); + // remember raw Pulley pos for tweaking the steps below + // because a 20mm (config::fsensorToNozzleAvoidGrindUnload) + // move is being executed while the Idler is fully engaging + // It is roughly -90 steps + // int32_t pulleySteppedAlready = mm::axes[config::Pulley].pos; // no changes to FINDA during unload - we'll pretend it never triggers // but set FSensor correctly // In this case it is vital to correctly compute the amount of steps // to make the unload state machine restart after the 1st attempt - uint32_t unlSteps = 1 + mm::unitToSteps(config::maximumBowdenLength + config::feedToFinda + config::filamentMinLoadedToMMU); + // The number of steps must be more than what the state machine expects for FINDA to trigger. + uint32_t unlSteps = 1 + mm::unitToSteps( + // standard fast move distance + config::maximumBowdenLength + config::feedToFinda + config::filamentMinLoadedToMMU + // slow start move distance + + config::fsensorToNozzleAvoidGrindUnload); + // compensation + // + pulleySteppedAlready; REQUIRE_FALSE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 10, 150000), unlSteps)); main_loop(); ff.Step(); + REQUIRE_FALSE(mi::idler.HomingValid()); REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler); REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector); + HomeIdler(); + + main_loop(); + ff.Step(); + REQUIRE(ff.State() == logic::UnloadToFinda::UnloadingToFinda); + + SimulateEngageIdlerPartially(ff); + main_loop(); ff.Step(); - REQUIRE(ff.State() == logic::UnloadToFinda::UnloadingToFinda); + SimulateEngageIdlerFully(ff); + REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector); // make arbitrary amount of steps