diff --git a/src/logic/command_base.cpp b/src/logic/command_base.cpp index f8d1784..7152897 100644 --- a/src/logic/command_base.cpp +++ b/src/logic/command_base.cpp @@ -73,19 +73,20 @@ static inline ErrorCode WithoutAxisBits(ErrorCode ec) { | static_cast(ErrorCode::TMC_PULLEY_BIT)))); } -bool CommandBase::WaitForOneModuleErrorRecovery(ErrorCode ec, modules::motion::MovableBase &m) { +bool CommandBase::WaitForOneModuleErrorRecovery(ErrorCode ec, modules::motion::MovableBase &m, uint8_t axisMask) { if (ec != ErrorCode::RUNNING) { - if (stateBeforeModuleFailed == ProgressCode::OK) { + if (stateBeforeModuleFailed == ProgressCode::Empty) { // a new problem with the movable modules // @@TODO not sure how to prevent losing the previously accumulated error ... or do I really need to do it? // May be the TMC error word just gets updated with new flags as the motion proceeds stateBeforeModuleFailed = state; + errorBeforeModuleFailed = error; error = ec; state = ProgressCode::ERRWaitingForUser; // such a situation always requires user's attention -> let the printer display an error screen } // are we already recovering an error - that would mean we got another one - if (recoveringMovableError) { + if (recoveringMovableErrorAxisMask) { error = ec; state = ProgressCode::ERRWaitingForUser; // such a situation always requires user's attention -> let the printer display an error screen } @@ -96,7 +97,7 @@ bool CommandBase::WaitForOneModuleErrorRecovery(ErrorCode ec, modules::motion::M // homing can be recovered mui::Event ev = mui::userInput.ConsumeEvent(); if (ev == mui::Event::Middle) { - recoveringMovableError = true; + recoveringMovableErrorAxisMask |= axisMask; m.PlanHome(); // force initiate a new homing attempt state = ProgressCode::Homing; error = ErrorCode::RUNNING; @@ -104,34 +105,33 @@ bool CommandBase::WaitForOneModuleErrorRecovery(ErrorCode ec, modules::motion::M } // TMC errors cannot be recovered safely, waiting for power cycling the MMU return true; + default: + return true; // prevent descendant from taking over while in an error state + } + } else if (recoveringMovableErrorAxisMask & axisMask) { + switch (state) { case ProgressCode::Homing: if (m.HomingValid()) { // managed to recover from a homing problem state = stateBeforeModuleFailed; - recoveringMovableError = false; - stateBeforeModuleFailed = ProgressCode::OK; + error = errorBeforeModuleFailed; + recoveringMovableErrorAxisMask &= (~axisMask); + stateBeforeModuleFailed = ProgressCode::Empty; return false; } - return true; + return true; // prevent descendant from taking over while recovering default: - return true; // no idea what to do in other states ... set internal fw error state? + return false; // let descendant do its processing? } - return true; } - return false; + return recoveringMovableErrorAxisMask & axisMask; } bool CommandBase::WaitForModulesErrorRecovery() { - if (WaitForOneModuleErrorRecovery(CheckMovable(mi::idler), mi::idler)) - return true; - - if (WaitForOneModuleErrorRecovery(CheckMovable(ms::selector), ms::selector)) - return true; - - if (WaitForOneModuleErrorRecovery(CheckMovable(mpu::pulley), mpu::pulley)) - return true; - - return false; + bool rv = WaitForOneModuleErrorRecovery(CheckMovable(mi::idler), mi::idler, 0x1); + rv |= WaitForOneModuleErrorRecovery(CheckMovable(ms::selector), ms::selector, 0x2); + rv |= WaitForOneModuleErrorRecovery(CheckMovable(mpu::pulley), mpu::pulley, 0x4); + return rv; } bool CommandBase::Step() { diff --git a/src/logic/command_base.h b/src/logic/command_base.h index 727746c..afb6688 100644 --- a/src/logic/command_base.h +++ b/src/logic/command_base.h @@ -26,8 +26,9 @@ public: inline CommandBase() : state(ProgressCode::OK) , error(ErrorCode::OK) - , stateBeforeModuleFailed(ProgressCode::OK) - , recoveringMovableError(false) {} + , stateBeforeModuleFailed(ProgressCode::Empty) + , errorBeforeModuleFailed(ErrorCode::OK) + , recoveringMovableErrorAxisMask(0) {} // Normally, a base class should (must) have a virtual destructor to enable correct deallocation of superstructures. // However, in our case we don't want ANY destruction of these objects and moreover - adding a destructor like this @@ -97,7 +98,7 @@ protected: bool WaitForModulesErrorRecovery(); /// @returns true when still waiting for a module to recover, false otherwise. - bool WaitForOneModuleErrorRecovery(ErrorCode iState, modules::motion::MovableBase &m); + bool WaitForOneModuleErrorRecovery(ErrorCode iState, modules::motion::MovableBase &m, uint8_t axisMask); /// Perform disengaging idler in ErrDisengagingIdler state void ErrDisengagingIdler(); @@ -111,7 +112,8 @@ protected: ProgressCode state; ///< current progress state of the state machine ErrorCode error; ///< current error code ProgressCode stateBeforeModuleFailed; ///< saved state of the state machine before a common error happened - bool recoveringMovableError; + ErrorCode errorBeforeModuleFailed; ///< saved error of the state machine before a common error happened + uint8_t recoveringMovableErrorAxisMask; }; } // namespace logic diff --git a/src/logic/progress_codes.h b/src/logic/progress_codes.h index eb97c56..bdb17c6 100644 --- a/src/logic/progress_codes.h +++ b/src/logic/progress_codes.h @@ -37,4 +37,6 @@ enum class ProgressCode : uint_fast8_t { RetractingFromFinda, // P25 Homing, + + Empty = 0xff // dummy empty state }; diff --git a/tests/unit/logic/cut_filament/CMakeLists.txt b/tests/unit/logic/cut_filament/CMakeLists.txt index 8afcaa4..1bb5360 100644 --- a/tests/unit/logic/cut_filament/CMakeLists.txt +++ b/tests/unit/logic/cut_filament/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_cut_filament.cpp diff --git a/tests/unit/logic/eject_filament/CMakeLists.txt b/tests/unit/logic/eject_filament/CMakeLists.txt index c0efedc..b60ef97 100644 --- a/tests/unit/logic/eject_filament/CMakeLists.txt +++ b/tests/unit/logic/eject_filament/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_eject_filament.cpp diff --git a/tests/unit/logic/failing_tmc/CMakeLists.txt b/tests/unit/logic/failing_tmc/CMakeLists.txt index 79b716f..b8603dc 100644 --- a/tests/unit/logic/failing_tmc/CMakeLists.txt +++ b/tests/unit/logic/failing_tmc/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_failing_tmc.cpp diff --git a/tests/unit/logic/feed_to_bondtech/CMakeLists.txt b/tests/unit/logic/feed_to_bondtech/CMakeLists.txt index 99b84ba..3ef41b4 100644 --- a/tests/unit/logic/feed_to_bondtech/CMakeLists.txt +++ b/tests/unit/logic/feed_to_bondtech/CMakeLists.txt @@ -1,6 +1,7 @@ # define the test executable add_executable( feed_to_bondtech_tests + ${CMAKE_SOURCE_DIR}/src/logic/command_base.cpp ${CMAKE_SOURCE_DIR}/src/logic/feed_to_bondtech.cpp ${CMAKE_SOURCE_DIR}/src/modules/buttons.cpp ${CMAKE_SOURCE_DIR}/src/modules/debouncer.cpp @@ -21,6 +22,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_feed_to_bondtech.cpp diff --git a/tests/unit/logic/feed_to_finda/CMakeLists.txt b/tests/unit/logic/feed_to_finda/CMakeLists.txt index ad87517..c5d8211 100644 --- a/tests/unit/logic/feed_to_finda/CMakeLists.txt +++ b/tests/unit/logic/feed_to_finda/CMakeLists.txt @@ -1,6 +1,7 @@ # define the test executable add_executable( feed_to_finda_tests + ${CMAKE_SOURCE_DIR}/src/logic/command_base.cpp ${CMAKE_SOURCE_DIR}/src/logic/feed_to_finda.cpp ${CMAKE_SOURCE_DIR}/src/modules/buttons.cpp ${CMAKE_SOURCE_DIR}/src/modules/debouncer.cpp @@ -21,6 +22,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_feed_to_finda.cpp diff --git a/tests/unit/logic/helpers/helpers.ipp b/tests/unit/logic/helpers/helpers.ipp index 7e48377..2521f26 100644 --- a/tests/unit/logic/helpers/helpers.ipp +++ b/tests/unit/logic/helpers/helpers.ipp @@ -19,17 +19,22 @@ bool VerifyEnvironmentState(mg::FilamentLoadState fls, uint8_t idlerSlotIndex, u return false; } } - CHECKED_ELSE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(idlerSlotIndex).v) { - return false; + + if( idlerSlotIndex < config::toolCount ){ // abusing invalid index to skip checking of slot and position + CHECKED_ELSE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(idlerSlotIndex).v) { + return false; + } + CHECKED_ELSE(mi::idler.Engaged() == (idlerSlotIndex < config::toolCount)) { + return false; + } } - CHECKED_ELSE(mi::idler.Engaged() == (idlerSlotIndex < config::toolCount)) { - return false; - } - CHECKED_ELSE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(selectorSlotIndex).v) { - return false; - } - CHECKED_ELSE(ms::selector.Slot() == selectorSlotIndex) { - return false; + if( selectorSlotIndex < config::toolCount ){ // abusing invalid index to skip checking of slot and position + CHECKED_ELSE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(selectorSlotIndex).v) { + return false; + } + CHECKED_ELSE(ms::selector.Slot() == selectorSlotIndex) { + return false; + } } CHECKED_ELSE(mf::finda.Pressed() == findaPressed) { return false; @@ -143,21 +148,3 @@ void InvalidSlot(SM &logicSM, uint8_t activeSlot, uint8_t invSlot){ logicSM.Reset(invSlot); REQUIRE(VerifyState(logicSM, mg::FilamentLoadState::AtPulley, mi::Idler::IdleSlotIndex(), activeSlot, false, false, ml::off, ml::off, ErrorCode::INVALID_TOOL, ProgressCode::OK)); } - -template -void PressButtonAndDebounce(SM &sm, uint8_t btnIndex){ - hal::adc::SetADC(config::buttonsADCIndex, config::buttonADCLimits[btnIndex][0] + 1); - while (!mb::buttons.ButtonPressed(btnIndex)) { - main_loop(); - sm.Step(); // Inner - } -} - -template -void ClearButtons(SM &sm){ - hal::adc::SetADC(config::buttonsADCIndex, config::buttonADCMaxValue); - while (mb::buttons.AnyButtonPressed()) { - main_loop(); - sm.Step(); // Inner - } -} diff --git a/tests/unit/logic/homing/CMakeLists.txt b/tests/unit/logic/homing/CMakeLists.txt index 29a2534..d41a763 100644 --- a/tests/unit/logic/homing/CMakeLists.txt +++ b/tests/unit/logic/homing/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_homing.cpp diff --git a/tests/unit/logic/homing/test_homing.cpp b/tests/unit/logic/homing/test_homing.cpp index b8f27ea..0dd3b9f 100644 --- a/tests/unit/logic/homing/test_homing.cpp +++ b/tests/unit/logic/homing/test_homing.cpp @@ -16,6 +16,7 @@ #include "../../modules/stubs/stub_adc.h" +#include "../stubs/homing.h" #include "../stubs/main_loop_stub.h" #include "../stubs/stub_motion.h" @@ -41,7 +42,7 @@ bool SuccessfulHome(uint8_t slot) { REQUIRE_FALSE(mi::idler.HomingValid()); REQUIRE_FALSE(ms::selector.HomingValid()); - SimulateIdlerAndSelectorHoming(); + SimulateIdlerAndSelectorHoming(h); REQUIRE(WhileTopState(h, ProgressCode::Homing, 5000)); @@ -58,120 +59,6 @@ TEST_CASE("homing::successful_run", "[homing]") { } } -template -bool SimulateFailedHomePostfix(T &h) { - REQUIRE(WhileTopState(h, ProgressCode::Homing, 5)); - REQUIRE(mi::idler.HomingValid()); - - REQUIRE(h.Error() == ErrorCode::HOMING_SELECTOR_FAILED); - REQUIRE(h.State() == ProgressCode::ERRWaitingForUser); - REQUIRE_FALSE(mm::motion.Enabled(mm::Selector)); - - // do a few steps before pushing the button - WhileTopState(h, ProgressCode::ERRWaitingForUser, 5); - - REQUIRE_FALSE(mm::motion.Enabled(mm::Selector)); - - PressButtonAndDebounce(h, mb::Middle); - - // it shall start homing again - REQUIRE(h.Error() == ErrorCode::RUNNING); - REQUIRE(h.State() == ProgressCode::Homing); - REQUIRE_FALSE(ms::selector.HomingValid()); - REQUIRE(mm::motion.Enabled(mm::Selector)); - - ClearButtons(h); - - return true; -} - -template -bool SimulateFailedHomeFirstTime(T &h) { - REQUIRE_FALSE(mi::idler.HomingValid()); - REQUIRE_FALSE(ms::selector.HomingValid()); - - { - // do 5 steps until we trigger the simulated stallguard - for (uint8_t i = 0; i < 5; ++i) { - main_loop(); - } - - mm::TriggerStallGuard(mm::Selector); - mm::TriggerStallGuard(mm::Idler); - main_loop(); - mm::motion.StallGuardReset(mm::Selector); - mm::motion.StallGuardReset(mm::Idler); - } - // now do a correct amount of steps of each axis towards the other end - uint32_t idlerSteps = mm::unitToSteps(config::idlerLimits.lenght); - // now do LESS steps than expected to simulate something is blocking the selector - uint32_t selectorSteps = mm::unitToSteps(config::selectorLimits.lenght) + 1; - uint32_t selectorTriggerShort = std::min(idlerSteps, selectorSteps) / 2; - uint32_t maxSteps = selectorTriggerShort + 1; - { - for (uint32_t i = 0; i < maxSteps; ++i) { - main_loop(); - - if (i == selectorTriggerShort) { - mm::TriggerStallGuard(mm::Selector); - } else { - mm::motion.StallGuardReset(mm::Selector); - } - } - - // make sure the Idler finishes its homing procedure (makes further checks much easier) - for (uint32_t i = maxSteps; i < idlerSteps + 1; ++i) { - main_loop(); - if (i == idlerSteps) { - mm::TriggerStallGuard(mm::Idler); - } else { - mm::motion.StallGuardReset(mm::Idler); - } - } - - while (ms::selector.State() != mm::MovableBase::HomingFailed) - main_loop(); - } - - return SimulateFailedHomePostfix(h); -} - -template -bool SimulateFailedHomeSelectorRepeated(T &h) { - // we leave Idler aside in this case - REQUIRE_FALSE(ms::selector.HomingValid()); - - { - // do 5 steps until we trigger the simulated stallguard - for (uint8_t i = 0; i < 5; ++i) { - main_loop(); - } - - mm::TriggerStallGuard(mm::Selector); - main_loop(); - mm::motion.StallGuardReset(mm::Selector); - } - uint32_t selectorSteps = mm::unitToSteps(config::selectorLimits.lenght) + 1; - uint32_t selectorTriggerShort = selectorSteps / 2; - uint32_t maxSteps = selectorTriggerShort + 1; - { - for (uint32_t i = 0; i < maxSteps; ++i) { - main_loop(); - - if (i == selectorTriggerShort) { - mm::TriggerStallGuard(mm::Selector); - } else { - mm::motion.StallGuardReset(mm::Selector); - } - } - - while (ms::selector.State() != mm::MovableBase::HomingFailed) - main_loop(); - } - - return SimulateFailedHomePostfix(h); -} - bool SelectorFailedRetry() { // prepare startup conditions ForceReinitAllAutomata(); @@ -194,7 +81,7 @@ bool SelectorFailedRetry() { REQUIRE(SimulateFailedHomeSelectorRepeated(h)); } - SimulateSelectorHoming(); + SimulateSelectorHoming(h); REQUIRE(WhileTopState(h, ProgressCode::Homing, 5000)); diff --git a/tests/unit/logic/load_filament/CMakeLists.txt b/tests/unit/logic/load_filament/CMakeLists.txt index 966750d..45524cb 100644 --- a/tests/unit/logic/load_filament/CMakeLists.txt +++ b/tests/unit/logic/load_filament/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_load_filament.cpp diff --git a/tests/unit/logic/load_filament/test_load_filament.cpp b/tests/unit/logic/load_filament/test_load_filament.cpp index 1f63008..06ca9f3 100644 --- a/tests/unit/logic/load_filament/test_load_filament.cpp +++ b/tests/unit/logic/load_filament/test_load_filament.cpp @@ -14,6 +14,7 @@ #include "../../modules/stubs/stub_adc.h" +#include "../stubs/homing.h" #include "../stubs/main_loop_stub.h" #include "../stubs/stub_motion.h" @@ -140,10 +141,10 @@ void FailedLoadToFindaResolveManual(uint8_t slot, logic::LoadFilament &lf) { PressButtonAndDebounce(lf, mb::Right); // the Idler also engages in this call as this is planned as the next step - SimulateIdlerHoming(); + SimulateIdlerHoming(lf); // pulling filament back - REQUIRE(VerifyState(lf, mg::FilamentLoadState::InSelector, slot, slot, true, false, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::RetractingFromFinda)); + REQUIRE(VerifyState(lf, mg::FilamentLoadState::InSelector, slot, slot, true, true, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::RetractingFromFinda)); ClearButtons(lf); @@ -168,7 +169,7 @@ void FailedLoadToFindaResolveManual(uint8_t slot, logic::LoadFilament &lf) { // // With the introduction of dual-side homing, the simulation gets even harder, // so let's assume the MMU does its job -> prefer simulating selector homing properly and check the machine's state afterwards - SimulateSelectorHoming(); + SimulateSelectorHoming(lf); // just one step is necessary to "finish" homing // but the selector then (correctly) plans its move to the original position @@ -184,7 +185,7 @@ void FailedLoadToFindaResolveManualNoFINDA(uint8_t slot, logic::LoadFilament &lf // Perform press on button 2 + debounce + keep FINDA OFF (i.e. the user didn't solve anything) PressButtonAndDebounce(lf, mb::Right); - SimulateIdlerHoming(); + SimulateIdlerHoming(lf); ClearButtons(lf); @@ -196,7 +197,8 @@ void FailedLoadToFindaResolveTryAgain(uint8_t slot, logic::LoadFilament &lf) { PressButtonAndDebounce(lf, mb::Middle); // the state machine should have restarted - REQUIRE(VerifyState(lf, mg::FilamentLoadState::InSelector, mi::Idler::IdleSlotIndex(), slot, false, false, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::FeedingToFinda)); + // Idler's position needs to be ignored as it has started homing after the button press + REQUIRE(VerifyState(lf, mg::FilamentLoadState::InSelector, config::toolCount, slot, false, false, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::FeedingToFinda)); ClearButtons(lf); LoadFilamentSuccessful(slot, lf); diff --git a/tests/unit/logic/stubs/homing.cpp b/tests/unit/logic/stubs/homing.cpp new file mode 100644 index 0000000..48d1d5e --- /dev/null +++ b/tests/unit/logic/stubs/homing.cpp @@ -0,0 +1,251 @@ +#include "homing.h" +#include "main_loop_stub.h" + +#include "../../../../src/modules/buttons.h" +#include "../../../../src/modules/idler.h" +#include "../../../../src/modules/motion.h" +#include "../../../../src/modules/selector.h" + +#include "../stubs/stub_motion.h" + +void SimulateIdlerAndSelectorHoming(logic::CommandBase &cb) { + // do 5 steps until we trigger the simulated stallguard + for (uint8_t i = 0; i < 5; ++i) { + main_loop(); + cb.Step(); + } + + mm::TriggerStallGuard(mm::Selector); + mm::TriggerStallGuard(mm::Idler); + main_loop(); + cb.Step(); + mm::motion.StallGuardReset(mm::Selector); + mm::motion.StallGuardReset(mm::Idler); + + // now do a correct amount of steps of each axis towards the other end + uint32_t idlerSteps = mm::unitToSteps(config::idlerLimits.lenght); + uint32_t selectorSteps = mm::unitToSteps(config::selectorLimits.lenght); + uint32_t maxSteps = std::max(idlerSteps, selectorSteps) + 1; + + for (uint32_t i = 0; i < maxSteps; ++i) { + main_loop(); + cb.Step(); + + if (i == idlerSteps) { + mm::TriggerStallGuard(mm::Idler); + } else { + mm::motion.StallGuardReset(mm::Idler); + } + if (i == selectorSteps) { + mm::TriggerStallGuard(mm::Selector); + } else { + mm::motion.StallGuardReset(mm::Selector); + } + } + + // now the Selector and Idler shall perform a move into their parking positions + while (ms::selector.State() != mm::MovableBase::Ready || mi::idler.State() != mm::MovableBase::Ready) { + main_loop(); + cb.Step(); + } +} + +void SimulateIdlerHoming(logic::CommandBase &cb) { + // do 5 steps until we trigger the simulated stallguard + for (uint8_t i = 0; i < 5; ++i) { + main_loop(); + cb.Step(); + } + + mm::TriggerStallGuard(mm::Idler); + main_loop(); + cb.Step(); + mm::motion.StallGuardReset(mm::Idler); + + // now do a correct amount of steps of each axis towards the other end + uint32_t idlerSteps = mm::unitToSteps(config::idlerLimits.lenght); + uint32_t maxSteps = idlerSteps + 1; + + for (uint32_t i = 0; i < maxSteps; ++i) { + main_loop(); + cb.Step(); + + if (i == idlerSteps) { + mm::TriggerStallGuard(mm::Idler); + } else { + mm::motion.StallGuardReset(mm::Idler); + } + } + + // now the Idler shall perform a move into their parking positions + while (mi::idler.State() != mm::MovableBase::Ready) { + main_loop(); + cb.Step(); + } +} + +void SimulateSelectorHoming(logic::CommandBase &cb) { + // do 5 steps until we trigger the simulated stallguard + for (uint8_t i = 0; i < 5; ++i) { + main_loop(); + cb.Step(); + } + + mm::TriggerStallGuard(mm::Selector); + main_loop(); + cb.Step(); + mm::motion.StallGuardReset(mm::Selector); + + // now do a correct amount of steps of each axis towards the other end + uint32_t selectorSteps = mm::unitToSteps(config::selectorLimits.lenght) + 1; + uint32_t maxSteps = selectorSteps + 1; + + for (uint32_t i = 0; i < maxSteps; ++i) { + main_loop(); + cb.Step(); + + if (i == selectorSteps) { + mm::TriggerStallGuard(mm::Selector); + } else { + mm::motion.StallGuardReset(mm::Selector); + } + } + + // now the Selector shall perform a move into their parking positions + while (ms::selector.State() != mm::MovableBase::Ready) { + main_loop(); + cb.Step(); + } +} + +bool SimulateFailedHomeSelectorPostfix(logic::CommandBase &cb) { + if (!WhileTopState(cb, ProgressCode::Homing, 5)) + return false; + if (cb.Error() != ErrorCode::HOMING_SELECTOR_FAILED) + return false; + if (cb.State() != ProgressCode::ERRWaitingForUser) + return false; + if (mm::motion.Enabled(mm::Selector)) + return false; + + // do a few steps before pushing the button + WhileTopState(cb, ProgressCode::ERRWaitingForUser, 5); + + if (mm::motion.Enabled(mm::Selector)) + return false; + + PressButtonAndDebounce(cb, mb::Middle); + + // it shall start homing again + if (cb.Error() != ErrorCode::RUNNING) + return false; + if (cb.State() != ProgressCode::Homing) + return false; + if (ms::selector.HomingValid()) + return false; + if (!mm::motion.Enabled(mm::Selector)) + return false; + + ClearButtons(cb); + + return true; +} + +bool SimulateFailedHomeFirstTime(logic::CommandBase &cb) { + if (mi::idler.HomingValid()) + return false; + if (ms::selector.HomingValid()) + return false; + + { + // do 5 steps until we trigger the simulated stallguard + for (uint8_t i = 0; i < 5; ++i) { + main_loop(); + cb.Step(); + } + + mm::TriggerStallGuard(mm::Selector); + mm::TriggerStallGuard(mm::Idler); + main_loop(); + cb.Step(); + mm::motion.StallGuardReset(mm::Selector); + mm::motion.StallGuardReset(mm::Idler); + } + // now do a correct amount of steps of each axis towards the other end + uint32_t idlerSteps = mm::unitToSteps(config::idlerLimits.lenght); + // now do LESS steps than expected to simulate something is blocking the selector + uint32_t selectorSteps = mm::unitToSteps(config::selectorLimits.lenght) + 1; + uint32_t selectorTriggerShort = std::min(idlerSteps, selectorSteps) / 2; + uint32_t maxSteps = selectorTriggerShort + 1; + { + for (uint32_t i = 0; i < maxSteps; ++i) { + main_loop(); + cb.Step(); + + if (i == selectorTriggerShort) { + mm::TriggerStallGuard(mm::Selector); + } else { + mm::motion.StallGuardReset(mm::Selector); + } + } + + // make sure the Idler finishes its homing procedure (makes further checks much easier) + for (uint32_t i = maxSteps; i < idlerSteps + 1; ++i) { + main_loop(); + cb.Step(); + if (i == idlerSteps) { + mm::TriggerStallGuard(mm::Idler); + } else { + mm::motion.StallGuardReset(mm::Idler); + } + } + + while (ms::selector.State() != mm::MovableBase::HomingFailed) { + main_loop(); + cb.Step(); + } + } + + return SimulateFailedHomeSelectorPostfix(cb); +} + +bool SimulateFailedHomeSelectorRepeated(logic::CommandBase &cb) { + // we leave Idler aside in this case + if (ms::selector.HomingValid()) + return false; + + { + // do 5 steps until we trigger the simulated stallguard + for (uint8_t i = 0; i < 5; ++i) { + main_loop(); + cb.Step(); + } + + mm::TriggerStallGuard(mm::Selector); + main_loop(); + cb.Step(); + mm::motion.StallGuardReset(mm::Selector); + } + uint32_t selectorSteps = mm::unitToSteps(config::selectorLimits.lenght) + 1; + uint32_t selectorTriggerShort = selectorSteps / 2; + uint32_t maxSteps = selectorTriggerShort + 1; + { + for (uint32_t i = 0; i < maxSteps; ++i) { + main_loop(); + cb.Step(); + + if (i == selectorTriggerShort) { + mm::TriggerStallGuard(mm::Selector); + } else { + mm::motion.StallGuardReset(mm::Selector); + } + } + + while (ms::selector.State() != mm::MovableBase::HomingFailed) { + main_loop(); + cb.Step(); + } + } + + return SimulateFailedHomeSelectorPostfix(cb); +} diff --git a/tests/unit/logic/stubs/homing.h b/tests/unit/logic/stubs/homing.h new file mode 100644 index 0000000..d7932ed --- /dev/null +++ b/tests/unit/logic/stubs/homing.h @@ -0,0 +1,11 @@ +#pragma once + +namespace logic { +class CommandBase; +} + +void SimulateIdlerHoming(logic::CommandBase &cb); +void SimulateSelectorHoming(logic::CommandBase &cb); +void SimulateIdlerAndSelectorHoming(logic::CommandBase &cb); +bool SimulateFailedHomeFirstTime(logic::CommandBase &cb); +bool SimulateFailedHomeSelectorRepeated(logic::CommandBase &cb); diff --git a/tests/unit/logic/stubs/main_loop_stub.cpp b/tests/unit/logic/stubs/main_loop_stub.cpp index c4009ac..2bd2c92 100644 --- a/tests/unit/logic/stubs/main_loop_stub.cpp +++ b/tests/unit/logic/stubs/main_loop_stub.cpp @@ -1,4 +1,5 @@ #include "main_loop_stub.h" +#include "homing.h" #include "../../modules/stubs/stub_adc.h" #include "../../modules/stubs/stub_eeprom.h" @@ -16,6 +17,8 @@ #include "../../../../src/modules/selector.h" #include "../../../../src/modules/user_input.h" +#include "../../../../src/logic/no_command.h" + #include "../stubs/stub_motion.h" #include // bring in placement new @@ -77,102 +80,8 @@ void ForceReinitAllAutomata() { void HomeIdlerAndSelector() { ms::selector.InvalidateHoming(); mi::idler.InvalidateHoming(); - SimulateIdlerAndSelectorHoming(); -} - -void SimulateIdlerAndSelectorHoming() { - // do 5 steps until we trigger the simulated stallguard - for (uint8_t i = 0; i < 5; ++i) { - main_loop(); - } - - mm::TriggerStallGuard(mm::Selector); - mm::TriggerStallGuard(mm::Idler); - main_loop(); - mm::motion.StallGuardReset(mm::Selector); - mm::motion.StallGuardReset(mm::Idler); - - // now do a correct amount of steps of each axis towards the other end - uint32_t idlerSteps = mm::unitToSteps(config::idlerLimits.lenght); - uint32_t selectorSteps = mm::unitToSteps(config::selectorLimits.lenght); - uint32_t maxSteps = std::max(idlerSteps, selectorSteps) + 1; - - for (uint32_t i = 0; i < maxSteps; ++i) { - main_loop(); - - if (i == idlerSteps) { - mm::TriggerStallGuard(mm::Idler); - } else { - mm::motion.StallGuardReset(mm::Idler); - } - if (i == selectorSteps) { - mm::TriggerStallGuard(mm::Selector); - } else { - mm::motion.StallGuardReset(mm::Selector); - } - } - - // now the Selector and Idler shall perform a move into their parking positions - while (ms::selector.State() != mm::MovableBase::Ready || mi::idler.State() != mm::MovableBase::Ready) - main_loop(); -} - -void SimulateIdlerHoming() { - // do 5 steps until we trigger the simulated stallguard - for (uint8_t i = 0; i < 5; ++i) { - main_loop(); - } - - mm::TriggerStallGuard(mm::Idler); - main_loop(); - mm::motion.StallGuardReset(mm::Idler); - - // now do a correct amount of steps of each axis towards the other end - uint32_t idlerSteps = mm::unitToSteps(config::idlerLimits.lenght); - uint32_t maxSteps = idlerSteps + 1; - - for (uint32_t i = 0; i < maxSteps; ++i) { - main_loop(); - - if (i == idlerSteps) { - mm::TriggerStallGuard(mm::Idler); - } else { - mm::motion.StallGuardReset(mm::Idler); - } - } - - // now the Idler shall perform a move into their parking positions - while (mi::idler.State() != mm::MovableBase::Ready) - main_loop(); -} - -void SimulateSelectorHoming() { - // do 5 steps until we trigger the simulated stallguard - for (uint8_t i = 0; i < 5; ++i) { - main_loop(); - } - - mm::TriggerStallGuard(mm::Selector); - main_loop(); - mm::motion.StallGuardReset(mm::Selector); - - // now do a correct amount of steps of each axis towards the other end - uint32_t selectorSteps = mm::unitToSteps(config::selectorLimits.lenght) + 1; - uint32_t maxSteps = selectorSteps + 1; - - for (uint32_t i = 0; i < maxSteps; ++i) { - main_loop(); - - if (i == selectorSteps) { - mm::TriggerStallGuard(mm::Selector); - } else { - mm::motion.StallGuardReset(mm::Selector); - } - } - - // now the Selector shall perform a move into their parking positions - while (ms::selector.State() != mm::MovableBase::Ready) - main_loop(); + logic::NoCommand nc; // just a dummy instance which has an empty Step() + SimulateIdlerAndSelectorHoming(nc); } void EnsureActiveSlotIndex(uint8_t slot, mg::FilamentLoadState loadState) { @@ -207,3 +116,19 @@ bool SimulateUnloadToFINDA(uint32_t step, uint32_t fsOff, uint32_t findaOff) { } return mf::finda.Pressed(); } + +void PressButtonAndDebounce(logic::CommandBase &cb, uint8_t btnIndex) { + hal::adc::SetADC(config::buttonsADCIndex, config::buttonADCLimits[btnIndex][0] + 1); + while (!mb::buttons.ButtonPressed(btnIndex)) { + main_loop(); + cb.Step(); // Inner + } +} + +void ClearButtons(logic::CommandBase &cb) { + hal::adc::SetADC(config::buttonsADCIndex, config::buttonADCMaxValue); + while (mb::buttons.AnyButtonPressed()) { + main_loop(); + cb.Step(); // Inner + } +} diff --git a/tests/unit/logic/stubs/main_loop_stub.h b/tests/unit/logic/stubs/main_loop_stub.h index 73cfcd0..4ac7de6 100644 --- a/tests/unit/logic/stubs/main_loop_stub.h +++ b/tests/unit/logic/stubs/main_loop_stub.h @@ -23,12 +23,13 @@ bool WhileTopState(SM &sm, ProgressCode state, uint32_t maxLoops = 5000) { } void EnsureActiveSlotIndex(uint8_t slot, modules::globals::FilamentLoadState loadState); + void SetFINDAStateAndDebounce(bool press); -void SimulateIdlerHoming(); -void SimulateSelectorHoming(); -void SimulateIdlerAndSelectorHoming(); bool SimulateUnloadToFINDA(uint32_t step, uint32_t fsOff, uint32_t findaOff); +void PressButtonAndDebounce(logic::CommandBase &cb, uint8_t btnIndex); +void ClearButtons(logic::CommandBase &cb); + // these are recommended max steps for simulated movement of the idler and selector // - roughly the amount of motion steps from one end to the other + some margin // ... could be computed in the future from the pre-set number of microsteps and real positions diff --git a/tests/unit/logic/tool_change/CMakeLists.txt b/tests/unit/logic/tool_change/CMakeLists.txt index b0057a7..d3e491c 100644 --- a/tests/unit/logic/tool_change/CMakeLists.txt +++ b/tests/unit/logic/tool_change/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_tool_change.cpp diff --git a/tests/unit/logic/unload_filament/CMakeLists.txt b/tests/unit/logic/unload_filament/CMakeLists.txt index dcaf237..82b0f6f 100644 --- a/tests/unit/logic/unload_filament/CMakeLists.txt +++ b/tests/unit/logic/unload_filament/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_unload_filament.cpp diff --git a/tests/unit/logic/unload_filament/test_unload_filament.cpp b/tests/unit/logic/unload_filament/test_unload_filament.cpp index 8cfc070..61b1785 100644 --- a/tests/unit/logic/unload_filament/test_unload_filament.cpp +++ b/tests/unit/logic/unload_filament/test_unload_filament.cpp @@ -14,6 +14,7 @@ #include "../../modules/stubs/stub_adc.h" +#include "../stubs/homing.h" #include "../stubs/main_loop_stub.h" #include "../stubs/stub_motion.h" @@ -39,7 +40,8 @@ void RegularUnloadFromSlot04Init(uint8_t slot, logic::UnloadFilament &uf) { uf.Reset(slot); } -void RegularUnloadFromSlot04(uint8_t slot, logic::UnloadFilament &uf, uint8_t entryIdlerSlotIndex, bool selectorShallHomeAtEnd) { +void RegularUnloadFromSlot04(uint8_t slot, logic::UnloadFilament &uf, uint8_t entryIdlerSlotIndex, + bool selectorShallHomeAtEnd, ml::Mode entryGreenLED) { // Stage 0 - verify state just after Reset() // we still think we have filament loaded at this stage // idler should have been activated by the underlying automaton @@ -47,7 +49,7 @@ void RegularUnloadFromSlot04(uint8_t slot, logic::UnloadFilament &uf, uint8_t en // FINDA on // green LED should blink, red off REQUIRE(VerifyState(uf, (mg::FilamentLoadState)(mg::FilamentLoadState::InNozzle | mg::FilamentLoadState::InSelector), - entryIdlerSlotIndex, slot, true, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda)); + entryIdlerSlotIndex, slot, true, true, entryGreenLED, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda)); // run the automaton // Stage 1 - unloading to FINDA @@ -74,7 +76,7 @@ void RegularUnloadFromSlot04(uint8_t slot, logic::UnloadFilament &uf, uint8_t en REQUIRE(WhileTopState(uf, ProgressCode::DisengagingIdler, idlerEngageDisengageMaxSteps)); if (selectorShallHomeAtEnd) { - SimulateSelectorHoming(); + SimulateSelectorHoming(uf); } // filament unloaded @@ -95,7 +97,7 @@ TEST_CASE("unload_filament::regular_unload_from_slot_0-4", "[unload_filament]") for (uint8_t slot = 0; slot < config::toolCount; ++slot) { logic::UnloadFilament uf; RegularUnloadFromSlot04Init(slot, uf); - RegularUnloadFromSlot04(slot, uf, mi::Idler::IdleSlotIndex(), false); + RegularUnloadFromSlot04(slot, uf, mi::Idler::IdleSlotIndex(), false, ml::off); } } @@ -256,7 +258,7 @@ void FindaDidntTriggerResolveTryAgain(uint8_t slot, logic::UnloadFilament &uf) { REQUIRE(VerifyState(uf, mg::FilamentLoadState::InSelector, mi::Idler::IdleSlotIndex(), slot, true, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda)); // Assume, the Idler homed (homing is invalidated after pressing the recovery button) - SimulateIdlerHoming(); + SimulateIdlerHoming(uf); } TEST_CASE("unload_filament::finda_didnt_trigger_resolve_try_again", "[unload_filament]") { @@ -264,7 +266,7 @@ TEST_CASE("unload_filament::finda_didnt_trigger_resolve_try_again", "[unload_fil logic::UnloadFilament uf; FindaDidntTriggerCommonSetup(slot, uf); FindaDidntTriggerResolveTryAgain(slot, uf); - RegularUnloadFromSlot04(slot, uf, slot, true); + RegularUnloadFromSlot04(slot, uf, slot, true, ml::blink0); } } @@ -298,7 +300,7 @@ void FailedUnloadResolveManual(uint8_t slot, logic::UnloadFilament &uf) { REQUIRE(VerifyState(uf, mg::FilamentLoadState::InSelector, mi::Idler::IdleSlotIndex(), slot, false, false, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::FeedingToFinda)); // we still need to feed to FINDA and back to verify the position of the filament - SimulateIdlerHoming(); + SimulateIdlerHoming(uf); REQUIRE(WhileTopState(uf, ProgressCode::FeedingToFinda, 5000)); @@ -306,7 +308,7 @@ void FailedUnloadResolveManual(uint8_t slot, logic::UnloadFilament &uf) { REQUIRE(WhileTopState(uf, ProgressCode::DisengagingIdler, idlerEngageDisengageMaxSteps)); - SimulateSelectorHoming(); + SimulateSelectorHoming(uf); REQUIRE(VerifyState(uf, mg::FilamentLoadState::AtPulley, mi::Idler::IdleSlotIndex(), slot, false, false, ml::off, ml::off, ErrorCode::OK, ProgressCode::OK)); } @@ -354,3 +356,31 @@ TEST_CASE("unload_filament::failed_unload_to_finda_0-4_resolve_manual_FSensor_on FailedUnloadResolveManualFSensorOn(slot, uf); } } + +TEST_CASE("unload_filament::unload_homing_retry", "[unload_filament][homing]") { + uint8_t slot = 0; + logic::UnloadFilament uf; + FindaDidntTriggerCommonSetup(slot, uf); + + // simulate the user fixed the issue himself (not really important, we are after a failed homing of the selector) + hal::gpio::WritePin(FINDA_PIN, hal::gpio::Level::low); + PressButtonAndDebounce(uf, mb::Right); + SimulateIdlerHoming(uf); // make Idler happy + REQUIRE(WhileTopState(uf, ProgressCode::FeedingToFinda, 5000)); + REQUIRE(WhileTopState(uf, ProgressCode::RetractingFromFinda, idlerEngageDisengageMaxSteps)); + REQUIRE(WhileTopState(uf, ProgressCode::DisengagingIdler, idlerEngageDisengageMaxSteps)); + + // now fail homing of the Selector + REQUIRE(SimulateFailedHomeSelectorRepeated(uf)); + + // REQUIRE(VerifyState(uf, mg::FilamentLoadState::AtPulley, mi::Idler::IdleSlotIndex(), slot, false, false, ml::blink0, ml::off, ErrorCode::RUNNING, ProgressCode::OK)); + REQUIRE(uf.State() == ProgressCode::Homing); + REQUIRE(uf.Error() == ErrorCode::RUNNING); + + // one retry + REQUIRE(SimulateFailedHomeSelectorRepeated(uf)); + + // success + SimulateSelectorHoming(uf); + REQUIRE(VerifyState(uf, mg::FilamentLoadState::AtPulley, mi::Idler::IdleSlotIndex(), slot, false, false, ml::off, ml::off, ErrorCode::OK, ProgressCode::OK)); +} diff --git a/tests/unit/logic/unload_to_finda/CMakeLists.txt b/tests/unit/logic/unload_to_finda/CMakeLists.txt index 6a0694c..a4b4471 100644 --- a/tests/unit/logic/unload_to_finda/CMakeLists.txt +++ b/tests/unit/logic/unload_to_finda/CMakeLists.txt @@ -1,6 +1,7 @@ # define the test executable add_executable( unload_to_finda_tests + ${CMAKE_SOURCE_DIR}/src/logic/command_base.cpp ${CMAKE_SOURCE_DIR}/src/logic/unload_to_finda.cpp ${CMAKE_SOURCE_DIR}/src/modules/buttons.cpp ${CMAKE_SOURCE_DIR}/src/modules/debouncer.cpp @@ -21,6 +22,7 @@ add_executable( ${MODULES_STUBS_DIR}/stub_shr16.cpp ${MODULES_STUBS_DIR}/stub_timebase.cpp ${MODULES_STUBS_DIR}/stub_tmc2130.cpp + ${LOGIC_STUBS_DIR}/homing.cpp ${LOGIC_STUBS_DIR}/main_loop_stub.cpp ${LOGIC_STUBS_DIR}/stub_motion.cpp test_unload_to_finda.cpp