Intake Log 1 - February 20, 2021
I need to design a controller for the intakes.
Features:
- use sensors to automatically shut off rollers when they hold a ball during intaking
- automatically poop unwanted balls
- space out balls when double shooting
- execute actions like shoot, outtake, purge, etc
To do this, I always start by defining all possible subsystem states:
enum class rollerStates {
off, // all off
offWithoutPoop, // all off
out, // all backwards
on, // all on
onWithoutPoop, // all on without poop
shoot, // all on but don't move intakes
shootWithoutPoop, // all on but don't move intakes and don't poop
intake, // load balls into robot. Disable rollers one by one, and auto poop
intakeWithoutPoop, // load balls into robot. Disable rollers one by one
poopIn, // poop while intaking
poopOut, // poop while outtaking
purge, // shoot while outtaking
topOut, // only spin top roller
deploy,
timedPoop,
timedShootPoop,
spacedShoot, // all on but space the ball and no intakes
topPoop, // bring down then poop
};
I can then plug in that state into my handy state machine helper:
class Roller : public StateMachine<rollerStates, rollerStates::off> {
public:
Roller(const std::shared_ptr<AbstractMotor>& iintakes,
const std::shared_ptr<AbstractMotor>& ibottomRoller,
const std::shared_ptr<AbstractMotor>& itopRoller,
const std::shared_ptr<OpticalSensor>& itoplight,
const std::shared_ptr<OpticalSensor>& ibottomLight,
const std::shared_ptr<GUI::Graph>& igraph);
public:
enum class colors { none = 0, red, blue };
colors getTopLight() const;
colors getBottomLight() const;
bool shouldPoop();
bool shouldShootPoop();
bool shouldSpacedShoot();
int getIntake();
void initialize() override;
void loop() override;
std::shared_ptr<AbstractMotor> intakes {nullptr};
std::shared_ptr<AbstractMotor> bottomRoller {nullptr};
std::shared_ptr<AbstractMotor> topRoller {nullptr};
std::shared_ptr<OpticalSensor> topLight {nullptr};
std::shared_ptr<OpticalSensor> bottomLight {nullptr};
std::shared_ptr<GUI::Graph> graph {nullptr};
Timer macroTime;
rollerStates macroReturnState = rollerStates::off;
int macroIntakeVel = 12000;
};
Then, it is easy for me to design a driver control program:
if ((mDigital(L1) || mDigital(R2)) && mDigital(R1)) {
system(roller, on);
} else if (mDigital(R1) && mDigital(A)) {
system(roller, on);
} else if (mDigital(A)) {
system(roller, poopIn);
} else if (mDigital(R1)) {
system(roller, shoot);
} else if (mDigital(L2)) {
system(roller, out);
} else if (mDigital(B)) {
system(roller, deploy);
} else if (mDigital(LEFT)) {
system(roller, poopOut);
} else if (mDigital(DOWN)) {
system(roller, topOut);
} else if (mDigital(L1) || mDigital(R2)) {
system(roller, intake);
} else {
system(roller, off);
}
Finally, I can implement the statemachine states.
void Roller::loop() {
Rate rate;
while (true) {
switch (state) {
case rollerStates::off:
if (shouldPoop()) continue;
case rollerStates::offWithoutPoop:
topRoller->moveVoltage(0);
bottomRoller->moveVoltage(0);
intakes->moveVoltage(0);
break;
case rollerStates::out:
// if (shouldPoop(-12000)) continue;
topRoller->moveVoltage(-12000);
bottomRoller->moveVoltage(-12000);
intakes->moveVoltage(-12000);
break;
case rollerStates::on:
case rollerStates::shoot:
if (shouldPoop()) continue;
if (shouldShootPoop()) continue;
[[fallthrough]];
case rollerStates::onWithoutPoop:
case rollerStates::shootWithoutPoop:
if (shouldSpacedShoot()) continue;
topRoller->moveVoltage(12000);
if (getBottomLight() == colors::blue) {
if (getTopLight() != colors::red) {
bottomRoller->moveVoltage(-2000);
} else {
bottomRoller->moveVoltage(2000);
}
} else {
bottomRoller->moveVoltage(12000);
}
intakes->moveVoltage(getIntake());
break;
case rollerStates::intake:
if (shouldPoop()) continue;
[[fallthrough]];
case rollerStates::intakeWithoutPoop:
if (getTopLight() != colors::none && getBottomLight() != colors::none) {
topRoller->moveVoltage(0);
if (getBottomLight() == colors::blue) {
bottomRoller->moveVoltage(-2000);
} else {
bottomRoller->moveVoltage(0);
}
intakes->moveVoltage(12000);
} else if (getTopLight() != colors::none) {
// balance between raising ball to prevent rubbing and bringing ball too high
topRoller->moveVoltage(1800);
// slow down
bottomRoller->moveVoltage(4000);
intakes->moveVoltage(12000);
} else {
// balance between bringing ball too fast and accidentally pooping
topRoller->moveVoltage(4000);
if (getBottomLight() == colors::blue) {
bottomRoller->moveVoltage(-2000);
} else {
bottomRoller->moveVoltage(12000);
}
intakes->moveVoltage(12000);
}
break;
case rollerStates::poopIn:
topRoller->moveVoltage(-12000);
bottomRoller->moveVoltage(12000);
intakes->moveVoltage(12000);
break;
case rollerStates::poopOut:
topRoller->moveVoltage(-12000);
bottomRoller->moveVoltage(12000);
intakes->moveVoltage(-12000);
break;
case rollerStates::purge:
topRoller->moveVoltage(12000);
bottomRoller->moveVoltage(12000);
intakes->moveVoltage(-12000);
break;
case rollerStates::topOut:
topRoller->moveVoltage(12000);
bottomRoller->moveVoltage(4000);
intakes->moveVoltage(0);
break;
case rollerStates::deploy:
topRoller->moveVoltage(12000);
bottomRoller->moveVoltage(-800);
intakes->moveVoltage(-12000);
break;
case rollerStates::timedPoop:
topRoller->moveVoltage(-12000);
bottomRoller->moveVoltage(12000);
intakes->moveVoltage(macroIntakeVel);
if (macroTime.getDtFromMark() >= 200_ms) {
macroTime.clearMark();
state = macroReturnState;
continue;
}
break;
case rollerStates::timedShootPoop:
topRoller->moveVoltage(12000);
bottomRoller->moveVoltage(0);
intakes->moveVoltage(macroIntakeVel);
if (macroTime.getDtFromMark() >= 400_ms) {
macroTime.placeMark();
state = rollerStates::timedPoop;
continue;
}
break;
case rollerStates::spacedShoot:
topRoller->moveVoltage(12000);
bottomRoller->moveVoltage(1000);
intakes->moveVoltage(macroIntakeVel);
if (macroTime.getDtFromMark() >= 10_ms) {
macroTime.clearMark();
state = macroReturnState;
continue;
}
break;
case rollerStates::topPoop:
topRoller->moveVoltage(-12000);
bottomRoller->moveVoltage(-12000);
intakes->moveVoltage(macroIntakeVel);
if (macroTime.getDtFromMark() >= 150_ms) {
macroTime.placeMark();
state = rollerStates::timedPoop;
continue;
}
break;
}
rate.delayUntil(5_ms);
}
}
This code is versatile because it is
- asynchronous
- responsive
- stateful (and the state can be easily retrieved and changed)
- common between driving and autonomous
It can:
- intake and hold balls
- shoot balls with spacing
- automatically engage poop states which auto return to the original state
- execute all the other actions needed
However, this code is slightly messy and inflexible, but I have an idea to improve it in the next build log.