mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-22 01:11:42 +00:00
The current DS thread model has some pretty major issues. It makes it difficult to know if all data is from the same remote packet, and if the data changes while the robot loop is running. Additionally, the DS thread is used for a few other things (MotorSafety and State Tracking for EducationalRobot). This also makes sim difficult, as user code has to wait for the thread to know it has new data. This change completely rethinks how threading works in the driver station model. First, the DS HAL system receives a new data callback, either from Netcomm or DriverStationSim. Inside the context of this callback, all the low latency data is read and put into a cache. Doing some investigation on the robot side, this is perfectly safe to do, and also ensures a ds packet will not be parsed before we finish reading the current packet data. After all data is read, the cache is swapped with a 2nd buffer. This buffer just stores the data, none of the HAL DS calls read from this buffer. An event is then fired, stating there is new data ready to go. Robot code calls HAL_UpdateDSData(). This swaps the 2nd buffer with a 3rd buffer, which always contains the current data. This data will not be updated until HAL_UpdateDSData is called again. Which solves the state problem. The high level driver station classes have. an updateData() call, which calls HAL_UpdateDSData, and then update button state variables, then data log and update the NT FMS data table (Java also caches across the JNI boundary here, but that could trivially be removed). An extra event provider is provided, allowing other threads to know when this call has been completed. IterativeRobotBase calls DS.updateData() at the beginning of each loop, and only once per loop. This means all commands will always have the same state. All of this means there is no longer a DS thread. Everything happens synchronously. This means Sim and testing is easier, as you can just call DriverStationSim.NotifyNewData(), and then DriverStation.UpdateData(), and you can guarantee that all the DriverStation.*** data is up to date. As for Motor Safety and Educational Robot State Handling, those can all be handled by their own threads. The Educational Thread only needs to run under EducationalRobot, and MotorSafety will only be started if there is a motor safety object enabled.
238 lines
5.6 KiB
C++
238 lines
5.6 KiB
C++
// 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 <hal/DriverStation.h>
|
|
#include <hal/HALBase.h>
|
|
#include <hal/Main.h>
|
|
#include <wpi/condition_variable.h>
|
|
#include <wpi/mutex.h>
|
|
|
|
#include "frc/Errors.h"
|
|
#include "frc/RuntimeType.h"
|
|
|
|
namespace frc {
|
|
|
|
int RunHALInitialization();
|
|
|
|
namespace impl {
|
|
|
|
template <class Robot>
|
|
void RunRobot(wpi::mutex& m, Robot** robot) {
|
|
try {
|
|
static Robot theRobot;
|
|
{
|
|
std::scoped_lock lock{m};
|
|
*robot = &theRobot;
|
|
}
|
|
theRobot.StartCompetition();
|
|
} catch (const frc::RuntimeError& e) {
|
|
e.Report();
|
|
FRC_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() {
|
|
int halInit = RunHALInitialization();
|
|
if (halInit != 0) {
|
|
return halInit;
|
|
}
|
|
|
|
static wpi::mutex m;
|
|
static wpi::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);
|
|
}
|
|
|
|
HAL_Shutdown();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Implement a Robot Program framework.
|
|
*
|
|
* The RobotBase class is intended to be subclassed by a user creating a robot
|
|
* program. Overridden Autonomous() and OperatorControl() methods are called at
|
|
* the appropriate time as the match proceeds. In the current implementation,
|
|
* the Autonomous code will run to completion before the OperatorControl code
|
|
* could start. In the future the Autonomous code might be spawned as a task,
|
|
* then killed at the end of the Autonomous period.
|
|
*/
|
|
class RobotBase {
|
|
public:
|
|
/**
|
|
* Determine if the Robot is currently enabled.
|
|
*
|
|
* @return True if the Robot is currently enabled by the field controls.
|
|
*/
|
|
bool IsEnabled() const;
|
|
|
|
/**
|
|
* Determine if the Robot is currently disabled.
|
|
*
|
|
* @return True if the Robot is currently disabled by the field controls.
|
|
*/
|
|
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 field controls.
|
|
*/
|
|
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 field controls.
|
|
*/
|
|
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 field controls.
|
|
*/
|
|
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
|
|
* wnabled as determined by the field-controls.
|
|
*/
|
|
bool IsTeleopEnabled() const;
|
|
|
|
/**
|
|
* Determine if the robot is currently in Test mode.
|
|
*
|
|
* @return True if the robot is currently running tests as determined by the
|
|
* field controls.
|
|
*/
|
|
bool IsTest() const;
|
|
|
|
/**
|
|
* Gets the ID of the main robot thread.
|
|
*/
|
|
static std::thread::id GetThreadId();
|
|
|
|
virtual void StartCompetition() = 0;
|
|
|
|
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_ROBORIO__
|
|
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() {
|
|
return !IsReal();
|
|
}
|
|
|
|
/**
|
|
* Constructor for a generic robot program.
|
|
*
|
|
* User code should 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;
|
|
};
|
|
|
|
} // namespace frc
|