Prusa-Firmware-MMU/src/logic/mm_control.cpp

195 lines
7.4 KiB
C++

#include "mm_control.h"
#include "../modules/motion.h"
#include "../modules/leds.h"
#include "../modules/buttons.h"
#include "../modules/finda.h"
#include "../modules/permanent_storage.h"
namespace logic {
// "small" state machines will serve as building blocks for high-level commands/operations
// - engage/disengage idler
// - rotate pulley to some direction as long as the FINDA is on/off
// - rotate some axis to some fixed direction
// - load/unload to finda
//
// motion planning
// - we need some kind of planner buffer, especially because of accelerations
// because we may need to match the ramps between moves seamlessly - just like on a printer
/// A "small" automaton example - Try to unload filament to FINDA and if it fails try to recover several times.
/// \dot
/// digraph example {
/// node [shape=record, fontname=Helvetica, fontsize=10];
/// b [ label="class B" URL="\ref B"];
/// c [ label="class C" URL="\ref C"];
/// b -> c [ arrowhead="open", style="dashed" ];
///}
///\enddot
struct UnloadToFinda {
enum {
WaitingForFINDA,
OK,
Failed
};
uint8_t state;
uint8_t maxTries;
inline UnloadToFinda(uint8_t maxTries)
: maxTries(maxTries) { Reset(); }
/// Restart the automaton
inline void Reset() {
namespace mm = modules::motion;
namespace mf = modules::finda;
// check the inital state of FINDA and plan the moves
if (mf::finda.Status() == mf::Off) {
state = OK; // FINDA is already off, we assume the fillament is not there, i.e. already unloaded
} else {
// FINDA is sensing the filament, plan moves to unload it
int unloadSteps = /*BowdenLength::get() +*/ 1100; // @@TODO
const int second_point = unloadSteps - 1300;
// mm::motion.PlanMove(mm::Pulley, -1400, 6000); // @@TODO constants
// mm::motion.PlanMove(mm::Pulley, -1800 + 1400, 2500); // @@TODO constants 1800-1400 = 400
// mm::motion.PlanMove(mm::Pulley, -second_point + 1800, 550); // @@TODO constants
state = WaitingForFINDA;
}
}
/// @returns true if the state machine finished its job, false otherwise
bool Step() {
namespace mm = modules::motion;
namespace mf = modules::finda;
switch (state) {
case WaitingForFINDA:
if (modules::finda::finda.Status() == modules::finda::Off) {
// detected end of filament
state = OK;
} else if (/*tmc2130_read_gstat() &&*/ mm::motion.QueueEmpty()) {
// we reached the end of move queue, but the FINDA didn't switch off
// two possible causes - grinded filament of malfunctioning FINDA
if (--maxTries) {
Reset(); // try again
} else {
state = Failed;
}
}
return false;
case OK:
case Failed:
default:
return true;
}
}
};
/// A high-level command state machine
/// Handles the complex logic of unloading filament
class UnloadFilament : public TaskBase {
enum State {
EngagingIdler,
UnloadingToFinda,
DisengagingIdler,
AvoidingGrind,
Finishing,
OK,
ERR1DisengagingIdler,
ERR1WaitingForUser
};
UnloadToFinda unl;
inline UnloadFilament()
: TaskBase()
, unl(3) { Reset(); }
/// Restart the automaton
void Reset() override {
namespace mm = modules::motion;
// unloads filament from extruder - filament is above Bondtech gears
mm::motion.InitAxis(mm::Pulley);
state = EngagingIdler;
mm::motion.Idler(mm::Engage);
}
/// @returns true if the state machine finished its job, false otherwise
bool Step() override {
namespace mm = modules::motion;
switch (state) {
case EngagingIdler: // state 1 engage idler
if (mm::motion.IdlerEngaged()) { // if idler is in parked position un-park it get in contact with filament
state = UnloadingToFinda;
unl.Reset();
}
return false;
case UnloadingToFinda: // state 2 rotate pulley as long as the FINDA is on
if (unl.Step()) {
if (unl.state == UnloadToFinda::Failed) {
// couldn't unload to FINDA, report error and wait for user to resolve it
state = ERR1DisengagingIdler;
// modules::leds::leds.SetMode(active_extruder, modules::leds::red, modules::leds::blink0);
} else {
state = DisengagingIdler;
}
// in all cases disengage the idler
mm::motion.Idler(mm::Disengage);
}
return false;
case DisengagingIdler:
if (mm::motion.IdlerDisengaged()) {
state = AvoidingGrind;
// mm::motion.PlanMove(mm::Pulley, -100, 10); // @@TODO constants
}
return false;
case AvoidingGrind: // state 3 move a little bit so it is not a grinded hole in filament
if (mm::motion.QueueEmpty()) {
state = Finishing;
mm::motion.Idler(mm::Disengage);
return true;
}
return false;
case Finishing:
if (mm::motion.QueueEmpty()) {
state = OK;
mm::motion.DisableAxis(mm::Pulley);
}
return false;
case ERR1DisengagingIdler: // couldn't unload to FINDA
if (mm::motion.IdlerDisengaged()) {
state = ERR1WaitingForUser;
}
return false;
case ERR1WaitingForUser: {
// waiting for user buttons and/or a command from the printer
bool help = modules::buttons::buttons.ButtonPressed(modules::buttons::Left) /*|| command_help()*/;
bool tryAgain = modules::buttons::buttons.ButtonPressed(modules::buttons::Middle) /*|| command_tryAgain()*/;
bool userResolved = modules::buttons::buttons.ButtonPressed(modules::buttons::Right) /*|| command_userResolved()*/;
if (help) {
// try to manually unload just a tiny bit - help the filament with the pulley
//@@TODO
} else if (tryAgain) {
// try again the whole sequence
Reset();
} else if (userResolved) {
// problem resolved - the user pulled the fillament by hand
// modules::leds::leds.SetMode(active_extruder, modules::leds::red, modules::leds::off);
// modules::leds::leds.SetMode(active_extruder, modules::leds::green, modules::leds::on);
// mm::motion.PlanMove(mm::Pulley, 450, 5000); // @@TODO constants
state = AvoidingGrind;
}
return false;
}
case OK:
// isFilamentLoaded = false; // filament unloaded
return true; // successfully finished
}
}
/// @returns progress of operation
virtual uint8_t Progress() const override {
return state; // for simplicity return state, will be more elaborate later in order to report the exact state of the MMU into the printer
}
};
} // namespace logic