Greatly improve unload filament unit test

+ relevant changes for other unit tests -> prepare for improving
of the other unit tests as well
pull/37/head
D.R.racer 2021-06-22 21:47:39 +02:00 committed by DRracer
parent 807eda7db3
commit ce20f0b001
20 changed files with 354 additions and 225 deletions

View File

@ -12,9 +12,6 @@ namespace logic {
ToolChange toolChange;
namespace mm = modules::motion;
namespace mi = modules::idler;
namespace ms = modules::selector;
namespace mg = modules::globals;
void ToolChange::Reset(uint8_t param) {
@ -36,35 +33,51 @@ bool ToolChange::Step() {
switch (state) {
case ProgressCode::UnloadingFilament:
if (unl.Step()) {
// unloading sequence finished
switch (unl.Error()) {
case ErrorCode::OK: // finished successfully
// unloading sequence finished - basically, no errors can occurr here
// as UnloadFilament should handle all the possible error states on its own
// There is no way the UnloadFilament to finish in an error state
state = ProgressCode::LoadingFilament;
load.Reset(plannedSlot);
break;
case ErrorCode::UNLOAD_ERROR2: // @@TODO what shall we do in case of this error?
case ErrorCode::FINDA_DIDNT_TRIGGER:
break;
}
}
break;
case ProgressCode::LoadingFilament:
if (load.Step()) {
// unloading sequence finished
switch (load.Error()) {
case ErrorCode::OK: // finished successfully
// loading sequence finished - basically, no errors can occurr here
// as LoadFilament should handle all the possible error states on its own
// There is no way the LoadFilament to finish in an error state
state = ProgressCode::OK;
break;
// case ErrorCode::LOAD_ERROR2: // @@TODO load errors?
// case ErrorCode::LOAD_FINDA_DIDNT_TRIGGER:
break;
}
}
break;
case ProgressCode::OK:
return true;
default: // we got into an unhandled state, better report it
state = ProgressCode::ERRInternal;
error = ErrorCode::INTERNAL;
return true;
}
return false;
}
ProgressCode ToolChange::State() const {
switch (state) {
case ProgressCode::UnloadingFilament:
return unl.State(); // report sub-automaton states properly
case ProgressCode::LoadingFilament:
return load.State(); // report sub-automaton states properly
default:
return state;
}
}
ErrorCode ToolChange::Error() const {
switch (state) {
case ProgressCode::UnloadingFilament:
return unl.Error(); // report sub-automaton errors properly
case ProgressCode::LoadingFilament:
return load.Error(); // report sub-automaton errors properly
default:
return error;
}
}
} // namespace logic

View File

@ -19,6 +19,10 @@ public:
/// @returns true if the state machine finished its job, false otherwise
bool Step() override;
ProgressCode State() const override;
ErrorCode Error() const override;
private:
UnloadFilament unl; ///< a high-level command/operation may be used as a building block of other operations as well
LoadFilament load;

View File

@ -17,12 +17,14 @@ namespace mi = modules::idler;
namespace ml = modules::leds;
namespace mg = modules::globals;
void UnloadFilament::Reset(uint8_t param) {
void UnloadFilament::Reset(uint8_t /*param*/) {
// unloads filament from extruder - filament is above Bondtech gears
mm::motion.InitAxis(mm::Pulley);
state = ProgressCode::UnloadingToFinda;
error = ErrorCode::OK;
unl.Reset(maxRetries);
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::blink0);
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::red, ml::off);
}
bool UnloadFilament::Step() {
@ -33,7 +35,9 @@ bool UnloadFilament::Step() {
if (unl.State() == UnloadToFinda::Failed) {
// couldn't unload to FINDA, report error and wait for user to resolve it
state = ProgressCode::ERR1DisengagingIdler;
error = ErrorCode::FINDA_DIDNT_TRIGGER;
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::red, ml::blink0);
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::off);
} else {
state = ProgressCode::DisengagingIdler;
}
@ -59,10 +63,11 @@ bool UnloadFilament::Step() {
if (mm::motion.QueueEmpty()) {
state = ProgressCode::OK;
mm::motion.DisableAxis(mm::Pulley);
mg::globals.SetFilamentLoaded(false); // filament unloaded
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::on);
}
return false;
case ProgressCode::ERR1DisengagingIdler: // couldn't unload to FINDA
error = ErrorCode::FINDA_DIDNT_TRIGGER;
if (!mi::idler.Engaged()) {
state = ProgressCode::ERR1WaitingForUser;
}
@ -88,7 +93,6 @@ bool UnloadFilament::Step() {
return false;
}
case ProgressCode::OK:
mg::globals.SetFilamentLoaded(false); // filament unloaded
return true; // successfully finished
default: // we got into an unhandled state, better report it
state = ProgressCode::ERRInternal;

View File

