mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-05 03:21:42 +00:00
[wpilib] Change opmodes to purely periodic (#8652)
1. Make the OpMode interface itself periodic; this means the only differences between `OpMode` and `PeriodicOpMode` are the latter's methods to add sideloaded periodic callbacks 2. Make OpModeRobot process callbacks in a similar fashion to TimedRobot and 3. Add some lifecycle functions (discussed below) 4. Pull the callback priority queue from TimedRobot to a new class called `PeriodicPriorityQueue` so that `TimedRobot` and `OpModeRobot` have less duplication 5. Fix a typo in the DriverStationJNI class that causes a memory leak when certain driver station sim calls 6. Port the C++ OpModeRobot tests to Java `OpModeRobot` now possesses some `IterativeRobotBase`-stye lifecycle functions; these functions 1. `robotPeriodic` 2. `simulationInit` and `simulationPeriodic` 3. `disabledInit`, `disabledPeriodic`, and `disabledExit` (note that `simulationInit` and `disabledInit` may be renamed to match wpilibsuite#8719) `OpModeRobot` also now processes `OpMode` changes (by the Driver Station) in its `loopFunc` method, similar to `IterativeRobotBase.loopFunc` processing game mode changes; `loopFunc` is, similarly to `TimedRobot`, provided as a default `Callback` --------- Signed-off-by: Zach Harel <zach@zharel.me> Co-authored-by: Joseph Eng <91924258+KangarooKoala@users.noreply.github.com>
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
|
||||
#include "wpi/framework/OpModeRobot.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
@@ -17,206 +17,196 @@
|
||||
#include "wpi/hal/DriverStationTypes.h"
|
||||
#include "wpi/hal/HAL.h"
|
||||
#include "wpi/hal/Notifier.hpp"
|
||||
#include "wpi/hal/UsageReporting.hpp"
|
||||
#include "wpi/nt/NetworkTableInstance.hpp"
|
||||
#include "wpi/opmode/OpMode.hpp"
|
||||
#include "wpi/smartdashboard/SmartDashboard.hpp"
|
||||
#include "wpi/system/Errors.hpp"
|
||||
#include "wpi/system/RobotController.hpp"
|
||||
#include "wpi/util/SafeThread.hpp"
|
||||
#include "wpi/util/Synchronization.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
namespace {
|
||||
class MonitorThread : public wpi::util::SafeThreadEvent {
|
||||
public:
|
||||
MonitorThread(int64_t modeId, wpi::util::Event& dsEvent,
|
||||
HAL_NotifierHandle notifier, std::weak_ptr<OpMode> activeOpMode)
|
||||
: m_modeId{modeId},
|
||||
m_dsEvent{dsEvent.GetHandle()},
|
||||
m_notifier{notifier},
|
||||
m_activeOpMode{std::move(activeOpMode)} {}
|
||||
|
||||
private:
|
||||
void Main() override {
|
||||
// Wait for DS to disable or change modes
|
||||
WPI_EventHandle events[] = {m_dsEvent, m_stopEvent.GetHandle()};
|
||||
WPI_Handle signaledBuf[2];
|
||||
for (;;) {
|
||||
auto signaled = wpi::util::WaitForObjects(events, signaledBuf);
|
||||
if (signaled.empty()) {
|
||||
return; // handles destroyed
|
||||
}
|
||||
for (auto signal : signaled) {
|
||||
if ((signal & 0x80000000) != 0 || signal == m_stopEvent.GetHandle()) {
|
||||
return; // handle destroyed or transitioned
|
||||
}
|
||||
}
|
||||
|
||||
// did the opmode or enable state change?
|
||||
HAL_ControlWord word;
|
||||
HAL_GetUncachedControlWord(&word);
|
||||
if (!HAL_ControlWord_IsEnabled(word) ||
|
||||
HAL_ControlWord_GetOpModeId(word) != m_modeId) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// call opmode stop
|
||||
auto opMode = m_activeOpMode.lock();
|
||||
if (opMode) {
|
||||
opMode->OpModeStop();
|
||||
}
|
||||
|
||||
events[0] = m_notifier;
|
||||
int32_t status = 0;
|
||||
HAL_SetNotifierAlarm(m_notifier, 1000000, 0, false, true, &status); // 1s
|
||||
auto signaled = wpi::util::WaitForObjects(events, signaledBuf);
|
||||
if (signaled.empty()) {
|
||||
return; // handles destroyed
|
||||
}
|
||||
for (auto signal : signaled) {
|
||||
if ((signal & 0x80000000) != 0 || signal == m_stopEvent.GetHandle()) {
|
||||
return; // handle destroyed or transitioned
|
||||
}
|
||||
}
|
||||
|
||||
// if it hasn't transitioned after 1 second, terminate the program
|
||||
WPILIB_ReportError(err::Error, "OpMode did not exit, terminating program");
|
||||
HAL_Shutdown();
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
int64_t m_modeId;
|
||||
WPI_EventHandle m_dsEvent;
|
||||
HAL_NotifierHandle m_notifier;
|
||||
std::weak_ptr<OpMode> m_activeOpMode;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void OpModeRobotBase::StartCompetition() {
|
||||
fmt::print("********** Robot program startup complete **********\n");
|
||||
|
||||
wpi::util::Event event;
|
||||
struct DSListener {
|
||||
wpi::util::Event& event;
|
||||
explicit DSListener(wpi::util::Event& event) : event{event} {
|
||||
HAL_ProvideNewDataEventHandle(event.GetHandle());
|
||||
}
|
||||
~DSListener() { HAL_RemoveNewDataEventHandle(event.GetHandle()); }
|
||||
} listener{event};
|
||||
|
||||
OpModeRobotBase::OpModeRobotBase(wpi::units::second_t period)
|
||||
: m_period{period},
|
||||
m_loopOverrunAlert{
|
||||
fmt::format("Loop time of {:.6f}s overrun", m_period.value()),
|
||||
Alert::Level::MEDIUM},
|
||||
m_watchdog{period, [this] { m_loopOverrunAlert.Set(true); }} {
|
||||
// Create our own notifier and callback queue
|
||||
int32_t status = 0;
|
||||
m_notifier = HAL_CreateNotifier(&status);
|
||||
HAL_SetNotifierName(m_notifier, "OpModeRobot", &status);
|
||||
|
||||
int64_t lastModeId = -1;
|
||||
bool calledObserveUserProgramStarting = false;
|
||||
bool calledDriverStationConnected = false;
|
||||
std::shared_ptr<OpMode> opMode;
|
||||
WPI_EventHandle events[] = {event.GetHandle(),
|
||||
static_cast<WPI_EventHandle>(m_notifier)};
|
||||
WPI_Handle signaledBuf[2];
|
||||
for (;;) {
|
||||
// Wait for new data from the driver station, with 50 ms timeout
|
||||
HAL_SetNotifierAlarm(m_notifier, 50000, 0, false, true, &status);
|
||||
m_startTime = std::chrono::microseconds{RobotController::GetMonotonicTime()};
|
||||
|
||||
// Call DriverStation::ObserveUserProgramStarting() here as a one-shot to
|
||||
// ensure it is called after the notifier alarm is set. The notifier alarm
|
||||
// is set using relative time, so tests that wait on the user program to
|
||||
// start and then step time won't work correctly if we call this before
|
||||
// setting the alarm.
|
||||
if (!calledObserveUserProgramStarting) {
|
||||
calledObserveUserProgramStarting = true;
|
||||
DriverStation::ObserveUserProgramStarting();
|
||||
}
|
||||
// Add LoopFunc as periodic callback
|
||||
AddPeriodic([this] { LoopFunc(); }, period);
|
||||
|
||||
auto signaled = wpi::util::WaitForObjects(events, signaledBuf);
|
||||
if (signaled.empty()) {
|
||||
return; // handles destroyed
|
||||
}
|
||||
for (auto signal : signaled) {
|
||||
if ((signal & 0x80000000) != 0) {
|
||||
return; // handle destroyed
|
||||
HAL_ReportUsage("Framework", "OpModeRobot");
|
||||
}
|
||||
|
||||
OpModeRobotBase::OpModeRobotBase() : OpModeRobotBase(DEFAULT_PERIOD) {}
|
||||
|
||||
void OpModeRobotBase::AddPeriodic(std::function<void()> callback,
|
||||
wpi::units::second_t period) {
|
||||
m_callbacks.Add(std::move(callback), m_startTime, period);
|
||||
}
|
||||
|
||||
void OpModeRobotBase::LoopFunc() {
|
||||
DriverStation::RefreshData();
|
||||
|
||||
// Get current enabled state and opmode
|
||||
const hal::ControlWord word = DriverStation::GetControlWord();
|
||||
m_watchdog.Reset();
|
||||
const bool enabled = word.IsEnabled();
|
||||
int64_t modeId = word.IsDSAttached() ? word.GetOpModeId() : 0;
|
||||
|
||||
if (!m_calledDriverStationConnected && word.IsDSAttached()) {
|
||||
m_calledDriverStationConnected = true;
|
||||
DriverStationConnected();
|
||||
m_watchdog.AddEpoch("DriverStationConnected()");
|
||||
}
|
||||
|
||||
// Handle opmode changes
|
||||
if (modeId != m_lastModeId) {
|
||||
// Clean up current opmode
|
||||
if (m_currentOpMode) {
|
||||
// Remove opmode callbacks
|
||||
if (m_opmodePeriodic) {
|
||||
m_callbacks.Remove(*m_opmodePeriodic);
|
||||
m_opmodePeriodic.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the latest control word and opmode
|
||||
DriverStation::RefreshData();
|
||||
hal::ControlWord ctlWord = DriverStation::GetControlWord();
|
||||
|
||||
if (!calledDriverStationConnected && ctlWord.IsDSAttached()) {
|
||||
calledDriverStationConnected = true;
|
||||
DriverStationConnected();
|
||||
}
|
||||
|
||||
int64_t modeId;
|
||||
if (!ctlWord.IsDSAttached()) {
|
||||
modeId = 0;
|
||||
} else {
|
||||
modeId = ctlWord.GetOpModeId();
|
||||
}
|
||||
|
||||
if (!opMode || modeId != lastModeId) {
|
||||
if (opMode) {
|
||||
// no or different opmode selected
|
||||
opMode.reset();
|
||||
}
|
||||
|
||||
if (modeId == 0) {
|
||||
// no opmode selected
|
||||
NonePeriodic();
|
||||
HAL_ObserveUserProgram(ctlWord.GetValue());
|
||||
continue;
|
||||
for (auto& cb : m_activeOpModeCallbacks) {
|
||||
m_callbacks.Remove(cb);
|
||||
}
|
||||
m_activeOpModeCallbacks.clear();
|
||||
m_currentOpMode.reset();
|
||||
}
|
||||
|
||||
// Set up new opmode
|
||||
if (modeId != 0) {
|
||||
auto data = m_opModes.lookup(modeId);
|
||||
if (!data.factory) {
|
||||
if (data.factory) {
|
||||
// Instantiate the new opmode
|
||||
fmt::print("********** Starting OpMode {} **********\n", data.name);
|
||||
m_currentOpMode = data.factory();
|
||||
if (m_currentOpMode) {
|
||||
// Ensure disabledPeriodic is called at least once
|
||||
m_currentOpMode->DisabledPeriodic();
|
||||
m_watchdog.AddEpoch("OpMode::DisabledPeriodic()");
|
||||
// Register the opmode's periodic callbacks
|
||||
m_opmodePeriodic = wpi::internal::PeriodicPriorityQueue::Callback{
|
||||
[op = m_currentOpMode.get()] { op->Periodic(); }, m_startTime,
|
||||
m_period};
|
||||
m_callbacks.Add(*m_opmodePeriodic);
|
||||
m_activeOpModeCallbacks = m_currentOpMode->GetCallbacks();
|
||||
for (auto& cb : m_activeOpModeCallbacks) {
|
||||
m_callbacks.Add(cb);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
WPILIB_ReportError(err::Error, "No OpMode found for mode {}", modeId);
|
||||
ctlWord.SetOpModeId(0);
|
||||
HAL_ObserveUserProgram(ctlWord.GetValue());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
m_lastModeId = modeId;
|
||||
}
|
||||
|
||||
// Instantiate the opmode
|
||||
fmt::print("********** Starting OpMode {} **********\n", data.name);
|
||||
opMode = data.factory();
|
||||
if (!opMode) {
|
||||
// could not construct
|
||||
ctlWord.SetOpModeId(0);
|
||||
HAL_ObserveUserProgram(ctlWord.GetValue());
|
||||
continue;
|
||||
// Handle enabled state changes
|
||||
bool justCalledDisabledInit = false;
|
||||
if (m_lastEnabledState != enabled) {
|
||||
if (enabled) {
|
||||
// Transitioning to enabled
|
||||
DisabledExit();
|
||||
m_watchdog.AddEpoch("DisabledExit()");
|
||||
if (m_currentOpMode) {
|
||||
m_currentOpMode->Start();
|
||||
m_watchdog.AddEpoch("OpMode::Start()");
|
||||
}
|
||||
{
|
||||
std::scoped_lock lock(m_opModeMutex);
|
||||
m_activeOpMode = opMode;
|
||||
} else {
|
||||
// Transitioning to disabled
|
||||
if (m_currentOpMode && m_lastEnabledState) {
|
||||
// Was enabled, now disabled
|
||||
m_currentOpMode->End();
|
||||
m_watchdog.AddEpoch("OpMode::End()");
|
||||
}
|
||||
lastModeId = modeId;
|
||||
// Ensure disabledPeriodic is called at least once
|
||||
opMode->DisabledPeriodic();
|
||||
DisabledInit();
|
||||
m_watchdog.AddEpoch("DisabledInit()");
|
||||
justCalledDisabledInit = true;
|
||||
}
|
||||
m_lastEnabledState = enabled;
|
||||
}
|
||||
|
||||
// Call periodic functions based on current state
|
||||
if (!enabled) {
|
||||
// Only call DisabledPeriodic if we didn't just call DisabledInit
|
||||
if (!justCalledDisabledInit) {
|
||||
DisabledPeriodic();
|
||||
m_watchdog.AddEpoch("DisabledPeriodic()");
|
||||
}
|
||||
|
||||
HAL_ObserveUserProgram(ctlWord.GetValue());
|
||||
// Call opmode DisabledPeriodic if we have one
|
||||
if (m_currentOpMode) {
|
||||
m_currentOpMode->DisabledPeriodic();
|
||||
m_watchdog.AddEpoch("OpMode::DisabledPeriodic()");
|
||||
}
|
||||
}
|
||||
|
||||
if (ctlWord.IsEnabled()) {
|
||||
// When enabled, call the opmode run function, then close and clear
|
||||
wpi::util::SafeThreadOwner<MonitorThread> monitor;
|
||||
monitor.Start(modeId, event, static_cast<HAL_NotifierHandle>(m_notifier),
|
||||
opMode);
|
||||
opMode->OpModeRun(modeId);
|
||||
opMode.reset();
|
||||
} else {
|
||||
// When disabled, call the DisabledPeriodic function
|
||||
opMode->DisabledPeriodic();
|
||||
// Call NonePeriodic when no opmode is selected
|
||||
if (modeId == 0) {
|
||||
NonePeriodic();
|
||||
m_watchdog.AddEpoch("NonePeriodic()");
|
||||
}
|
||||
|
||||
// Always call RobotPeriodic
|
||||
RobotPeriodic();
|
||||
m_watchdog.AddEpoch("RobotPeriodic()");
|
||||
|
||||
// Always observe user program state
|
||||
HAL_ObserveUserProgram(word.GetValue());
|
||||
|
||||
SmartDashboard::UpdateValues();
|
||||
m_watchdog.AddEpoch("SmartDashboard::UpdateValues()");
|
||||
|
||||
if constexpr (IsSimulation()) {
|
||||
HAL_SimPeriodicBefore();
|
||||
SimulationPeriodic();
|
||||
HAL_SimPeriodicAfter();
|
||||
m_watchdog.AddEpoch("SimulationPeriodic()");
|
||||
}
|
||||
|
||||
m_watchdog.Disable();
|
||||
|
||||
// Flush NetworkTables
|
||||
wpi::nt::NetworkTableInstance::GetDefault().FlushLocal();
|
||||
|
||||
// Warn on loop time overruns
|
||||
if (m_watchdog.IsExpired()) {
|
||||
m_watchdog.PrintEpochs();
|
||||
}
|
||||
}
|
||||
|
||||
void OpModeRobotBase::StartCompetition() {
|
||||
fmt::print("********** Robot program startup complete **********\n");
|
||||
|
||||
if constexpr (IsSimulation()) {
|
||||
SimulationInit();
|
||||
}
|
||||
|
||||
// Tell the DS that the robot is ready to be enabled
|
||||
DriverStation::ObserveUserProgramStarting();
|
||||
|
||||
// Loop forever, calling the callback system which handles periodic functions
|
||||
while (true) {
|
||||
if (!m_callbacks.RunCallbacks(m_notifier)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpModeRobotBase::EndCompetition() {
|
||||
m_notifier = {};
|
||||
std::shared_ptr<OpMode> opMode;
|
||||
{
|
||||
std::scoped_lock lock(m_opModeMutex);
|
||||
opMode = m_activeOpMode.lock();
|
||||
}
|
||||
if (opMode) {
|
||||
opMode->OpModeStop();
|
||||
if (m_notifier != HAL_INVALID_HANDLE) {
|
||||
HAL_DestroyNotifier(m_notifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
#include <utility>
|
||||
|
||||
#include "wpi/driverstation/DriverStation.hpp"
|
||||
#include "wpi/hal/Notifier.hpp"
|
||||
#include "wpi/hal/DriverStation.hpp"
|
||||
#include "wpi/hal/UsageReporting.hpp"
|
||||
#include "wpi/system/Errors.hpp"
|
||||
#include "wpi/system/RobotController.hpp"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
@@ -27,45 +28,9 @@ void TimedRobot::StartCompetition() {
|
||||
|
||||
// Loop forever, calling the appropriate mode-dependent function
|
||||
while (true) {
|
||||
// We don't have to check there's an element in the queue first because
|
||||
// there's always at least one (the constructor adds one). It's reenqueued
|
||||
// at the end of the loop.
|
||||
auto callback = m_callbacks.pop();
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_SetNotifierAlarm(m_notifier, callback.expirationTime.count(), 0, true,
|
||||
true, &status);
|
||||
WPILIB_CheckErrorStatus(status, "SetNotifierAlarm");
|
||||
|
||||
if (WPI_WaitForObject(m_notifier) == 0) {
|
||||
if (!m_callbacks.RunCallbacks(m_notifier)) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_loopStartTimeUs = RobotController::GetMonotonicTime();
|
||||
std::chrono::microseconds currentTime{m_loopStartTimeUs};
|
||||
|
||||
callback.func();
|
||||
|
||||
// Increment the expiration time by the number of full periods it's behind
|
||||
// plus one to avoid rapid repeat fires from a large loop overrun. We assume
|
||||
// currentTime ≥ expirationTime rather than checking for it since the
|
||||
// callback wouldn't be running otherwise.
|
||||
callback.expirationTime +=
|
||||
callback.period + (currentTime - callback.expirationTime) /
|
||||
callback.period * callback.period;
|
||||
m_callbacks.push(std::move(callback));
|
||||
|
||||
// Process all other callbacks that are ready to run
|
||||
while (m_callbacks.top().expirationTime <= currentTime) {
|
||||
callback = m_callbacks.pop();
|
||||
|
||||
callback.func();
|
||||
|
||||
callback.expirationTime +=
|
||||
callback.period + (currentTime - callback.expirationTime) /
|
||||
callback.period * callback.period;
|
||||
m_callbacks.push(std::move(callback));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,15 +61,8 @@ TimedRobot::~TimedRobot() {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t TimedRobot::GetLoopStartTime() {
|
||||
return m_loopStartTimeUs;
|
||||
}
|
||||
|
||||
void TimedRobot::AddPeriodic(std::function<void()> callback,
|
||||
wpi::units::second_t period,
|
||||
wpi::units::second_t offset) {
|
||||
m_callbacks.emplace(
|
||||
callback, m_startTime,
|
||||
std::chrono::microseconds{static_cast<int64_t>(period.value() * 1e6)},
|
||||
std::chrono::microseconds{static_cast<int64_t>(offset.value() * 1e6)});
|
||||
m_callbacks.Add(std::move(callback), m_startTime, period, offset);
|
||||
}
|
||||
|
||||
127
wpilibc/src/main/native/cpp/internal/PeriodicPriorityQueue.cpp
Normal file
127
wpilibc/src/main/native/cpp/internal/PeriodicPriorityQueue.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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.
|
||||
|
||||
#include "wpi/internal/PeriodicPriorityQueue.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "wpi/hal/Notifier.h"
|
||||
#include "wpi/system/Errors.hpp"
|
||||
#include "wpi/system/RobotController.hpp"
|
||||
#include "wpi/util/Synchronization.h"
|
||||
|
||||
using namespace wpi::internal;
|
||||
|
||||
PeriodicPriorityQueue::Callback::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{RobotController::GetMonotonicTime()} -
|
||||
startTime) /
|
||||
period * period) {}
|
||||
|
||||
PeriodicPriorityQueue::Callback::Callback(std::function<void()> func,
|
||||
std::chrono::microseconds startTime,
|
||||
wpi::units::second_t period,
|
||||
wpi::units::second_t offset)
|
||||
: Callback{
|
||||
std::move(func), startTime,
|
||||
std::chrono::microseconds{static_cast<int64_t>(period.value() * 1e6)},
|
||||
std::chrono::microseconds{
|
||||
static_cast<int64_t>(offset.value() * 1e6)}} {}
|
||||
|
||||
PeriodicPriorityQueue::Callback::Callback(std::function<void()> func,
|
||||
std::chrono::microseconds startTime,
|
||||
wpi::units::second_t period)
|
||||
: Callback{std::move(func), startTime, period,
|
||||
std::chrono::microseconds{0}} {}
|
||||
|
||||
void PeriodicPriorityQueue::Add(std::function<void()> func,
|
||||
std::chrono::microseconds startTime,
|
||||
std::chrono::microseconds period) {
|
||||
Add(std::move(func), startTime, period, std::chrono::microseconds{0});
|
||||
}
|
||||
|
||||
void PeriodicPriorityQueue::Add(std::function<void()> func,
|
||||
std::chrono::microseconds startTime,
|
||||
std::chrono::microseconds period,
|
||||
std::chrono::microseconds offset) {
|
||||
m_queue.emplace(std::move(func), startTime, period, offset);
|
||||
}
|
||||
|
||||
void PeriodicPriorityQueue::Add(std::function<void()> func,
|
||||
std::chrono::microseconds startTime,
|
||||
wpi::units::second_t period) {
|
||||
Add(std::move(func), startTime, period, wpi::units::second_t{0});
|
||||
}
|
||||
|
||||
void PeriodicPriorityQueue::Add(std::function<void()> func,
|
||||
std::chrono::microseconds startTime,
|
||||
wpi::units::second_t period,
|
||||
wpi::units::second_t offset) {
|
||||
m_queue.emplace(std::move(func), startTime, period, offset);
|
||||
}
|
||||
|
||||
void PeriodicPriorityQueue::Add(Callback callback) {
|
||||
m_queue.push(std::move(callback));
|
||||
}
|
||||
|
||||
bool PeriodicPriorityQueue::Remove(const Callback& callback) {
|
||||
return m_queue.remove(callback);
|
||||
}
|
||||
|
||||
void PeriodicPriorityQueue::Clear() {
|
||||
while (!m_queue.empty()) {
|
||||
m_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
bool PeriodicPriorityQueue::RunCallbacks(HAL_NotifierHandle notifier) {
|
||||
// We don't have to check there's an element in the queue first because
|
||||
// there's always at least one (the constructor adds one). It's reenqueued
|
||||
// at the end of the loop.
|
||||
auto callback = m_queue.pop();
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_SetNotifierAlarm(notifier, callback.expirationTime.count(), 0, true, true,
|
||||
&status);
|
||||
WPILIB_CheckErrorStatus(status, "SetNotifierAlarm");
|
||||
|
||||
if (WPI_WaitForObject(notifier) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::chrono::microseconds currentTime{
|
||||
RobotController::GetMonotonicTime()};
|
||||
m_loopStartTime = wpi::units::microsecond_t{currentTime};
|
||||
|
||||
callback.func();
|
||||
|
||||
// Increment the expiration time by the number of full periods it's behind
|
||||
// plus one to avoid rapid repeat fires from a large loop overrun. We assume
|
||||
// currentTime >= expirationTime rather than checking for it since the
|
||||
// callback wouldn't be running otherwise.
|
||||
callback.expirationTime +=
|
||||
callback.period + (currentTime - callback.expirationTime) /
|
||||
callback.period * callback.period;
|
||||
m_queue.push(std::move(callback));
|
||||
|
||||
// Process all other callbacks that are ready to run
|
||||
while (m_queue.top().expirationTime <= currentTime) {
|
||||
callback = m_queue.pop();
|
||||
|
||||
callback.func();
|
||||
|
||||
callback.expirationTime +=
|
||||
callback.period + (currentTime - callback.expirationTime) /
|
||||
callback.period * callback.period;
|
||||
m_queue.push(std::move(callback));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -6,154 +6,22 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "wpi/driverstation/DriverStation.hpp"
|
||||
#include "wpi/hal/DriverStation.h"
|
||||
#include "wpi/hal/Notifier.hpp"
|
||||
#include "wpi/hal/UsageReporting.hpp"
|
||||
#include "wpi/nt/NetworkTableInstance.hpp"
|
||||
#include "wpi/smartdashboard/SmartDashboard.hpp"
|
||||
#include "wpi/system/Errors.hpp"
|
||||
#include "wpi/system/RobotController.hpp"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
PeriodicOpMode::Callback::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::GetMonotonicTime()} -
|
||||
startTime) /
|
||||
period * period) {}
|
||||
|
||||
PeriodicOpMode::~PeriodicOpMode() {
|
||||
if (m_notifier != HAL_INVALID_HANDLE) {
|
||||
HAL_DestroyNotifier(m_notifier);
|
||||
}
|
||||
}
|
||||
|
||||
PeriodicOpMode::PeriodicOpMode(wpi::units::second_t period)
|
||||
: m_period{period},
|
||||
m_watchdog(period, [this] { PrintLoopOverrunMessage(); }) {
|
||||
m_startTime = std::chrono::microseconds{RobotController::GetMonotonicTime()};
|
||||
AddPeriodic([=, this] { LoopFunc(); }, period);
|
||||
|
||||
int32_t status = 0;
|
||||
m_notifier = HAL_CreateNotifier(&status);
|
||||
WPILIB_CheckErrorStatus(status, "CreateNotifier");
|
||||
HAL_SetNotifierName(m_notifier, "PeriodicOpMode", &status);
|
||||
|
||||
PeriodicOpMode::PeriodicOpMode()
|
||||
: m_startTime{
|
||||
std::chrono::microseconds{RobotController::GetMonotonicTime()}} {
|
||||
HAL_ReportUsage("OpMode", "PeriodicOpMode");
|
||||
}
|
||||
|
||||
void PeriodicOpMode::AddPeriodic(std::function<void()> callback,
|
||||
wpi::units::second_t period,
|
||||
wpi::units::second_t offset) {
|
||||
m_callbacks.emplace(
|
||||
callback, m_startTime,
|
||||
m_callbacks.emplace_back(
|
||||
std::move(callback), m_startTime,
|
||||
std::chrono::microseconds{static_cast<int64_t>(period.value() * 1e6)},
|
||||
std::chrono::microseconds{static_cast<int64_t>(offset.value() * 1e6)});
|
||||
}
|
||||
|
||||
void PeriodicOpMode::LoopFunc() {
|
||||
DriverStation::RefreshData();
|
||||
HAL_ControlWord word;
|
||||
HAL_GetControlWord(&word);
|
||||
HAL_ControlWord_SetOpModeId(&word, m_opModeId);
|
||||
HAL_ObserveUserProgram(word);
|
||||
|
||||
if (!DriverStation::IsEnabled() ||
|
||||
DriverStation::GetOpModeId() != m_opModeId) {
|
||||
m_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_watchdog.Reset();
|
||||
Periodic();
|
||||
m_watchdog.AddEpoch("Periodic()");
|
||||
|
||||
SmartDashboard::UpdateValues();
|
||||
m_watchdog.AddEpoch("SmartDashboard::UpdateValues()");
|
||||
|
||||
// if constexpr (IsSimulation()) {
|
||||
// HAL_SimPeriodicBefore();
|
||||
// SimulationPeriodic();
|
||||
// HAL_SimPeriodicAfter();
|
||||
// m_watchdog.AddEpoch("SimulationPeriodic()");
|
||||
// }
|
||||
|
||||
m_watchdog.Disable();
|
||||
|
||||
// Flush NetworkTables
|
||||
nt::NetworkTableInstance::GetDefault().FlushLocal();
|
||||
|
||||
// Warn on loop time overruns
|
||||
if (m_watchdog.IsExpired()) {
|
||||
m_watchdog.PrintEpochs();
|
||||
}
|
||||
}
|
||||
|
||||
void PeriodicOpMode::OpModeRun(int64_t opModeId) {
|
||||
m_opModeId = opModeId;
|
||||
|
||||
Start();
|
||||
|
||||
while (m_running) {
|
||||
// We don't have to check there's an element in the queue first because
|
||||
// there's always at least one (the constructor adds one). It's reenqueued
|
||||
// at the end of the loop.
|
||||
auto callback = m_callbacks.pop();
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_SetNotifierAlarm(m_notifier, callback.expirationTime.count(), 0, true,
|
||||
true, &status);
|
||||
WPILIB_CheckErrorStatus(status, "SetNotifierAlarm");
|
||||
|
||||
if (WPI_WaitForObject(m_notifier) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_loopStartTimeUs = RobotController::GetMonotonicTime();
|
||||
std::chrono::microseconds currentTime{m_loopStartTimeUs};
|
||||
|
||||
callback.func();
|
||||
|
||||
// Increment the expiration time by the number of full periods it's behind
|
||||
// plus one to avoid rapid repeat fires from a large loop overrun. We assume
|
||||
// currentTime ≥ expirationTime rather than checking for it since the
|
||||
// callback wouldn't be running otherwise.
|
||||
callback.expirationTime +=
|
||||
callback.period + (currentTime - callback.expirationTime) /
|
||||
callback.period * callback.period;
|
||||
m_callbacks.push(std::move(callback));
|
||||
|
||||
// Process all other callbacks that are ready to run
|
||||
while (m_callbacks.top().expirationTime <= currentTime) {
|
||||
callback = m_callbacks.pop();
|
||||
|
||||
callback.func();
|
||||
|
||||
callback.expirationTime +=
|
||||
callback.period + (currentTime - callback.expirationTime) /
|
||||
callback.period * callback.period;
|
||||
m_callbacks.push(std::move(callback));
|
||||
}
|
||||
}
|
||||
End();
|
||||
}
|
||||
|
||||
void PeriodicOpMode::OpModeStop() {
|
||||
HAL_DestroyNotifier(m_notifier);
|
||||
m_notifier = HAL_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void PeriodicOpMode::PrintLoopOverrunMessage() {
|
||||
WPILIB_ReportWarning("Loop time of {:.6f}s overrun", m_period.value());
|
||||
}
|
||||
|
||||
void PeriodicOpMode::PrintWatchdogEpochs() {
|
||||
m_watchdog.PrintEpochs();
|
||||
}
|
||||
|
||||
@@ -5,17 +5,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/driverstation/Alert.hpp"
|
||||
#include "wpi/framework/RobotBase.hpp"
|
||||
#include "wpi/hal/DriverStationTypes.hpp"
|
||||
#include "wpi/hal/Notifier.h"
|
||||
#include "wpi/hal/Types.hpp"
|
||||
#include "wpi/internal/PeriodicPriorityQueue.hpp"
|
||||
#include "wpi/opmode/OpMode.hpp"
|
||||
#include "wpi/system/Watchdog.hpp"
|
||||
#include "wpi/units/time.hpp"
|
||||
#include "wpi/util/DenseMap.hpp"
|
||||
#include "wpi/util/SafeThread.hpp"
|
||||
#include "wpi/util/Synchronization.hpp"
|
||||
#include "wpi/util/mutex.hpp"
|
||||
|
||||
namespace wpi::util {
|
||||
@@ -37,7 +42,7 @@ concept OneArgOpMode = std::constructible_from<T, R&> && OpModeDerived<T>;
|
||||
|
||||
/**
|
||||
* Concept indicating a class is derived from OpMode and has either a
|
||||
* no-argument constructor or a constructorthat accepts R&.
|
||||
* no-argument constructor or a constructor that accepts R&.
|
||||
*
|
||||
* @tparam T opmode class
|
||||
* @tparam R robot class
|
||||
@@ -60,6 +65,9 @@ class OpModeRobotBase : public RobotBase {
|
||||
public:
|
||||
using OpModeFactory = std::function<std::unique_ptr<OpMode>()>;
|
||||
|
||||
/// Default loop period.
|
||||
static constexpr auto DEFAULT_PERIOD = 20_ms;
|
||||
|
||||
/**
|
||||
* Provide an alternate "main loop" via StartCompetition().
|
||||
*/
|
||||
@@ -72,14 +80,20 @@ class OpModeRobotBase : public RobotBase {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param period The period of the robot loop function.
|
||||
*/
|
||||
OpModeRobotBase() = default;
|
||||
explicit OpModeRobotBase(wpi::units::second_t period);
|
||||
|
||||
/**
|
||||
* Constructor for an OpModeRobot with a default loop time of 0.02 seconds.
|
||||
*/
|
||||
explicit OpModeRobotBase();
|
||||
|
||||
OpModeRobotBase(OpModeRobotBase&&) = delete;
|
||||
OpModeRobotBase& operator=(OpModeRobotBase&&) = delete;
|
||||
|
||||
/**
|
||||
* Function called exactly once after the DS is connected.
|
||||
*
|
||||
* Code that needs to know the DS state should go here.
|
||||
*
|
||||
* Users should override this method for initialization that needs to occur
|
||||
@@ -87,12 +101,65 @@ class OpModeRobotBase : public RobotBase {
|
||||
*/
|
||||
virtual void DriverStationConnected() {}
|
||||
|
||||
/**
|
||||
* Function called periodically every loop, regardless of enabled state or
|
||||
* OpMode selection.
|
||||
*/
|
||||
virtual void RobotPeriodic() {}
|
||||
|
||||
/**
|
||||
* Function called once during robot initialization in simulation.
|
||||
*/
|
||||
virtual void SimulationInit() {}
|
||||
|
||||
/**
|
||||
* Function called periodically in simulation.
|
||||
*/
|
||||
virtual void SimulationPeriodic() {}
|
||||
|
||||
/**
|
||||
* Function called once when the robot becomes disabled.
|
||||
*/
|
||||
virtual void DisabledInit() {}
|
||||
|
||||
/**
|
||||
* Function called periodically while the robot is disabled.
|
||||
*/
|
||||
virtual void DisabledPeriodic() {}
|
||||
|
||||
/**
|
||||
* Function called once when the robot exits disabled state.
|
||||
*/
|
||||
virtual void DisabledExit() {}
|
||||
|
||||
/**
|
||||
* Function called periodically anytime when no opmode is selected, including
|
||||
* when the Driver Station is disconnected.
|
||||
*/
|
||||
virtual void NonePeriodic() {}
|
||||
|
||||
/**
|
||||
* Add a callback to run at a specific period.
|
||||
*
|
||||
* @param callback The callback to run.
|
||||
* @param period The period at which to run the callback.
|
||||
*/
|
||||
void AddPeriodic(std::function<void()> callback, wpi::units::second_t period);
|
||||
|
||||
/**
|
||||
* Return the system clock time in microseconds for the start of the current
|
||||
* periodic loop. This is in the same time base as
|
||||
* Timer.getMonotonicTimeStamp(), 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.
|
||||
*/
|
||||
wpi::units::microsecond_t GetLoopStartTime() const {
|
||||
return m_callbacks.GetLoopStartTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an operating mode option using a factory function that creates the
|
||||
* opmode. It's necessary to call PublishOpModes() to make the added modes
|
||||
@@ -147,15 +214,37 @@ class OpModeRobotBase : public RobotBase {
|
||||
*/
|
||||
void ClearOpModes();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Main robot loop function. Handles disabled state logic.
|
||||
*/
|
||||
void LoopFunc();
|
||||
|
||||
private:
|
||||
struct OpModeData {
|
||||
std::string name;
|
||||
OpModeFactory factory;
|
||||
};
|
||||
|
||||
wpi::util::DenseMap<int64_t, OpModeData> m_opModes;
|
||||
wpi::hal::Handle<HAL_NotifierHandle, HAL_DestroyNotifier> m_notifier;
|
||||
wpi::util::mutex m_opModeMutex;
|
||||
std::weak_ptr<OpMode> m_activeOpMode;
|
||||
|
||||
wpi::internal::PeriodicPriorityQueue m_callbacks;
|
||||
HAL_NotifierHandle m_notifier;
|
||||
wpi::units::second_t m_period;
|
||||
std::chrono::microseconds m_startTime;
|
||||
Alert m_loopOverrunAlert;
|
||||
Watchdog m_watchdog;
|
||||
|
||||
// OpMode lifecycle state
|
||||
int64_t m_lastModeId = -1;
|
||||
bool m_calledDriverStationConnected = false;
|
||||
bool m_lastEnabledState = false;
|
||||
std::shared_ptr<OpMode> m_currentOpMode;
|
||||
std::vector<wpi::internal::PeriodicPriorityQueue::Callback>
|
||||
m_activeOpModeCallbacks;
|
||||
std::optional<wpi::internal::PeriodicPriorityQueue::Callback>
|
||||
m_opmodePeriodic;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -176,6 +265,18 @@ class OpModeRobotBase : public RobotBase {
|
||||
template <typename Derived>
|
||||
class OpModeRobot : public OpModeRobotBase {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param period The period of the robot loop function.
|
||||
*/
|
||||
explicit OpModeRobot(wpi::units::second_t period) : OpModeRobotBase{period} {}
|
||||
|
||||
/**
|
||||
* Constructor for an OpModeRobot with a default loop time of 0.02 seconds.
|
||||
*/
|
||||
explicit OpModeRobot() : OpModeRobotBase{} {}
|
||||
|
||||
/**
|
||||
* Adds an operating mode option. It's necessary to call PublishOpModes() to
|
||||
* make the added modes visible to the driver station.
|
||||
|
||||
@@ -4,18 +4,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/framework/IterativeRobotBase.hpp"
|
||||
#include "wpi/hal/Notifier.h"
|
||||
#include "wpi/hal/Notifier.hpp"
|
||||
#include "wpi/hal/Types.hpp"
|
||||
#include "wpi/system/RobotController.hpp"
|
||||
#include "wpi/internal/PeriodicPriorityQueue.hpp"
|
||||
#include "wpi/units/frequency.hpp"
|
||||
#include "wpi/units/time.hpp"
|
||||
#include "wpi/util/priority_queue.hpp"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
@@ -64,7 +60,7 @@ class TimedRobot : public IterativeRobotBase {
|
||||
|
||||
/**
|
||||
* Return the system clock time in microseconds for the start of the current
|
||||
* periodic loop. This is in the same time base as
|
||||
* periodic loop. This is in the same time base as
|
||||
* Timer.GetMonotonicTimestamp(), but is stable through a loop. It is updated
|
||||
* at the beginning of every periodic callback (including the normal periodic
|
||||
* loop).
|
||||
@@ -72,7 +68,9 @@ class TimedRobot : public IterativeRobotBase {
|
||||
* @return Robot running time in microseconds, as of the start of the current
|
||||
* periodic function.
|
||||
*/
|
||||
uint64_t GetLoopStartTime();
|
||||
wpi::units::microsecond_t GetLoopStartTime() const {
|
||||
return m_callbacks.GetLoopStartTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to run at a specific period with a starting time offset.
|
||||
@@ -89,43 +87,12 @@ class TimedRobot : public IterativeRobotBase {
|
||||
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::GetMonotonicTime()} -
|
||||
startTime) /
|
||||
period * period) {}
|
||||
|
||||
bool operator>(const Callback& rhs) const {
|
||||
return expirationTime > rhs.expirationTime;
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
wpi::hal::Handle<HAL_NotifierHandle, HAL_DestroyNotifier> 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;
|
||||
private:
|
||||
wpi::internal::PeriodicPriorityQueue m_callbacks;
|
||||
};
|
||||
|
||||
} // namespace wpi
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include "wpi/hal/Notifier.h"
|
||||
#include "wpi/units/time.hpp"
|
||||
#include "wpi/util/priority_queue.hpp"
|
||||
|
||||
namespace wpi::internal {
|
||||
|
||||
/**
|
||||
* A priority queue for scheduling periodic callbacks based on their next
|
||||
* execution time.
|
||||
*
|
||||
* <p>This class manages a collection of periodic callbacks that execute at
|
||||
* specified intervals. Callbacks are scheduled using FPGA timestamps and
|
||||
* automatically rescheduled after execution to maintain their periodic
|
||||
* behavior. The queue uses a priority heap to efficiently determine the next
|
||||
* callback to execute.
|
||||
*
|
||||
* <p>This is an internal scheduling primitive used by robot frameworks like
|
||||
* TimedRobot.
|
||||
*/
|
||||
class PeriodicPriorityQueue {
|
||||
public:
|
||||
/**
|
||||
* A periodic callback with scheduling metadata.
|
||||
*
|
||||
* Each callback tracks its target function, period, and next expiration
|
||||
* time. After execution, the expiration time is automatically advanced by
|
||||
* full periods to maintain precise timing even when execution is delayed.
|
||||
*/
|
||||
class Callback {
|
||||
public:
|
||||
/** The function to execute when the callback fires. */
|
||||
std::function<void()> func;
|
||||
|
||||
/** The period at which to run the callback. */
|
||||
std::chrono::microseconds period;
|
||||
|
||||
/** The next scheduled execution time in FPGA timestamp microseconds. */
|
||||
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);
|
||||
|
||||
/**
|
||||
* Construct a callback container using units-based period and offset.
|
||||
*
|
||||
* @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,
|
||||
wpi::units::second_t period, wpi::units::second_t offset);
|
||||
|
||||
/**
|
||||
* Construct a callback container using units-based period.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
Callback(std::function<void()> func, std::chrono::microseconds startTime,
|
||||
wpi::units::second_t period);
|
||||
|
||||
bool operator>(const Callback& rhs) const {
|
||||
return expirationTime > rhs.expirationTime;
|
||||
}
|
||||
|
||||
bool operator==(const Callback& rhs) const {
|
||||
return expirationTime == rhs.expirationTime;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a periodic callback to the queue.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
void Add(std::function<void()> func, std::chrono::microseconds startTime,
|
||||
std::chrono::microseconds period);
|
||||
|
||||
/**
|
||||
* Adds a periodic callback to the queue.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
void Add(std::function<void()> func, std::chrono::microseconds startTime,
|
||||
std::chrono::microseconds period, std::chrono::microseconds offset);
|
||||
|
||||
/**
|
||||
* Adds a periodic callback to the queue.
|
||||
*
|
||||
* @param func The callback to run.
|
||||
* @param startTime The common starting point for all callback scheduling in
|
||||
* FPGA timestamp microseconds.
|
||||
* @param period The period at which to run the callback.
|
||||
*/
|
||||
void Add(std::function<void()> func, std::chrono::microseconds startTime,
|
||||
wpi::units::second_t period);
|
||||
|
||||
/**
|
||||
* Adds a periodic callback to the queue.
|
||||
*
|
||||
* @param func The callback to run.
|
||||
* @param startTime The common starting point for all callback scheduling in
|
||||
* FPGA timestamp microseconds.
|
||||
* @param period The period at which to run the callback.
|
||||
* @param offset The offset from the common starting time.
|
||||
*/
|
||||
void Add(std::function<void()> func, std::chrono::microseconds startTime,
|
||||
wpi::units::second_t period, wpi::units::second_t offset);
|
||||
|
||||
/**
|
||||
* Adds a pre-constructed callback to the queue.
|
||||
*
|
||||
* @param callback The callback to add.
|
||||
*/
|
||||
void Add(Callback callback);
|
||||
|
||||
/**
|
||||
* Removes a specific callback from the queue.
|
||||
*
|
||||
* @param callback The callback to remove.
|
||||
* @return true if the callback was found and removed, false otherwise.
|
||||
*/
|
||||
bool Remove(const Callback& callback);
|
||||
|
||||
/**
|
||||
* Removes all callbacks from the queue.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* Executes all callbacks that are due, then waits for the next callback's
|
||||
* scheduled time.
|
||||
*
|
||||
* This method performs the following steps:
|
||||
* -# Retrieves the callback with the earliest expiration time from the queue
|
||||
* -# Sets a hardware notifier alarm to wait until that callback's expiration
|
||||
* time
|
||||
* -# Blocks until the notifier signals or is interrupted
|
||||
* -# Executes the callback and reschedules it for its next period
|
||||
* -# Processes any additional callbacks that have become due during execution
|
||||
*
|
||||
* When rescheduling callbacks, this method automatically compensates for
|
||||
* execution delays by advancing the expiration time by the number of full
|
||||
* periods that have elapsed, ensuring callbacks maintain their scheduled
|
||||
* phase over time.
|
||||
*
|
||||
* @param notifier The HAL notifier handle to use for timing.
|
||||
* @return false if the notifier was destroyed (loop should exit), true
|
||||
* otherwise.
|
||||
*/
|
||||
bool RunCallbacks(HAL_NotifierHandle notifier);
|
||||
|
||||
/**
|
||||
* Returns the underlying priority queue.
|
||||
*/
|
||||
wpi::util::priority_queue<Callback, std::vector<Callback>, std::greater<>>&
|
||||
GetQueue() {
|
||||
return m_queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system clock time in microseconds for the start of the current
|
||||
* periodic loop. This is in the same time base as
|
||||
* Timer.getMonotonicTimeStamp(), 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.
|
||||
*/
|
||||
wpi::units::microsecond_t GetLoopStartTime() const { return m_loopStartTime; }
|
||||
|
||||
private:
|
||||
wpi::util::priority_queue<Callback, std::vector<Callback>, std::greater<>>
|
||||
m_queue;
|
||||
|
||||
wpi::units::microsecond_t m_loopStartTime{0};
|
||||
};
|
||||
|
||||
} // namespace wpi::internal
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/internal/PeriodicPriorityQueue.hpp"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
@@ -12,34 +14,69 @@ namespace wpi {
|
||||
* Top-level interface for opmode classes. Users should generally extend one of
|
||||
* the abstract implementations of this interface (e.g. PeriodicOpMode) rather
|
||||
* than directly implementing this interface.
|
||||
*
|
||||
* <p><b>Lifecycle</b>:
|
||||
* <ul>
|
||||
* <li>constructed when opmode selected on driver station
|
||||
* <li>DisabledPeriodic() called periodically as long as DS is disabled
|
||||
* <li>when DS transitions from disabled to enabled, Start() is called once
|
||||
* <li>while DS is enabled, Periodic() is called periodically and additional
|
||||
* periodic callbacks added via GetCallbacks() are called periodically
|
||||
* <li>when DS transitions from enabled to disabled, or a different opmode is
|
||||
* selected while enabled, End() is called, followed by Close(); the
|
||||
* object is not reused
|
||||
* <li>if a different opmode is selected while disabled, only Close() is
|
||||
* called; the object is not reused
|
||||
* </ul>
|
||||
*/
|
||||
class OpMode {
|
||||
public:
|
||||
/**
|
||||
* The object is destroyed when the opmode is no longer selected on the DS or
|
||||
* after OpModeRun() returns.
|
||||
* after an enabled run ends. The object will not be reused after the
|
||||
* destructor is called.
|
||||
*/
|
||||
virtual ~OpMode() = default;
|
||||
|
||||
/**
|
||||
* This function is called periodically while the opmode is selected on the DS
|
||||
* (robot is disabled). Code that should only run once when the opmode is
|
||||
* This function is called periodically while the opmode is selected on the
|
||||
* DS (robot is disabled). Code that should only run once when the opmode is
|
||||
* selected should go in the opmode constructor.
|
||||
*/
|
||||
virtual void DisabledPeriodic() {}
|
||||
|
||||
/**
|
||||
* This function is called when the opmode starts (robot is enabled).
|
||||
*
|
||||
* @param opModeId opmode unique ID
|
||||
*/
|
||||
virtual void OpModeRun(int64_t opModeId) = 0;
|
||||
/** Called once when this opmode transitions to enabled. */
|
||||
virtual void Start() {}
|
||||
|
||||
/**
|
||||
* This function is called asynchronously when the robot is disabled, to
|
||||
* request the opmode return from OpModeRun().
|
||||
* This function is called periodically while the opmode is enabled.
|
||||
*/
|
||||
virtual void OpModeStop() = 0;
|
||||
virtual void Periodic() {}
|
||||
|
||||
/**
|
||||
* This function is called when the robot disables or switches opmodes while
|
||||
* this opmode is enabled. Implementations should stop blocking work
|
||||
* promptly.
|
||||
*/
|
||||
virtual void End() {}
|
||||
|
||||
/**
|
||||
* Returns a vector of custom periodic callbacks to be executed while the
|
||||
* opmode is enabled.
|
||||
*
|
||||
* <p>This method allows opmodes to register arbitrary periodic callbacks
|
||||
* with custom execution intervals. The callbacks are executed by the robot
|
||||
* framework at their scheduled times, in addition to the primary Periodic()
|
||||
* callback.
|
||||
*
|
||||
* @return A vector of custom callbacks to execute, or an empty vector if no
|
||||
* custom callbacks are needed. The default implementation returns an
|
||||
* empty vector.
|
||||
*/
|
||||
virtual std::vector<wpi::internal::PeriodicPriorityQueue::Callback>
|
||||
GetCallbacks() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wpi
|
||||
|
||||
@@ -4,29 +4,24 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/hal/Notifier.h"
|
||||
#include "wpi/hal/Types.hpp"
|
||||
#include "wpi/internal/PeriodicPriorityQueue.hpp"
|
||||
#include "wpi/opmode/OpMode.hpp"
|
||||
#include "wpi/system/Watchdog.hpp"
|
||||
#include "wpi/system/RobotController.hpp"
|
||||
#include "wpi/units/time.hpp"
|
||||
#include "wpi/util/priority_queue.hpp"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
/**
|
||||
* An opmode structure for periodic operation. This base class implements a loop
|
||||
* that runs one or more functions periodically (on a set time interval aka loop
|
||||
* period). The primary periodic callback function is the Periodic() function;
|
||||
* the time interval for this callback is 20 ms by default, but may be changed
|
||||
* via passing a different time interval to the constructor. Additional periodic
|
||||
* callbacks with different intervals can be added using the AddPeriodic() set
|
||||
* of functions.
|
||||
* period). The primary periodic callback function is the abstract Periodic()
|
||||
* function. Additional periodic callbacks with different intervals can be added
|
||||
* using the AddPeriodic() set of functions.
|
||||
*
|
||||
* Lifecycle:
|
||||
*
|
||||
@@ -39,33 +34,25 @@ namespace wpi {
|
||||
* - When DS transitions from disabled to enabled, Start() is called once
|
||||
*
|
||||
* - While DS is enabled, Periodic() is called periodically on the time interval
|
||||
* set by the constructor, and additional periodic callbacks added via
|
||||
* set by the robot framework, and additional periodic callbacks added via
|
||||
* AddPeriodic() are called periodically on their set time intervals
|
||||
*
|
||||
* - When DS transitions from enabled to disabled, or a different opmode is
|
||||
* selected on the driver station when the DS is enabled, End() is called,
|
||||
* followed by the object being destroyed; the object is not reused
|
||||
* followed by Close(); the object is not reused
|
||||
*
|
||||
* - If a different opmode is selected on the driver station when the DS is
|
||||
* disabled, the object is destroyed (without End() being called); the object
|
||||
* is not reused
|
||||
* disabled, only Close() is called; the object is not reused
|
||||
*/
|
||||
class PeriodicOpMode : public OpMode {
|
||||
public:
|
||||
/** Default loop period. */
|
||||
static constexpr auto DEFAULT_PERIOD = 20_ms;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Constructor. Periodic opmodes may specify the period used for the
|
||||
* Periodic() function.
|
||||
*
|
||||
* @param period period for callbacks to the Periodic() function
|
||||
* Constructor.
|
||||
*/
|
||||
explicit PeriodicOpMode(wpi::units::second_t period = DEFAULT_PERIOD);
|
||||
PeriodicOpMode();
|
||||
|
||||
public:
|
||||
~PeriodicOpMode() override;
|
||||
~PeriodicOpMode() override = default;
|
||||
|
||||
/**
|
||||
* Called periodically while the opmode is selected on the DS (robot is
|
||||
@@ -77,35 +64,52 @@ class PeriodicOpMode : public OpMode {
|
||||
* Called a single time when the robot transitions from disabled to enabled.
|
||||
* This is called prior to Periodic() being called.
|
||||
*/
|
||||
virtual void Start() {}
|
||||
void Start() override {}
|
||||
|
||||
/** Called periodically while the robot is enabled. */
|
||||
virtual void Periodic() = 0;
|
||||
/**
|
||||
* Called periodically while the robot is enabled. The framework calls this
|
||||
* at OpModeRobot's configured loop period.
|
||||
*/
|
||||
void Periodic() override {}
|
||||
|
||||
/**
|
||||
* Called a single time when the robot transitions from enabled to disabled,
|
||||
* or just before the destructor is called if a different opmode is selected
|
||||
* while the robot is enabled.
|
||||
* or just before Close() is called if a different opmode is selected while
|
||||
* the robot is enabled.
|
||||
*/
|
||||
virtual void End() {}
|
||||
void End() override {}
|
||||
|
||||
/**
|
||||
* Return the system clock time in microseconds for the start of the current
|
||||
* periodic loop. This is in the same time base as
|
||||
* Timer::GetMonotonicTimestamp(), but is stable through a loop. It is updated
|
||||
* at the beginning of every periodic callback (including the normal periodic
|
||||
* loop).
|
||||
* Returns the set of additional periodic callbacks registered via
|
||||
* AddPeriodic(). These are executed by the robot framework while the opmode
|
||||
* is enabled, in addition to the primary Periodic() callback.
|
||||
*
|
||||
* @return Robot running time in microseconds, as of the start of the current
|
||||
* periodic function.
|
||||
* @return The vector of additional periodic callbacks.
|
||||
*/
|
||||
int64_t GetLoopStartTime() const { return m_loopStartTimeUs; }
|
||||
std::vector<wpi::internal::PeriodicPriorityQueue::Callback> GetCallbacks()
|
||||
override {
|
||||
return m_callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to run at a specific period.
|
||||
*
|
||||
* This is scheduled on the same Notifier as Periodic(), so Periodic() 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.
|
||||
*/
|
||||
void AddPeriodic(std::function<void()> callback,
|
||||
wpi::units::second_t period) {
|
||||
AddPeriodic(std::move(callback), period, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to run at a specific period with a starting time offset.
|
||||
*
|
||||
* This is scheduled on the same Notifier as Periodic(), so Periodic() and the
|
||||
* callback run synchronously. Interactions between them are thread-safe.
|
||||
* This is scheduled on the same Notifier as Periodic(), so Periodic() 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.
|
||||
@@ -114,66 +118,11 @@ class PeriodicOpMode : public OpMode {
|
||||
* to TimedRobot.
|
||||
*/
|
||||
void AddPeriodic(std::function<void()> callback, wpi::units::second_t period,
|
||||
wpi::units::second_t offset = 0_s);
|
||||
|
||||
/**
|
||||
* Gets time period between calls to Periodic() functions.
|
||||
*/
|
||||
wpi::units::second_t GetPeriod() const { return m_period; }
|
||||
|
||||
/**
|
||||
* Prints list of epochs added so far and their times.
|
||||
*/
|
||||
void PrintWatchdogEpochs();
|
||||
|
||||
protected:
|
||||
/** Loop function. */
|
||||
void LoopFunc();
|
||||
|
||||
public:
|
||||
// implements OpMode interface
|
||||
void OpModeRun(int64_t opModeId) final;
|
||||
|
||||
void OpModeStop() final;
|
||||
wpi::units::second_t offset);
|
||||
|
||||
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);
|
||||
|
||||
bool operator>(const Callback& rhs) const {
|
||||
return expirationTime > rhs.expirationTime;
|
||||
}
|
||||
};
|
||||
|
||||
int64_t m_opModeId;
|
||||
bool m_running = true;
|
||||
|
||||
wpi::hal::Handle<HAL_NotifierHandle, HAL_DestroyNotifier> m_notifier;
|
||||
std::vector<wpi::internal::PeriodicPriorityQueue::Callback> m_callbacks;
|
||||
std::chrono::microseconds m_startTime;
|
||||
int64_t m_loopStartTimeUs = 0;
|
||||
wpi::units::second_t m_period;
|
||||
Watchdog m_watchdog;
|
||||
|
||||
wpi::util::priority_queue<Callback, std::vector<Callback>,
|
||||
std::greater<Callback>>
|
||||
m_callbacks;
|
||||
|
||||
void PrintLoopOverrunMessage();
|
||||
};
|
||||
|
||||
} // namespace wpi
|
||||
|
||||
Reference in New Issue
Block a user