mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
1076 lines
33 KiB
C++
1076 lines
33 KiB
C++
// Copyright (c) FIRST and other WPILib contributors.
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
// the WPILib BSD license file in the root directory of this project.
|
|
|
|
#include "wpi/driverstation/internal/DriverStationBackend.hpp"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include "wpi/datalog/DataLog.hpp"
|
|
#include "wpi/hal/DriverStation.h"
|
|
#include "wpi/hal/DriverStationTypes.h"
|
|
#include "wpi/hal/HAL.h"
|
|
#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"
|
|
#include "wpi/nt/StructTopic.hpp"
|
|
#include "wpi/system/Errors.hpp"
|
|
#include "wpi/system/Timer.hpp"
|
|
#include "wpi/util/Color.hpp"
|
|
#include "wpi/util/DenseMap.hpp"
|
|
#include "wpi/util/EventVector.hpp"
|
|
#include "wpi/util/StringExtras.hpp"
|
|
#include "wpi/util/json.hpp"
|
|
#include "wpi/util/mutex.hpp"
|
|
#include "wpi/util/string.hpp"
|
|
#include "wpi/util/timestamp.hpp"
|
|
|
|
using namespace wpi;
|
|
using namespace wpi::internal;
|
|
|
|
static constexpr int availableToCount(uint64_t available) {
|
|
return 64 - std::countl_zero(available);
|
|
}
|
|
|
|
namespace {
|
|
// A simple class which caches the previous value written to an NT entry
|
|
// Used to prevent redundant, repeated writes of the same value
|
|
template <typename Topic>
|
|
class MatchDataSenderEntry {
|
|
public:
|
|
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())
|
|
: publisher{Topic{table->GetTopic(key)}.PublishEx(Topic::TYPE_STRING,
|
|
topicProperties)},
|
|
prevVal{initialVal} {
|
|
publisher.Set(initialVal);
|
|
}
|
|
|
|
void Set(typename Topic::ParamType val) {
|
|
if (val != prevVal) {
|
|
publisher.Set(val);
|
|
prevVal = val;
|
|
}
|
|
}
|
|
|
|
private:
|
|
typename Topic::PublisherType publisher;
|
|
typename Topic::ValueType prevVal;
|
|
};
|
|
|
|
static constexpr std::string_view kSmartDashboardType = "FMSInfo";
|
|
|
|
struct MatchDataSender {
|
|
MatchDataSender()
|
|
: controlWord{table->GetStructTopic<wpi::hal::ControlWord>("ControlWord")
|
|
.Publish()},
|
|
opMode{table->GetStringTopic("OpMode").Publish()} {
|
|
controlWord.Set(prevControlWord);
|
|
opMode.Set("");
|
|
}
|
|
|
|
std::shared_ptr<wpi::nt::NetworkTable> table =
|
|
wpi::nt::NetworkTableInstance::GetDefault().GetTable("FMSInfo");
|
|
MatchDataSenderEntry<wpi::nt::StringTopic> typeMetaData{
|
|
table, ".type", kSmartDashboardType,
|
|
wpi::util::json::object("SmartDashboard", kSmartDashboardType)};
|
|
MatchDataSenderEntry<wpi::nt::StringTopic> gameData{table, "GameData", ""};
|
|
MatchDataSenderEntry<wpi::nt::StringTopic> eventName{table, "EventName", ""};
|
|
MatchDataSenderEntry<wpi::nt::IntegerTopic> matchNumber{table, "MatchNumber",
|
|
0};
|
|
MatchDataSenderEntry<wpi::nt::IntegerTopic> replayNumber{table,
|
|
"ReplayNumber", 0};
|
|
MatchDataSenderEntry<wpi::nt::IntegerTopic> matchType{table, "MatchType", 0};
|
|
MatchDataSenderEntry<wpi::nt::BooleanTopic> alliance{table, "IsRedAlliance",
|
|
true};
|
|
MatchDataSenderEntry<wpi::nt::IntegerTopic> station{table, "StationNumber",
|
|
1};
|
|
wpi::nt::StructPublisher<wpi::hal::ControlWord> controlWord;
|
|
wpi::nt::StringPublisher opMode;
|
|
wpi::hal::ControlWord prevControlWord;
|
|
};
|
|
|
|
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::StructLogEntry<hal::ControlWord> m_logControlWord;
|
|
wpi::log::StringLogEntry m_logOpMode;
|
|
|
|
bool m_logJoysticks;
|
|
std::array<JoystickLogSender, DriverStationBackend::JOYSTICK_PORTS>
|
|
m_joysticks;
|
|
};
|
|
|
|
struct Instance {
|
|
Instance();
|
|
~Instance();
|
|
|
|
wpi::util::EventVector refreshEvents;
|
|
MatchDataSender matchDataSender;
|
|
std::atomic<DataLogSender*> dataLogSender{nullptr};
|
|
|
|
// Joystick button rising/falling edge flags
|
|
wpi::util::mutex buttonEdgeMutex;
|
|
std::array<HAL_JoystickButtons, DriverStationBackend::JOYSTICK_PORTS>
|
|
previousButtonStates;
|
|
std::array<uint32_t, DriverStationBackend::JOYSTICK_PORTS>
|
|
joystickButtonsPressed;
|
|
std::array<uint32_t, DriverStationBackend::JOYSTICK_PORTS>
|
|
joystickButtonsReleased;
|
|
|
|
bool silenceJoystickWarning = false;
|
|
|
|
// Op mode lookup
|
|
wpi::util::mutex opModeMutex;
|
|
wpi::util::DenseMap<int64_t, HAL_OpModeOption> opModes;
|
|
bool userProgramStarted = false;
|
|
|
|
wpi::units::second_t nextMessageTime = 0_s;
|
|
|
|
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);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
static constexpr auto kJoystickUnpluggedMessageInterval = 1_s;
|
|
|
|
static Instance& GetInstance() {
|
|
static Instance instance;
|
|
return instance;
|
|
}
|
|
|
|
static void SendMatchData();
|
|
|
|
template <typename S, typename... Args>
|
|
static inline void ReportJoystickError(int stick, const S& format,
|
|
Args&&... args) {
|
|
ReportJoystickErrorV(stick, format, fmt::make_format_args(args...));
|
|
}
|
|
|
|
/**
|
|
* Reports errors related to joystick availability.
|
|
*
|
|
* Throttles the errors so that they don't overwhelm the DS.
|
|
*/
|
|
static void ReportJoystickWarningV(int stick, fmt::string_view format,
|
|
fmt::format_args args);
|
|
|
|
template <typename S, typename... Args>
|
|
static inline void ReportJoystickWarning(int stick, const S& format,
|
|
Args&&... args) {
|
|
ReportJoystickWarningV(stick, format, fmt::make_format_args(args...));
|
|
}
|
|
|
|
Instance::Instance() {
|
|
HAL_Initialize(500, 0);
|
|
|
|
// All joysticks should default to having zero axes, povs and buttons, so
|
|
// uninitialized memory doesn't get sent to motor controllers.
|
|
for (unsigned int i = 0; i < DriverStationBackend::JOYSTICK_PORTS; i++) {
|
|
joystickButtonsPressed[i] = 0;
|
|
joystickButtonsReleased[i] = 0;
|
|
previousButtonStates[i].available = 0;
|
|
previousButtonStates[i].buttons = 0;
|
|
}
|
|
}
|
|
|
|
Instance::~Instance() {
|
|
if (dataLogSender) {
|
|
delete dataLogSender.load();
|
|
}
|
|
}
|
|
|
|
bool DriverStationBackend::GetStickButton(int stick, int button) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return false;
|
|
}
|
|
if (button < 0 || button >= 64) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "button {} out of range",
|
|
button);
|
|
return false;
|
|
}
|
|
|
|
uint64_t mask = 1LLU << button;
|
|
|
|
HAL_JoystickButtons buttons;
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
if ((buttons.available & mask) == 0) {
|
|
ReportJoystickWarning(stick, "Joystick Button {} on port {} not available",
|
|
button, stick);
|
|
return false;
|
|
}
|
|
|
|
return (buttons.buttons & mask) != 0;
|
|
}
|
|
|
|
std::optional<bool> DriverStationBackend::GetStickButtonIfAvailable(
|
|
int stick, int button) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return false;
|
|
}
|
|
if (button < 0 || button >= 64) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "button {} out of range",
|
|
button);
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool DriverStationBackend::GetStickButtonPressed(int stick, int button) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return false;
|
|
}
|
|
if (button < 0 || button >= 64) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "button {} out of range",
|
|
button);
|
|
return false;
|
|
}
|
|
|
|
HAL_JoystickButtons buttons;
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
uint64_t mask = 1LLU << button;
|
|
|
|
if ((buttons.available & mask) == 0) {
|
|
ReportJoystickWarning(stick, "Joystick Button {} on port {} not available",
|
|
button, stick);
|
|
return false;
|
|
}
|
|
auto& inst = ::GetInstance();
|
|
std::unique_lock lock(inst.buttonEdgeMutex);
|
|
// If button was pressed, clear flag and return true
|
|
if (inst.joystickButtonsPressed[stick] & mask) {
|
|
inst.joystickButtonsPressed[stick] &= ~mask;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DriverStationBackend::GetStickButtonReleased(int stick, int button) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return false;
|
|
}
|
|
if (button < 0 || button >= 64) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "button {} out of range",
|
|
button);
|
|
return false;
|
|
}
|
|
|
|
HAL_JoystickButtons buttons;
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
uint64_t mask = 1LLU << button;
|
|
|
|
if ((buttons.available & mask) == 0) {
|
|
ReportJoystickWarning(stick, "Joystick Button {} on port {} not available",
|
|
button, stick);
|
|
return false;
|
|
}
|
|
auto& inst = ::GetInstance();
|
|
std::unique_lock lock(inst.buttonEdgeMutex);
|
|
// If button was released, clear flag and return true
|
|
if (inst.joystickButtonsReleased[stick] & mask) {
|
|
inst.joystickButtonsReleased[stick] &= ~mask;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
double DriverStationBackend::GetStickAxis(int stick, int axis) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return 0.0;
|
|
}
|
|
if (axis < 0 || axis >= HAL_MAX_JOYSTICK_AXES) {
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "axis {} out of range", axis);
|
|
return 0.0;
|
|
}
|
|
|
|
uint16_t mask = 1 << axis;
|
|
|
|
HAL_JoystickAxes axes;
|
|
HAL_GetJoystickAxes(stick, &axes);
|
|
|
|
if ((axes.available & mask) == 0) {
|
|
ReportJoystickWarning(stick, "Joystick axis {} on port {} not available",
|
|
axis, stick);
|
|
return 0.0;
|
|
}
|
|
|
|
return axes.axes[axis];
|
|
}
|
|
|
|
TouchpadFinger DriverStationBackend::GetStickTouchpadFinger(int stick,
|
|
int touchpad,
|
|
int finger) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return TouchpadFinger{false, 0.0f, 0.0f};
|
|
}
|
|
if (touchpad < 0 || touchpad >= HAL_MAX_JOYSTICK_TOUCHPADS) {
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "touchpad {} out of range",
|
|
touchpad);
|
|
return TouchpadFinger{false, 0.0f, 0.0f};
|
|
}
|
|
if (finger < 0 || finger >= HAL_MAX_JOYSTICK_TOUCHPAD_FINGERS) {
|
|
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};
|
|
}
|
|
}
|
|
|
|
ReportJoystickWarning(
|
|
stick,
|
|
"Joystick touchpad finger {} on touchpad {} on port {} not available",
|
|
finger, touchpad, stick);
|
|
return TouchpadFinger{false, 0.0f, 0.0f};
|
|
}
|
|
|
|
bool DriverStationBackend::GetStickTouchpadFingerAvailable(int stick,
|
|
int touchpad,
|
|
int finger) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return false;
|
|
}
|
|
if (touchpad < 0 || touchpad >= HAL_MAX_JOYSTICK_TOUCHPADS) {
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "touchpad {} out of range",
|
|
touchpad);
|
|
return false;
|
|
}
|
|
if (finger < 0 || finger >= HAL_MAX_JOYSTICK_TOUCHPAD_FINGERS) {
|
|
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;
|
|
}
|
|
|
|
std::optional<double> DriverStationBackend::GetStickAxisIfAvailable(int stick,
|
|
int axis) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return 0.0;
|
|
}
|
|
if (axis < 0 || axis >= HAL_MAX_JOYSTICK_AXES) {
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "axis {} out of range", axis);
|
|
return 0.0;
|
|
}
|
|
|
|
uint16_t mask = 1 << axis;
|
|
|
|
HAL_JoystickAxes axes;
|
|
HAL_GetJoystickAxes(stick, &axes);
|
|
|
|
if ((axes.available & mask) == 0) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return axes.axes[axis];
|
|
}
|
|
|
|
POVDirection DriverStationBackend::GetStickPOV(int stick, int pov) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return POVDirection::CENTER;
|
|
}
|
|
if (pov < 0 || pov >= HAL_MAX_JOYSTICK_POVS) {
|
|
WPILIB_ReportError(warn::BadJoystickAxis, "POV {} out of range", pov);
|
|
return POVDirection::CENTER;
|
|
}
|
|
|
|
uint16_t mask = 1 << pov;
|
|
|
|
HAL_JoystickPOVs povs;
|
|
HAL_GetJoystickPOVs(stick, &povs);
|
|
|
|
if ((povs.available & mask) == 0) {
|
|
ReportJoystickWarning(stick, "Joystick POV {} on port {} not available",
|
|
pov, stick);
|
|
return POVDirection::CENTER;
|
|
}
|
|
|
|
return static_cast<POVDirection>(povs.povs[pov]);
|
|
}
|
|
|
|
uint64_t DriverStationBackend::GetStickButtons(int stick) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return 0;
|
|
}
|
|
|
|
HAL_JoystickButtons buttons;
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
return buttons.buttons;
|
|
}
|
|
|
|
int DriverStationBackend::GetStickAxesMaximumIndex(int stick) {
|
|
return availableToCount(GetStickAxesAvailable(stick));
|
|
}
|
|
|
|
int DriverStationBackend::GetStickAxesAvailable(int stick) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return 0;
|
|
}
|
|
|
|
HAL_JoystickAxes axes;
|
|
HAL_GetJoystickAxes(stick, &axes);
|
|
|
|
return axes.available;
|
|
}
|
|
|
|
int DriverStationBackend::GetStickPOVsMaximumIndex(int stick) {
|
|
return availableToCount(GetStickPOVsAvailable(stick));
|
|
}
|
|
|
|
int DriverStationBackend::GetStickPOVsAvailable(int stick) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return 0;
|
|
}
|
|
|
|
HAL_JoystickPOVs povs;
|
|
HAL_GetJoystickPOVs(stick, &povs);
|
|
|
|
return povs.available;
|
|
}
|
|
|
|
int DriverStationBackend::GetStickButtonsMaximumIndex(int stick) {
|
|
return availableToCount(GetStickButtonsAvailable(stick));
|
|
}
|
|
|
|
uint64_t DriverStationBackend::GetStickButtonsAvailable(int stick) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return 0;
|
|
}
|
|
|
|
HAL_JoystickButtons buttons;
|
|
HAL_GetJoystickButtons(stick, &buttons);
|
|
|
|
return buttons.available;
|
|
}
|
|
|
|
bool DriverStationBackend::GetJoystickIsGamepad(int stick) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return false;
|
|
}
|
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
return static_cast<bool>(descriptor.isGamepad);
|
|
}
|
|
|
|
int DriverStationBackend::GetJoystickGamepadType(int stick) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return -1;
|
|
}
|
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
return static_cast<int>(descriptor.gamepadType);
|
|
}
|
|
|
|
int DriverStationBackend::GetJoystickSupportedOutputs(int stick) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
return 0;
|
|
}
|
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
return static_cast<int>(descriptor.supportedOutputs);
|
|
}
|
|
|
|
std::string DriverStationBackend::GetJoystickName(int stick) {
|
|
if (stick < 0 || stick >= JOYSTICK_PORTS) {
|
|
WPILIB_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
|
|
}
|
|
|
|
HAL_JoystickDescriptor descriptor;
|
|
HAL_GetJoystickDescriptor(stick, &descriptor);
|
|
|
|
return descriptor.name;
|
|
}
|
|
|
|
bool DriverStationBackend::IsJoystickConnected(int stick) {
|
|
return GetStickAxesAvailable(stick) != 0 ||
|
|
GetStickButtonsAvailable(stick) != 0 ||
|
|
GetStickPOVsAvailable(stick) != 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
WPI_String nameWpi = wpi::util::make_string(name);
|
|
WPI_String groupWpi = wpi::util::make_string(group);
|
|
WPI_String descriptionWpi = wpi::util::make_string(description);
|
|
|
|
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 += ' ';
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int64_t DriverStationBackend::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));
|
|
}
|
|
|
|
int64_t DriverStationBackend::AddOpMode(RobotMode mode, std::string_view name,
|
|
std::string_view group,
|
|
std::string_view description) {
|
|
return DoAddOpMode(mode, name, group, description, -1, -1);
|
|
}
|
|
|
|
int64_t DriverStationBackend::RemoveOpMode(RobotMode mode,
|
|
std::string_view name) {
|
|
if (wpi::util::trim(name).empty()) {
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void DriverStationBackend::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());
|
|
}
|
|
|
|
void DriverStationBackend::ClearOpModes() {
|
|
auto& inst = ::GetInstance();
|
|
std::scoped_lock lock{inst.opModeMutex};
|
|
inst.opModes.clear();
|
|
HAL_SetOpModeOptions(nullptr, 0);
|
|
}
|
|
|
|
void DriverStationBackend::ObserveUserProgramStarting() {
|
|
::GetInstance().userProgramStarted = true;
|
|
HAL_ObserveUserProgramStarting();
|
|
}
|
|
|
|
int64_t DriverStationBackend::GetOpModeId() {
|
|
if (!::GetInstance().userProgramStarted) {
|
|
return 0;
|
|
}
|
|
|
|
return GetControlWord().GetOpModeId();
|
|
}
|
|
|
|
std::string DriverStationBackend::GetOpMode() {
|
|
if (!::GetInstance().userProgramStarted) {
|
|
return "";
|
|
}
|
|
|
|
return GetInstance().OpModeToString(GetOpModeId());
|
|
}
|
|
|
|
std::optional<std::string> DriverStationBackend::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);
|
|
}
|
|
|
|
std::string DriverStationBackend::GetEventName() {
|
|
HAL_MatchInfo info;
|
|
HAL_GetMatchInfo(&info);
|
|
return info.eventName;
|
|
}
|
|
|
|
MatchType DriverStationBackend::GetMatchType() {
|
|
HAL_MatchInfo info;
|
|
HAL_GetMatchInfo(&info);
|
|
return static_cast<MatchType>(info.matchType);
|
|
}
|
|
|
|
int DriverStationBackend::GetMatchNumber() {
|
|
HAL_MatchInfo info;
|
|
HAL_GetMatchInfo(&info);
|
|
return info.matchNumber;
|
|
}
|
|
|
|
int DriverStationBackend::GetReplayNumber() {
|
|
HAL_MatchInfo info;
|
|
HAL_GetMatchInfo(&info);
|
|
return info.replayNumber;
|
|
}
|
|
|
|
std::optional<Alliance> DriverStationBackend::GetAlliance() {
|
|
int32_t status = 0;
|
|
auto allianceStationID = HAL_GetAllianceStation(&status);
|
|
switch (allianceStationID) {
|
|
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;
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
std::optional<int> DriverStationBackend::GetLocation() {
|
|
int32_t status = 0;
|
|
auto allianceStationID = HAL_GetAllianceStation(&status);
|
|
switch (allianceStationID) {
|
|
case HAL_ALLIANCE_STATION_RED_1:
|
|
case HAL_ALLIANCE_STATION_BLUE_1:
|
|
return 1;
|
|
case HAL_ALLIANCE_STATION_RED_2:
|
|
case HAL_ALLIANCE_STATION_BLUE_2:
|
|
return 2;
|
|
case HAL_ALLIANCE_STATION_RED_3:
|
|
case HAL_ALLIANCE_STATION_BLUE_3:
|
|
return 3;
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
wpi::units::second_t DriverStationBackend::GetMatchTime() {
|
|
int32_t status = 0;
|
|
return wpi::units::second_t{HAL_GetMatchTime(&status)};
|
|
}
|
|
|
|
double DriverStationBackend::GetBatteryVoltage() {
|
|
int32_t status = 0;
|
|
double voltage = HAL_GetVinVoltage(&status);
|
|
WPILIB_CheckErrorStatus(status, "getVinVoltage");
|
|
|
|
return voltage;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
void DriverStationBackend::RefreshData() {
|
|
HAL_RefreshDSData();
|
|
auto& inst = ::GetInstance();
|
|
{
|
|
// Compute the pressed and released buttons
|
|
HAL_JoystickButtons currentButtons;
|
|
std::unique_lock lock(inst.buttonEdgeMutex);
|
|
|
|
for (int32_t i = 0; i < DriverStationBackend::JOYSTICK_PORTS; i++) {
|
|
HAL_GetJoystickButtons(i, ¤tButtons);
|
|
|
|
// If buttons weren't pressed and are now, set flags in m_buttonsPressed
|
|
inst.joystickButtonsPressed[i] |=
|
|
~inst.previousButtonStates[i].buttons & currentButtons.buttons;
|
|
|
|
// If buttons were pressed and aren't now, set flags in m_buttonsReleased
|
|
inst.joystickButtonsReleased[i] |=
|
|
inst.previousButtonStates[i].buttons & ~currentButtons.buttons;
|
|
|
|
inst.previousButtonStates[i] = currentButtons;
|
|
}
|
|
}
|
|
|
|
inst.refreshEvents.Wakeup();
|
|
|
|
SendMatchData();
|
|
if (auto sender = inst.dataLogSender.load()) {
|
|
sender->Send(wpi::util::Now());
|
|
}
|
|
}
|
|
|
|
void DriverStationBackend::ProvideRefreshedDataEventHandle(
|
|
WPI_EventHandle handle) {
|
|
auto& inst = ::GetInstance();
|
|
inst.refreshEvents.Add(handle);
|
|
}
|
|
|
|
void DriverStationBackend::RemoveRefreshedDataEventHandle(
|
|
WPI_EventHandle handle) {
|
|
auto& inst = ::GetInstance();
|
|
inst.refreshEvents.Remove(handle);
|
|
}
|
|
|
|
void DriverStationBackend::SilenceJoystickConnectionWarning(bool silence) {
|
|
::GetInstance().silenceJoystickWarning = silence;
|
|
}
|
|
|
|
bool DriverStationBackend::IsJoystickConnectionWarningSilenced() {
|
|
return !IsFMSAttached() && ::GetInstance().silenceJoystickWarning;
|
|
}
|
|
|
|
void DriverStationBackend::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::util::Now());
|
|
}
|
|
}
|
|
|
|
void ReportJoystickWarningV(int stick, fmt::string_view format,
|
|
fmt::format_args args) {
|
|
auto& inst = GetInstance();
|
|
if (DriverStationBackend::IsFMSAttached() || !inst.silenceJoystickWarning) {
|
|
auto currentTime = Timer::GetTimestamp();
|
|
if (currentTime > inst.nextMessageTime) {
|
|
if (DriverStationBackend::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);
|
|
}
|
|
inst.nextMessageTime = currentTime + kJoystickUnpluggedMessageInterval;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SendMatchData() {
|
|
int32_t status = 0;
|
|
HAL_AllianceStationID alliance = HAL_GetAllianceStation(&status);
|
|
bool isRedAlliance = false;
|
|
int stationNumber = 1;
|
|
switch (alliance) {
|
|
case HAL_ALLIANCE_STATION_BLUE_1:
|
|
isRedAlliance = false;
|
|
stationNumber = 1;
|
|
break;
|
|
case HAL_ALLIANCE_STATION_BLUE_2:
|
|
isRedAlliance = false;
|
|
stationNumber = 2;
|
|
break;
|
|
case HAL_ALLIANCE_STATION_BLUE_3:
|
|
isRedAlliance = false;
|
|
stationNumber = 3;
|
|
break;
|
|
case HAL_ALLIANCE_STATION_RED_1:
|
|
isRedAlliance = true;
|
|
stationNumber = 1;
|
|
break;
|
|
case HAL_ALLIANCE_STATION_RED_2:
|
|
isRedAlliance = true;
|
|
stationNumber = 2;
|
|
break;
|
|
default:
|
|
isRedAlliance = true;
|
|
stationNumber = 3;
|
|
break;
|
|
}
|
|
|
|
HAL_MatchInfo tmpDataStore;
|
|
HAL_GetMatchInfo(&tmpDataStore);
|
|
|
|
HAL_GameData tmpGameData;
|
|
HAL_GetGameData(&tmpGameData);
|
|
|
|
auto& inst = GetInstance();
|
|
inst.matchDataSender.alliance.Set(isRedAlliance);
|
|
inst.matchDataSender.station.Set(stationNumber);
|
|
inst.matchDataSender.eventName.Set(tmpDataStore.eventName);
|
|
inst.matchDataSender.gameData.Set(
|
|
std::string(reinterpret_cast<char*>(tmpGameData.gameData)));
|
|
inst.matchDataSender.matchNumber.Set(tmpDataStore.matchNumber);
|
|
inst.matchDataSender.replayNumber.Set(tmpDataStore.replayNumber);
|
|
inst.matchDataSender.matchType.Set(static_cast<int>(tmpDataStore.matchType));
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
int axesCount = availableToCount(m_prevAxes.available);
|
|
m_logAxes.Append(
|
|
std::span<const float>{m_prevAxes.axes, static_cast<size_t>(axesCount)},
|
|
timestamp);
|
|
AppendPOVs(m_prevPOVs, timestamp);
|
|
}
|
|
|
|
void JoystickLogSender::Send(uint64_t timestamp) {
|
|
HAL_JoystickButtons buttons;
|
|
HAL_GetJoystickButtons(m_stick, &buttons);
|
|
if (buttons.available != m_prevButtons.available ||
|
|
buttons.buttons != m_prevButtons.buttons) {
|
|
AppendButtons(buttons, timestamp);
|
|
}
|
|
m_prevButtons = buttons;
|
|
|
|
HAL_JoystickAxes axes;
|
|
HAL_GetJoystickAxes(m_stick, &axes);
|
|
int axesCount = availableToCount(axes.available);
|
|
if (axes.available != m_prevAxes.available ||
|
|
std::memcmp(axes.axes, m_prevAxes.axes,
|
|
sizeof(axes.axes[0]) * axesCount) != 0) {
|
|
m_logAxes.Append(
|
|
std::span<const float>{axes.axes, static_cast<size_t>(axesCount)},
|
|
timestamp);
|
|
}
|
|
m_prevAxes = axes;
|
|
|
|
HAL_JoystickPOVs povs;
|
|
HAL_GetJoystickPOVs(m_stick, &povs);
|
|
int povsCount = availableToCount(povs.available);
|
|
if (povs.available != m_prevPOVs.available ||
|
|
std::memcmp(povs.povs, m_prevPOVs.povs,
|
|
sizeof(povs.povs[0]) * povsCount) != 0) {
|
|
AppendPOVs(povs, timestamp);
|
|
}
|
|
m_prevPOVs = povs;
|
|
}
|
|
|
|
void JoystickLogSender::AppendButtons(HAL_JoystickButtons buttons,
|
|
uint64_t timestamp) {
|
|
int count = availableToCount(buttons.available);
|
|
uint8_t buttonsArr[64];
|
|
for (int i = 0; i < count; ++i) {
|
|
buttonsArr[i] = (buttons.buttons & (1llu << i)) != 0;
|
|
}
|
|
m_logButtons.Append(
|
|
std::span<const uint8_t>{buttonsArr, static_cast<size_t>(count)},
|
|
timestamp);
|
|
}
|
|
|
|
void JoystickLogSender::AppendPOVs(const HAL_JoystickPOVs& povs,
|
|
uint64_t timestamp) {
|
|
int count = availableToCount(povs.available);
|
|
int64_t povsArr[HAL_MAX_JOYSTICK_POVS];
|
|
for (int i = 0; i < count; ++i) {
|
|
povsArr[i] = povs.povs[i];
|
|
}
|
|
m_logPOVs.Append(
|
|
std::span<const int64_t>{povsArr, static_cast<size_t>(count)}, timestamp);
|
|
}
|
|
|
|
void DataLogSender::Init(wpi::log::DataLog& log, bool logJoysticks,
|
|
int64_t timestamp) {
|
|
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()));
|
|
|
|
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();
|
|
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);
|
|
}
|
|
|
|
if (m_logJoysticks) {
|
|
// append joystick value changes
|
|
for (auto&& joystick : m_joysticks) {
|
|
joystick.Send(timestamp);
|
|
}
|
|
}
|
|
}
|