diff --git a/src/logic/eject_filament.h b/src/logic/eject_filament.h index fd43d9b..b77bde9 100644 --- a/src/logic/eject_filament.h +++ b/src/logic/eject_filament.h @@ -40,7 +40,9 @@ public: ErrorCode Error() const override; +#ifndef UNITTEST private: +#endif UnloadFilament unl; ///< a high-level command/operation may be used as a building block of other operations as well uint8_t slot; void MoveSelectorAside(); diff --git a/tests/unit/logic/eject_filament/test_eject_filament.cpp b/tests/unit/logic/eject_filament/test_eject_filament.cpp index 5812c9d..277bcce 100644 --- a/tests/unit/logic/eject_filament/test_eject_filament.cpp +++ b/tests/unit/logic/eject_filament/test_eject_filament.cpp @@ -1,3 +1,4 @@ +#include #include "catch2/catch_test_macros.hpp" #include "catch2/generators/catch_generators.hpp" @@ -10,10 +11,12 @@ #include "../../../../src/modules/motion.h" #include "../../../../src/modules/permanent_storage.h" #include "../../../../src/modules/selector.h" +#include "../../../../src/modules/user_input.h" #include "../../../../src/logic/eject_filament.h" #include "../../modules/stubs/stub_adc.h" +#include "../../modules/stubs/stub_timebase.h" #include "../stubs/main_loop_stub.h" #include "../stubs/stub_motion.h" @@ -25,6 +28,7 @@ TEST_CASE("eject_filament::eject0-4", "[eject_filament]") { uint8_t ejectSlot = GENERATE(0, 1, 2, 3, 4); uint8_t selectorParkedPos = (ejectSlot <= 2) ? 4 : 0; + INFO("Testing ejecting slot " << (int)ejectSlot); ForceReinitAllAutomata(); @@ -82,3 +86,94 @@ TEST_CASE("eject_filament::invalid_slot", "[eject_filament]") { InvalidSlot(ef, activeSlot, config::toolCount); } } + +TEST_CASE("eject_filament::flickering_FINDA", "[eject_filament]") { + using namespace logic; + using namespace std::placeholders; + + uint8_t ejectSlot = GENERATE(0, 1, 2, 3, 4); + uint8_t selectorParkedPos = (ejectSlot <= 2) ? 4 : 0; + INFO("Testing ejecting slot " << (int)ejectSlot); + + ForceReinitAllAutomata(); + + REQUIRE(EnsureActiveSlotIndex(ejectSlot, mg::FilamentLoadState::AtPulley)); + + EjectFilament ef; + // restart the automaton + ef.Reset(ejectSlot); + + main_loop(); + + // Start at UnloadingFilament + REQUIRE(ef.TopLevelState() == ProgressCode::UnloadingFilament); + + // This is something else than WhileTopState()==UnloadingFilament + // We need to catch the very moment, when the unload finished and a move to another slot is being planned + REQUIRE(WhileCondition( + ef, [&](uint32_t) -> bool { return ef.unl.State() != ProgressCode::OK; }, 5000)); + + // now press FINDA again, but prevent stepping other state machines + REQUIRE_FALSE(mf::finda.Pressed()); + hal::gpio::WritePin(FINDA_PIN, hal::gpio::Level::high); + while (!mf::finda.Pressed()) { + mf::finda.Step(); + mt::IncMillis(); + } + REQUIRE(mf::finda.Pressed()); + REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::AtPulley); + + main_loop(); + ef.Step(); + + // Idler disengages, and error is pending + REQUIRE(VerifyState2(ef, mg::FilamentLoadState::AtPulley, mi::idler.IdleSlotIndex(), ejectSlot, true, false, ejectSlot, ml::off, ml::blink0, ErrorCode::RUNNING, ProgressCode::ERRDisengagingIdler)); + + SimulateErrDisengagingIdler(ef, ErrorCode::FINDA_FLICKERS); // this should be a single step, Idler should remain disengaged due to previous error + + // Validate waiting for user state + REQUIRE_FALSE(mui::userInput.AnyEvent()); + REQUIRE(VerifyState2(ef, mg::FilamentLoadState::AtPulley, mi::idler.IdleSlotIndex(), ejectSlot, true, false, ejectSlot, ml::off, ml::blink0, ErrorCode::FINDA_FLICKERS, ProgressCode::ERRWaitingForUser)); + + // Reset FINDA + SetFINDAStateAndDebounce(false); + + // Press middle button to 'retry' + PressButtonAndDebounce(ef, mb::Middle, true); + ClearButtons(ef); + + // Now everything should continue as normal + REQUIRE(VerifyState2(ef, mg::FilamentLoadState::AtPulley, mi::idler.IdleSlotIndex(), ejectSlot, false, false, ejectSlot, ml::off, ml::blink0, ErrorCode::RUNNING, ProgressCode::ParkingSelector)); + + REQUIRE(WhileTopState(ef, ProgressCode::ParkingSelector, selectorMoveMaxSteps)); + + // Engaging idler + REQUIRE(ef.TopLevelState() == ProgressCode::EngagingIdler); + + REQUIRE(WhileTopState(ef, ProgressCode::EngagingIdler, 5000)); + + REQUIRE(mi::idler.Engaged()); + REQUIRE(ef.TopLevelState() == ProgressCode::EjectingFilament); + + REQUIRE(WhileTopState(ef, ProgressCode::EjectingFilament, 5000)); + + // should end up in error disengage idler + REQUIRE(VerifyState2(ef, mg::FilamentLoadState::AtPulley, ejectSlot, selectorParkedPos, false, true, ejectSlot, ml::off, ml::blink0, ErrorCode::RUNNING, ProgressCode::ERRDisengagingIdler)); + + SimulateErrDisengagingIdler(ef, ErrorCode::FILAMENT_EJECTED); + + // Pulley should now be disabled + REQUIRE(VerifyState2(ef, mg::FilamentLoadState::AtPulley, mi::idler.IdleSlotIndex(), selectorParkedPos, false, false, ejectSlot, ml::off, ml::blink0, ErrorCode::FILAMENT_EJECTED, ProgressCode::ERRWaitingForUser)); + + // Now press Done button + PressButtonAndDebounce(ef, mb::Middle, true); + ClearButtons(ef); + + // Idler and Selector are not on HOLD state + REQUIRE(mi::idler.State() != mm::MovableBase::OnHold); + REQUIRE(ms::selector.State() != mm::MovableBase::OnHold); + + // Error code is now OK + // LEDs turn off at the ejected slot + REQUIRE(VerifyState2(ef, mg::FilamentLoadState::AtPulley, mi::idler.IdleSlotIndex(), selectorParkedPos, false, false, ejectSlot, ml::off, ml::off, ErrorCode::OK, ProgressCode::OK)); +}