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
|
|
|
|
2025-11-07 19:56:21 -05:00
|
|
|
#include "wpi/driverstation/DriverStation.hpp"
|
2016-09-14 20:52:06 -07:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <atomic>
|
2025-12-12 21:25:57 -07:00
|
|
|
#include <functional>
|
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>
|
2025-12-12 21:25:57 -07:00
|
|
|
#include <vector>
|
2016-10-09 11:46:01 -07:00
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
#include <fmt/format.h>
|
2025-11-07 19:57:55 -05:00
|
|
|
|
|
|
|
|
#include "wpi/datalog/DataLog.hpp"
|
2025-11-11 18:05:12 -08:00
|
|
|
#include "wpi/hal/DriverStation.h"
|
2025-11-07 19:56:21 -05:00
|
|
|
#include "wpi/hal/DriverStationTypes.h"
|
2026-03-13 17:19:39 -07:00
|
|
|
#include "wpi/hal/HAL.h"
|
2025-11-07 19:56:21 -05:00
|
|
|
#include "wpi/hal/Power.h"
|
|
|
|
|
#include "wpi/nt/BooleanTopic.hpp"
|
|
|
|
|
#include "wpi/nt/IntegerTopic.hpp"
|
|
|
|
|
#include "wpi/nt/NetworkTable.hpp"
|
|
|
|
|
#include "wpi/nt/NetworkTableInstance.hpp"
|
|
|
|
|
#include "wpi/nt/StringTopic.hpp"
|
2025-12-12 21:25:57 -07:00
|
|
|
#include "wpi/nt/StructTopic.hpp"
|
2025-11-07 19:57:55 -05:00
|
|
|
#include "wpi/system/Errors.hpp"
|
|
|
|
|
#include "wpi/system/Timer.hpp"
|
2025-12-12 21:25:57 -07:00
|
|
|
#include "wpi/util/Color.hpp"
|
|
|
|
|
#include "wpi/util/DenseMap.hpp"
|
2025-11-07 19:56:21 -05:00
|
|
|
#include "wpi/util/EventVector.hpp"
|
2025-12-12 21:25:57 -07:00
|
|
|
#include "wpi/util/StringExtras.hpp"
|
2025-11-07 19:56:21 -05:00
|
|
|
#include "wpi/util/json.hpp"
|
|
|
|
|
#include "wpi/util/mutex.hpp"
|
2026-01-04 10:22:33 -08:00
|
|
|
#include "wpi/util/string.hpp"
|
|
|
|
|
#include "wpi/util/timestamp.hpp"
|
2025-11-07 19:56:21 -05:00
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
using namespace wpi;
|
2021-06-15 23:06:03 -07:00
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
static constexpr int availableToCount(uint64_t available) {
|
|
|
|
|
return 64 - std::countl_zero(available);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
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:
|
2025-11-07 20:01:58 -05:00
|
|
|
MatchDataSenderEntry(
|
|
|
|
|
const std::shared_ptr<wpi::nt::NetworkTable>& table, std::string_view key,
|
|
|
|
|
typename Topic::ParamType initialVal,
|
|
|
|
|
wpi::util::json topicProperties = wpi::util::json::object())
|
2026-03-15 20:44:45 -07:00
|
|
|
: publisher{Topic{table->GetTopic(key)}.PublishEx(Topic::TYPE_STRING,
|
2024-07-02 16:31:50 -04:00
|
|
|
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 {
|
2025-12-12 21:25:57 -07:00
|
|
|
MatchDataSender()
|
|
|
|
|
: controlWord{table->GetStructTopic<wpi::hal::ControlWord>("ControlWord")
|
|
|
|
|
.Publish()},
|
|
|
|
|
opMode{table->GetStringTopic("OpMode").Publish()} {
|
|
|
|
|
controlWord.Set(prevControlWord);
|
|
|
|
|
opMode.Set("");
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
std::shared_ptr<wpi::nt::NetworkTable> table =
|
|
|
|
|
wpi::nt::NetworkTableInstance::GetDefault().GetTable("FMSInfo");
|
|
|
|
|
MatchDataSenderEntry<wpi::nt::StringTopic> typeMetaData{
|
2026-03-29 15:38:18 -07:00
|
|
|
table, ".type", kSmartDashboardType,
|
|
|
|
|
wpi::util::json::object("SmartDashboard", kSmartDashboardType)};
|
2026-02-06 21:38:15 -08:00
|
|
|
MatchDataSenderEntry<wpi::nt::StringTopic> gameData{table, "GameData", ""};
|
2025-11-07 20:00:05 -05:00
|
|
|
MatchDataSenderEntry<wpi::nt::StringTopic> eventName{table, "EventName", ""};
|
2025-11-07 20:01:58 -05:00
|
|
|
MatchDataSenderEntry<wpi::nt::IntegerTopic> matchNumber{table, "MatchNumber",
|
|
|
|
|
0};
|
|
|
|
|
MatchDataSenderEntry<wpi::nt::IntegerTopic> replayNumber{table,
|
|
|
|
|
"ReplayNumber", 0};
|
2025-11-07 20:00:05 -05:00
|
|
|
MatchDataSenderEntry<wpi::nt::IntegerTopic> matchType{table, "MatchType", 0};
|
2025-11-07 20:01:58 -05:00
|
|
|
MatchDataSenderEntry<wpi::nt::BooleanTopic> alliance{table, "IsRedAlliance",
|
|
|
|
|
true};
|
|
|
|
|
MatchDataSenderEntry<wpi::nt::IntegerTopic> station{table, "StationNumber",
|
|
|
|
|
1};
|
2025-12-12 21:25:57 -07:00
|
|
|
wpi::nt::StructPublisher<wpi::hal::ControlWord> controlWord;
|
|
|
|
|
wpi::nt::StringPublisher opMode;
|
|
|
|
|
wpi::hal::ControlWord prevControlWord;
|
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};
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
hal::ControlWord m_prevControlWord;
|
|
|
|
|
wpi::log::StructLogEntry<hal::ControlWord> m_logControlWord;
|
|
|
|
|
wpi::log::StringLogEntry m_logOpMode;
|
2022-01-27 00:15:43 -08:00
|
|
|
|
|
|
|
|
bool m_logJoysticks;
|
2026-03-13 01:04:29 -07:00
|
|
|
std::array<JoystickLogSender, DriverStation::JOYSTICK_PORTS> m_joysticks;
|
2022-01-27 00:15:43 -08:00
|
|
|
};
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
struct Instance {
|
|
|
|
|
Instance();
|
|
|
|
|
~Instance();
|
|
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::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
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::mutex buttonEdgeMutex;
|
2026-03-13 01:04:29 -07:00
|
|
|
std::array<HAL_JoystickButtons, DriverStation::JOYSTICK_PORTS>
|
2021-06-15 23:06:03 -07:00
|
|
|
previousButtonStates;
|
2026-03-13 01:04:29 -07:00
|
|
|
std::array<uint32_t, DriverStation::JOYSTICK_PORTS> joystickButtonsPressed;
|
|
|
|
|
std::array<uint32_t, DriverStation::JOYSTICK_PORTS> joystickButtonsReleased;
|
2021-06-15 23:06:03 -07:00
|
|
|
|
|
|
|
|
bool silenceJoystickWarning = false;
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
// Op mode lookup
|
|
|
|
|
wpi::util::mutex opModeMutex;
|
|
|
|
|
wpi::util::DenseMap<int64_t, HAL_OpModeOption> opModes;
|
[cmd3] Enforce command lifetimes across all opmode and command scopes (#8705)
Commands are no longer able to outlive their schedule-site's scope,
regardless of how they were scheduled (set as a default command, bound
to a trigger, or manually scheduled)
As a consequence, default commands need better tracking so the default
command setting can be released when their scope exits and the next-most
appropriate default command can be rescheduled (eg, an opmode sets a
default command, then the globally-scoped default is restored when the
opmode exits). Some complexity is required here to make it work well for
edge cases.
Like `schedule()`, `setDefaultCommand()` will immediately start the new
default command if called inside of another command to avoid 1-loop
delays. However, this does not apply when called by the _current_
default command, as it would result in attempting to cancel the default
command while it's mounted (which is impossible and would throw an
exception)
```java
class Robot extends OpModeRobot {
final Drive drive = new Drive();
final CommandXboxController controller = new CommandXboxController(1);
public Robot() {
// global default command, active unless overridden in an opmode or command
drive.setDefaultCommand(drive.stop());
// global trigger binding, always active
controller.rightBumper().onTrue(drive.setX());
}
}
@Teleop
class ExampleOpMode extends PeriodicOpMode {
public ExampleOpMode(Robot robot) {
// opmode-specific default command
robot.drive.setDefaultCommand(robot.drive.operatorControl(robot.controller));
// opmode-specific binding
robot.controller.leftBumper().whileTrue(robot.drive.stop());
// opmode-specific binding that takes precedence over the global binding
// because it happens last; it "wins out" over the `setX()` binding
robot.controller.rightBumper().onTrue(robot.drive.selfTest());
}
@Override
public void periodic() {
Scheduler.getDefault().run();
}
}
```
2026-04-09 20:05:42 -04:00
|
|
|
bool userProgramStarted = false;
|
2021-06-15 23:06:03 -07:00
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::second_t nextMessageTime = 0_s;
|
2025-12-12 21:25:57 -07:00
|
|
|
|
|
|
|
|
std::string OpModeToString(int64_t id) {
|
|
|
|
|
std::scoped_lock lock{opModeMutex};
|
|
|
|
|
if (id == 0) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
auto it = opModes.find(id);
|
|
|
|
|
if (it != opModes.end()) {
|
|
|
|
|
return std::string{wpi::util::to_string_view(&it->second.name)};
|
|
|
|
|
}
|
|
|
|
|
return fmt::format("<{}>", id);
|
|
|
|
|
}
|
2021-06-15 23:06:03 -07:00
|
|
|
};
|
|
|
|
|
} // 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();
|
|
|
|
|
|
|
|
|
|
template <typename S, typename... Args>
|
2026-02-17 23:22:02 -05:00
|
|
|
static inline void ReportJoystickError(int stick, const S& format,
|
|
|
|
|
Args&&... args) {
|
|
|
|
|
ReportJoystickErrorV(stick, format, fmt::make_format_args(args...));
|
2021-06-15 23:06:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-17 23:22:02 -05:00
|
|
|
* Reports errors related to joystick availability.
|
2021-06-15 23:06:03 -07:00
|
|
|
*
|
|
|
|
|
* Throttles the errors so that they don't overwhelm the DS.
|
|
|
|
|
*/
|
2026-02-17 23:22:02 -05:00
|
|
|
static void ReportJoystickWarningV(int stick, fmt::string_view format,
|
|
|
|
|
fmt::format_args args);
|
2021-06-15 23:06:03 -07:00
|
|
|
|
|
|
|
|
template <typename S, typename... Args>
|
2026-02-17 23:22:02 -05:00
|
|
|
static inline void ReportJoystickWarning(int stick, const S& format,
|
|
|
|
|
Args&&... args) {
|
|
|
|
|
ReportJoystickWarningV(stick, 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.
|
2026-03-13 01:04:29 -07:00
|
|
|
for (unsigned int i = 0; i < DriverStation::JOYSTICK_PORTS; i++) {
|
2021-06-15 23:06:03 -07:00
|
|
|
joystickButtonsPressed[i] = 0;
|
|
|
|
|
joystickButtonsReleased[i] = 0;
|
2025-10-25 23:03:50 -07:00
|
|
|
previousButtonStates[i].available = 0;
|
2021-06-15 23:06:03 -07:00
|
|
|
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) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-10-25 23:03:50 -07:00
|
|
|
if (button < 0 || button >= 64) {
|
2025-11-07 20:01:58 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "button {} out of range",
|
|
|
|
|
button);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-05-16 00:13:52 -07:00
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
uint64_t mask = 1LLU << button;
|
|
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
if ((buttons.available & mask) == 0) {
|
2026-02-17 23:22:02 -05:00
|
|
|
ReportJoystickWarning(stick, "Joystick Button {} on port {} not available",
|
|
|
|
|
button, stick);
|
2025-10-25 23:03:50 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (buttons.buttons & mask) != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<bool> DriverStation::GetStickButtonIfAvailable(int stick,
|
|
|
|
|
int button) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2025-10-25 23:03:50 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (button < 0 || button >= 64) {
|
2025-11-07 20:01:58 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "button {} out of range",
|
|
|
|
|
button);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
uint64_t mask = 1LLU << button;
|
|
|
|
|
|
|
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
|
|
|
|
|
|
if ((buttons.available & mask) == 0) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (buttons.buttons & mask) != 0;
|
2017-10-27 21:45:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DriverStation::GetStickButtonPressed(int stick, int button) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-10-25 23:03:50 -07:00
|
|
|
if (button < 0 || button >= 64) {
|
2025-11-07 20:01:58 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "button {} out of range",
|
|
|
|
|
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);
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
uint64_t mask = 1LLU << button;
|
|
|
|
|
|
|
|
|
|
if ((buttons.available & mask) == 0) {
|
2026-02-17 23:22:02 -05:00
|
|
|
ReportJoystickWarning(stick, "Joystick Button {} on port {} not available",
|
|
|
|
|
button, stick);
|
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
|
2025-10-25 23:03:50 -07:00
|
|
|
if (inst.joystickButtonsPressed[stick] & mask) {
|
|
|
|
|
inst.joystickButtonsPressed[stick] &= ~mask;
|
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) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2017-10-27 21:45:56 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-10-25 23:03:50 -07:00
|
|
|
if (button < 0 || button >= 64) {
|
2025-11-07 20:01:58 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "button {} out of range",
|
|
|
|
|
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);
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
uint64_t mask = 1LLU << button;
|
|
|
|
|
|
|
|
|
|
if ((buttons.available & mask) == 0) {
|
2026-02-17 23:22:02 -05:00
|
|
|
ReportJoystickWarning(stick, "Joystick Button {} on port {} not available",
|
|
|
|
|
button, stick);
|
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
|
2025-10-25 23:03:50 -07:00
|
|
|
if (inst.joystickButtonsReleased[stick] & mask) {
|
|
|
|
|
inst.joystickButtonsReleased[stick] &= ~mask;
|
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) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2018-05-16 00:13:52 -07:00
|
|
|
return 0.0;
|
|
|
|
|
}
|
2026-03-13 01:04:29 -07:00
|
|
|
if (axis < 0 || axis >= HAL_MAX_JOYSTICK_AXES) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
uint16_t mask = 1 << axis;
|
|
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickAxes axes;
|
|
|
|
|
HAL_GetJoystickAxes(stick, &axes);
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
if ((axes.available & mask) == 0) {
|
2026-02-17 23:22:02 -05:00
|
|
|
ReportJoystickWarning(stick, "Joystick axis {} on port {} not available",
|
|
|
|
|
axis, stick);
|
2025-10-25 23:03:50 -07:00
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return axes.axes[axis];
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-21 13:57:11 -08:00
|
|
|
DriverStation::TouchpadFinger DriverStation::GetStickTouchpadFinger(
|
|
|
|
|
int stick, int touchpad, int finger) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-21 13:57:11 -08:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
|
|
|
return TouchpadFinger{false, 0.0f, 0.0f};
|
|
|
|
|
}
|
2026-03-13 01:04:29 -07:00
|
|
|
if (touchpad < 0 || touchpad >= HAL_MAX_JOYSTICK_TOUCHPADS) {
|
2025-11-21 13:57:11 -08:00
|
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "touchpad {} out of range",
|
|
|
|
|
touchpad);
|
|
|
|
|
return TouchpadFinger{false, 0.0f, 0.0f};
|
|
|
|
|
}
|
2026-03-13 01:04:29 -07:00
|
|
|
if (finger < 0 || finger >= HAL_MAX_JOYSTICK_TOUCHPAD_FINGERS) {
|
2025-11-21 13:57:11 -08:00
|
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "finger {} out of range", finger);
|
|
|
|
|
return TouchpadFinger{false, 0.0f, 0.0f};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HAL_JoystickTouchpads touchpads;
|
|
|
|
|
HAL_GetJoystickTouchpads(stick, &touchpads);
|
|
|
|
|
|
|
|
|
|
auto touchpadCount = touchpads.count;
|
|
|
|
|
if (touchpad < touchpadCount) {
|
|
|
|
|
if (finger < touchpads.touchpads[touchpad].count) {
|
|
|
|
|
return TouchpadFinger{
|
|
|
|
|
touchpads.touchpads[touchpad].fingers[finger].down != 0,
|
|
|
|
|
touchpads.touchpads[touchpad].fingers[finger].x,
|
|
|
|
|
touchpads.touchpads[touchpad].fingers[finger].y};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 23:22:02 -05:00
|
|
|
ReportJoystickWarning(
|
|
|
|
|
stick,
|
|
|
|
|
"Joystick touchpad finger {} on touchpad {} on port {} not available",
|
|
|
|
|
finger, touchpad, stick);
|
2025-11-21 13:57:11 -08:00
|
|
|
return TouchpadFinger{false, 0.0f, 0.0f};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DriverStation::GetStickTouchpadFingerAvailable(int stick, int touchpad,
|
|
|
|
|
int finger) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-21 13:57:11 -08:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-13 01:04:29 -07:00
|
|
|
if (touchpad < 0 || touchpad >= HAL_MAX_JOYSTICK_TOUCHPADS) {
|
2025-11-21 13:57:11 -08:00
|
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "touchpad {} out of range",
|
|
|
|
|
touchpad);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-13 01:04:29 -07:00
|
|
|
if (finger < 0 || finger >= HAL_MAX_JOYSTICK_TOUCHPAD_FINGERS) {
|
2025-11-21 13:57:11 -08:00
|
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "finger {} out of range", finger);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HAL_JoystickTouchpads touchpads;
|
|
|
|
|
HAL_GetJoystickTouchpads(stick, &touchpads);
|
|
|
|
|
|
|
|
|
|
auto touchpadCount = touchpads.count;
|
|
|
|
|
if (touchpad < touchpadCount) {
|
|
|
|
|
if (finger < touchpads.touchpads[touchpad].count) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
std::optional<double> DriverStation::GetStickAxisIfAvailable(int stick,
|
|
|
|
|
int axis) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2025-10-25 23:03:50 -07:00
|
|
|
return 0.0;
|
|
|
|
|
}
|
2026-03-13 01:04:29 -07:00
|
|
|
if (axis < 0 || axis >= HAL_MAX_JOYSTICK_AXES) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "axis {} out of range", axis);
|
2016-11-20 07:25:03 -08:00
|
|
|
return 0.0;
|
2016-07-14 20:50:38 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
uint16_t mask = 1 << axis;
|
|
|
|
|
|
|
|
|
|
HAL_JoystickAxes axes;
|
|
|
|
|
HAL_GetJoystickAxes(stick, &axes);
|
|
|
|
|
|
|
|
|
|
if ((axes.available & mask) == 0) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
2026-03-13 01:04:29 -07:00
|
|
|
return POVDirection::CENTER;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2026-03-13 01:04:29 -07:00
|
|
|
if (pov < 0 || pov >= HAL_MAX_JOYSTICK_POVS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "POV {} out of range", pov);
|
2026-03-13 01:04:29 -07:00
|
|
|
return POVDirection::CENTER;
|
2018-05-16 00:13:52 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
uint16_t mask = 1 << pov;
|
|
|
|
|
|
2018-07-18 22:22:41 -07:00
|
|
|
HAL_JoystickPOVs povs;
|
|
|
|
|
HAL_GetJoystickPOVs(stick, &povs);
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
if ((povs.available & mask) == 0) {
|
2026-02-17 23:22:02 -05:00
|
|
|
ReportJoystickWarning(stick, "Joystick POV {} on port {} not available",
|
|
|
|
|
pov, stick);
|
2026-03-13 01:04:29 -07:00
|
|
|
return POVDirection::CENTER;
|
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
|
|
|
}
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
uint64_t DriverStation::GetStickButtons(int stick) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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
|
|
|
}
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
int DriverStation::GetStickAxesMaximumIndex(int stick) {
|
|
|
|
|
return availableToCount(GetStickAxesAvailable(stick));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int DriverStation::GetStickAxesAvailable(int stick) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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);
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
return axes.available;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int DriverStation::GetStickPOVsMaximumIndex(int stick) {
|
|
|
|
|
return availableToCount(GetStickPOVsAvailable(stick));
|
2015-06-15 12:34:57 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
int DriverStation::GetStickPOVsAvailable(int stick) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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);
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
return povs.available;
|
2014-12-05 20:13:23 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
int DriverStation::GetStickButtonsMaximumIndex(int stick) {
|
|
|
|
|
return availableToCount(GetStickButtonsAvailable(stick));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint64_t DriverStation::GetStickButtonsAvailable(int stick) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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);
|
|
|
|
|
|
2025-10-25 23:03:50 -07:00
|
|
|
return buttons.available;
|
2014-12-05 20:13:23 -05:00
|
|
|
}
|
|
|
|
|
|
2025-05-16 22:15:14 -07:00
|
|
|
bool DriverStation::GetJoystickIsGamepad(int stick) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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
|
|
|
}
|
|
|
|
|
|
2025-11-17 14:36:14 -08:00
|
|
|
int DriverStation::GetJoystickGamepadType(int stick) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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);
|
|
|
|
|
|
2025-11-17 14:36:14 -08:00
|
|
|
return static_cast<int>(descriptor.gamepadType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int DriverStation::GetJoystickSupportedOutputs(int stick) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-17 14:36:14 -08:00
|
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
|
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
|
|
|
|
|
|
return static_cast<int>(descriptor.supportedOutputs);
|
2014-10-17 14:46:25 -04:00
|
|
|
}
|
|
|
|
|
|
2021-06-15 23:06:03 -07:00
|
|
|
std::string DriverStation::GetJoystickName(int stick) {
|
2026-03-13 01:04:29 -07:00
|
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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
|
|
|
bool DriverStation::IsJoystickConnected(int stick) {
|
2025-10-25 23:03:50 -07:00
|
|
|
return GetStickAxesAvailable(stick) != 0 ||
|
|
|
|
|
GetStickButtonsAvailable(stick) != 0 ||
|
|
|
|
|
GetStickPOVsAvailable(stick) != 0;
|
2020-11-13 14:11:10 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
static int64_t DoAddOpMode(RobotMode mode, std::string_view name,
|
|
|
|
|
std::string_view group, std::string_view description,
|
|
|
|
|
int32_t textColor, int32_t backgroundColor) {
|
|
|
|
|
if (wpi::util::trim(name).empty()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2013-12-15 18:30:16 -05:00
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
WPI_String nameWpi = wpi::util::make_string(name);
|
|
|
|
|
WPI_String groupWpi = wpi::util::make_string(group);
|
|
|
|
|
WPI_String descriptionWpi = wpi::util::make_string(description);
|
2013-12-15 18:30:16 -05:00
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
std::scoped_lock lock{inst.opModeMutex};
|
|
|
|
|
std::string nameCopy{name};
|
|
|
|
|
for (;;) {
|
|
|
|
|
int64_t id = HAL_MakeOpModeId(static_cast<HAL_RobotMode>(mode),
|
|
|
|
|
std::hash<std::string_view>{}(nameCopy));
|
|
|
|
|
auto [it, isNew] = inst.opModes.try_emplace(
|
|
|
|
|
id, HAL_OpModeOption{id, nameWpi, groupWpi, descriptionWpi, textColor,
|
|
|
|
|
backgroundColor});
|
|
|
|
|
if (isNew) {
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
if (HAL_OpMode_GetRobotMode(it->second.id) ==
|
|
|
|
|
static_cast<HAL_RobotMode>(mode) &&
|
|
|
|
|
wpi::util::to_string_view(&it->second.name) == name) {
|
|
|
|
|
return 0; // can't insert duplicate name
|
|
|
|
|
}
|
|
|
|
|
// collision, try again with space appended
|
|
|
|
|
nameCopy += ' ';
|
|
|
|
|
}
|
2019-07-16 09:18:23 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
static int32_t ConvertColorToInt(const wpi::util::Color& color) {
|
|
|
|
|
return ((static_cast<int32_t>(color.red * 255) & 0xff) << 16) |
|
|
|
|
|
((static_cast<int32_t>(color.green * 255) & 0xff) << 8) |
|
|
|
|
|
(static_cast<int32_t>(color.blue * 255) & 0xff);
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
int64_t DriverStation::AddOpMode(RobotMode mode, std::string_view name,
|
|
|
|
|
std::string_view group,
|
|
|
|
|
std::string_view description,
|
|
|
|
|
const wpi::util::Color& textColor,
|
|
|
|
|
const wpi::util::Color& backgroundColor) {
|
|
|
|
|
return DoAddOpMode(mode, name, group, description,
|
|
|
|
|
ConvertColorToInt(textColor),
|
|
|
|
|
ConvertColorToInt(backgroundColor));
|
2020-08-29 16:32:19 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
int64_t DriverStation::AddOpMode(RobotMode mode, std::string_view name,
|
|
|
|
|
std::string_view group,
|
|
|
|
|
std::string_view description) {
|
|
|
|
|
return DoAddOpMode(mode, name, group, description, -1, -1);
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
int64_t DriverStation::RemoveOpMode(RobotMode mode, std::string_view name) {
|
|
|
|
|
if (wpi::util::trim(name).empty()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2020-08-29 16:32:19 -04:00
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
std::scoped_lock lock{inst.opModeMutex};
|
|
|
|
|
// we have to loop over all entries to find the one with the correct name
|
|
|
|
|
// because the of the unique ID generation scheme
|
|
|
|
|
for (auto it = inst.opModes.begin(), end = inst.opModes.end(); it != end;
|
|
|
|
|
++it) {
|
|
|
|
|
if (HAL_OpMode_GetRobotMode(it->second.id) ==
|
|
|
|
|
static_cast<HAL_RobotMode>(mode) &&
|
|
|
|
|
wpi::util::to_string_view(&it->second.name) == name) {
|
|
|
|
|
int64_t id = it->second.id;
|
|
|
|
|
inst.opModes.erase(it);
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
2013-12-15 18:30:16 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
void DriverStation::PublishOpModes() {
|
|
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
std::scoped_lock lock{inst.opModeMutex};
|
|
|
|
|
std::vector<HAL_OpModeOption> options;
|
|
|
|
|
options.reserve(inst.opModes.size());
|
|
|
|
|
for (auto&& [id, option] : inst.opModes) {
|
|
|
|
|
options.emplace_back(option);
|
|
|
|
|
}
|
|
|
|
|
HAL_SetOpModeOptions(options.data(), options.size());
|
2023-03-10 22:23:57 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
void DriverStation::ClearOpModes() {
|
|
|
|
|
auto& inst = ::GetInstance();
|
|
|
|
|
std::scoped_lock lock{inst.opModeMutex};
|
|
|
|
|
inst.opModes.clear();
|
|
|
|
|
HAL_SetOpModeOptions(nullptr, 0);
|
2014-11-18 10:56:25 -05:00
|
|
|
}
|
|
|
|
|
|
[cmd3] Enforce command lifetimes across all opmode and command scopes (#8705)
Commands are no longer able to outlive their schedule-site's scope,
regardless of how they were scheduled (set as a default command, bound
to a trigger, or manually scheduled)
As a consequence, default commands need better tracking so the default
command setting can be released when their scope exits and the next-most
appropriate default command can be rescheduled (eg, an opmode sets a
default command, then the globally-scoped default is restored when the
opmode exits). Some complexity is required here to make it work well for
edge cases.
Like `schedule()`, `setDefaultCommand()` will immediately start the new
default command if called inside of another command to avoid 1-loop
delays. However, this does not apply when called by the _current_
default command, as it would result in attempting to cancel the default
command while it's mounted (which is impossible and would throw an
exception)
```java
class Robot extends OpModeRobot {
final Drive drive = new Drive();
final CommandXboxController controller = new CommandXboxController(1);
public Robot() {
// global default command, active unless overridden in an opmode or command
drive.setDefaultCommand(drive.stop());
// global trigger binding, always active
controller.rightBumper().onTrue(drive.setX());
}
}
@Teleop
class ExampleOpMode extends PeriodicOpMode {
public ExampleOpMode(Robot robot) {
// opmode-specific default command
robot.drive.setDefaultCommand(robot.drive.operatorControl(robot.controller));
// opmode-specific binding
robot.controller.leftBumper().whileTrue(robot.drive.stop());
// opmode-specific binding that takes precedence over the global binding
// because it happens last; it "wins out" over the `setX()` binding
robot.controller.rightBumper().onTrue(robot.drive.selfTest());
}
@Override
public void periodic() {
Scheduler.getDefault().run();
}
}
```
2026-04-09 20:05:42 -04:00
|
|
|
void DriverStation::ObserveUserProgramStarting() {
|
|
|
|
|
::GetInstance().userProgramStarted = true;
|
|
|
|
|
HAL_ObserveUserProgramStarting();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int64_t DriverStation::GetOpModeId() {
|
|
|
|
|
if (!::GetInstance().userProgramStarted) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GetControlWord().GetOpModeId();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
std::string DriverStation::GetOpMode() {
|
[cmd3] Enforce command lifetimes across all opmode and command scopes (#8705)
Commands are no longer able to outlive their schedule-site's scope,
regardless of how they were scheduled (set as a default command, bound
to a trigger, or manually scheduled)
As a consequence, default commands need better tracking so the default
command setting can be released when their scope exits and the next-most
appropriate default command can be rescheduled (eg, an opmode sets a
default command, then the globally-scoped default is restored when the
opmode exits). Some complexity is required here to make it work well for
edge cases.
Like `schedule()`, `setDefaultCommand()` will immediately start the new
default command if called inside of another command to avoid 1-loop
delays. However, this does not apply when called by the _current_
default command, as it would result in attempting to cancel the default
command while it's mounted (which is impossible and would throw an
exception)
```java
class Robot extends OpModeRobot {
final Drive drive = new Drive();
final CommandXboxController controller = new CommandXboxController(1);
public Robot() {
// global default command, active unless overridden in an opmode or command
drive.setDefaultCommand(drive.stop());
// global trigger binding, always active
controller.rightBumper().onTrue(drive.setX());
}
}
@Teleop
class ExampleOpMode extends PeriodicOpMode {
public ExampleOpMode(Robot robot) {
// opmode-specific default command
robot.drive.setDefaultCommand(robot.drive.operatorControl(robot.controller));
// opmode-specific binding
robot.controller.leftBumper().whileTrue(robot.drive.stop());
// opmode-specific binding that takes precedence over the global binding
// because it happens last; it "wins out" over the `setX()` binding
robot.controller.rightBumper().onTrue(robot.drive.selfTest());
}
@Override
public void periodic() {
Scheduler.getDefault().run();
}
}
```
2026-04-09 20:05:42 -04:00
|
|
|
if (!::GetInstance().userProgramStarted) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
return GetInstance().OpModeToString(GetOpModeId());
|
2016-07-14 20:50:38 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-06 21:38:15 -08:00
|
|
|
std::optional<std::string> DriverStation::GetGameData() {
|
|
|
|
|
HAL_GameData info;
|
|
|
|
|
HAL_GetGameData(&info);
|
|
|
|
|
std::string_view gameDataView{reinterpret_cast<char*>(info.gameData)};
|
|
|
|
|
if (gameDataView.empty()) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
return std::string(gameDataView);
|
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) {
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_RED_1:
|
|
|
|
|
case HAL_ALLIANCE_STATION_RED_2:
|
|
|
|
|
case HAL_ALLIANCE_STATION_RED_3:
|
|
|
|
|
return Alliance::RED;
|
|
|
|
|
case HAL_ALLIANCE_STATION_BLUE_1:
|
|
|
|
|
case HAL_ALLIANCE_STATION_BLUE_2:
|
|
|
|
|
case HAL_ALLIANCE_STATION_BLUE_3:
|
|
|
|
|
return Alliance::BLUE;
|
2015-06-25 15:07:55 -04:00
|
|
|
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) {
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_RED_1:
|
|
|
|
|
case HAL_ALLIANCE_STATION_BLUE_1:
|
2015-06-25 15:07:55 -04:00
|
|
|
return 1;
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_RED_2:
|
|
|
|
|
case HAL_ALLIANCE_STATION_BLUE_2:
|
2015-06-25 15:07:55 -04:00
|
|
|
return 2;
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_RED_3:
|
|
|
|
|
case HAL_ALLIANCE_STATION_BLUE_3:
|
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
|
|
|
}
|
|
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::units::second_t DriverStation::GetMatchTime() {
|
2021-06-08 21:18:59 -07:00
|
|
|
int32_t status = 0;
|
2025-11-07 20:00:05 -05:00
|
|
|
return wpi::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);
|
2025-11-07 20:00:43 -05:00
|
|
|
WPILIB_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
|
|
|
|
2026-03-13 01:04:29 -07:00
|
|
|
for (int32_t i = 0; i < DriverStation::JOYSTICK_PORTS; 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()) {
|
2025-11-07 20:00:05 -05:00
|
|
|
sender->Send(wpi::util::Now());
|
2022-01-27 00:15:43 -08:00
|
|
|
}
|
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 {
|
2025-11-07 20:00:05 -05:00
|
|
|
newSender->Init(log, logJoysticks, wpi::util::Now());
|
2022-01-27 00:15:43 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 23:22:02 -05:00
|
|
|
void ReportJoystickWarningV(int stick, fmt::string_view format,
|
|
|
|
|
fmt::format_args args) {
|
2021-06-15 23:06:03 -07:00
|
|
|
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) {
|
2026-02-17 23:22:02 -05:00
|
|
|
if (DriverStation::IsJoystickConnected(stick)) {
|
|
|
|
|
ReportErrorV(warn::Warning, "", 0, "", format, args);
|
|
|
|
|
} else {
|
|
|
|
|
ReportError(
|
|
|
|
|
warn::Warning, "", 0, "",
|
|
|
|
|
"Joystick on port {} not available, check if all controllers are "
|
|
|
|
|
"plugged in",
|
|
|
|
|
stick);
|
|
|
|
|
}
|
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) {
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_BLUE_1:
|
2018-05-31 20:47:15 -07:00
|
|
|
isRedAlliance = false;
|
|
|
|
|
stationNumber = 1;
|
|
|
|
|
break;
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_BLUE_2:
|
2018-05-31 20:47:15 -07:00
|
|
|
isRedAlliance = false;
|
|
|
|
|
stationNumber = 2;
|
|
|
|
|
break;
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_BLUE_3:
|
2018-05-31 20:47:15 -07:00
|
|
|
isRedAlliance = false;
|
|
|
|
|
stationNumber = 3;
|
|
|
|
|
break;
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_RED_1:
|
2018-05-31 20:47:15 -07:00
|
|
|
isRedAlliance = true;
|
|
|
|
|
stationNumber = 1;
|
|
|
|
|
break;
|
2026-03-13 01:04:29 -07:00
|
|
|
case HAL_ALLIANCE_STATION_RED_2:
|
2018-05-31 20:47:15 -07:00
|
|
|
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
|
|
|
|
2026-02-06 21:38:15 -08:00
|
|
|
HAL_GameData tmpGameData;
|
|
|
|
|
HAL_GetGameData(&tmpGameData);
|
|
|
|
|
|
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);
|
2026-02-06 21:38:15 -08:00
|
|
|
inst.matchDataSender.gameData.Set(
|
|
|
|
|
std::string(reinterpret_cast<char*>(tmpGameData.gameData)));
|
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
|
|
|
|
2025-12-12 21:25:57 -07:00
|
|
|
hal::ControlWord ctlWord = hal::GetControlWord();
|
|
|
|
|
if (ctlWord != inst.matchDataSender.prevControlWord) {
|
|
|
|
|
int64_t opModeId = ctlWord.GetOpModeId();
|
|
|
|
|
if (opModeId != inst.matchDataSender.prevControlWord.GetOpModeId()) {
|
|
|
|
|
inst.matchDataSender.opMode.Set(inst.OpModeToString(opModeId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inst.matchDataSender.prevControlWord = ctlWord;
|
|
|
|
|
inst.matchDataSender.controlWord.Set(ctlWord);
|
|
|
|
|
}
|
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);
|
2025-10-25 23:03:50 -07:00
|
|
|
int axesCount = availableToCount(m_prevAxes.available);
|
2022-01-27 00:15:43 -08:00
|
|
|
m_logAxes.Append(
|
2025-10-25 23:03:50 -07:00
|
|
|
std::span<const float>{m_prevAxes.axes, static_cast<size_t>(axesCount)},
|
2022-01-27 00:15:43 -08:00
|
|
|
timestamp);
|
|
|
|
|
AppendPOVs(m_prevPOVs, timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JoystickLogSender::Send(uint64_t timestamp) {
|
|
|
|
|
HAL_JoystickButtons buttons;
|
|
|
|
|
HAL_GetJoystickButtons(m_stick, &buttons);
|
2025-10-25 23:03:50 -07:00
|
|
|
if (buttons.available != m_prevButtons.available ||
|
2022-01-27 00:15:43 -08:00
|
|
|
buttons.buttons != m_prevButtons.buttons) {
|
|
|
|
|
AppendButtons(buttons, timestamp);
|
|
|
|
|
}
|
|
|
|
|
m_prevButtons = buttons;
|
|
|
|
|
|
|
|
|
|
HAL_JoystickAxes axes;
|
|
|
|
|
HAL_GetJoystickAxes(m_stick, &axes);
|
2025-10-25 23:03:50 -07:00
|
|
|
int axesCount = availableToCount(axes.available);
|
|
|
|
|
if (axes.available != m_prevAxes.available ||
|
2022-01-27 00:15:43 -08:00
|
|
|
std::memcmp(axes.axes, m_prevAxes.axes,
|
2025-10-25 23:03:50 -07:00
|
|
|
sizeof(axes.axes[0]) * axesCount) != 0) {
|
2022-01-27 00:15:43 -08:00
|
|
|
m_logAxes.Append(
|
2025-10-25 23:03:50 -07:00
|
|
|
std::span<const float>{axes.axes, static_cast<size_t>(axesCount)},
|
2022-01-27 00:15:43 -08:00
|
|
|
timestamp);
|
|
|
|
|
}
|
|
|
|
|
m_prevAxes = axes;
|
|
|
|
|
|
|
|
|
|
HAL_JoystickPOVs povs;
|
|
|
|
|
HAL_GetJoystickPOVs(m_stick, &povs);
|
2025-10-25 23:03:50 -07:00
|
|
|
int povsCount = availableToCount(povs.available);
|
|
|
|
|
if (povs.available != m_prevPOVs.available ||
|
2022-01-27 00:15:43 -08:00
|
|
|
std::memcmp(povs.povs, m_prevPOVs.povs,
|
2025-10-25 23:03:50 -07:00
|
|
|
sizeof(povs.povs[0]) * povsCount) != 0) {
|
2022-01-27 00:15:43 -08:00
|
|
|
AppendPOVs(povs, timestamp);
|
|
|
|
|
}
|
|
|
|
|
m_prevPOVs = povs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JoystickLogSender::AppendButtons(HAL_JoystickButtons buttons,
|
|
|
|
|
uint64_t timestamp) {
|
2025-10-25 23:03:50 -07:00
|
|
|
int count = availableToCount(buttons.available);
|
|
|
|
|
uint8_t buttonsArr[64];
|
|
|
|
|
for (int i = 0; i < count; ++i) {
|
|
|
|
|
buttonsArr[i] = (buttons.buttons & (1llu << i)) != 0;
|
2022-01-27 00:15:43 -08:00
|
|
|
}
|
2025-10-25 23:03:50 -07:00
|
|
|
m_logButtons.Append(
|
|
|
|
|
std::span<const uint8_t>{buttonsArr, static_cast<size_t>(count)},
|
|
|
|
|
timestamp);
|
2022-01-27 00:15:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JoystickLogSender::AppendPOVs(const HAL_JoystickPOVs& povs,
|
|
|
|
|
uint64_t timestamp) {
|
2025-10-25 23:03:50 -07:00
|
|
|
int count = availableToCount(povs.available);
|
2026-03-13 01:04:29 -07:00
|
|
|
int64_t povsArr[HAL_MAX_JOYSTICK_POVS];
|
2025-10-25 23:03:50 -07:00
|
|
|
for (int i = 0; i < count; ++i) {
|
2022-01-27 00:15:43 -08:00
|
|
|
povsArr[i] = povs.povs[i];
|
|
|
|
|
}
|
|
|
|
|
m_logPOVs.Append(
|
2025-10-25 23:03:50 -07:00
|
|
|
std::span<const int64_t>{povsArr, static_cast<size_t>(count)}, timestamp);
|
2022-01-27 00:15:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLogSender::Init(wpi::log::DataLog& log, bool logJoysticks,
|
|
|
|
|
int64_t timestamp) {
|
2025-12-12 21:25:57 -07:00
|
|
|
m_logControlWord = wpi::log::StructLogEntry<hal::ControlWord>{
|
|
|
|
|
log, "DS:controlWord", timestamp};
|
|
|
|
|
m_logOpMode = wpi::log::StringLogEntry{log, "DS:opMode", timestamp};
|
|
|
|
|
|
|
|
|
|
// append initial control word value
|
|
|
|
|
m_prevControlWord = hal::GetControlWord();
|
|
|
|
|
m_logControlWord.Append(m_prevControlWord);
|
|
|
|
|
|
|
|
|
|
// append initial opmode value
|
|
|
|
|
auto& inst = GetInstance();
|
|
|
|
|
m_logOpMode.Append(inst.OpModeToString(m_prevControlWord.GetOpModeId()));
|
2022-01-27 00:15:43 -08:00
|
|
|
|
|
|
|
|
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
|
2025-12-12 21:25:57 -07:00
|
|
|
hal::ControlWord ctlWord = hal::GetControlWord();
|
|
|
|
|
if (ctlWord != m_prevControlWord) {
|
|
|
|
|
// append opmode value changes
|
|
|
|
|
int64_t opModeId = ctlWord.GetOpModeId();
|
|
|
|
|
if (opModeId != m_prevControlWord.GetOpModeId()) {
|
|
|
|
|
auto& inst = GetInstance();
|
|
|
|
|
m_logOpMode.Append(inst.OpModeToString(opModeId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_prevControlWord = ctlWord;
|
|
|
|
|
m_logControlWord.Append(ctlWord);
|
2022-01-27 00:15:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_logJoysticks) {
|
|
|
|
|
// append joystick value changes
|
|
|
|
|
for (auto&& joystick : m_joysticks) {
|
|
|
|
|
joystick.Send(timestamp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|