Move robot base classes from opmode to framework (#8344)

Having these in opmode will be confusing to users when opmodes are added.
This commit is contained in:
Peter Johnson
2025-11-08 15:08:38 -08:00
committed by GitHub
parent aeedfa588c
commit 18efd1e534
275 changed files with 285 additions and 285 deletions

View File

@@ -0,0 +1,240 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include "wpi/framework/RobotBase.hpp"
#include "wpi/system/Watchdog.hpp"
#include "wpi/units/time.hpp"
namespace wpi {
/**
* IterativeRobotBase implements a specific type of robot program framework,
* extending the RobotBase class.
*
* The IterativeRobotBase class does not implement StartCompetition(), so it
* should not be used by teams directly.
*
* This class provides the following functions which are called by the main
* loop, StartCompetition(), at the appropriate times:
*
* DriverStationConnected() -- provide for initialization the first time the DS
* is connected
*
* Init() functions -- each of the following functions is called once when the
* appropriate mode is entered:
*
* \li DisabledInit() -- called each and every time disabled is entered from
* another mode
* \li AutonomousInit() -- called each and every time autonomous is entered from
* another mode
* \li TeleopInit() -- called each and every time teleop is entered from another
* mode
* \li TestInit() -- called each and every time test is entered from another
* mode
*
* Periodic() functions -- each of these functions is called on an interval:
*
* \li RobotPeriodic()
* \li DisabledPeriodic()
* \li AutonomousPeriodic()
* \li TeleopPeriodic()
* \li TestPeriodic()
*
* Exit() functions -- each of the following functions is called once when the
* appropriate mode is exited:
*
* \li DisabledExit() -- called each and every time disabled is exited
* \li AutonomousExit() -- called each and every time autonomous is exited
* \li TeleopExit() -- called each and every time teleop is exited
* \li TestExit() -- called each and every time test is exited
*/
class IterativeRobotBase : public RobotBase {
public:
/**
* Code that needs to know the DS state should go here.
*
* Users should override this method for initialization that needs to occur
* after the DS is connected, such as needing the alliance information.
*/
virtual void DriverStationConnected();
/**
* Robot-wide simulation initialization code should go here.
*
* Users should override this method for default Robot-wide simulation
* related initialization which will be called when the robot is first
* started. It will be called exactly one time after the robot class
* constructor is called only when the robot is in simulation.
*/
virtual void SimulationInit();
/**
* Initialization code for disabled mode should go here.
*
* Users should override this method for initialization code which will be
* called each time
* the robot enters disabled mode.
*/
virtual void DisabledInit();
/**
* Initialization code for autonomous mode should go here.
*
* Users should override this method for initialization code which will be
* called each time the robot enters autonomous mode.
*/
virtual void AutonomousInit();
/**
* Initialization code for teleop mode should go here.
*
* Users should override this method for initialization code which will be
* called each time the robot enters teleop mode.
*/
virtual void TeleopInit();
/**
* Initialization code for test mode should go here.
*
* Users should override this method for initialization code which will be
* called each time the robot enters test mode.
*/
virtual void TestInit();
/**
* Periodic code for all modes should go here.
*
* This function is called each time a new packet is received from the driver
* station.
*/
virtual void RobotPeriodic();
/**
* Periodic simulation code should go here.
*
* This function is called in a simulated robot after user code executes.
*/
virtual void SimulationPeriodic();
/**
* Periodic code for disabled mode should go here.
*
* Users should override this method for code which will be called each time a
* new packet is received from the driver station and the robot is in disabled
* mode.
*/
virtual void DisabledPeriodic();
/**
* Periodic code for autonomous mode should go here.
*
* Users should override this method for code which will be called each time a
* new packet is received from the driver station and the robot is in
* autonomous mode.
*/
virtual void AutonomousPeriodic();
/**
* Periodic code for teleop mode should go here.
*
* Users should override this method for code which will be called each time a
* new packet is received from the driver station and the robot is in teleop
* mode.
*/
virtual void TeleopPeriodic();
/**
* Periodic code for test mode should go here.
*
* Users should override this method for code which will be called each time a
* new packet is received from the driver station and the robot is in test
* mode.
*/
virtual void TestPeriodic();
/**
* Exit code for disabled mode should go here.
*
* Users should override this method for code which will be called each time
* the robot exits disabled mode.
*/
virtual void DisabledExit();
/**
* Exit code for autonomous mode should go here.
*
* Users should override this method for code which will be called each time
* the robot exits autonomous mode.
*/
virtual void AutonomousExit();
/**
* Exit code for teleop mode should go here.
*
* Users should override this method for code which will be called each time
* the robot exits teleop mode.
*/
virtual void TeleopExit();
/**
* Exit code for test mode should go here.
*
* Users should override this method for code which will be called each time
* the robot exits test mode.
*/
virtual void TestExit();
/**
* Enables or disables flushing NetworkTables every loop iteration.
* By default, this is enabled.
*
* @param enabled True to enable, false to disable
* @deprecated Deprecated without replacement.
*/
[[deprecated("Deprecated without replacement.")]]
void SetNetworkTablesFlushEnabled(bool enabled);
/**
* Gets time period between calls to Periodic() functions.
*/
wpi::units::second_t GetPeriod() const;
/**
* Prints list of epochs added so far and their times.
*/
void PrintWatchdogEpochs();
/**
* Constructor for IterativeRobotBase.
*
* @param period Period.
*/
explicit IterativeRobotBase(wpi::units::second_t period);
~IterativeRobotBase() override = default;
protected:
IterativeRobotBase(IterativeRobotBase&&) = default;
IterativeRobotBase& operator=(IterativeRobotBase&&) = default;
/**
* Loop function.
*/
void LoopFunc();
private:
enum class Mode { kNone, kDisabled, kAutonomous, kTeleop, kTest };
Mode m_lastMode = Mode::kNone;
wpi::units::second_t m_period;
Watchdog m_watchdog;
bool m_ntFlushEnabled = true;
bool m_calledDsConnected = false;
void PrintLoopOverrunMessage();
};
} // namespace wpi

View File

@@ -0,0 +1,280 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <chrono>
#include <thread>
#include "wpi/hal/DriverStation.h"
#include "wpi/hal/HALBase.h"
#include "wpi/hal/Main.h"
#include "wpi/nt/NetworkTable.hpp"
#include "wpi/system/Errors.hpp"
#include "wpi/system/RuntimeType.hpp"
#include "wpi/util/RuntimeCheck.h"
#include "wpi/util/condition_variable.hpp"
#include "wpi/util/mutex.hpp"
#include "wpi/util/string.h"
namespace wpi {
int RunHALInitialization();
namespace impl {
#ifndef __FRC_SYSTEMCORE__
void ResetMotorSafety();
#endif
template <class Robot>
void RunRobot(wpi::util::mutex& m, Robot** robot) {
try {
static Robot theRobot;
{
std::scoped_lock lock{m};
*robot = &theRobot;
}
theRobot.StartCompetition();
} catch (const wpi::RuntimeError& e) {
e.Report();
WPILIB_ReportError(
err::Error,
"The robot program quit unexpectedly."
" This is usually due to a code error.\n"
" The above stacktrace can help determine where the error occurred.\n"
" See https://wpilib.org/stacktrace for more information.\n");
throw;
} catch (const std::exception& e) {
HAL_SendError(1, err::Error, 0, e.what(), "", "", 1);
throw;
}
}
} // namespace impl
template <class Robot>
int StartRobot() {
uint32_t foundMajor;
uint32_t foundMinor;
uint32_t expectedMajor;
uint32_t expectedMinor;
WPI_String runtimePath;
if (!WPI_IsRuntimeValid(&foundMajor, &foundMinor, &expectedMajor,
&expectedMinor, &runtimePath)) {
// We could make this error better, however unlike Java, there is only a
// single scenario that could be occuring. The entirety of VS is too out
// of date. In most cases the linker should detect this, but not always.
fmt::println(
"Your copy of Visual Studio is out of date. Please update it.\n");
return 1;
}
int halInit = RunHALInitialization();
if (halInit != 0) {
return halInit;
}
static wpi::util::mutex m;
static wpi::util::condition_variable cv;
static Robot* robot = nullptr;
static bool exited = false;
if (HAL_HasMain()) {
std::thread thr([] {
try {
impl::RunRobot<Robot>(m, &robot);
} catch (...) {
HAL_ExitMain();
{
std::scoped_lock lock{m};
robot = nullptr;
exited = true;
}
cv.notify_all();
throw;
}
HAL_ExitMain();
{
std::scoped_lock lock{m};
robot = nullptr;
exited = true;
}
cv.notify_all();
});
HAL_RunMain();
// signal loop to exit
if (robot) {
robot->EndCompetition();
}
// prefer to join, but detach to exit if it doesn't exit in a timely manner
using namespace std::chrono_literals;
std::unique_lock lock{m};
if (cv.wait_for(lock, 1s, [] { return exited; })) {
thr.join();
} else {
thr.detach();
}
} else {
impl::RunRobot<Robot>(m, &robot);
}
#ifndef __FRC_SYSTEMCORE__
wpi::impl::ResetMotorSafety();
#endif
HAL_Shutdown();
return 0;
}
/**
* Implement a Robot Program framework. The RobotBase class is intended to be
* subclassed to create a robot program. The user must implement
* StartCompetition() which will be called once and is not expected to exit. The
* user must also implement EndCompetition(), which signals to the code in
* StartCompetition() that it should exit.
*
* It is not recommended to subclass this class directly - instead subclass
* IterativeRobotBase or TimedRobot.
*/
class RobotBase {
public:
/**
* Determine if the Robot is currently enabled.
*
* @return True if the Robot is currently enabled by the Driver Station.
*/
bool IsEnabled() const;
/**
* Determine if the Robot is currently disabled.
*
* @return True if the Robot is currently disabled by the Driver Station.
*/
bool IsDisabled() const;
/**
* Determine if the robot is currently in Autonomous mode.
*
* @return True if the robot is currently operating Autonomously as determined
* by the Driver Station.
*/
bool IsAutonomous() const;
/**
* Determine if the robot is currently in Autonomous mode and enabled.
*
* @return True if the robot us currently operating Autonomously while enabled
* as determined by the Driver Station.
*/
bool IsAutonomousEnabled() const;
/**
* Determine if the robot is currently in Operator Control mode.
*
* @return True if the robot is currently operating in Tele-Op mode as
* determined by the Driver Station.
*/
bool IsTeleop() const;
/**
* Determine if the robot is current in Operator Control mode and enabled.
*
* @return True if the robot is currently operating in Tele-Op mode while
* enabled as determined by the Driver Station.
*/
bool IsTeleopEnabled() const;
/**
* Determine if the robot is currently in Test mode.
*
* @return True if the robot is currently running in Test mode as determined
* by the Driver Station.
*/
bool IsTest() const;
/**
* Determine if the robot is current in Test mode and enabled.
*
* @return True if the robot is currently operating in Test mode while
* enabled as determined by the Driver Station.
*/
bool IsTestEnabled() const;
/**
* Returns the main thread ID.
*
* @return The main thread ID.
*/
static std::thread::id GetThreadId();
/**
* Start the main robot code. This function will be called once and should not
* exit until signalled by EndCompetition()
*/
virtual void StartCompetition() = 0;
/** Ends the main loop in StartCompetition(). */
virtual void EndCompetition() = 0;
/**
* Get the current runtime type.
*
* @return Current runtime type.
*/
static RuntimeType GetRuntimeType();
/**
* Get if the robot is real.
*
* @return If the robot is running in the real world.
*/
static constexpr bool IsReal() {
#ifdef __FRC_SYSTEMCORE__
return true;
#else
return false;
#endif
}
/**
* Get if the robot is a simulation.
*
* @return If the robot is running in simulation.
*/
static constexpr bool IsSimulation() {
#ifdef __FRC_SYSTEMCORE__
return false;
#else
return true;
#endif
}
/**
* Constructor for a generic robot program.
*
* User code can be placed in the constructor that runs before the
* Autonomous or Operator Control period starts. The constructor will run to
* completion before Autonomous is entered.
*
* This must be used to ensure that the communications code starts. In the
* future it would be nice to put this code into it's own task that loads on
* boot so ensure that it runs.
*/
RobotBase();
virtual ~RobotBase() = default;
protected:
RobotBase(RobotBase&&) = default;
RobotBase& operator=(RobotBase&&) = default;
static std::thread::id m_threadId;
NT_Listener connListenerHandle;
};
} // namespace wpi

View File

@@ -0,0 +1,59 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
namespace wpi {
/**
* Robot state utility functions.
*/
class RobotState {
public:
RobotState() = delete;
/**
* Returns true if the robot is disabled.
*
* @return True if the robot is disabled.
*/
static bool IsDisabled();
/**
* Returns true if the robot is enabled.
*
* @return True if the robot is enabled.
*/
static bool IsEnabled();
/**
* Returns true if the robot is E-stopped.
*
* @return True if the robot is E-stopped.
*/
static bool IsEStopped();
/**
* Returns true if the robot is in teleop mode.
*
* @return True if the robot is in teleop mode.
*/
static bool IsTeleop();
/**
* Returns true if the robot is in autonomous mode.
*
* @return True if the robot is in autonomous mode.
*/
static bool IsAutonomous();
/**
* Returns true if the robot is in test mode.
*
* @return True if the robot is in test mode.
*/
static bool IsTest();
};
} // namespace wpi

View File

@@ -0,0 +1,131 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <chrono>
#include <functional>
#include <utility>
#include <vector>
#include "wpi/framework/IterativeRobotBase.hpp"
#include "wpi/hal/Notifier.h"
#include "wpi/hal/Types.h"
#include "wpi/system/RobotController.hpp"
#include "wpi/units/frequency.hpp"
#include "wpi/units/math.hpp"
#include "wpi/units/time.hpp"
#include "wpi/util/priority_queue.hpp"
namespace wpi {
/**
* TimedRobot implements the IterativeRobotBase robot program framework.
*
* The TimedRobot class is intended to be subclassed by a user creating a
* robot program.
*
* Periodic() functions from the base class are called on an interval by a
* Notifier instance.
*/
class TimedRobot : public IterativeRobotBase {
public:
/// Default loop period.
static constexpr auto kDefaultPeriod = 20_ms;
/**
* Provide an alternate "main loop" via StartCompetition().
*/
void StartCompetition() override;
/**
* Ends the main loop in StartCompetition().
*/
void EndCompetition() override;
/**
* Constructor for TimedRobot.
*
* @param period The period of the robot loop function.
*/
explicit TimedRobot(wpi::units::second_t period = kDefaultPeriod);
/**
* Constructor for TimedRobot.
*
* @param frequency The frequency of the robot loop function.
*/
explicit TimedRobot(wpi::units::hertz_t frequency);
TimedRobot(TimedRobot&&) = default;
TimedRobot& operator=(TimedRobot&&) = default;
~TimedRobot() override;
/**
* Return the system clock time in micrseconds for the start of the current
* periodic loop. This is in the same time base as Timer.GetFPGATimestamp(),
* but is stable through a loop. It is updated at the beginning of every
* periodic callback (including the normal periodic loop).
*
* @return Robot running time in microseconds, as of the start of the current
* periodic function.
*/
uint64_t GetLoopStartTime();
/**
* Add a callback to run at a specific period with a starting time offset.
*
* This is scheduled on TimedRobot's Notifier, so TimedRobot and the callback
* run synchronously. Interactions between them are thread-safe.
*
* @param callback The callback to run.
* @param period The period at which to run the callback.
* @param offset The offset from the common starting time. This is useful
* for scheduling a callback in a different timeslot relative
* to TimedRobot.
*/
void AddPeriodic(std::function<void()> callback, wpi::units::second_t period,
wpi::units::second_t offset = 0_s);
private:
class Callback {
public:
std::function<void()> func;
std::chrono::microseconds period;
std::chrono::microseconds expirationTime;
/**
* Construct a callback container.
*
* @param func The callback to run.
* @param startTime The common starting point for all callback scheduling.
* @param period The period at which to run the callback.
* @param offset The offset from the common starting time.
*/
Callback(std::function<void()> func, std::chrono::microseconds startTime,
std::chrono::microseconds period, std::chrono::microseconds offset)
: func{std::move(func)},
period{period},
expirationTime(
startTime + offset + period +
(std::chrono::microseconds{wpi::RobotController::GetFPGATime()} -
startTime) /
period * period) {}
bool operator>(const Callback& rhs) const {
return expirationTime > rhs.expirationTime;
}
};
wpi::hal::Handle<HAL_NotifierHandle, HAL_CleanNotifier> m_notifier;
std::chrono::microseconds m_startTime;
uint64_t m_loopStartTimeUs = 0;
wpi::util::priority_queue<Callback, std::vector<Callback>,
std::greater<Callback>>
m_callbacks;
};
} // namespace wpi

View File

@@ -0,0 +1,119 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <functional>
#include "wpi/framework/TimedRobot.hpp"
#include "wpi/units/time.hpp"
namespace wpi {
/**
* TimesliceRobot extends the TimedRobot robot program framework to provide
* timeslice scheduling of periodic functions.
*
* The TimesliceRobot class is intended to be subclassed by a user creating a
* robot program.
*
* This class schedules robot operations serially in a timeslice format.
* TimedRobot's periodic functions are the first in the timeslice table with 0
* ms offset and 20 ms period. You can schedule additional controller periodic
* functions at a shorter period (5 ms by default). You give each one a
* timeslice duration, then they're run sequentially. The main benefit of this
* approach is consistent starting times for each controller periodic, which can
* make odometry and estimators more accurate and controller outputs change more
* consistently.
*
* Here's an example of measured subsystem durations and their timeslice
* allocations:
*
* <table>
* <tr>
* <td><b>Subsystem</b></td>
* <td><b>Duration (ms)</b></td>
* <td><b>Allocation (ms)</b></td>
* </tr>
* <tr>
* <td><b>Total</b></td>
* <td>5.0</td>
* <td>5.0</td>
* </tr>
* <tr>
* <td>TimedRobot</td>
* <td>?</td>
* <td>2.0</td>
* </tr>
* <tr>
* <td>Drivetrain</td>
* <td>1.32</td>
* <td>1.5</td>
* </tr>
* <tr>
* <td>Flywheel</td>
* <td>0.6</td>
* <td>0.7</td>
* </tr>
* <tr>
* <td>Turret</td>
* <td>0.6</td>
* <td>0.8</td>
* </tr>
* <tr>
* <td><b>Free</b></td>
* <td>0.0</td>
* <td>N/A</td>
* </tr>
* </table>
*
* Since TimedRobot periodic functions only run every 20ms, that leaves a 2 ms
* empty spot in the allocation table for three of the four 5 ms cycles
* comprising 20 ms. That's OK because the OS needs time to do other things.
*
* If the robot periodic functions and the controller periodic functions have a
* lot of scheduling jitter that cause them to occasionally overlap with later
* timeslices, consider giving the main robot thread a real-time priority using
* wpi::SetCurrentThreadPriority(). An RT priority of 15 is a reasonable choice.
*
* If you do enable RT though, <i>make sure your periodic functions do not
* block</i>. If they do, the operating system will lock up, and you'll have to
* boot the roboRIO into safe mode and delete the robot program to recover.
*/
class TimesliceRobot : public TimedRobot {
public:
/**
* Constructor for TimesliceRobot.
*
* @param robotPeriodicAllocation The allocation to give the TimesliceRobot
* periodic functions.
* @param controllerPeriod The controller period. The sum of all scheduler
* allocations should be less than or equal to this
* value.
*/
explicit TimesliceRobot(wpi::units::second_t robotPeriodicAllocation,
wpi::units::second_t controllerPeriod);
/**
* Schedule a periodic function with the constructor's controller period and
* the given allocation. The function's runtime allocation will be placed
* after the end of the previous one's.
*
* If a call to this function makes the allocations exceed the controller
* period, an exception will be thrown since that means the TimesliceRobot
* periodic functions and the given function will have conflicting
* timeslices.
*
* @param func Function to schedule.
* @param allocation The function's runtime allocation out of the controller
* period.
*/
void Schedule(std::function<void()> func, wpi::units::second_t allocation);
private:
wpi::units::second_t m_nextOffset;
wpi::units::second_t m_controllerPeriod;
};
} // namespace wpi