Controller
Někdy je třeba řídit robot v různých okamžicích různým způsobem (např. robot nejprve musí zkalibrovat senzor čáry, aby mohl vyrazit na soutěžní trať ), přičemž potřebujeme, aby důležité části chodu supersmyčky probíhaly ve správném čase
Ideální je implementace stavového automatu. Jde ji řešit hloupým a nepřehledným způsobem switch-case nebo funkcionálně
Funkcionální přístup
Nejprve musíme deklarovat ukazatel na prováděcí funkci stavu.
Pokud budou všechny stavy obsluhovány jedním objektem, je možné vytvořit ukazatel na metodu aktuálního objektu.
using State = void(StateController::*)(); // ukazatel na stavovou funkci
State state; // stav automatu
state = &StateController::DoSearchLine; // změna stavu
(this->*state)(); // vyvolání stavové funkce
Pokud však bude obsluha ve více objektech, je nutné použít lambda funkcí a std::function
using State = void(); // ukazatel na stavovou funkci
std::function<State> state; // stav automatu
state = [=](){ regulator->DoSearchLine(); } // změna stavu
(*state)(); // vyvolání stavové funkce
Doporučuji první způsob.
Řešení funkcionálním přístupem s pomocí metod aktuálního objektu
Deklarace Controlleru
class StateController {
using State = void(StateController::*)(); // MAGIE: ukazatel na metodu controlleru
public:
State state{&StateController::DoSearchLine} // Robot po startu zacne hledat caru, prvni stav
void Control(); // funkce která bude volána z main
// jednotlive stavove "funkce"
void DoSearchLine();
void DoFollowLine();
void DoTryFollowMissingLine();
void DoDanceOnFloor();
void DoBurnEverything();
// ...
}
Příklad implementací jednotlivých stavů automatu:
void StateController::Control()
{
(this->*state)(); // MAGIE: provede volání aktuálně vybrané funkce
}
// Robot hleda caru
void StateController::DoSearchLine()
{
if (!sensor.LineMissing)
state = &StateController::DoFollowLine;
}
// robot reguluje pozici na care
void StateController::DoFollowLine()
{
drive.Regulate(ForwardSpeed, sensor.ComputedDistanceFromLine);
if (sensor.LineMissing)
state = &StateController::DoTryFollowMissingLine;
}
// robot se pokousi znovu nalezt prerusenou caru
void StateController::DoTryFollowMissingLine()
{
if (!sensor.LineMissing)
state = &StateController::DoTryFollowMissingLine;
}
A samozřejmě supersmyčka v main:
StateController controller{&drive, &sensor}; // konstrukce controlleru nad daty
while(rum) {
comm.send(sensor.BuildReads());
comm.send(drive.BuildReads());
while (ms < millis())
comm.loop();
ms = millis() + T;
sensor.ProcessLineSensors();
drive.ComputeOdometry();
controller.Control(); // provede jeden krok aktualniho stavu
drive.ComputeMotorRamps();
comm.send(sensor.BuildWrites());
comm.send(drive.BuildWrites());
}
Přístup switch-case
Deklarace Controlleru
enum State {
SearchLine,
FollowLine,
TryFollowMissingLine,
DanceOnFloor,
DoBurnEverything,
}; // Deklarace stavu
class StateController {
public:
State state{SearchLine}; // Robot po startu zacne hledat caru, prvni stav
void Control(); // funkce která bude volána z main
}
Příklad implementací jednotlivých stavů automatu:
void StateController::Control()
{
switch (state) {
default:
case SearchLine:
if (!sensor.LineMissing)
state = FollowLine;
break;
case FollowLine:
drive.Regulate(ForwardSpeed, sensor.ComputedDistanceFromLine);
if (sensor.LineMissing)
state = TryFollowMissingLine;
break;
case TryFollowMissingLine:
if (!sensor.LineMissing)
state = FollowLine;
break;
}
}
Poznámka: V příkladech chybí deklarace lokálních proměnných objektu, příklady jsou uvedeny jen jako vzorové, pro pochopení funkce. Konkrétní implementaci si musíte vytvořit sami.
Chytrého nakopni, hloupého kopni, blbého zakopej 4 metry pod zem ...