@ -13,7 +13,8 @@ public:
: CommandBase() {}
/// Restart the automaton
void Reset(uint8_t param) override;
/// @param param is not used, always unloads from the active slot
void Reset(uint8_t /*param*/) override;
/// @returns true if the state machine finished its job, false otherwise
bool Step() override;

View File

@ -15,7 +15,7 @@ void Debouncer::Step(uint16_t time, bool press) {
break;
case State::Detected:
if (f.tmp == press) {
if (time - timeLastChange > debounceTimeout) {
if (time - timeLastChange >= debounceTimeout) {
f.state = State::WaitForRelease;
}
} else {

View File

@ -7,16 +7,15 @@ namespace finda {
class FINDA : protected debounce::Debouncer {
public:
inline constexpr FINDA()
: debounce::Debouncer(debounce) {};
void Step();
using debounce::Debouncer::Pressed;
private:
/// time interval for debouncing @@TODO specify units
constexpr static const uint16_t debounce = 100;
/// ADC decision level when a FINDA is considered pressed/not pressed
constexpr static const uint16_t adcDecisionLevel = 512;
inline constexpr FINDA()
: debounce::Debouncer(debounce) {};
void Step();
using debounce::Debouncer::Pressed;
};
extern FINDA finda;

View File

@ -34,7 +34,6 @@ TEST_CASE("cut_filament::cut0", "[cut_filament]") {
CutFilament cf;
// restart the automaton
currentCommand = &cf;
cf.Reset(0);
main_loop();
@ -45,7 +44,7 @@ TEST_CASE("cut_filament::cut0", "[cut_filament]") {
CHECK(modules::motion::axes[modules::motion::Selector].targetPos == ms::Selector::SlotPosition(0));
// now cycle at most some number of cycles (to be determined yet) and then verify, that the idler and selector reached their target positions
REQUIRE(WhileCondition([&]() { return cf.TopLevelState() == ProgressCode::SelectingFilamentSlot; }, 5000));
REQUIRE(WhileTopState(cf, ProgressCode::SelectingFilamentSlot, 5000));
CHECK(modules::motion::axes[modules::motion::Idler].pos == mi::Idler::SlotPosition(0));
CHECK(modules::motion::axes[modules::motion::Selector].pos == ms::Selector::SlotPosition(0));
@ -53,27 +52,32 @@ TEST_CASE("cut_filament::cut0", "[cut_filament]") {
// idler and selector reached their target positions and the CF automaton will start feeding to FINDA as the next step
REQUIRE(cf.TopLevelState() == ProgressCode::FeedingToFinda);
// prepare for simulated finda trigger
hal::adc::ReinitADC(1, hal::adc::TADCData({ 0, 0, 0, 0, 600, 700, 800, 900 }), 10);
REQUIRE(WhileCondition([&]() { return cf.TopLevelState() == ProgressCode::FeedingToFinda; }, 5000));
REQUIRE(WhileCondition(
cf,
[&](int step) -> bool {
if( step == 1000 ){ // simulate FINDA trigger - will get pressed in 100 steps (due to debouncing)
hal::adc::SetADC(1, 900);
}
return cf.TopLevelState() == ProgressCode::FeedingToFinda; }, 5000));
// filament fed into FINDA, cutting...
REQUIRE(cf.TopLevelState() == ProgressCode::PreparingBlade);
REQUIRE(WhileCondition([&]() { return cf.TopLevelState() == ProgressCode::PreparingBlade; }, 5000));
REQUIRE(WhileTopState(cf, ProgressCode::PreparingBlade, 5000));
REQUIRE(cf.TopLevelState() == ProgressCode::EngagingIdler);
REQUIRE(WhileCondition([&]() { return cf.TopLevelState() == ProgressCode::EngagingIdler; }, 5000));
REQUIRE(WhileTopState(cf, ProgressCode::EngagingIdler, 5000));
// the idler should be at the active slot @@TODO
REQUIRE(cf.TopLevelState() == ProgressCode::PushingFilament);
REQUIRE(WhileCondition([&]() { return cf.TopLevelState() == ProgressCode::PushingFilament; }, 5000));
REQUIRE(WhileTopState(cf, ProgressCode::PushingFilament, 5000));
// filament pushed - performing cut
REQUIRE(cf.TopLevelState() == ProgressCode::PerformingCut);
REQUIRE(WhileCondition([&]() { return cf.TopLevelState() == ProgressCode::PerformingCut; }, 5000));
REQUIRE(WhileTopState(cf, ProgressCode::PerformingCut, 5000));
// returning selector
REQUIRE(cf.TopLevelState() == ProgressCode::ReturningSelector);
REQUIRE(WhileCondition([&]() { return cf.TopLevelState() == ProgressCode::ReturningSelector; }, 5000));
REQUIRE(WhileTopState(cf, ProgressCode::ReturningSelector, 5000));
// the next states are still @@TODO
}

View File

@ -34,7 +34,6 @@ TEST_CASE("eject_filament::eject0", "[eject_filament]") {
EjectFilament ef;
// restart the automaton
currentCommand = &ef;
ef.Reset(0);
main_loop();
@ -45,32 +44,32 @@ TEST_CASE("eject_filament::eject0", "[eject_filament]") {
CHECK(modules::motion::axes[modules::motion::Selector].targetPos == ms::Selector::SlotPosition(4));
// now cycle at most some number of cycles (to be determined yet) and then verify, that the idler and selector reached their target positions
REQUIRE(WhileCondition([&]() { return ef.TopLevelState() == ProgressCode::SelectingFilamentSlot; }, 5000));
REQUIRE(WhileTopState(ef, ProgressCode::SelectingFilamentSlot, 5000));
// idler and selector reached their target positions and the CF automaton will start feeding to FINDA as the next step
REQUIRE(ef.TopLevelState() == ProgressCode::FeedingToFinda);
// prepare for simulated finda trigger
hal::adc::ReinitADC(1, hal::adc::TADCData({ 0, 0, 0, 0, 600, 700, 800, 900 }), 10);
REQUIRE(WhileCondition([&]() { return ef.TopLevelState() == ProgressCode::FeedingToFinda; }, 50000));
REQUIRE(WhileTopState(ef, ProgressCode::FeedingToFinda, 50000));
// filament fed into FINDA, cutting...
REQUIRE(ef.TopLevelState() == ProgressCode::PreparingBlade);
REQUIRE(WhileCondition([&]() { return ef.TopLevelState() == ProgressCode::PreparingBlade; }, 5000));
REQUIRE(WhileTopState(ef, ProgressCode::PreparingBlade, 5000));
REQUIRE(ef.TopLevelState() == ProgressCode::EngagingIdler);
REQUIRE(WhileCondition([&]() { return ef.TopLevelState() == ProgressCode::EngagingIdler; }, 5000));
REQUIRE(WhileTopState(ef, ProgressCode::EngagingIdler, 5000));
// the idler should be at the active slot @@TODO
REQUIRE(ef.TopLevelState() == ProgressCode::PushingFilament);
REQUIRE(WhileCondition([&]() { return ef.TopLevelState() == ProgressCode::PushingFilament; }, 5000));
REQUIRE(WhileTopState(ef, ProgressCode::PushingFilament, 5000));
// filament pushed - performing cut
REQUIRE(ef.TopLevelState() == ProgressCode::PerformingCut);
REQUIRE(WhileCondition([&]() { return ef.TopLevelState() == ProgressCode::PerformingCut; }, 5000));
REQUIRE(WhileTopState(ef, ProgressCode::PerformingCut, 5000));
// returning selector
REQUIRE(ef.TopLevelState() == ProgressCode::ReturningSelector);
REQUIRE(WhileCondition([&]() { return ef.TopLevelState() == ProgressCode::ReturningSelector; }, 5000));
REQUIRE(WhileTopState(ef, ProgressCode::ReturningSelector, 5000));
// the next states are still @@TODO
}

View File

@ -29,15 +29,6 @@ namespace ms = modules::selector;
namespace ha = hal::adc;
template <typename COND>
bool WhileCondition(logic::FeedToBondtech &ff, COND cond, uint32_t maxLoops = 5000) {
while (cond() && --maxLoops) {
main_loop();
ff.Step();
}
return maxLoops > 0;
}
TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
using namespace logic;
@ -47,108 +38,50 @@ TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
main_loop();
// restart the automaton
fb.Reset(false);
fb.Reset(1);
// REQUIRE(ff.State() == FeedToBondtech::EngagingIdler);
REQUIRE(fb.State() == FeedToBondtech::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::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0));
// CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0));
// CHECK(mm::axes[mm::Idler].enabled == true);
// it should have instructed the selector and idler to move to slot 0
// check if the idler and selector have the right command
CHECK(mm::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0));
CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0));
CHECK(mm::axes[mm::Idler].enabled == true);
// // engaging idler
// REQUIRE(WhileCondition(
// ff,
// [&]() { return !mi::idler.Engaged(); },
// 5000));
// engaging idler
REQUIRE(WhileCondition(
fb,
[&](int) { return !mi::idler.Engaged(); },
5000));
// CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(0));
// CHECK(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(0));
CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(0));
CHECK(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(0));
// // idler engaged, selector in position, we'll start pushing filament
// REQUIRE(ff.State() == FeedToBondtech::PushingFilament);
// // at least at the beginning the LED should shine green (it should be blinking, but this mode has been already verified in the LED's unit test)
// REQUIRE(ml::leds.LedOn(mg::globals.ActiveSlot(), ml::Color::green));
// idler engaged, selector in position, we'll start pushing filament
REQUIRE(fb.State() == FeedToBondtech::PushingFilament);
// at least at the beginning the LED should shine green (it should be blinking, but this mode has been already verified in the LED's unit test)
REQUIRE(ml::leds.LedOn(mg::globals.ActiveSlot(), ml::Color::green));
// // now let the filament be pushed into the FINDA - do 500 steps without triggering the condition
// // and then let the simulated ADC channel 1 create a FINDA switch
// ha::ReinitADC(1, ha::TADCData({ 600, 700, 800, 900 }), 1);
// @@TODO simulate incoming message from the printer - fsensor triggered
// REQUIRE(WhileCondition(
// ff,
// [&]() { return ff.State() == FeedToBondtech::PushingFilament; },
// 1500));
// // From now on the FINDA is reported as ON
REQUIRE(WhileCondition(
fb,
[&](int) { return fb.State() == FeedToBondtech::PushingFilament; },
1500));
// // unloading back to PTFE
// REQUIRE(ff.State() == FeedToBondtech::UnloadBackToPTFE);
// REQUIRE(WhileCondition(
// ff,
// [&]() { return ff.State() == FeedToBondtech::UnloadBackToPTFE; },
// 5000));
// disengaging idler
REQUIRE(fb.State() == FeedToBondtech::DisengagingIdler);
REQUIRE(WhileCondition(
fb,
[&](int) { return fb.State() == FeedToBondtech::DisengagingIdler; },
5000));
// // disengaging idler
// REQUIRE(ff.State() == FeedToBondtech::DisengagingIdler);
// REQUIRE(WhileCondition(
// ff,
// [&]() { return mi::idler.Engaged(); },
// 5000));
CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // @@TODO constants
CHECK(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(0));
// CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // @@TODO constants
// CHECK(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(0));
// state machine finished ok, the green LED should be on
REQUIRE(fb.State() == FeedToBondtech::OK);
REQUIRE(ml::leds.LedOn(mg::globals.ActiveSlot(), ml::Color::green));
// // state machine finished ok, the green LED should be on
// REQUIRE(ff.State() == FeedToBondtech::OK);
// REQUIRE(ml::leds.LedOn(mg::globals.ActiveSlot(), ml::Color::green));
// REQUIRE(ff.Step() == true); // the automaton finished its work, any consecutive calls to Step must return true
//}
//TEST_CASE("feed_to_finda::FINDA_failed", "[feed_to_finda]") {
// using namespace logic;
// ForceReinitAllAutomata();
// FeedToBondtech ff;
// main_loop();
// // restart the automaton - we want the limited version of the feed
// ff.Reset(true);
// REQUIRE(ff.State() == FeedToBondtech::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::axes[mm::Idler].targetPos == mi::Idler::SlotPosition(0));
// CHECK(mm::axes[mm::Selector].targetPos == ms::Selector::SlotPosition(0));
// // engaging idler
// REQUIRE(WhileCondition(
// ff,
// [&]() { return !mi::idler.Engaged(); },
// 5000));
// CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(0));
// CHECK(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(0));
// // idler engaged, we'll start pushing filament
// REQUIRE(ff.State() == FeedToBondtech::PushingFilament);
// // at least at the beginning the LED should shine green (it should be blinking, but this mode has been already verified in the LED's unit test)
// REQUIRE(ml::leds.Mode(mg::globals.ActiveSlot(), ml::Color::green) == ml::blink0);
// // now let the filament be pushed into the FINDA - but we make sure the FINDA doesn't trigger at all
// ha::ReinitADC(1, ha::TADCData({ 0 }), 100);
// REQUIRE(WhileCondition(
// ff, // boo, this formatting is UGLY!
// [&]() { return ff.State() == FeedToBondtech::PushingFilament; },
// 5000));
// // the FINDA didn't trigger, we should be in the Failed state
// REQUIRE(ff.State() == FeedToBondtech::Failed);
// REQUIRE(ml::leds.Mode(mg::globals.ActiveSlot(), ml::Color::green) == ml::off);
// REQUIRE(ml::leds.Mode(mg::globals.ActiveSlot(), ml::Color::red) == ml::blink0);
// REQUIRE(ff.Step() == true); // the automaton finished its work, any consecutive calls to Step must return true
REQUIRE(fb.Step() == true); // the automaton finished its work, any consecutive calls to Step must return true
}

View File

@ -29,15 +29,6 @@ namespace ms = modules::selector;
namespace ha = hal::adc;
template <typename COND>
bool WhileCondition(logic::FeedToFinda &ff, COND cond, uint32_t maxLoops = 5000) {
while (cond() && --maxLoops) {
main_loop();
ff.Step();
}
return maxLoops > 0;
}
TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
using namespace logic;
@ -60,7 +51,7 @@ TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
// engaging idler
REQUIRE(WhileCondition(
ff,
[&]() { return !mi::idler.Engaged(); },
[&](int) { return !mi::idler.Engaged(); },
5000));
CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(0));
@ -77,7 +68,7 @@ TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
REQUIRE(WhileCondition(
ff,
[&]() { return ff.State() == FeedToFinda::PushingFilament; },
[&](int) { return ff.State() == FeedToFinda::PushingFilament; },
1500));
// From now on the FINDA is reported as ON
@ -85,14 +76,14 @@ TEST_CASE("feed_to_finda::feed_phase_unlimited", "[feed_to_finda]") {
REQUIRE(ff.State() == FeedToFinda::UnloadBackToPTFE);
REQUIRE(WhileCondition(
ff,
[&]() { return ff.State() == FeedToFinda::UnloadBackToPTFE; },
[&](int) { return ff.State() == FeedToFinda::UnloadBackToPTFE; },
5000));
// disengaging idler
REQUIRE(ff.State() == FeedToFinda::DisengagingIdler);
REQUIRE(WhileCondition(
ff,
[&]() { return mi::idler.Engaged(); },
[&](int) { return mi::idler.Engaged(); },
5000));
CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // @@TODO constants
@ -126,7 +117,7 @@ TEST_CASE("feed_to_finda::FINDA_failed", "[feed_to_finda]") {
// engaging idler
REQUIRE(WhileCondition(
ff,
[&]() { return !mi::idler.Engaged(); },
[&](int) { return !mi::idler.Engaged(); },
5000));
CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(0));
@ -141,8 +132,8 @@ TEST_CASE("feed_to_finda::FINDA_failed", "[feed_to_finda]") {
ha::ReinitADC(1, ha::TADCData({ 0 }), 100);
REQUIRE(WhileCondition(
ff, // boo, this formatting is UGLY!
[&]() { return ff.State() == FeedToFinda::PushingFilament; },
ff,
[&](int) { return ff.State() == FeedToFinda::PushingFilament; },
5000));
// the FINDA didn't trigger, we should be in the Failed state

View File

@ -34,7 +34,6 @@ TEST_CASE("unload_filament::unload0", "[unload_filament]") {
LoadFilament lf;
// restart the automaton
currentCommand = &lf;
lf.Reset(0);
main_loop();
@ -53,4 +52,5 @@ TEST_CASE("unload_filament::unload0", "[unload_filament]") {
// REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::FinishingMoves; }, 5000));
// REQUIRE(uf.TopLevelState() == ProgressCode::OK);
REQUIRE(modules::globals::globals.FilamentLoaded() == true);
}

View File

@ -13,9 +13,9 @@
#include "../../../../src/modules/permanent_storage.h"
#include "../../../../src/modules/selector.h"
#include <new> // bring in placement new
#include "../stubs/stub_motion.h"
logic::CommandBase *currentCommand = nullptr;
#include <new> // bring in placement new
void main_loop() {
modules::buttons::buttons.Step();
@ -25,8 +25,6 @@ void main_loop() {
modules::idler::idler.Step();
modules::selector::selector.Step();
modules::motion::motion.Step();
if (currentCommand)
currentCommand->Step();
modules::time::IncMillis();
}
@ -57,8 +55,12 @@ void ForceReinitAllAutomata() {
// finda OFF
hal::adc::ReinitADC(1, hal::adc::TADCData({ 0 }), 1);
// reinit timing
modules::time::ReinitTimebase();
// reinit axes positions
modules::motion::ReinitMotion();
// let's assume we have the filament NOT loaded and active slot 0
modules::globals::globals.SetFilamentLoaded(false);
modules::globals::globals.SetActiveSlot(0);

View File

@ -4,12 +4,17 @@
extern void main_loop();
extern void ForceReinitAllAutomata();
extern logic::CommandBase *currentCommand;
template <typename COND>
bool WhileCondition(COND cond, uint32_t maxLoops = 5000) {
while (cond() && --maxLoops) {
template <typename SM, typename COND>
bool WhileCondition(SM &sm, COND cond, uint32_t maxLoops = 5000) {
while (cond(maxLoops) && --maxLoops) {
main_loop();
sm.Step();
}
return maxLoops > 0;
}
template <typename SM>
bool WhileTopState(SM &sm, ProgressCode state, uint32_t maxLoops = 5000) {
return WhileCondition(
sm, [&](int) { return sm.TopLevelState() == state; }, maxLoops);
}

View File

@ -5,10 +5,13 @@ namespace modules {
namespace motion {
Motion motion;
// Intentionally inited with strange values
// Need to call ReinitMotion() each time we start some unit test
AxisSim axes[3] = {
{ 0, 0, false, false, false }, // pulley
{ 1, 1, false, false, false }, // selector //@@TODO proper selector positions once defined
{ 0, 0, false, false, false }, // idler
{ -32767, -32767, false, false, false }, // pulley
{ -32767, -32767, false, false, false }, // selector //@@TODO proper selector positions once defined
{ -32767, -32767, false, false, false }, // idler
};
void Motion::InitAxis(Axis axis) {
@ -72,6 +75,13 @@ void Motion::AbortPlannedMoves() {
}
}
void ReinitMotion() {
// reset the simulation data to defaults
axes[0] = AxisSim({ 0, 0, false, false, false }); // pulley
axes[1] = AxisSim({ 1, 1, false, false, false }); // selector //@@TODO proper selector positions once defined
axes[2] = AxisSim({ 0, 0, false, false, false }); // idler
}
/// probably higher-level operations knowing the semantic meaning of axes
} // namespace motion

View File

@ -14,5 +14,7 @@ struct AxisSim {
extern AxisSim axes[3];
extern void ReinitMotion();
} // namespace motion
} // namespace modules

View File

@ -34,23 +34,16 @@ TEST_CASE("tool_change::test0", "[tool_change]") {
ToolChange tc;
// restart the automaton
currentCommand = &tc;
tc.Reset(0);
main_loop();
// REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::UnloadingToFinda; }, 5000));
REQUIRE(WhileTopState(tc, ProgressCode::UnloadingFilament, 5000));
REQUIRE(modules::globals::globals.FilamentLoaded() == false);
// REQUIRE(uf.TopLevelState() == ProgressCode::DisengagingIdler);
// REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::DisengagingIdler; }, 5000));
REQUIRE(tc.TopLevelState() == ProgressCode::LoadingFilament);
REQUIRE(WhileTopState(tc, ProgressCode::LoadingFilament, 5000));
// CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5));
// REQUIRE(uf.TopLevelState() == ProgressCode::AvoidingGrind);
// REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::AvoidingGrind; }, 5000));
// REQUIRE(uf.TopLevelState() == ProgressCode::FinishingMoves);
// REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::FinishingMoves; }, 5000));
// REQUIRE(uf.TopLevelState() == ProgressCode::OK);
REQUIRE(tc.TopLevelState() == ProgressCode::OK);
REQUIRE(modules::globals::globals.FilamentLoaded() == true);
}

View File

@ -27,30 +27,198 @@ namespace mb = modules::buttons;
namespace mg = modules::globals;
namespace ms = modules::selector;
TEST_CASE("unload_filament::unload0", "[unload_filament]") {
using namespace logic;
void RegularUnloadFromSlot04(uint8_t slot) {
// prepare startup conditions
ForceReinitAllAutomata();
UnloadFilament uf;
// restart the automaton
currentCommand = &uf;
uf.Reset(0);
// change the startup to what we need here
// move selector to the right spot
ms::selector.MoveToSlot(slot);
while (ms::selector.Slot() != slot)
main_loop();
REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::UnloadingToFinda; }, 5000));
mg::globals.SetActiveSlot(slot);
mg::globals.SetFilamentLoaded(true);
// set FINDA ON + debounce
hal::adc::SetADC(1, mf::FINDA::adcDecisionLevel + 1);
for (size_t i = 0; i < mf::FINDA::debounce + 1; ++i)
main_loop();
// verify startup conditions
REQUIRE(mg::globals.FilamentLoaded() == true);
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5));
REQUIRE(!mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot));
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == true);
// restart the automaton
logic::UnloadFilament uf;
uf.Reset(slot);
// Stage 0 - verify state just after Reset()
REQUIRE(mg::globals.FilamentLoaded() == true); // we still think we have filament loaded at this stage
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // idler should have been activated by the underlying automaton
REQUIRE(!mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot)); // no change in selector's position
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == true); // FINDA triggered off
REQUIRE(ml::leds.Mode(slot, ml::green) == ml::blink0); // green LED should blink
REQUIRE(uf.Error() == ErrorCode::OK); // no error so far
// run the automaton
// Stage 1 - unloading to FINDA
REQUIRE(WhileCondition(
uf,
[&](int step) -> bool {
if(step == 100){ // on 100th step make FINDA trigger
hal::adc::SetADC(1, 0);
}
return uf.TopLevelState() == ProgressCode::UnloadingToFinda; },
5000));
REQUIRE(mg::globals.FilamentLoaded() == true); // we still think we have filament loaded at this stage
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(slot)); // idler should have been activated by the underlying automaton
REQUIRE(mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot)); // no change in selector's position
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == false); // FINDA triggered off
REQUIRE(ml::leds.Mode(slot, ml::green) == ml::blink0); // green LED should blink
REQUIRE(uf.Error() == ErrorCode::OK); // no error so far
// Stage 2 - idler was engaged, disengage it
REQUIRE(uf.TopLevelState() == ProgressCode::DisengagingIdler);
REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::DisengagingIdler; }, 5000));
REQUIRE(WhileTopState(uf, ProgressCode::DisengagingIdler, 5000));
CHECK(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5));
REQUIRE(mg::globals.FilamentLoaded() == true); // we still think we have filament loaded at this stage
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // idler should have been disengaged
REQUIRE(!mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot)); // no change in selector's position
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == false); // FINDA still triggered off
REQUIRE(ml::leds.Mode(slot, ml::green) == ml::blink0); // green LED should blink
REQUIRE(uf.Error() == ErrorCode::OK); // no error so far
// Stage 3 - avoiding grind (whatever is that @@TODO)
REQUIRE(uf.TopLevelState() == ProgressCode::AvoidingGrind);
REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::AvoidingGrind; }, 5000));
REQUIRE(WhileTopState(uf, ProgressCode::AvoidingGrind, 5000));
REQUIRE(mg::globals.FilamentLoaded() == true); // we still think we have filament loaded at this stage
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // idler should have been disengaged
REQUIRE(!mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot)); // no change in selector's position
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == false); // FINDA still triggered off
REQUIRE(ml::leds.Mode(slot, ml::green) == ml::blink0); // green LED should blink
REQUIRE(uf.Error() == ErrorCode::OK); // no error so far
// Stage 4 - finishing moves and setting global state correctly
REQUIRE(uf.TopLevelState() == ProgressCode::FinishingMoves);
REQUIRE(WhileCondition([&]() { return uf.TopLevelState() == ProgressCode::FinishingMoves; }, 5000));
REQUIRE(WhileTopState(uf, ProgressCode::FinishingMoves, 5000));
REQUIRE(mg::globals.FilamentLoaded() == false); // filament unloaded
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // idler should have been disengaged
REQUIRE(!mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot)); // no change in selector's position
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == false); // FINDA still triggered off
REQUIRE(ml::leds.Mode(slot, ml::green) == ml::on); // green LED should be ON
REQUIRE(uf.Error() == ErrorCode::OK); // no error so far
// Stage 5 - repeated calls to TopLevelState should return "OK"
REQUIRE(uf.TopLevelState() == ProgressCode::OK);
REQUIRE(mg::globals.FilamentLoaded() == false);
REQUIRE(mf::finda.Pressed() == false);
REQUIRE(uf.Error() == ErrorCode::OK); // no error
}
TEST_CASE("unload_filament::regular_unload_from_slot_0-4", "[unload_filament]") {
for (uint8_t slot = 0; slot < 5; ++slot) {
RegularUnloadFromSlot04(slot);
}
}
void FindaDidntTrigger(uint8_t slot) {
// prepare startup conditions
ForceReinitAllAutomata();
// change the startup to what we need here
// move selector to the right spot
ms::selector.MoveToSlot(slot);
while (ms::selector.Slot() != slot)
main_loop();
// set FINDA ON + debounce
hal::adc::SetADC(1, mf::FINDA::adcDecisionLevel + 1);
for (size_t i = 0; i < mf::FINDA::debounce + 1; ++i)
main_loop();
mg::globals.SetActiveSlot(slot);
mg::globals.SetFilamentLoaded(true);
// verify startup conditions
REQUIRE(mg::globals.FilamentLoaded() == true);
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5));
REQUIRE(!mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot));
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == true);
// restart the automaton
logic::UnloadFilament uf;
uf.Reset(slot);
// Stage 0 - verify state just after Reset()
REQUIRE(mg::globals.FilamentLoaded() == true); // we still think we have filament loaded at this stage
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // idler should have been activated by the underlying automaton
REQUIRE(!mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot)); // no change in selector's position
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == true); // FINDA triggered off
REQUIRE(ml::leds.Mode(slot, ml::green) == ml::blink0); // green LED should blink
REQUIRE(uf.Error() == ErrorCode::OK); // no error so far
// run the automaton
// Stage 1 - unloading to FINDA - do NOT let it trigger - keep it pressed, the automaton should finish all moves with the pulley
// without reaching the FINDA and report an error
REQUIRE(WhileTopState(uf, ProgressCode::UnloadingToFinda, 50000));
REQUIRE(mg::globals.FilamentLoaded() == true); // we still think we have filament loaded at this stage
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(slot)); // idler should have been activated by the underlying automaton
REQUIRE(mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot)); // no change in selector's position
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == true); // FINDA still on
REQUIRE(ml::leds.Mode(slot, ml::red) == ml::blink0); // red LED should blink
REQUIRE(ml::leds.Mode(slot, ml::green) == ml::off); // green LED should be off
REQUIRE(uf.Error() == ErrorCode::FINDA_DIDNT_TRIGGER); // didn't get any response from FINDA
REQUIRE(uf.TopLevelState() == ProgressCode::ERR1DisengagingIdler);
// Stage 2 - idler should get disengaged
REQUIRE(WhileTopState(uf, ProgressCode::ERR1DisengagingIdler, 5000));
REQUIRE(mg::globals.FilamentLoaded() == true); // we still think we have filament loaded at this stage
REQUIRE(mm::axes[mm::Idler].pos == mi::Idler::SlotPosition(5)); // idler should have been disengaged
REQUIRE(!mi::idler.Engaged());
REQUIRE(mm::axes[mm::Selector].pos == ms::Selector::SlotPosition(slot)); // no change in selector's position
REQUIRE(ms::selector.Slot() == slot);
REQUIRE(mf::finda.Pressed() == true); // FINDA still on
REQUIRE(ml::leds.Mode(slot, ml::red) == ml::blink0); // red LED should blink
REQUIRE(ml::leds.Mode(slot, ml::green) == ml::off); // green LED should be off
REQUIRE(uf.Error() == ErrorCode::FINDA_DIDNT_TRIGGER);
REQUIRE(uf.TopLevelState() == ProgressCode::ERR1WaitingForUser);
// Stage 3 - the user has to do something
// there are 3 options:
// - help the filament a bit
// - try again the whole sequence
// - resolve the problem by hand - after pressing the button we shall check, that FINDA is off and we should do what?
}
TEST_CASE("unload_filament::finda_didnt_trigger", "[unload_filament]") {
for (uint8_t slot = 0; slot < 5; ++slot) {
FindaDidntTrigger(slot);
}
}

