// 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 "frc/DriverStation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "frc/Errors.h" #include "frc/MotorSafety.h" #include "frc/Timer.h" using namespace frc; 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 class MatchDataSenderEntry { public: MatchDataSenderEntry(const std::shared_ptr& table, std::string_view key, const T& initialVal) { static_assert(std::is_same_v || std::is_same_v || std::is_same_v, "Invalid type for MatchDataSenderEntry - must be " "to bool, double or std::string"); ntEntry = table->GetEntry(key); if constexpr (std::is_same_v) { ntEntry.ForceSetBoolean(initialVal); } else if constexpr (std::is_same_v) { ntEntry.ForceSetDouble(initialVal); } else if constexpr (std::is_same_v) { ntEntry.ForceSetString(initialVal); } prevVal = initialVal; } void Set(const T& val) { if (val != prevVal) { SetValue(val); prevVal = val; } } private: nt::NetworkTableEntry ntEntry; T prevVal; void SetValue(bool val) { ntEntry.SetBoolean(val); } void SetValue(double val) { ntEntry.SetDouble(val); } void SetValue(std::string_view val) { ntEntry.SetString(val); } }; struct MatchDataSender { std::shared_ptr table = nt::NetworkTableInstance::GetDefault().GetTable("FMSInfo"); MatchDataSenderEntry typeMetaData{table, ".type", "FMSInfo"}; MatchDataSenderEntry gameSpecificMessage{ table, "GameSpecificMessage", ""}; MatchDataSenderEntry eventName{table, "EventName", ""}; MatchDataSenderEntry matchNumber{table, "MatchNumber", 0.0}; MatchDataSenderEntry replayNumber{table, "ReplayNumber", 0.0}; MatchDataSenderEntry matchType{table, "MatchType", 0.0}; MatchDataSenderEntry alliance{table, "IsRedAlliance", true}; MatchDataSenderEntry station{table, "StationNumber", 1.0}; MatchDataSenderEntry controlWord{table, "FMSControlData", 0.0}; }; struct Instance { Instance(); ~Instance(); MatchDataSender matchDataSender; // Joystick button rising/falling edge flags wpi::mutex buttonEdgeMutex; std::array previousButtonStates; std::array joystickButtonsPressed; std::array joystickButtonsReleased; // Internal Driver Station thread std::thread dsThread; std::atomic isRunning{false}; mutable wpi::mutex waitForDataMutex; wpi::condition_variable waitForDataCond; int waitForDataCounter = 0; bool silenceJoystickWarning = false; // Robot state status variables bool userInDisabled = false; bool userInAutonomous = false; bool userInTeleop = false; bool userInTest = false; units::second_t nextMessageTime = 0_s; }; } // namespace static constexpr auto kJoystickUnpluggedMessageInterval = 1_s; static Instance& GetInstance() { static Instance instance; return instance; } static void Run(); static void SendMatchData(); /** * Reports errors related to unplugged joysticks. * * Throttles the errors so that they don't overwhelm the DS. */ static void ReportJoystickUnpluggedErrorV(fmt::string_view format, fmt::format_args args); template static inline void ReportJoystickUnpluggedError(const S& format, Args&&... args) { ReportJoystickUnpluggedErrorV( format, fmt::make_args_checked(format, args...)); } /** * Reports errors related to unplugged joysticks. * * Throttles the errors so that they don't overwhelm the DS. */ static void ReportJoystickUnpluggedWarningV(fmt::string_view format, fmt::format_args args); template static inline void ReportJoystickUnpluggedWarning(const S& format, Args&&... args) { ReportJoystickUnpluggedWarningV( format, fmt::make_args_checked(format, args...)); } static int& GetDSLastCount() { // There is a rollover error condition here. At Packet# = n * (uintmax), this // will return false when instead it should return true. However, this at a // 20ms rate occurs once every 2.7 years of DS connected runtime, so not // worth the cycles to check. thread_local int lastCount{0}; return lastCount; } 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 < DriverStation::kJoystickPorts; i++) { joystickButtonsPressed[i] = 0; joystickButtonsReleased[i] = 0; previousButtonStates[i].count = 0; previousButtonStates[i].buttons = 0; } dsThread = std::thread(&Run); } Instance::~Instance() { isRunning = false; // Trigger a DS mutex release in case there is no driver station running. HAL_ReleaseDSMutex(); dsThread.join(); } DriverStation& DriverStation::GetInstance() { ::GetInstance(); static DriverStation instance; return instance; } bool DriverStation::GetStickButton(int stick, int button) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return false; } if (button <= 0) { ReportJoystickUnpluggedError( "Joystick Button {} index out of range; indexes begin at 1", button); return false; } HAL_JoystickButtons buttons; HAL_GetJoystickButtons(stick, &buttons); if (button > buttons.count) { ReportJoystickUnpluggedWarning( "Joystick Button {} missing (max {}), check if all controllers are " "plugged in", button, buttons.count); return false; } return buttons.buttons & 1 << (button - 1); } bool DriverStation::GetStickButtonPressed(int stick, int button) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return false; } if (button <= 0) { ReportJoystickUnpluggedError( "Joystick Button {} index out of range; indexes begin at 1", button); return false; } HAL_JoystickButtons buttons; HAL_GetJoystickButtons(stick, &buttons); if (button > buttons.count) { ReportJoystickUnpluggedWarning( "Joystick Button {} missing (max {}), check if all controllers are " "plugged in", button, buttons.count); return false; } auto& inst = ::GetInstance(); std::unique_lock lock(inst.buttonEdgeMutex); // If button was pressed, clear flag and return true if (inst.joystickButtonsPressed[stick] & 1 << (button - 1)) { inst.joystickButtonsPressed[stick] &= ~(1 << (button - 1)); return true; } return false; } bool DriverStation::GetStickButtonReleased(int stick, int button) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return false; } if (button <= 0) { ReportJoystickUnpluggedError( "Joystick Button {} index out of range; indexes begin at 1", button); return false; } HAL_JoystickButtons buttons; HAL_GetJoystickButtons(stick, &buttons); if (button > buttons.count) { ReportJoystickUnpluggedWarning( "Joystick Button {} missing (max {}), check if all controllers are " "plugged in", button, buttons.count); return false; } auto& inst = ::GetInstance(); std::unique_lock lock(inst.buttonEdgeMutex); // If button was released, clear flag and return true if (inst.joystickButtonsReleased[stick] & 1 << (button - 1)) { inst.joystickButtonsReleased[stick] &= ~(1 << (button - 1)); return true; } return false; } double DriverStation::GetStickAxis(int stick, int axis) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return 0.0; } if (axis < 0 || axis >= HAL_kMaxJoystickAxes) { FRC_ReportError(warn::BadJoystickAxis, "axis {} out of range", axis); return 0.0; } HAL_JoystickAxes axes; HAL_GetJoystickAxes(stick, &axes); if (axis >= axes.count) { ReportJoystickUnpluggedWarning( "Joystick Axis {} missing (max {}), check if all controllers are " "plugged in", axis, axes.count); return 0.0; } return axes.axes[axis]; } int DriverStation::GetStickPOV(int stick, int pov) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return -1; } if (pov < 0 || pov >= HAL_kMaxJoystickPOVs) { FRC_ReportError(warn::BadJoystickAxis, "POV {} out of range", pov); return -1; } HAL_JoystickPOVs povs; HAL_GetJoystickPOVs(stick, &povs); if (pov >= povs.count) { ReportJoystickUnpluggedWarning( "Joystick POV {} missing (max {}), check if all controllers are " "plugged in", pov, povs.count); return -1; } return povs.povs[pov]; } int DriverStation::GetStickButtons(int stick) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return 0; } HAL_JoystickButtons buttons; HAL_GetJoystickButtons(stick, &buttons); return buttons.buttons; } int DriverStation::GetStickAxisCount(int stick) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return 0; } HAL_JoystickAxes axes; HAL_GetJoystickAxes(stick, &axes); return axes.count; } int DriverStation::GetStickPOVCount(int stick) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return 0; } HAL_JoystickPOVs povs; HAL_GetJoystickPOVs(stick, &povs); return povs.count; } int DriverStation::GetStickButtonCount(int stick) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return 0; } HAL_JoystickButtons buttons; HAL_GetJoystickButtons(stick, &buttons); return buttons.count; } bool DriverStation::GetJoystickIsXbox(int stick) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return false; } HAL_JoystickDescriptor descriptor; HAL_GetJoystickDescriptor(stick, &descriptor); return static_cast(descriptor.isXbox); } int DriverStation::GetJoystickType(int stick) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return -1; } HAL_JoystickDescriptor descriptor; HAL_GetJoystickDescriptor(stick, &descriptor); return static_cast(descriptor.type); } std::string DriverStation::GetJoystickName(int stick) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); } HAL_JoystickDescriptor descriptor; HAL_GetJoystickDescriptor(stick, &descriptor); return descriptor.name; } int DriverStation::GetJoystickAxisType(int stick, int axis) { if (stick < 0 || stick >= kJoystickPorts) { FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick); return -1; } HAL_JoystickDescriptor descriptor; HAL_GetJoystickDescriptor(stick, &descriptor); return static_cast(descriptor.axisTypes); } bool DriverStation::IsJoystickConnected(int stick) { return GetStickAxisCount(stick) > 0 || GetStickButtonCount(stick) > 0 || GetStickPOVCount(stick) > 0; } bool DriverStation::IsEnabled() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return controlWord.enabled && controlWord.dsAttached; } bool DriverStation::IsDisabled() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return !(controlWord.enabled && controlWord.dsAttached); } bool DriverStation::IsEStopped() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return controlWord.eStop; } bool DriverStation::IsAutonomous() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return controlWord.autonomous; } bool DriverStation::IsAutonomousEnabled() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return controlWord.autonomous && controlWord.enabled; } bool DriverStation::IsOperatorControl() { return IsTeleop(); } bool DriverStation::IsTeleop() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return !(controlWord.autonomous || controlWord.test); } bool DriverStation::IsOperatorControlEnabled() { return IsTeleopEnabled(); } bool DriverStation::IsTeleopEnabled() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return !controlWord.autonomous && !controlWord.test && controlWord.enabled; } bool DriverStation::IsTest() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return controlWord.test; } bool DriverStation::IsDSAttached() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return controlWord.dsAttached; } bool DriverStation::IsNewControlData() { auto& inst = ::GetInstance(); std::unique_lock lock(inst.waitForDataMutex); int& lastCount = GetDSLastCount(); int currentCount = inst.waitForDataCounter; if (lastCount == currentCount) { return false; } lastCount = currentCount; return true; } bool DriverStation::IsFMSAttached() { HAL_ControlWord controlWord; HAL_GetControlWord(&controlWord); return controlWord.fmsAttached; } std::string DriverStation::GetGameSpecificMessage() { HAL_MatchInfo info; HAL_GetMatchInfo(&info); return std::string(reinterpret_cast(info.gameSpecificMessage), info.gameSpecificMessageSize); } std::string DriverStation::GetEventName() { HAL_MatchInfo info; HAL_GetMatchInfo(&info); return info.eventName; } DriverStation::MatchType DriverStation::GetMatchType() { HAL_MatchInfo info; HAL_GetMatchInfo(&info); return static_cast(info.matchType); } int DriverStation::GetMatchNumber() { HAL_MatchInfo info; HAL_GetMatchInfo(&info); return info.matchNumber; } int DriverStation::GetReplayNumber() { HAL_MatchInfo info; HAL_GetMatchInfo(&info); return info.replayNumber; } DriverStation::Alliance DriverStation::GetAlliance() { int32_t status = 0; auto allianceStationID = HAL_GetAllianceStation(&status); switch (allianceStationID) { case HAL_AllianceStationID_kRed1: case HAL_AllianceStationID_kRed2: case HAL_AllianceStationID_kRed3: return kRed; case HAL_AllianceStationID_kBlue1: case HAL_AllianceStationID_kBlue2: case HAL_AllianceStationID_kBlue3: return kBlue; default: return kInvalid; } } int DriverStation::GetLocation() { int32_t status = 0; auto allianceStationID = HAL_GetAllianceStation(&status); switch (allianceStationID) { case HAL_AllianceStationID_kRed1: case HAL_AllianceStationID_kBlue1: return 1; case HAL_AllianceStationID_kRed2: case HAL_AllianceStationID_kBlue2: return 2; case HAL_AllianceStationID_kRed3: case HAL_AllianceStationID_kBlue3: return 3; default: return 0; } } void DriverStation::WaitForData() { WaitForData(0_s); } bool DriverStation::WaitForData(units::second_t timeout) { auto& inst = ::GetInstance(); auto timeoutTime = std::chrono::steady_clock::now() + std::chrono::steady_clock::duration{timeout}; std::unique_lock lock(inst.waitForDataMutex); int& lastCount = GetDSLastCount(); int currentCount = inst.waitForDataCounter; if (lastCount != currentCount) { lastCount = currentCount; return true; } while (inst.waitForDataCounter == currentCount) { if (timeout > 0_s) { auto timedOut = inst.waitForDataCond.wait_until(lock, timeoutTime); if (timedOut == std::cv_status::timeout) { return false; } } else { inst.waitForDataCond.wait(lock); } } lastCount = inst.waitForDataCounter; return true; } double DriverStation::GetMatchTime() { int32_t status = 0; return HAL_GetMatchTime(&status); } double DriverStation::GetBatteryVoltage() { int32_t status = 0; double voltage = HAL_GetVinVoltage(&status); FRC_CheckErrorStatus(status, "{}", "getVinVoltage"); return voltage; } void DriverStation::InDisabled(bool entering) { ::GetInstance().userInDisabled = entering; } void DriverStation::InAutonomous(bool entering) { ::GetInstance().userInAutonomous = entering; } void DriverStation::InOperatorControl(bool entering) { InTeleop(entering); } void DriverStation::InTeleop(bool entering) { ::GetInstance().userInTeleop = entering; } void DriverStation::InTest(bool entering) { ::GetInstance().userInTest = entering; } void DriverStation::WakeupWaitForData() { auto& inst = ::GetInstance(); std::scoped_lock waitLock(inst.waitForDataMutex); // Nofify all threads inst.waitForDataCounter++; inst.waitForDataCond.notify_all(); } /** * 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 GetData() { auto& inst = ::GetInstance(); { // Compute the pressed and released buttons HAL_JoystickButtons currentButtons; std::unique_lock lock(inst.buttonEdgeMutex); for (int32_t i = 0; i < DriverStation::kJoystickPorts; 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; } } DriverStation::WakeupWaitForData(); SendMatchData(); } void DriverStation::SilenceJoystickConnectionWarning(bool silence) { ::GetInstance().silenceJoystickWarning = silence; } bool DriverStation::IsJoystickConnectionWarningSilenced() { return !IsFMSAttached() && ::GetInstance().silenceJoystickWarning; } void ReportJoystickUnpluggedErrorV(fmt::string_view format, fmt::format_args args) { auto& inst = GetInstance(); auto currentTime = Timer::GetFPGATimestamp(); if (currentTime > inst.nextMessageTime) { ReportErrorV(err::Error, "", 0, "", format, args); inst.nextMessageTime = currentTime + kJoystickUnpluggedMessageInterval; } } void ReportJoystickUnpluggedWarningV(fmt::string_view format, fmt::format_args args) { auto& inst = GetInstance(); if (DriverStation::IsFMSAttached() || !inst.silenceJoystickWarning) { auto currentTime = Timer::GetFPGATimestamp(); if (currentTime > inst.nextMessageTime) { ReportErrorV(warn::Warning, "", 0, "", format, args); inst.nextMessageTime = currentTime + kJoystickUnpluggedMessageInterval; } } } void Run() { auto& inst = GetInstance(); inst.isRunning = true; int safetyCounter = 0; while (inst.isRunning) { HAL_WaitForDSData(); GetData(); if (DriverStation::IsDisabled()) { safetyCounter = 0; } if (++safetyCounter >= 4) { MotorSafety::CheckMotors(); safetyCounter = 0; } if (inst.userInDisabled) { HAL_ObserveUserProgramDisabled(); } if (inst.userInAutonomous) { HAL_ObserveUserProgramAutonomous(); } if (inst.userInTeleop) { HAL_ObserveUserProgramTeleop(); } if (inst.userInTest) { HAL_ObserveUserProgramTest(); } } } void SendMatchData() { int32_t status = 0; HAL_AllianceStationID alliance = HAL_GetAllianceStation(&status); bool isRedAlliance = false; int stationNumber = 1; switch (alliance) { case HAL_AllianceStationID::HAL_AllianceStationID_kBlue1: isRedAlliance = false; stationNumber = 1; break; case HAL_AllianceStationID::HAL_AllianceStationID_kBlue2: isRedAlliance = false; stationNumber = 2; break; case HAL_AllianceStationID::HAL_AllianceStationID_kBlue3: isRedAlliance = false; stationNumber = 3; break; case HAL_AllianceStationID::HAL_AllianceStationID_kRed1: isRedAlliance = true; stationNumber = 1; break; case HAL_AllianceStationID::HAL_AllianceStationID_kRed2: isRedAlliance = true; stationNumber = 2; break; default: isRedAlliance = true; stationNumber = 3; break; } HAL_MatchInfo tmpDataStore; HAL_GetMatchInfo(&tmpDataStore); auto& inst = GetInstance(); inst.matchDataSender.alliance.Set(isRedAlliance); inst.matchDataSender.station.Set(stationNumber); inst.matchDataSender.eventName.Set(tmpDataStore.eventName); inst.matchDataSender.gameSpecificMessage.Set( std::string(reinterpret_cast(tmpDataStore.gameSpecificMessage), tmpDataStore.gameSpecificMessageSize)); inst.matchDataSender.matchNumber.Set(tmpDataStore.matchNumber); inst.matchDataSender.replayNumber.Set(tmpDataStore.replayNumber); inst.matchDataSender.matchType.Set(static_cast(tmpDataStore.matchType)); HAL_ControlWord ctlWord; HAL_GetControlWord(&ctlWord); int32_t wordInt = 0; std::memcpy(&wordInt, &ctlWord, sizeof(wordInt)); inst.matchDataSender.controlWord.Set(wordInt); }