diff --git a/src/logic/cut_filament.cpp b/src/logic/cut_filament.cpp index ea65972..a4eaf12 100644 --- a/src/logic/cut_filament.cpp +++ b/src/logic/cut_filament.cpp @@ -48,6 +48,9 @@ bool CutFilament::Step() { case ErrorCode::UNLOAD_ERROR2: // @@TODO what shall we do in case of this error? case ErrorCode::FINDA_DIDNT_TRIGGER: break; + default: + state = ProgressCode::ERRInternal; + break; } } break; @@ -98,6 +101,10 @@ bool CutFilament::Step() { feed.Reset(true); } break; + default: // we got into an unhandled state, better report it + state = ProgressCode::ERRInternal; + error = ErrorCode::INTERNAL; + return true; } return false; } diff --git a/src/logic/eject_filament.cpp b/src/logic/eject_filament.cpp index 9ef3e66..3db91d9 100644 --- a/src/logic/eject_filament.cpp +++ b/src/logic/eject_filament.cpp @@ -37,24 +37,20 @@ void EjectFilament::MoveSelectorAside() { } bool EjectFilament::Step() { - constexpr const uint16_t eject_steps = 500; //@@TODO switch (state) { case ProgressCode::UnloadingFilament: if (unl.Step()) { - // unloading sequence finished - switch (unl.Error()) { - case ErrorCode::OK: // finished successfully - case ErrorCode::UNLOAD_ERROR2: // @@TODO what shall we do in case of this error? - case ErrorCode::FINDA_DIDNT_TRIGGER: - break; - } + // 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 + MoveSelectorAside(); } break; case ProgressCode::ParkingSelector: if (mm::motion.QueueEmpty()) { // selector parked aside state = ProgressCode::EjectingFilament; mm::motion.InitAxis(mm::Pulley); - mm::motion.PlanMove(eject_steps, 0, 0, 1500, 0, 0); + mm::motion.PlanMove(ejectSteps, 0, 0, 1500, 0, 0); } break; case ProgressCode::EjectingFilament: @@ -64,15 +60,37 @@ bool EjectFilament::Step() { } break; case ProgressCode::DisengagingIdler: - if (mm::motion.QueueEmpty()) { // idler disengaged + if (!mi::idler.Engaged()) { // idler disengaged mm::motion.DisableAxis(mm::Pulley); state = ProgressCode::OK; } 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 EjectFilament::State() const { + switch (state) { + case ProgressCode::UnloadingFilament: + return unl.State(); // report sub-automaton states properly + default: + return state; + } +} + +ErrorCode EjectFilament::Error() const { + switch (state) { + case ProgressCode::UnloadingFilament: + return unl.Error(); // report sub-automaton errors properly + default: + return error; + } +} + } // namespace logic diff --git a/src/logic/eject_filament.h b/src/logic/eject_filament.h index 759bdde..c3a0d15 100644 --- a/src/logic/eject_filament.h +++ b/src/logic/eject_filament.h @@ -27,7 +27,12 @@ public: /// @returns true if the state machine finished its job, false otherwise bool Step() override; + virtual ProgressCode State() const override; + + virtual ErrorCode Error() const override; + private: + constexpr static const uint16_t ejectSteps = 500; //@@TODO UnloadFilament unl; ///< a high-level command/operation may be used as a building block of other operations as well uint8_t slot; void MoveSelectorAside(); diff --git a/src/logic/error_codes.h b/src/logic/error_codes.h index f1d6f3e..77a24f8 100644 --- a/src/logic/error_codes.h +++ b/src/logic/error_codes.h @@ -13,4 +13,5 @@ enum class ErrorCode : int_fast8_t { FINDA_DIDNT_TRIGGER = -1, ///< FINDA didn't trigger while unloading filament - either there is something blocking the metal ball or a cable is broken/disconnected UNLOAD_ERROR2 = -2, + INTERNAL = -127, ///< internal runtime error (software) }; diff --git a/src/logic/progress_codes.h b/src/logic/progress_codes.h index 1a4a6a4..98c8175 100644 --- a/src/logic/progress_codes.h +++ b/src/logic/progress_codes.h @@ -18,6 +18,7 @@ enum class ProgressCode : uint_fast8_t { ERR1DisengagingIdler, ERR1WaitingForUser, + ERRInternal, UnloadingFilament, LoadingFilament, diff --git a/tests/unit/logic/CMakeLists.txt b/tests/unit/logic/CMakeLists.txt index d08d796..acea3a7 100644 --- a/tests/unit/logic/CMakeLists.txt +++ b/tests/unit/logic/CMakeLists.txt @@ -6,7 +6,7 @@ add_subdirectory(feed_to_finda) # add_subdirectory(unload_to_finda) -# add_subdirectory(eject_filament) +add_subdirectory(eject_filament) # add_subdirectory(load_filament) diff --git a/tests/unit/logic/eject_filament/CMakeLists.txt b/tests/unit/logic/eject_filament/CMakeLists.txt new file mode 100644 index 0000000..bdd2ef6 --- /dev/null +++ b/tests/unit/logic/eject_filament/CMakeLists.txt @@ -0,0 +1,33 @@ +# define the test executable +add_executable( + eject_filament_tests + ../../../../src/logic/eject_filament.cpp + ../../../../src/logic/feed_to_finda.cpp + ../../../../src/logic/unload_filament.cpp + ../../../../src/logic/unload_to_finda.cpp + ../../../../src/modules/buttons.cpp + ../../../../src/modules/debouncer.cpp + ../../../../src/modules/finda.cpp + ../../../../src/modules/fsensor.cpp + ../../../../src/modules/globals.cpp + ../../../../src/modules/idler.cpp + ../../../../src/modules/leds.cpp + ../../../../src/modules/permanent_storage.cpp + ../../../../src/modules/selector.cpp + ../../modules/stubs/stub_adc.cpp + ../../modules/stubs/stub_eeprom.cpp + ../../modules/stubs/stub_shr16.cpp + ../../modules/stubs/stub_timebase.cpp + ../stubs/main_loop_stub.cpp + ../stubs/stub_motion.cpp + test_eject_filament.cpp + ) + +# define required search paths +target_include_directories( + eject_filament_tests PUBLIC ${CMAKE_SOURCE_DIR}/src/modules ${CMAKE_SOURCE_DIR}/src/hal + ${CMAKE_SOURCE_DIR}/src/logic + ) + +# tell build system about the test case +add_catch_test(eject_filament_tests) diff --git a/tests/unit/logic/eject_filament/test_eject_filament.cpp b/tests/unit/logic/eject_filament/test_eject_filament.cpp new file mode 100644 index 0000000..3530cb6 --- /dev/null +++ b/tests/unit/logic/eject_filament/test_eject_filament.cpp @@ -0,0 +1,81 @@ +#include "catch2/catch.hpp" + +#include "../../../../src/modules/buttons.h" +#include "../../../../src/modules/finda.h" +#include "../../../../src/modules/fsensor.h" +#include "../../../../src/modules/globals.h" +#include "../../../../src/modules/idler.h" +#include "../../../../src/modules/leds.h" +#include "../../../../src/modules/motion.h" +#include "../../../../src/modules/permanent_storage.h" +#include "../../../../src/modules/selector.h" + +#include "../../../../src/logic/eject_filament.h" + +#include "../../modules/stubs/stub_adc.h" + +#include "../stubs/main_loop_stub.h" +#include "../stubs/stub_motion.h" + +using Catch::Matchers::Equals; + +template +bool WhileCondition(COND cond, uint32_t maxLoops = 5000) { + while (cond() && --maxLoops) { + main_loop(); + } + return maxLoops > 0; +} + +TEST_CASE("eject_filament::eject0", "[eject_filament]") { + using namespace logic; + + ForceReinitAllAutomata(); + + EjectFilament ef; + // restart the automaton + currentCommand = &ef; + ef.Reset(0); + + main_loop(); + + // it should have instructed the selector and idler to move to slot 1 + // check if the idler and selector have the right command + CHECK(modules::motion::axes[modules::motion::Idler].targetPos == 0); // @@TODO constants + CHECK(modules::motion::axes[modules::motion::Selector].targetPos == 0); // @@TODO constants + + // 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.State() == 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.State() == 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.State() == ProgressCode::FeedingToFinda; }, 50000)); + + // filament fed into FINDA, cutting... + REQUIRE(ef.State() == ProgressCode::PreparingBlade); + REQUIRE(WhileCondition([&]() { return ef.State() == ProgressCode::PreparingBlade; }, 5000)); + + REQUIRE(ef.State() == ProgressCode::EngagingIdler); + REQUIRE(WhileCondition([&]() { return ef.State() == ProgressCode::EngagingIdler; }, 5000)); + + // the idler should be at the active slot @@TODO + REQUIRE(ef.State() == ProgressCode::PushingFilament); + REQUIRE(WhileCondition([&]() { return ef.State() == ProgressCode::PushingFilament; }, 5000)); + + // filament pushed - performing cut + REQUIRE(ef.State() == ProgressCode::PerformingCut); + REQUIRE(WhileCondition([&]() { return ef.State() == ProgressCode::PerformingCut; }, 5000)); + + // returning selector + REQUIRE(ef.State() == ProgressCode::ReturningSelector); + REQUIRE(WhileCondition([&]() { return ef.State() == ProgressCode::ReturningSelector; }, 5000)); + + // the next states are still @@TODO +} + +// comments: +// The tricky part of the whole state machine are the edge cases - filament not loaded, stall guards etc. +// ... all the external influence we can get on the real HW +// But the good news is we can simulate them all in the unit test and thus ensure proper handling