View File

@ -29,15 +29,6 @@ namespace ms = modules::selector;
namespace ha = hal::adc;
template <typename COND>
bool WhileConditionFF(logic::UnloadToFinda &ff, COND cond, uint32_t maxLoops = 5000) {
while (cond() && --maxLoops) {
main_loop();
ff.Step();
}
return maxLoops > 0;
}
TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
using namespace logic;
@ -50,7 +41,8 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
// wait for FINDA to debounce
REQUIRE(WhileCondition(
[&]() { return !mf::finda.Pressed(); },
ff,
[&](int) { return !mf::finda.Pressed(); },
5000));
// restart the automaton - just 1 attempt
@ -65,17 +57,17 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
CHECK(mm::axes[mm::Idler].enabled == true);
// engaging idler
REQUIRE(WhileConditionFF(
REQUIRE(WhileCondition(
ff,
[&]() { return !mi::idler.Engaged(); },
[&](int) { return !mi::idler.Engaged(); },
5000));
// now pulling the filament until finda triggers
REQUIRE(ff.State() == UnloadToFinda::WaitingForFINDA);
hal::adc::ReinitADC(1, hal::adc::TADCData({ 1023, 900, 800, 500, 0 }), 10);
REQUIRE(WhileConditionFF(
REQUIRE(WhileCondition(
ff,
[&]() { return mf::finda.Pressed(); },
[&](int) { return mf::finda.Pressed(); },
50000));
REQUIRE(ff.State() == UnloadToFinda::OK);
@ -108,7 +100,8 @@ TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]")
// wait for FINDA to debounce
REQUIRE(WhileCondition(
[&]() { return !mf::finda.Pressed(); },
ff,
[&](int) { return !mf::finda.Pressed(); },
5000));
// restart the automaton - just 1 attempt
@ -123,18 +116,18 @@ TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]")
CHECK(mm::axes[mm::Idler].enabled == true);
// engaging idler
REQUIRE(WhileConditionFF(
REQUIRE(WhileCondition(
ff,
[&]() { return !mi::idler.Engaged(); },
[&](int) { return !mi::idler.Engaged(); },
5000));
// now pulling the filament until finda triggers
REQUIRE(ff.State() == UnloadToFinda::WaitingForFINDA);
// no changes to FINDA during unload - we'll pretend it never triggers
REQUIRE(!WhileConditionFF(
REQUIRE(!WhileCondition(
ff,
[&]() { return mf::finda.Pressed(); },
[&](int) { return mf::finda.Pressed(); },
50000));
REQUIRE(ff.State() == UnloadToFinda::Failed);

View File

@ -29,5 +29,9 @@ uint16_t ReadADC(uint8_t adc) {
return rdptr[adc] != values2Return[adc].end() ? *rdptr[adc] : values2Return[adc].back();
}
void SetADC(uint8_t channel, uint16_t value) {
ReinitADC(channel, TADCData({ value }), 1);
}
} // namespace adc
} // namespace hal

View File

@ -8,7 +8,11 @@ namespace adc {
using TADCData = std::vector<uint16_t>;
/// plan a vector of ADC values for the next steps
void ReinitADC(uint8_t channel, TADCData &&d, uint8_t ovsmpl);
/// set ADC value on a channel to some fixed value from now on
void SetADC(uint8_t channel, uint16_t value);
} // namespace adc
} // namespace hal