Add more unit tests for homing + fix the homing

It is a carpet-bombing-commit again, but solving the problem correctly required such an approach.
pull/154/head
D.R.racer 2022-02-08 09:16:04 +01:00 committed by DRracer
parent 684051abe8
commit c95c6677b1
21 changed files with 391 additions and 280 deletions

View File

@ -73,19 +73,20 @@ static inline ErrorCode WithoutAxisBits(ErrorCode ec) {
| static_cast<uint16_t>(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() {

View File

@ -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

View File

@ -37,4 +37,6 @@ enum class ProgressCode : uint_fast8_t {
RetractingFromFinda, // P25
Homing,
Empty = 0xff // dummy empty state
};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <typename SM>
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 <typename SM>
void ClearButtons(SM &sm){
hal::adc::SetADC(config::buttonsADCIndex, config::buttonADCMaxValue);
while (mb::buttons.AnyButtonPressed()) {
main_loop();
sm.Step(); // Inner
}
}

View File

@ -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

View File

@ -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 <typename T>
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 <typename T>
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<mm::I_pos_t>(config::idlerLimits.lenght);
// now do LESS steps than expected to simulate something is blocking the selector
uint32_t selectorSteps = mm::unitToSteps<mm::S_pos_t>(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 <typename T>
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<mm::S_pos_t>(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));

View File

@ -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

View File

@ -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);

View File

@ -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<mm::I_pos_t>(config::idlerLimits.lenght);
uint32_t selectorSteps = mm::unitToSteps<mm::S_pos_t>(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<mm::I_pos_t>(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<mm::S_pos_t>(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<mm::I_pos_t>(config::idlerLimits.lenght);
// now do LESS steps than expected to simulate something is blocking the selector
uint32_t selectorSteps = mm::unitToSteps<mm::S_pos_t>(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<mm::S_pos_t>(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);
}

View File

@ -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);

View File

@ -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 <new> // 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<mm::I_pos_t>(config::idlerLimits.lenght);
uint32_t selectorSteps = mm::unitToSteps<mm::S_pos_t>(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<mm::I_pos_t>(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<mm::S_pos_t>(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
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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));
}

View File

@ -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