2020-12-26 14:12:05 -08:00
|
|
|
// 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.
|
2013-12-15 18:30:16 -05:00
|
|
|
|
2018-07-20 00:03:45 -07:00
|
|
|
#include "frc/DriverStation.h"
|
2016-09-14 20:52:06 -07:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <atomic>
|
2024-09-20 17:43:39 -07:00
|
|
|
#include <memory>
|
2022-10-15 16:33:14 -07:00
|
|
|
#include <span>
|
2020-11-15 13:48:54 -05:00
|
|
|
#include <string>
|
2021-05-26 17:44:18 -07:00
|
|
|
#include <string_view>
|
2016-10-09 11:46:01 -07:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
#include <fmt/format.h>
|
2019-11-08 22:53:20 -08:00
|
|
|
#include <hal/DriverStation.h>
|
2021-06-15 23:06:03 -07:00
|
|
|
#include <hal/DriverStationTypes.h>
|
2019-11-08 22:53:20 -08:00
|
|
|
#include <hal/HALBase.h>
|
2018-07-20 00:03:45 -07:00
|
|
|
#include <hal/Power.h>
|
2022-10-08 10:01:31 -07:00
|
|
|
#include <networktables/BooleanTopic.h>
|
|
|
|
|
#include <networktables/IntegerTopic.h>
|
2018-01-18 23:17:28 -08:00
|
|
|
#include <networktables/NetworkTable.h>
|
|
|
|
|
#include <networktables/NetworkTableInstance.h>
|
2022-10-08 10:01:31 -07:00
|
|
|
#include <networktables/StringTopic.h>
|
[hal, wpilib] New DS thread model and implementation (#3787)
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.
2022-10-21 22:01:55 -07:00
|
|
|
#include <wpi/EventVector.h>
|
2021-06-15 23:06:03 -07:00
|
|
|
#include <wpi/condition_variable.h>
|
2025-02-19 23:08:17 -06:00
|
|
|
#include <wpi/datalog/DataLog.h>
|
2024-07-02 16:31:50 -04:00
|
|
|
#include <wpi/json.h>
|
2021-06-15 23:06:03 -07:00
|
|
|
#include <wpi/mutex.h>
|
2022-01-27 00:15:43 -08:00
|
|
|
#include <wpi/timestamp.h>
|
2017-08-27 00:11:52 -07:00
|
|
|
|
2021-04-18 20:35:29 -07:00
|
|
|
#include "frc/Errors.h"
|
2018-07-20 00:03:45 -07:00
|
|
|
#include "frc/Timer.h"
|
2014-01-06 10:12:21 -05:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
using namespace frc;
|
|
|
|
|
|
|
|
|
|
namespace {
|
2020-11-15 13:48:54 -05:00
|
|
|
// A simple class which caches the previous value written to an NT entry
|
|
|
|
|
// Used to prevent redundant, repeated writes of the same value
|
2022-10-08 10:01:31 -07:00
|
|
|
template <typename Topic>
|
2020-11-15 13:48:54 -05:00
|
|
|
class MatchDataSenderEntry {
|
|
|
|
|
public:
|
|
|
|
|
MatchDataSenderEntry(const std::shared_ptr<nt::NetworkTable>& table,
|
2022-10-08 10:01:31 -07:00
|
|
|
std::string_view key,
|
2024-07-02 16:31:50 -04:00
|
|
|
typename Topic::ParamType initialVal,
|
2024-08-02 22:07:31 -04:00
|
|
|
wpi::json topicProperties = wpi::json::object())
|
2024-07-02 16:31:50 -04:00
|
|
|
: publisher{Topic{table->GetTopic(key)}.PublishEx(Topic::kTypeString,
|
|
|
|
|
topicProperties)},
|
|
|
|
|
prevVal{initialVal} {
|
2022-10-08 10:01:31 -07:00
|
|
|
publisher.Set(initialVal);
|
2020-11-15 13:48:54 -05:00
|
|
|
}
|
|
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
void Set(typename Topic::ParamType val) {
|
2020-11-15 13:48:54 -05:00
|
|
|
if (val != prevVal) {
|
2022-10-08 10:01:31 -07:00
|
|
|
publisher.Set(val);
|
2020-11-15 13:48:54 -05:00
|
|
|
prevVal = val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2022-10-08 10:01:31 -07:00
|
|
|
typename Topic::PublisherType publisher;
|
|
|
|
|
typename Topic::ValueType prevVal;
|
2020-11-15 13:48:54 -05:00
|
|
|
};
|
2018-01-18 23:17:28 -08:00
|
|
|
|
2024-07-02 16:31:50 -04:00
|
|
|
static constexpr std::string_view kSmartDashboardType = "FMSInfo";
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
struct MatchDataSender {
|
|
|
|
|
std::shared_ptr<nt::NetworkTable> table =
|
|
|
|
|
nt::NetworkTableInstance::GetDefault().GetTable("FMSInfo");
|
2024-07-02 16:31:50 -04:00
|
|
|
MatchDataSenderEntry<nt::StringTopic> typeMetaData{
|
|
|
|
|
table,
|
|
|
|
|
".type",
|
|
|
|
|
kSmartDashboardType,
|
|
|
|
|
{{"SmartDashboard", kSmartDashboardType}}};
|
2022-10-08 10:01:31 -07:00
|
|
|
MatchDataSenderEntry<nt::StringTopic> gameSpecificMessage{
|
2021-06-15 23:06:03 -07:00
|
|
|
table, "GameSpecificMessage", ""};
|
2022-10-08 10:01:31 -07:00
|
|
|
MatchDataSenderEntry<nt::StringTopic> eventName{table, "EventName", ""};
|
|
|
|
|
MatchDataSenderEntry<nt::IntegerTopic> matchNumber{table, "MatchNumber", 0};
|
|
|
|
|
MatchDataSenderEntry<nt::IntegerTopic> replayNumber{table, "ReplayNumber", 0};
|
|
|
|
|
MatchDataSenderEntry<nt::IntegerTopic> matchType{table, "MatchType", 0};
|
|
|
|
|
MatchDataSenderEntry<nt::BooleanTopic> alliance{table, "IsRedAlliance", true};
|
|
|
|
|
MatchDataSenderEntry<nt::IntegerTopic> station{table, "StationNumber", 1};
|
|
|
|
|
MatchDataSenderEntry<nt::IntegerTopic> controlWord{table, "FMSControlData",
|
|
|
|
|
0};
|
2018-01-18 23:17:28 -08:00
|
|
|
};
|
2017-11-09 19:59:29 -08:00
|
|
|
|
2022-01-27 00:15:43 -08:00
|
|
|
class JoystickLogSender {
|
|
|
|
|
public:
|
|
|
|
|
void Init(wpi::log::DataLog& log, unsigned int stick, int64_t timestamp);
|
|
|
|
|
void Send(uint64_t timestamp);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void AppendButtons(HAL_JoystickButtons buttons, uint64_t timestamp);
|
|
|
|
|
void AppendPOVs(const HAL_JoystickPOVs& povs, uint64_t timestamp);
|
|
|
|
|
|
|
|
|
|
unsigned int m_stick;
|
|
|
|
|
HAL_JoystickButtons m_prevButtons;
|
|
|
|
|
HAL_JoystickAxes m_prevAxes;
|
|
|
|
|
HAL_JoystickPOVs m_prevPOVs;
|
|
|
|
|
wpi::log::BooleanArrayLogEntry m_logButtons;
|
|
|
|
|
wpi::log::FloatArrayLogEntry m_logAxes;
|
|
|
|
|
wpi::log::IntegerArrayLogEntry m_logPOVs;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class DataLogSender {
|
|
|
|
|
public:
|
|
|
|
|
void Init(wpi::log::DataLog& log, bool logJoysticks, int64_t timestamp);
|
|
|
|
|
void Send(uint64_t timestamp);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
std::atomic_bool m_initialized{false};
|
|
|
|
|
|
|
|
|
|
HAL_ControlWord m_prevControlWord;
|
|
|
|
|
wpi::log::BooleanLogEntry m_logEnabled;
|
|
|
|
|
wpi::log::BooleanLogEntry m_logAutonomous;
|
|
|
|
|
wpi::log::BooleanLogEntry m_logTest;
|
|
|
|
|
wpi::log::BooleanLogEntry m_logEstop;
|
|
|
|
|
|
|
|
|
|
bool m_logJoysticks;
|
|
|
|
|
std::array<JoystickLogSender, DriverStation::kJoystickPorts> m_joysticks;
|
|
|
|
|
};
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
struct Instance {
|
|
|
|
|
Instance();
|
|
|
|
|
~Instance();
|
|
|
|
|
|
[hal, wpilib] New DS thread model and implementation (#3787)
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.
2022-10-21 22:01:55 -07:00
|
|
|
wpi::EventVector refreshEvents;
|
2021-06-15 23:06:03 -07:00
|
|
|
MatchDataSender matchDataSender;
|
2022-01-27 00:15:43 -08:00
|
|
|
std::atomic<DataLogSender*> dataLogSender{nullptr};
|
2021-06-15 23:06:03 -07:00
|
|
|
|
|
|
|
|
// Joystick button rising/falling edge flags
|
|
|
|
|
wpi::mutex buttonEdgeMutex;
|
|
|
|
|
std::array<HAL_JoystickButtons, DriverStation::kJoystickPorts>
|
|
|
|
|
previousButtonStates;
|
|
|
|
|
std::array<uint32_t, DriverStation::kJoystickPorts> joystickButtonsPressed;
|
|
|
|
|
std::array<uint32_t, DriverStation::kJoystickPorts> joystickButtonsReleased;
|
|
|
|
|
|
|
|
|
|
bool silenceJoystickWarning = false;
|
|
|
|
|
|
|
|
|
|
// Robot state status variables
|
|
|
|
|
bool userInDisabled = false;
|
|
|
|
|
bool userInAutonomous = false;
|
|
|
|
|
bool userInTeleop = false;
|
|
|
|
|
bool userInTest = false;
|
|
|
|
|
|
|
|
|
|
units::second_t nextMessageTime = 0_s;
|
|
|
|
|
};
|
|
|
|
|
} // namespace
|
2016-11-01 22:33:12 -07:00
|
|
|
|
2021-05-28 22:06:59 -07:00
|
|
|
static constexpr auto kJoystickUnpluggedMessageInterval = 1_s;
|
2013-12-15 18:30:16 -05:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
static Instance& GetInstance() {
|
|
|
|
|
static Instance instance;
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void SendMatchData();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reports errors related to unplugged joysticks.
|
|
|
|
|
*
|
|
|
|
|
* Throttles the errors so that they don't overwhelm the DS.
|
|
|
|
|
*/
|
|
|
|
|
static void ReportJoystickUnpluggedErrorV(fmt::string_view format,
|
|
|
|
|
fmt::format_args args);
|
|
|
|
|
|
|
|
|
|
template <typename S, typename... Args>
|
|
|
|
|
static inline void ReportJoystickUnpluggedError(const S& format,
|
|
|
|
|
Args&&... args) {
|
2022-08-16 15:35:26 -07:00
|
|
|
ReportJoystickUnpluggedErrorV(format, fmt::make_format_args(args...));
|
2021-06-15 23:06:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reports errors related to unplugged joysticks.
|
|
|
|
|
*
|
|
|
|
|
* Throttles the errors so that they don't overwhelm the DS.
|
|
|
|
|
*/
|
|
|
|
|
static void ReportJoystickUnpluggedWarningV(fmt::string_view format,
|
|
|
|
|
fmt::format_args args);
|
|
|
|
|
|
|
|
|
|
template <typename S, typename... Args>
|
|
|
|
|
static inline void ReportJoystickUnpluggedWarning(const S& format,
|
|
|
|
|
Args&&... args) {
|
2022-08-16 15:35:26 -07:00
|
|
|
ReportJoystickUnpluggedWarningV(format, fmt::make_format_args(args...));
|
2021-06-15 23:06:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Instance::Instance() {
|
|
|
|
|
HAL_Initialize(500, 0);
|
|
|
|
|
|
|
|
|
|
// All joysticks should default to having zero axes, povs and buttons, so
|
2021-09-08 22:09:08 -07:00
|
|
|
// uninitialized memory doesn't get sent to motor controllers.
|
2021-06-15 23:06:03 -07:00
|
|
|
for (unsigned int i = 0; i < DriverStation::kJoystickPorts; i++) {
|
|
|
|
|
joystickButtonsPressed[i] = 0;
|
|
|
|
|
joystickButtonsReleased[i] = 0;
|
|
|
|
|
previousButtonStates[i].count = 0;
|
|
|
|
|
previousButtonStates[i].buttons = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Instance::~Instance() {
|
2022-01-27 00:15:43 -08:00
|
|
|
if (dataLogSender) {
|
|
|
|
|
delete dataLogSender.load();
|
|
|
|
|
}
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2017-10-27 21:45:56 -07:00
|
|
|
bool DriverStation::GetStickButton(int stick, int button) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
if (button <= 0) {
|
2017-10-27 21:45:56 -07:00
|
|
|
ReportJoystickUnpluggedError(
|
2021-05-24 23:36:26 -07:00
|
|
|
"Joystick Button {} index out of range; indexes begin at 1", button);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
|
|
|
|
|
|
if (button > buttons.count) {
|
2017-10-27 21:45:56 -07:00
|
|
|
ReportJoystickUnpluggedWarning(
|
2021-05-24 23:36:26 -07:00
|
|
|
"Joystick Button {} missing (max {}), check if all controllers are "
|
|
|
|
|
"plugged in",
|
|
|
|
|
button, buttons.count);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
return buttons.buttons & 1 << (button - 1);
|
2017-10-27 21:45:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DriverStation::GetStickButtonPressed(int stick, int button) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
if (button <= 0) {
|
2017-10-27 21:45:56 -07:00
|
|
|
ReportJoystickUnpluggedError(
|
2021-05-24 23:36:26 -07:00
|
|
|
"Joystick Button {} index out of range; indexes begin at 1", button);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
|
|
|
|
|
|
if (button > buttons.count) {
|
2017-10-27 21:45:56 -07:00
|
|
|
ReportJoystickUnpluggedWarning(
|
2021-05-24 23:36:26 -07:00
|
|
|
"Joystick Button {} missing (max {}), check if all controllers are "
|
|
|
|
|
"plugged in",
|
|
|
|
|
button, buttons.count);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2021-06-15 23:06:03 -07:00
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
std::unique_lock lock(inst.buttonEdgeMutex);
|
2017-10-27 21:45:56 -07:00
|
|
|
// If button was pressed, clear flag and return true
|
2021-06-15 23:06:03 -07:00
|
|
|
if (inst.joystickButtonsPressed[stick] & 1 << (button - 1)) {
|
|
|
|
|
inst.joystickButtonsPressed[stick] &= ~(1 << (button - 1));
|
2017-10-27 21:45:56 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
2021-06-15 23:06:03 -07:00
|
|
|
return false;
|
2017-10-27 21:45:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DriverStation::GetStickButtonReleased(int stick, int button) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-12-07 01:31:14 -05:00
|
|
|
if (button <= 0) {
|
2017-10-27 21:45:56 -07:00
|
|
|
ReportJoystickUnpluggedError(
|
2021-05-24 23:36:26 -07:00
|
|
|
"Joystick Button {} index out of range; indexes begin at 1", button);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
|
|
|
|
|
|
if (button > buttons.count) {
|
2017-10-27 21:45:56 -07:00
|
|
|
ReportJoystickUnpluggedWarning(
|
2021-05-24 23:36:26 -07:00
|
|
|
"Joystick Button {} missing (max {}), check if all controllers are "
|
|
|
|
|
"plugged in",
|
|
|
|
|
button, buttons.count);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2021-06-15 23:06:03 -07:00
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
std::unique_lock lock(inst.buttonEdgeMutex);
|
2017-10-27 21:45:56 -07:00
|
|
|
// If button was released, clear flag and return true
|
2021-06-15 23:06:03 -07:00
|
|
|
if (inst.joystickButtonsReleased[stick] & 1 << (button - 1)) {
|
|
|
|
|
inst.joystickButtonsReleased[stick] &= ~(1 << (button - 1));
|
2017-10-27 21:45:56 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
2021-06-15 23:06:03 -07:00
|
|
|
return false;
|
2017-10-27 21:45:56 -07:00
|
|
|
}
|
|
|
|
|
|
2016-11-20 07:25:03 -08:00
|
|
|
double DriverStation::GetStickAxis(int stick, int axis) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2018-05-16 00:13:52 -07:00
|
|
|
return 0.0;
|
|
|
|
|
}
|
2018-12-07 01:31:14 -05:00
|
|
|
if (axis < 0 || axis >= HAL_kMaxJoystickAxes) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickAxis, "axis {} out of range", axis);
|
2018-05-16 00:13:52 -07:00
|
|
|
return 0.0;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickAxes axes;
|
|
|
|
|
HAL_GetJoystickAxes(stick, &axes);
|
|
|
|
|
|
|
|
|
|
if (axis >= axes.count) {
|
2018-05-16 00:13:52 -07:00
|
|
|
ReportJoystickUnpluggedWarning(
|
2021-05-24 23:36:26 -07:00
|
|
|
"Joystick Axis {} missing (max {}), check if all controllers are "
|
|
|
|
|
"plugged in",
|
|
|
|
|
axis, axes.count);
|
2016-11-20 07:25:03 -08:00
|
|
|
return 0.0;
|
2016-07-14 20:50:38 -07:00
|
|
|
}
|
|
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
return axes.axes[axis];
|
2014-12-05 20:13:23 -05:00
|
|
|
}
|
|
|
|
|
|
2025-06-29 18:32:26 -07:00
|
|
|
DriverStation::POVDirection DriverStation::GetStickPOV(int stick, int pov) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2025-06-29 18:32:26 -07:00
|
|
|
return kCenter;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2018-12-07 01:31:14 -05:00
|
|
|
if (pov < 0 || pov >= HAL_kMaxJoystickPOVs) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickAxis, "POV {} out of range", pov);
|
2025-06-29 18:32:26 -07:00
|
|
|
return kCenter;
|
2018-05-16 00:13:52 -07:00
|
|
|
}
|
|
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickPOVs povs;
|
|
|
|
|
HAL_GetJoystickPOVs(stick, &povs);
|
|
|
|
|
|
|
|
|
|
if (pov >= povs.count) {
|
2018-05-16 00:13:52 -07:00
|
|
|
ReportJoystickUnpluggedWarning(
|
2021-05-24 23:36:26 -07:00
|
|
|
"Joystick POV {} missing (max {}), check if all controllers are "
|
|
|
|
|
"plugged in",
|
|
|
|
|
pov, povs.count);
|
2025-06-29 18:32:26 -07:00
|
|
|
return kCenter;
|
2016-07-14 20:50:38 -07:00
|
|
|
}
|
|
|
|
|
|
2025-06-29 18:32:26 -07:00
|
|
|
return static_cast<POVDirection>(povs.povs[pov]);
|
2015-06-15 12:34:57 -04:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
int DriverStation::GetStickButtons(int stick) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2016-07-14 20:50:38 -07:00
|
|
|
return 0;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
|
|
|
|
|
|
return buttons.buttons;
|
2015-06-15 12:34:57 -04:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
int DriverStation::GetStickAxisCount(int stick) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2016-07-14 20:50:38 -07:00
|
|
|
return 0;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickAxes axes;
|
|
|
|
|
HAL_GetJoystickAxes(stick, &axes);
|
|
|
|
|
|
|
|
|
|
return axes.count;
|
2015-06-15 12:34:57 -04:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
int DriverStation::GetStickPOVCount(int stick) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2015-06-25 15:07:55 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickPOVs povs;
|
|
|
|
|
HAL_GetJoystickPOVs(stick, &povs);
|
|
|
|
|
|
|
|
|
|
return povs.count;
|
2014-12-05 20:13:23 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
int DriverStation::GetStickButtonCount(int stick) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2015-06-25 15:07:55 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
|
|
|
|
|
|
return buttons.count;
|
2014-12-05 20:13:23 -05:00
|
|
|
}
|
|
|
|
|
|
2025-05-16 22:15:14 -07:00
|
|
|
bool DriverStation::GetJoystickIsGamepad(int stick) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2016-07-14 20:50:38 -07:00
|
|
|
return false;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
|
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
|
|
2025-05-16 22:15:14 -07:00
|
|
|
return static_cast<bool>(descriptor.isGamepad);
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
int DriverStation::GetJoystickType(int stick) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2015-06-25 15:07:55 -04:00
|
|
|
return -1;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
|
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
|
|
|
|
|
|
return static_cast<int>(descriptor.type);
|
2014-10-17 14:46:25 -04:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
std::string DriverStation::GetJoystickName(int stick) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
|
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
|
|
|
|
|
|
return descriptor.name;
|
2014-12-18 10:57:11 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
int DriverStation::GetJoystickAxisType(int stick, int axis) {
|
2018-05-16 00:13:52 -07:00
|
|
|
if (stick < 0 || stick >= kJoystickPorts) {
|
2021-05-23 19:33:33 -07:00
|
|
|
FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2016-07-14 20:50:38 -07:00
|
|
|
return -1;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2022-05-18 14:36:11 -07:00
|
|
|
if (axis < 0 || axis >= HAL_kMaxJoystickAxes) {
|
|
|
|
|
FRC_ReportError(warn::BadJoystickAxis, "axis {} out of range", axis);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
|
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
|
|
2022-05-18 14:36:11 -07:00
|
|
|
return descriptor.axisTypes[axis];
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsJoystickConnected(int stick) {
|
2020-11-13 14:11:10 -05:00
|
|
|
return GetStickAxisCount(stick) > 0 || GetStickButtonCount(stick) > 0 ||
|
|
|
|
|
GetStickPOVCount(stick) > 0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsEnabled() {
|
2016-07-09 00:24:26 -07:00
|
|
|
HAL_ControlWord controlWord;
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetControlWord(&controlWord);
|
2015-06-25 15:07:55 -04:00
|
|
|
return controlWord.enabled && controlWord.dsAttached;
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsDisabled() {
|
2016-07-09 00:24:26 -07:00
|
|
|
HAL_ControlWord controlWord;
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetControlWord(&controlWord);
|
2015-06-25 15:07:55 -04:00
|
|
|
return !(controlWord.enabled && controlWord.dsAttached);
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsEStopped() {
|
2019-07-16 09:18:23 -05:00
|
|
|
HAL_ControlWord controlWord;
|
|
|
|
|
HAL_GetControlWord(&controlWord);
|
|
|
|
|
return controlWord.eStop;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsAutonomous() {
|
2016-07-09 00:24:26 -07:00
|
|
|
HAL_ControlWord controlWord;
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetControlWord(&controlWord);
|
2015-06-25 15:07:55 -04:00
|
|
|
return controlWord.autonomous;
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsAutonomousEnabled() {
|
2020-08-29 16:32:19 -04:00
|
|
|
HAL_ControlWord controlWord;
|
|
|
|
|
HAL_GetControlWord(&controlWord);
|
|
|
|
|
return controlWord.autonomous && controlWord.enabled;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-11 23:04:43 -07:00
|
|
|
bool DriverStation::IsTeleop() {
|
2016-07-09 00:24:26 -07:00
|
|
|
HAL_ControlWord controlWord;
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetControlWord(&controlWord);
|
2015-06-25 15:07:55 -04:00
|
|
|
return !(controlWord.autonomous || controlWord.test);
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2021-08-11 23:04:43 -07:00
|
|
|
bool DriverStation::IsTeleopEnabled() {
|
2020-08-29 16:32:19 -04:00
|
|
|
HAL_ControlWord controlWord;
|
|
|
|
|
HAL_GetControlWord(&controlWord);
|
|
|
|
|
return !controlWord.autonomous && !controlWord.test && controlWord.enabled;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsTest() {
|
2016-07-09 00:24:26 -07:00
|
|
|
HAL_ControlWord controlWord;
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetControlWord(&controlWord);
|
2015-06-25 15:07:55 -04:00
|
|
|
return controlWord.test;
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2023-03-10 22:23:57 -05:00
|
|
|
bool DriverStation::IsTestEnabled() {
|
|
|
|
|
HAL_ControlWord controlWord;
|
|
|
|
|
HAL_GetControlWord(&controlWord);
|
|
|
|
|
return controlWord.test && controlWord.enabled;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsDSAttached() {
|
2016-07-09 00:24:26 -07:00
|
|
|
HAL_ControlWord controlWord;
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetControlWord(&controlWord);
|
2015-06-25 15:07:55 -04:00
|
|
|
return controlWord.dsAttached;
|
2014-11-18 10:56:25 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsFMSAttached() {
|
2016-07-14 20:50:38 -07:00
|
|
|
HAL_ControlWord controlWord;
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetControlWord(&controlWord);
|
2016-07-14 20:50:38 -07:00
|
|
|
return controlWord.fmsAttached;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
std::string DriverStation::GetGameSpecificMessage() {
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_MatchInfo info;
|
|
|
|
|
HAL_GetMatchInfo(&info);
|
|
|
|
|
return std::string(reinterpret_cast<char*>(info.gameSpecificMessage),
|
|
|
|
|
info.gameSpecificMessageSize);
|
2017-11-09 19:59:29 -08:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
std::string DriverStation::GetEventName() {
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_MatchInfo info;
|
|
|
|
|
HAL_GetMatchInfo(&info);
|
|
|
|
|
return info.eventName;
|
2017-11-09 19:59:29 -08:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
DriverStation::MatchType DriverStation::GetMatchType() {
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_MatchInfo info;
|
|
|
|
|
HAL_GetMatchInfo(&info);
|
|
|
|
|
return static_cast<DriverStation::MatchType>(info.matchType);
|
2017-11-09 19:59:29 -08:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
int DriverStation::GetMatchNumber() {
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_MatchInfo info;
|
|
|
|
|
HAL_GetMatchInfo(&info);
|
|
|
|
|
return info.matchNumber;
|
2017-11-09 19:59:29 -08:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
int DriverStation::GetReplayNumber() {
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_MatchInfo info;
|
|
|
|
|
HAL_GetMatchInfo(&info);
|
|
|
|
|
return info.replayNumber;
|
2017-11-09 19:59:29 -08:00
|
|
|
}
|
|
|
|
|
|
2023-07-22 15:19:28 -07:00
|
|
|
std::optional<DriverStation::Alliance> DriverStation::GetAlliance() {
|
2016-07-10 16:24:57 -07:00
|
|
|
int32_t status = 0;
|
|
|
|
|
auto allianceStationID = HAL_GetAllianceStation(&status);
|
2015-06-25 15:07:55 -04:00
|
|
|
switch (allianceStationID) {
|
2016-07-09 00:24:26 -07:00
|
|
|
case HAL_AllianceStationID_kRed1:
|
|
|
|
|
case HAL_AllianceStationID_kRed2:
|
|
|
|
|
case HAL_AllianceStationID_kRed3:
|
2015-06-25 15:07:55 -04:00
|
|
|
return kRed;
|
2016-07-09 00:24:26 -07:00
|
|
|
case HAL_AllianceStationID_kBlue1:
|
|
|
|
|
case HAL_AllianceStationID_kBlue2:
|
|
|
|
|
case HAL_AllianceStationID_kBlue3:
|
2015-06-25 15:07:55 -04:00
|
|
|
return kBlue;
|
|
|
|
|
default:
|
2023-07-22 15:19:28 -07:00
|
|
|
return {};
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2023-07-22 15:19:28 -07:00
|
|
|
std::optional<int> DriverStation::GetLocation() {
|
2016-07-10 16:24:57 -07:00
|
|
|
int32_t status = 0;
|
|
|
|
|
auto allianceStationID = HAL_GetAllianceStation(&status);
|
2015-06-25 15:07:55 -04:00
|
|
|
switch (allianceStationID) {
|
2016-07-09 00:24:26 -07:00
|
|
|
case HAL_AllianceStationID_kRed1:
|
|
|
|
|
case HAL_AllianceStationID_kBlue1:
|
2015-06-25 15:07:55 -04:00
|
|
|
return 1;
|
2016-07-09 00:24:26 -07:00
|
|
|
case HAL_AllianceStationID_kRed2:
|
|
|
|
|
case HAL_AllianceStationID_kBlue2:
|
2015-06-25 15:07:55 -04:00
|
|
|
return 2;
|
2016-07-09 00:24:26 -07:00
|
|
|
case HAL_AllianceStationID_kRed3:
|
|
|
|
|
case HAL_AllianceStationID_kBlue3:
|
2015-06-25 15:07:55 -04:00
|
|
|
return 3;
|
|
|
|
|
default:
|
2023-07-22 15:19:28 -07:00
|
|
|
return {};
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2023-08-03 21:45:26 -07:00
|
|
|
units::second_t DriverStation::GetMatchTime() {
|
2021-06-08 21:18:59 -07:00
|
|
|
int32_t status = 0;
|
2023-08-03 21:45:26 -07:00
|
|
|
return units::second_t{HAL_GetMatchTime(&status)};
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
2014-10-20 17:19:28 -04:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
double DriverStation::GetBatteryVoltage() {
|
2016-07-14 20:50:38 -07:00
|
|
|
int32_t status = 0;
|
2016-11-20 07:25:03 -08:00
|
|
|
double voltage = HAL_GetVinVoltage(&status);
|
2022-10-19 10:49:27 -07:00
|
|
|
FRC_CheckErrorStatus(status, "getVinVoltage");
|
2016-07-14 20:50:38 -07:00
|
|
|
|
|
|
|
|
return voltage;
|
2016-02-04 22:29:11 -08:00
|
|
|
}
|
2015-06-25 15:07:55 -04:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
/**
|
|
|
|
|
* Copy data from the DS task for the user.
|
|
|
|
|
*
|
|
|
|
|
* If no new data exists, it will just be returned, otherwise
|
|
|
|
|
* the data will be copied from the DS polling loop.
|
|
|
|
|
*/
|
[hal, wpilib] New DS thread model and implementation (#3787)
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.
2022-10-21 22:01:55 -07:00
|
|
|
void DriverStation::RefreshData() {
|
|
|
|
|
HAL_RefreshDSData();
|
2021-06-15 23:06:03 -07:00
|
|
|
auto& inst = ::GetInstance();
|
2018-01-18 23:17:28 -08:00
|
|
|
{
|
2018-07-18 22:22:41 -07:00
|
|
|
// Compute the pressed and released buttons
|
|
|
|
|
HAL_JoystickButtons currentButtons;
|
2021-06-15 23:06:03 -07:00
|
|
|
std::unique_lock lock(inst.buttonEdgeMutex);
|
2018-01-18 23:17:28 -08:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
for (int32_t i = 0; i < DriverStation::kJoystickPorts; i++) {
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetJoystickButtons(i, ¤tButtons);
|
|
|
|
|
|
2018-01-18 23:17:28 -08:00
|
|
|
// If buttons weren't pressed and are now, set flags in m_buttonsPressed
|
2021-06-15 23:06:03 -07:00
|
|
|
inst.joystickButtonsPressed[i] |=
|
|
|
|
|
~inst.previousButtonStates[i].buttons & currentButtons.buttons;
|
2017-10-27 21:45:56 -07:00
|
|
|
|
2018-01-18 23:17:28 -08:00
|
|
|
// If buttons were pressed and aren't now, set flags in m_buttonsReleased
|
2021-06-15 23:06:03 -07:00
|
|
|
inst.joystickButtonsReleased[i] |=
|
|
|
|
|
inst.previousButtonStates[i].buttons & ~currentButtons.buttons;
|
2018-01-18 23:17:28 -08:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
inst.previousButtonStates[i] = currentButtons;
|
2018-07-18 22:22:41 -07:00
|
|
|
}
|
2017-10-27 21:45:56 -07:00
|
|
|
}
|
|
|
|
|
|
[hal, wpilib] New DS thread model and implementation (#3787)
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.
2022-10-21 22:01:55 -07:00
|
|
|
inst.refreshEvents.Wakeup();
|
|
|
|
|
|
2018-01-18 23:17:28 -08:00
|
|
|
SendMatchData();
|
2022-01-27 00:15:43 -08:00
|
|
|
if (auto sender = inst.dataLogSender.load()) {
|
|
|
|
|
sender->Send(wpi::Now());
|
|
|
|
|
}
|
2016-02-04 22:29:11 -08:00
|
|
|
}
|
|
|
|
|
|
[hal, wpilib] New DS thread model and implementation (#3787)
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.
2022-10-21 22:01:55 -07:00
|
|
|
void DriverStation::ProvideRefreshedDataEventHandle(WPI_EventHandle handle) {
|
|
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
inst.refreshEvents.Add(handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DriverStation::RemoveRefreshedDataEventHandle(WPI_EventHandle handle) {
|
|
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
inst.refreshEvents.Remove(handle);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-14 15:00:56 -05:00
|
|
|
void DriverStation::SilenceJoystickConnectionWarning(bool silence) {
|
2021-06-15 23:06:03 -07:00
|
|
|
::GetInstance().silenceJoystickWarning = silence;
|
2020-11-14 15:00:56 -05:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
bool DriverStation::IsJoystickConnectionWarningSilenced() {
|
|
|
|
|
return !IsFMSAttached() && ::GetInstance().silenceJoystickWarning;
|
2016-07-14 20:50:38 -07:00
|
|
|
}
|
|
|
|
|
|
2022-01-27 00:15:43 -08:00
|
|
|
void DriverStation::StartDataLog(wpi::log::DataLog& log, bool logJoysticks) {
|
|
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
// Note: cannot safely replace, because we wouldn't know when to delete the
|
|
|
|
|
// "old" one. Instead do a compare and exchange with nullptr. We check first
|
|
|
|
|
// with a simple load to avoid the new in the common case.
|
|
|
|
|
if (inst.dataLogSender.load()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
DataLogSender* oldSender = nullptr;
|
|
|
|
|
DataLogSender* newSender = new DataLogSender;
|
|
|
|
|
inst.dataLogSender.compare_exchange_strong(oldSender, newSender);
|
|
|
|
|
if (oldSender) {
|
|
|
|
|
delete newSender; // already had a sender
|
|
|
|
|
} else {
|
|
|
|
|
newSender->Init(log, logJoysticks, wpi::Now());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
void ReportJoystickUnpluggedErrorV(fmt::string_view format,
|
|
|
|
|
fmt::format_args args) {
|
|
|
|
|
auto& inst = GetInstance();
|
2024-11-16 10:43:38 -05:00
|
|
|
auto currentTime = Timer::GetTimestamp();
|
2021-06-15 23:06:03 -07:00
|
|
|
if (currentTime > inst.nextMessageTime) {
|
2021-05-24 23:36:26 -07:00
|
|
|
ReportErrorV(err::Error, "", 0, "", format, args);
|
2021-06-15 23:06:03 -07:00
|
|
|
inst.nextMessageTime = currentTime + kJoystickUnpluggedMessageInterval;
|
2016-07-14 20:50:38 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
void ReportJoystickUnpluggedWarningV(fmt::string_view format,
|
|
|
|
|
fmt::format_args args) {
|
|
|
|
|
auto& inst = GetInstance();
|
|
|
|
|
if (DriverStation::IsFMSAttached() || !inst.silenceJoystickWarning) {
|
2024-11-16 10:43:38 -05:00
|
|
|
auto currentTime = Timer::GetTimestamp();
|
2021-06-15 23:06:03 -07:00
|
|
|
if (currentTime > inst.nextMessageTime) {
|
2021-05-24 23:36:26 -07:00
|
|
|
ReportErrorV(warn::Warning, "", 0, "", format, args);
|
2021-06-15 23:06:03 -07:00
|
|
|
inst.nextMessageTime = currentTime + kJoystickUnpluggedMessageInterval;
|
2020-11-14 15:00:56 -05:00
|
|
|
}
|
2016-07-14 20:50:38 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
void SendMatchData() {
|
2018-05-31 20:47:15 -07:00
|
|
|
int32_t status = 0;
|
|
|
|
|
HAL_AllianceStationID alliance = HAL_GetAllianceStation(&status);
|
|
|
|
|
bool isRedAlliance = false;
|
|
|
|
|
int stationNumber = 1;
|
|
|
|
|
switch (alliance) {
|
|
|
|
|
case HAL_AllianceStationID::HAL_AllianceStationID_kBlue1:
|
|
|
|
|
isRedAlliance = false;
|
|
|
|
|
stationNumber = 1;
|
|
|
|
|
break;
|
|
|
|
|
case HAL_AllianceStationID::HAL_AllianceStationID_kBlue2:
|
|
|
|
|
isRedAlliance = false;
|
|
|
|
|
stationNumber = 2;
|
|
|
|
|
break;
|
|
|
|
|
case HAL_AllianceStationID::HAL_AllianceStationID_kBlue3:
|
|
|
|
|
isRedAlliance = false;
|
|
|
|
|
stationNumber = 3;
|
|
|
|
|
break;
|
|
|
|
|
case HAL_AllianceStationID::HAL_AllianceStationID_kRed1:
|
|
|
|
|
isRedAlliance = true;
|
|
|
|
|
stationNumber = 1;
|
|
|
|
|
break;
|
|
|
|
|
case HAL_AllianceStationID::HAL_AllianceStationID_kRed2:
|
|
|
|
|
isRedAlliance = true;
|
|
|
|
|
stationNumber = 2;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
isRedAlliance = true;
|
|
|
|
|
stationNumber = 3;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_MatchInfo tmpDataStore;
|
|
|
|
|
HAL_GetMatchInfo(&tmpDataStore);
|
2018-05-31 20:47:15 -07:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
auto& inst = GetInstance();
|
|
|
|
|
inst.matchDataSender.alliance.Set(isRedAlliance);
|
|
|
|
|
inst.matchDataSender.station.Set(stationNumber);
|
|
|
|
|
inst.matchDataSender.eventName.Set(tmpDataStore.eventName);
|
|
|
|
|
inst.matchDataSender.gameSpecificMessage.Set(
|
2018-07-18 22:22:41 -07:00
|
|
|
std::string(reinterpret_cast<char*>(tmpDataStore.gameSpecificMessage),
|
|
|
|
|
tmpDataStore.gameSpecificMessageSize));
|
2021-06-15 23:06:03 -07:00
|
|
|
inst.matchDataSender.matchNumber.Set(tmpDataStore.matchNumber);
|
|
|
|
|
inst.matchDataSender.replayNumber.Set(tmpDataStore.replayNumber);
|
|
|
|
|
inst.matchDataSender.matchType.Set(static_cast<int>(tmpDataStore.matchType));
|
2018-05-31 20:47:15 -07:00
|
|
|
|
|
|
|
|
HAL_ControlWord ctlWord;
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_GetControlWord(&ctlWord);
|
2018-05-31 20:47:15 -07:00
|
|
|
int32_t wordInt = 0;
|
|
|
|
|
std::memcpy(&wordInt, &ctlWord, sizeof(wordInt));
|
2021-06-15 23:06:03 -07:00
|
|
|
inst.matchDataSender.controlWord.Set(wordInt);
|
2018-05-31 20:47:15 -07:00
|
|
|
}
|
2022-01-27 00:15:43 -08:00
|
|
|
|
|
|
|
|
void JoystickLogSender::Init(wpi::log::DataLog& log, unsigned int stick,
|
|
|
|
|
int64_t timestamp) {
|
|
|
|
|
m_stick = stick;
|
|
|
|
|
|
|
|
|
|
m_logButtons = wpi::log::BooleanArrayLogEntry{
|
|
|
|
|
log, fmt::format("DS:joystick{}/buttons", stick), timestamp};
|
|
|
|
|
m_logAxes = wpi::log::FloatArrayLogEntry{
|
|
|
|
|
log, fmt::format("DS:joystick{}/axes", stick), timestamp};
|
|
|
|
|
m_logPOVs = wpi::log::IntegerArrayLogEntry{
|
|
|
|
|
log, fmt::format("DS:joystick{}/povs", stick), timestamp};
|
|
|
|
|
|
|
|
|
|
HAL_GetJoystickButtons(m_stick, &m_prevButtons);
|
|
|
|
|
HAL_GetJoystickAxes(m_stick, &m_prevAxes);
|
|
|
|
|
HAL_GetJoystickPOVs(m_stick, &m_prevPOVs);
|
|
|
|
|
AppendButtons(m_prevButtons, timestamp);
|
|
|
|
|
m_logAxes.Append(
|
2022-10-15 16:33:14 -07:00
|
|
|
std::span<const float>{m_prevAxes.axes,
|
2022-01-27 00:15:43 -08:00
|
|
|
static_cast<size_t>(m_prevAxes.count)},
|
|
|
|
|
timestamp);
|
|
|
|
|
AppendPOVs(m_prevPOVs, timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JoystickLogSender::Send(uint64_t timestamp) {
|
|
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(m_stick, &buttons);
|
|
|
|
|
if (buttons.count != m_prevButtons.count ||
|
|
|
|
|
buttons.buttons != m_prevButtons.buttons) {
|
|
|
|
|
AppendButtons(buttons, timestamp);
|
|
|
|
|
}
|
|
|
|
|
m_prevButtons = buttons;
|
|
|
|
|
|
|
|
|
|
HAL_JoystickAxes axes;
|
|
|
|
|
HAL_GetJoystickAxes(m_stick, &axes);
|
|
|
|
|
if (axes.count != m_prevAxes.count ||
|
|
|
|
|
std::memcmp(axes.axes, m_prevAxes.axes,
|
|
|
|
|
sizeof(axes.axes[0]) * axes.count) != 0) {
|
|
|
|
|
m_logAxes.Append(
|
2022-10-15 16:33:14 -07:00
|
|
|
std::span<const float>{axes.axes, static_cast<size_t>(axes.count)},
|
2022-01-27 00:15:43 -08:00
|
|
|
timestamp);
|
|
|
|
|
}
|
|
|
|
|
m_prevAxes = axes;
|
|
|
|
|
|
|
|
|
|
HAL_JoystickPOVs povs;
|
|
|
|
|
HAL_GetJoystickPOVs(m_stick, &povs);
|
|
|
|
|
if (povs.count != m_prevPOVs.count ||
|
|
|
|
|
std::memcmp(povs.povs, m_prevPOVs.povs,
|
|
|
|
|
sizeof(povs.povs[0]) * povs.count) != 0) {
|
|
|
|
|
AppendPOVs(povs, timestamp);
|
|
|
|
|
}
|
|
|
|
|
m_prevPOVs = povs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JoystickLogSender::AppendButtons(HAL_JoystickButtons buttons,
|
|
|
|
|
uint64_t timestamp) {
|
|
|
|
|
uint8_t buttonsArr[32];
|
|
|
|
|
for (unsigned int i = 0; i < buttons.count; ++i) {
|
|
|
|
|
buttonsArr[i] = (buttons.buttons & (1u << i)) != 0;
|
|
|
|
|
}
|
2022-10-15 16:33:14 -07:00
|
|
|
m_logButtons.Append(std::span<const uint8_t>{buttonsArr, buttons.count},
|
2022-01-27 00:15:43 -08:00
|
|
|
timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JoystickLogSender::AppendPOVs(const HAL_JoystickPOVs& povs,
|
|
|
|
|
uint64_t timestamp) {
|
|
|
|
|
int64_t povsArr[HAL_kMaxJoystickPOVs];
|
|
|
|
|
for (int i = 0; i < povs.count; ++i) {
|
|
|
|
|
povsArr[i] = povs.povs[i];
|
|
|
|
|
}
|
|
|
|
|
m_logPOVs.Append(
|
2022-10-15 16:33:14 -07:00
|
|
|
std::span<const int64_t>{povsArr, static_cast<size_t>(povs.count)},
|
2022-01-27 00:15:43 -08:00
|
|
|
timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLogSender::Init(wpi::log::DataLog& log, bool logJoysticks,
|
|
|
|
|
int64_t timestamp) {
|
|
|
|
|
m_logEnabled = wpi::log::BooleanLogEntry{log, "DS:enabled", timestamp};
|
|
|
|
|
m_logAutonomous = wpi::log::BooleanLogEntry{log, "DS:autonomous", timestamp};
|
|
|
|
|
m_logTest = wpi::log::BooleanLogEntry{log, "DS:test", timestamp};
|
|
|
|
|
m_logEstop = wpi::log::BooleanLogEntry{log, "DS:estop", timestamp};
|
|
|
|
|
|
|
|
|
|
// append initial control word values
|
|
|
|
|
HAL_GetControlWord(&m_prevControlWord);
|
|
|
|
|
m_logEnabled.Append(m_prevControlWord.enabled, timestamp);
|
|
|
|
|
m_logAutonomous.Append(m_prevControlWord.autonomous, timestamp);
|
|
|
|
|
m_logTest.Append(m_prevControlWord.test, timestamp);
|
|
|
|
|
m_logEstop.Append(m_prevControlWord.eStop, timestamp);
|
|
|
|
|
|
|
|
|
|
m_logJoysticks = logJoysticks;
|
|
|
|
|
if (logJoysticks) {
|
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
for (auto&& joystick : m_joysticks) {
|
|
|
|
|
joystick.Init(log, i++, timestamp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_initialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLogSender::Send(uint64_t timestamp) {
|
|
|
|
|
if (!m_initialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// append control word value changes
|
|
|
|
|
HAL_ControlWord ctlWord;
|
|
|
|
|
HAL_GetControlWord(&ctlWord);
|
|
|
|
|
if (ctlWord.enabled != m_prevControlWord.enabled) {
|
|
|
|
|
m_logEnabled.Append(ctlWord.enabled, timestamp);
|
|
|
|
|
}
|
|
|
|
|
if (ctlWord.autonomous != m_prevControlWord.autonomous) {
|
|
|
|
|
m_logAutonomous.Append(ctlWord.autonomous, timestamp);
|
|
|
|
|
}
|
|
|
|
|
if (ctlWord.test != m_prevControlWord.test) {
|
|
|
|
|
m_logTest.Append(ctlWord.test, timestamp);
|
|
|
|
|
}
|
|
|
|
|
if (ctlWord.eStop != m_prevControlWord.eStop) {
|
|
|
|
|
m_logEstop.Append(ctlWord.eStop, timestamp);
|
|
|
|
|
}
|
|
|
|
|
m_prevControlWord = ctlWord;
|
|
|
|
|
|
|
|
|
|
if (m_logJoysticks) {
|
|
|
|
|
// append joystick value changes
|
|
|
|
|
for (auto&& joystick : m_joysticks) {
|
|
|
|
|
joystick.Send(timestamp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|