// 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. package org.wpilib.driverstation; import org.wpilib.datalog.BooleanArrayLogEntry; import org.wpilib.datalog.BooleanLogEntry; import org.wpilib.datalog.DataLog; import org.wpilib.datalog.FloatArrayLogEntry; import org.wpilib.datalog.IntegerArrayLogEntry; import org.wpilib.hardware.hal.AllianceStationID; import org.wpilib.hardware.hal.ControlWord; import org.wpilib.hardware.hal.DriverStationJNI; import org.wpilib.hardware.hal.HAL; import org.wpilib.hardware.hal.MatchInfoData; import org.wpilib.math.geometry.Rotation2d; import org.wpilib.networktables.BooleanPublisher; import org.wpilib.networktables.IntegerPublisher; import org.wpilib.networktables.NetworkTableInstance; import org.wpilib.networktables.StringPublisher; import org.wpilib.networktables.StringTopic; import org.wpilib.util.concurrent.EventVector; import org.wpilib.util.WPIUtilJNI; import java.util.Map; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.concurrent.locks.ReentrantLock; /** Provide access to the network communication data to / from the Driver Station. */ public final class DriverStation { /** Number of Joystick ports. */ public static final int kJoystickPorts = 6; private static final long[] m_metadataCache = new long[4]; private static int availableToCount(long available) { // Top bit has to be set if (available < 0) { return 64; } int count = 0; // Top bit not set, we will eventually get a 0 bit while ((available & 0x1) != 0) { count++; available >>= 1; } return count; } private static final class HALJoystickButtons { public long m_buttons; public long m_available; } private static class HALJoystickAxes { public final float[] m_axes; public int m_available; HALJoystickAxes(int count) { m_axes = new float[count]; } } private static class HALJoystickAxesRaw { public final short[] m_axes; @SuppressWarnings("unused") public int m_available; HALJoystickAxesRaw(int count) { m_axes = new short[count]; } } private static class HALJoystickPOVs { public final byte[] m_povs; public int m_available; HALJoystickPOVs(int count) { m_povs = new byte[count]; for (int i = 0; i < count; i++) { m_povs[i] = 0; } } } /** The robot alliance that the robot is a part of. */ public enum Alliance { /** Red alliance. */ Red, /** Blue alliance. */ Blue } /** The type of robot match that the robot is part of. */ public enum MatchType { /** None. */ None, /** Practice. */ Practice, /** Qualification. */ Qualification, /** Elimination. */ Elimination } /** A controller POV direction. */ public enum POVDirection { /** POV center. */ Center(0x00), /** POV up. */ Up(0x01), /** POV up right. */ UpRight(0x01 | 0x02), /** POV right. */ Right(0x02), /** POV down right. */ DownRight(0x02 | 0x04), /** POV down. */ Down(0x04), /** POV down left. */ DownLeft(0x04 | 0x08), /** POV left. */ Left(0x08), /** POV up left. */ UpLeft(0x01 | 0x08); private static final double INVALID_POV_VALUE_INTERVAL = 1.0; private static double s_nextMessageTime; /** * Converts a byte value into a POVDirection enum value. * * @param value The byte value to convert. * @return The corresponding POVDirection enum value. * @throws IllegalArgumentException If value does not correspond to a POVDirection. */ private static POVDirection of(byte value) { for (var direction : values()) { if (direction.value == value) { return direction; } } double currentTime = Timer.getTimestamp(); if (currentTime > s_nextMessageTime) { reportError("Invalid POV value " + value + "!", false); s_nextMessageTime = currentTime + INVALID_POV_VALUE_INTERVAL; } return Center; } /** The corresponding HAL value. */ public final byte value; POVDirection(int value) { this.value = (byte) value; } /** * Gets the angle of a POVDirection. * * @return The angle clockwise from straight up, or Optional.empty() if this POVDirection is * Center. */ public Optional getAngle() { return switch (this) { case Center -> Optional.empty(); case Up -> Optional.of(Rotation2d.fromDegrees(0)); case UpRight -> Optional.of(Rotation2d.fromDegrees(45)); case Right -> Optional.of(Rotation2d.fromDegrees(90)); case DownRight -> Optional.of(Rotation2d.fromDegrees(135)); case Down -> Optional.of(Rotation2d.fromDegrees(180)); case DownLeft -> Optional.of(Rotation2d.fromDegrees(225)); case Left -> Optional.of(Rotation2d.fromDegrees(270)); case UpLeft -> Optional.of(Rotation2d.fromDegrees(315)); }; } } private static final double JOYSTICK_UNPLUGGED_MESSAGE_INTERVAL = 1.0; private static double m_nextMessageTime; @SuppressWarnings("MemberName") private static class MatchDataSender { private static final String kSmartDashboardType = "FMSInfo"; final StringPublisher gameSpecificMessage; final StringPublisher eventName; final IntegerPublisher matchNumber; final IntegerPublisher replayNumber; final IntegerPublisher matchType; final BooleanPublisher alliance; final IntegerPublisher station; final IntegerPublisher controlWord; boolean oldIsRedAlliance = true; int oldStationNumber = 1; String oldEventName = ""; String oldGameSpecificMessage = ""; int oldMatchNumber; int oldReplayNumber; int oldMatchType; int oldControlWord; MatchDataSender() { var table = NetworkTableInstance.getDefault().getTable("FMSInfo"); table .getStringTopic(".type") .publishEx( StringTopic.kTypeString, "{\"SmartDashboard\":\"" + kSmartDashboardType + "\"}") .set(kSmartDashboardType); gameSpecificMessage = table.getStringTopic("GameSpecificMessage").publish(); gameSpecificMessage.set(""); eventName = table.getStringTopic("EventName").publish(); eventName.set(""); matchNumber = table.getIntegerTopic("MatchNumber").publish(); matchNumber.set(0); replayNumber = table.getIntegerTopic("ReplayNumber").publish(); replayNumber.set(0); matchType = table.getIntegerTopic("MatchType").publish(); matchType.set(0); alliance = table.getBooleanTopic("IsRedAlliance").publish(); alliance.set(true); station = table.getIntegerTopic("StationNumber").publish(); station.set(1); controlWord = table.getIntegerTopic("FMSControlData").publish(); controlWord.set(0); } private void sendMatchData() { AllianceStationID allianceID = DriverStationJNI.getAllianceStation(); final int stationNumber = switch (allianceID) { case Blue1, Red1 -> 1; case Blue2, Red2 -> 2; case Blue3, Red3, Unknown -> 3; }; final boolean isRedAlliance = switch (allianceID) { case Blue1, Blue2, Blue3 -> false; case Red1, Red2, Red3, Unknown -> true; }; String currentEventName; String currentGameSpecificMessage; int currentMatchNumber; int currentReplayNumber; int currentMatchType; int currentControlWord; m_cacheDataMutex.lock(); try { currentEventName = DriverStation.m_matchInfo.eventName; currentGameSpecificMessage = DriverStation.m_matchInfo.gameSpecificMessage; currentMatchNumber = DriverStation.m_matchInfo.matchNumber; currentReplayNumber = DriverStation.m_matchInfo.replayNumber; currentMatchType = DriverStation.m_matchInfo.matchType; } finally { m_cacheDataMutex.unlock(); } currentControlWord = DriverStationJNI.nativeGetControlWord(); if (oldIsRedAlliance != isRedAlliance) { alliance.set(isRedAlliance); oldIsRedAlliance = isRedAlliance; } if (oldStationNumber != stationNumber) { station.set(stationNumber); oldStationNumber = stationNumber; } if (!oldEventName.equals(currentEventName)) { eventName.set(currentEventName); oldEventName = currentEventName; } if (!oldGameSpecificMessage.equals(currentGameSpecificMessage)) { gameSpecificMessage.set(currentGameSpecificMessage); oldGameSpecificMessage = currentGameSpecificMessage; } if (currentMatchNumber != oldMatchNumber) { matchNumber.set(currentMatchNumber); oldMatchNumber = currentMatchNumber; } if (currentReplayNumber != oldReplayNumber) { replayNumber.set(currentReplayNumber); oldReplayNumber = currentReplayNumber; } if (currentMatchType != oldMatchType) { matchType.set(currentMatchType); oldMatchType = currentMatchType; } if (currentControlWord != oldControlWord) { controlWord.set(currentControlWord); oldControlWord = currentControlWord; } } } private static class JoystickLogSender { JoystickLogSender(DataLog log, int stick, long timestamp) { m_stick = stick; m_logButtons = new BooleanArrayLogEntry(log, "DS:joystick" + stick + "/buttons", timestamp); m_logAxes = new FloatArrayLogEntry(log, "DS:joystick" + stick + "/axes", timestamp); m_logPOVs = new IntegerArrayLogEntry(log, "DS:joystick" + stick + "/povs", timestamp); appendButtons(m_joystickButtons[m_stick], timestamp); appendAxes(m_joystickAxes[m_stick], timestamp); appendPOVs(m_joystickPOVs[m_stick], timestamp); } public void send(long timestamp) { HALJoystickButtons buttons = m_joystickButtons[m_stick]; if (buttons.m_available != m_prevButtons.m_available || buttons.m_buttons != m_prevButtons.m_buttons) { appendButtons(buttons, timestamp); } HALJoystickAxes axes = m_joystickAxes[m_stick]; int available = axes.m_available; boolean needToLog = false; if (available != m_prevAxes.m_available) { needToLog = true; } else { for (int i = 0; i < m_prevAxes.m_axes.length; i++) { if (axes.m_axes[i] != m_prevAxes.m_axes[i]) { needToLog = true; break; } } } if (needToLog) { appendAxes(axes, timestamp); } HALJoystickPOVs povs = m_joystickPOVs[m_stick]; available = m_joystickPOVs[m_stick].m_available; needToLog = false; if (available != m_prevPOVs.m_available) { needToLog = true; } else { for (int i = 0; i < m_prevPOVs.m_povs.length; i++) { if (povs.m_povs[i] != m_prevPOVs.m_povs[i]) { needToLog = true; break; } } } if (needToLog) { appendPOVs(povs, timestamp); } } void appendButtons(HALJoystickButtons buttons, long timestamp) { int count = availableToCount(buttons.m_available); if (m_sizedButtons == null || m_sizedButtons.length != count) { m_sizedButtons = new boolean[count]; } long buttonsValue = buttons.m_buttons; for (int i = 0; i < count; i++) { m_sizedButtons[i] = (buttonsValue & (1L << i)) != 0; } m_logButtons.append(m_sizedButtons, timestamp); m_prevButtons.m_available = buttons.m_available; m_prevButtons.m_buttons = buttons.m_buttons; } void appendAxes(HALJoystickAxes axes, long timestamp) { int count = availableToCount(axes.m_available); if (m_sizedAxes == null || m_sizedAxes.length != count) { m_sizedAxes = new float[count]; } System.arraycopy(axes.m_axes, 0, m_sizedAxes, 0, count); m_logAxes.append(m_sizedAxes, timestamp); m_prevAxes.m_available = axes.m_available; System.arraycopy(axes.m_axes, 0, m_prevAxes.m_axes, 0, count); } void appendPOVs(HALJoystickPOVs povs, long timestamp) { int count = availableToCount(povs.m_available); if (m_sizedPOVs == null || m_sizedPOVs.length != count) { m_sizedPOVs = new long[count]; } for (int i = 0; i < count; i++) { m_sizedPOVs[i] = povs.m_povs[i]; } m_logPOVs.append(m_sizedPOVs, timestamp); m_prevPOVs.m_available = povs.m_available; System.arraycopy(povs.m_povs, 0, m_prevPOVs.m_povs, 0, count); } final int m_stick; boolean[] m_sizedButtons; float[] m_sizedAxes; long[] m_sizedPOVs; final HALJoystickButtons m_prevButtons = new HALJoystickButtons(); final HALJoystickAxes m_prevAxes = new HALJoystickAxes(DriverStationJNI.kMaxJoystickAxes); final HALJoystickPOVs m_prevPOVs = new HALJoystickPOVs(DriverStationJNI.kMaxJoystickPOVs); final BooleanArrayLogEntry m_logButtons; final FloatArrayLogEntry m_logAxes; final IntegerArrayLogEntry m_logPOVs; } private static class DataLogSender { DataLogSender(DataLog log, boolean logJoysticks, long timestamp) { m_logEnabled = new BooleanLogEntry(log, "DS:enabled", timestamp); m_logAutonomous = new BooleanLogEntry(log, "DS:autonomous", timestamp); m_logTest = new BooleanLogEntry(log, "DS:test", timestamp); m_logEstop = new BooleanLogEntry(log, "DS:estop", timestamp); // append initial control word values m_wasEnabled = m_controlWordCache.getEnabled(); m_wasAutonomous = m_controlWordCache.getAutonomous(); m_wasTest = m_controlWordCache.getTest(); m_wasEstop = m_controlWordCache.getEStop(); m_logEnabled.append(m_wasEnabled, timestamp); m_logAutonomous.append(m_wasAutonomous, timestamp); m_logTest.append(m_wasTest, timestamp); m_logEstop.append(m_wasEstop, timestamp); if (logJoysticks) { m_joysticks = new JoystickLogSender[kJoystickPorts]; for (int i = 0; i < kJoystickPorts; i++) { m_joysticks[i] = new JoystickLogSender(log, i, timestamp); } } else { m_joysticks = new JoystickLogSender[0]; } } public void send(long timestamp) { // append control word value changes boolean enabled = m_controlWordCache.getEnabled(); if (enabled != m_wasEnabled) { m_logEnabled.append(enabled, timestamp); } m_wasEnabled = enabled; boolean autonomous = m_controlWordCache.getAutonomous(); if (autonomous != m_wasAutonomous) { m_logAutonomous.append(autonomous, timestamp); } m_wasAutonomous = autonomous; boolean test = m_controlWordCache.getTest(); if (test != m_wasTest) { m_logTest.append(test, timestamp); } m_wasTest = test; boolean estop = m_controlWordCache.getEStop(); if (estop != m_wasEstop) { m_logEstop.append(estop, timestamp); } m_wasEstop = estop; // append joystick value changes for (JoystickLogSender joystick : m_joysticks) { joystick.send(timestamp); } } boolean m_wasEnabled; boolean m_wasAutonomous; boolean m_wasTest; boolean m_wasEstop; final BooleanLogEntry m_logEnabled; final BooleanLogEntry m_logAutonomous; final BooleanLogEntry m_logTest; final BooleanLogEntry m_logEstop; final JoystickLogSender[] m_joysticks; } // Joystick User Data private static HALJoystickAxes[] m_joystickAxes = new HALJoystickAxes[kJoystickPorts]; private static HALJoystickAxesRaw[] m_joystickAxesRaw = new HALJoystickAxesRaw[kJoystickPorts]; private static HALJoystickPOVs[] m_joystickPOVs = new HALJoystickPOVs[kJoystickPorts]; private static HALJoystickButtons[] m_joystickButtons = new HALJoystickButtons[kJoystickPorts]; private static MatchInfoData m_matchInfo = new MatchInfoData(); private static ControlWord m_controlWord = new ControlWord(); private static EventVector m_refreshEvents = new EventVector(); // Joystick Cached Data private static HALJoystickAxes[] m_joystickAxesCache = new HALJoystickAxes[kJoystickPorts]; private static HALJoystickAxesRaw[] m_joystickAxesRawCache = new HALJoystickAxesRaw[kJoystickPorts]; private static HALJoystickPOVs[] m_joystickPOVsCache = new HALJoystickPOVs[kJoystickPorts]; private static HALJoystickButtons[] m_joystickButtonsCache = new HALJoystickButtons[kJoystickPorts]; private static MatchInfoData m_matchInfoCache = new MatchInfoData(); private static ControlWord m_controlWordCache = new ControlWord(); // Joystick button rising/falling edge flags private static long[] m_joystickButtonsPressed = new long[kJoystickPorts]; private static long[] m_joystickButtonsReleased = new long[kJoystickPorts]; private static final MatchDataSender m_matchDataSender; private static DataLogSender m_dataLogSender; private static final ReentrantLock m_cacheDataMutex = new ReentrantLock(); private static boolean m_silenceJoystickWarning; /** * DriverStation constructor. * *

The single DriverStation instance is created statically with the instance static member * variable. */ private DriverStation() {} static { HAL.initialize(500, 0); for (int i = 0; i < kJoystickPorts; i++) { m_joystickButtons[i] = new HALJoystickButtons(); m_joystickAxes[i] = new HALJoystickAxes(DriverStationJNI.kMaxJoystickAxes); m_joystickAxesRaw[i] = new HALJoystickAxesRaw(DriverStationJNI.kMaxJoystickAxes); m_joystickPOVs[i] = new HALJoystickPOVs(DriverStationJNI.kMaxJoystickPOVs); m_joystickButtonsCache[i] = new HALJoystickButtons(); m_joystickAxesCache[i] = new HALJoystickAxes(DriverStationJNI.kMaxJoystickAxes); m_joystickAxesRawCache[i] = new HALJoystickAxesRaw(DriverStationJNI.kMaxJoystickAxes); m_joystickPOVsCache[i] = new HALJoystickPOVs(DriverStationJNI.kMaxJoystickPOVs); } m_matchDataSender = new MatchDataSender(); } /** * Report error to Driver Station. Optionally appends Stack trace to error message. * * @param error The error to report. * @param printTrace If true, append stack trace to error string */ public static void reportError(String error, boolean printTrace) { reportErrorImpl(true, 1, error, printTrace); } /** * Report error to Driver Station. Appends provided stack trace to error message. * * @param error The error to report. * @param stackTrace The stack trace to append */ public static void reportError(String error, StackTraceElement[] stackTrace) { reportErrorImpl(true, 1, error, stackTrace); } /** * Report warning to Driver Station. Optionally appends Stack trace to warning message. * * @param warning The warning to report. * @param printTrace If true, append stack trace to warning string */ public static void reportWarning(String warning, boolean printTrace) { reportErrorImpl(false, 1, warning, printTrace); } /** * Report warning to Driver Station. Appends provided stack trace to warning message. * * @param warning The warning to report. * @param stackTrace The stack trace to append */ public static void reportWarning(String warning, StackTraceElement[] stackTrace) { reportErrorImpl(false, 1, warning, stackTrace); } private static void reportErrorImpl(boolean isError, int code, String error, boolean printTrace) { reportErrorImpl(isError, code, error, printTrace, Thread.currentThread().getStackTrace(), 3); } private static void reportErrorImpl( boolean isError, int code, String error, StackTraceElement[] stackTrace) { reportErrorImpl(isError, code, error, true, stackTrace, 0); } private static void reportErrorImpl( boolean isError, int code, String error, boolean printTrace, StackTraceElement[] stackTrace, int stackTraceFirst) { String locString; if (stackTrace.length >= stackTraceFirst + 1) { locString = stackTrace[stackTraceFirst].toString(); } else { locString = ""; } StringBuilder traceString = new StringBuilder(); if (printTrace) { boolean haveLoc = false; for (int i = stackTraceFirst; i < stackTrace.length; i++) { String loc = stackTrace[i].toString(); traceString.append("\tat ").append(loc).append('\n'); // get first user function if (!haveLoc && !loc.startsWith("edu.wpi.first")) { locString = loc; haveLoc = true; } } } DriverStationJNI.sendError( isError, code, false, error, locString, traceString.toString(), true); } /** * The state of one joystick button. * * @param stick The joystick to read. * @param button The button index. * @return The state of the joystick button. */ public static boolean getStickButton(final int stick, final int button) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } if (button < 0 || button >= DriverStationJNI.kMaxJoystickButtons) { throw new IllegalArgumentException("Joystick Button is out of range"); } long mask = 1L << button; m_cacheDataMutex.lock(); try { if ((m_joystickButtons[stick].m_available & mask) != 0) { return (m_joystickButtons[stick].m_buttons & mask) != 0; } } finally { m_cacheDataMutex.unlock(); } reportJoystickUnpluggedWarning( "Joystick Button " + button + " on port " + stick + " not available, check if controller is plugged in"); return false; } /** * The state of one joystick button if available. * * @param stick The joystick to read. * @param button The button index. * @return The state of the joystick button, or false if the button is not available. */ public static Optional getStickButtonIfAvailable(final int stick, final int button) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } if (button < 0 || button >= DriverStationJNI.kMaxJoystickButtons) { throw new IllegalArgumentException("Joystick Button is out of range"); } long mask = 1L << button; m_cacheDataMutex.lock(); try { if ((m_joystickButtons[stick].m_available & mask) != 0) { return Optional.of((m_joystickButtons[stick].m_buttons & mask) != 0); } } finally { m_cacheDataMutex.unlock(); } return Optional.empty(); } /** * Whether one joystick button was pressed since the last check. * * @param stick The joystick to read. * @param button The button index. * @return Whether the joystick button was pressed since the last check. */ public static boolean getStickButtonPressed(final int stick, final int button) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } if (button < 0 || button >= DriverStationJNI.kMaxJoystickButtons) { throw new IllegalArgumentException("Joystick Button is out of range"); } long mask = 1L << button; m_cacheDataMutex.lock(); try { if ((m_joystickButtons[stick].m_available & mask) != 0) { // If button was pressed, clear flag and return true if ((m_joystickButtonsPressed[stick] & mask) != 0) { m_joystickButtonsPressed[stick] &= ~mask; return true; } else { return false; } } } finally { m_cacheDataMutex.unlock(); } reportJoystickUnpluggedWarning( "Joystick Button " + button + " on port " + stick + " not available, check if controller is plugged in"); return false; } /** * Whether one joystick button was released since the last check. * * @param stick The joystick to read. * @param button The button index, beginning at 0. * @return Whether the joystick button was released since the last check. */ public static boolean getStickButtonReleased(final int stick, final int button) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } if (button < 0 || button >= DriverStationJNI.kMaxJoystickButtons) { throw new IllegalArgumentException("Joystick Button is out of range"); } long mask = 1L << button; m_cacheDataMutex.lock(); try { if ((m_joystickButtons[stick].m_available & mask) != 0) { // If button was released, clear flag and return true if ((m_joystickButtonsReleased[stick] & mask) != 0) { m_joystickButtonsReleased[stick] &= ~mask; return true; } else { return false; } } } finally { m_cacheDataMutex.unlock(); } reportJoystickUnpluggedWarning( "Joystick Button " + button + " on port " + stick + " not available, check if controller is plugged in"); return false; } /** * Get the value of the axis on a joystick. This depends on the mapping of the joystick connected * to the specified port. * * @param stick The joystick to read. * @param axis The analog axis value to read from the joystick. * @return The value of the axis on the joystick. */ public static double getStickAxis(int stick, int axis) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } if (axis < 0 || axis >= DriverStationJNI.kMaxJoystickAxes) { throw new IllegalArgumentException("Joystick axis is out of range"); } int mask = 1 << axis; m_cacheDataMutex.lock(); try { if ((m_joystickAxes[stick].m_available & mask) != 0) { return m_joystickAxes[stick].m_axes[axis]; } } finally { m_cacheDataMutex.unlock(); } reportJoystickUnpluggedWarning( "Joystick axis " + axis + " on port " + stick + " not available, check if controller is plugged in"); return 0.0; } /** * Get the value of the axis on a joystick if available. This depends on the mapping of the * joystick connected to the specified port. * * @param stick The joystick to read. * @param axis The analog axis value to read from the joystick. * @return The value of the axis on the joystick, or 0 if the axis is not available. */ public static OptionalDouble getStickAxisIfAvailable(int stick, int axis) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } if (axis < 0 || axis >= DriverStationJNI.kMaxJoystickAxes) { throw new IllegalArgumentException("Joystick axis is out of range"); } int mask = 1 << axis; m_cacheDataMutex.lock(); try { if ((m_joystickAxes[stick].m_available & mask) != 0) { return OptionalDouble.of(m_joystickAxes[stick].m_axes[axis]); } } finally { m_cacheDataMutex.unlock(); } return OptionalDouble.empty(); } /** * Get the state of a POV on the joystick. * * @param stick The joystick to read. * @param pov The POV to read. * @return the angle of the POV. */ public static POVDirection getStickPOV(int stick, int pov) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } if (pov < 0 || pov >= DriverStationJNI.kMaxJoystickPOVs) { throw new IllegalArgumentException("Joystick POV is out of range"); } int mask = 1 << pov; m_cacheDataMutex.lock(); try { if ((m_joystickPOVs[stick].m_available & mask) != 0) { return POVDirection.of(m_joystickPOVs[stick].m_povs[pov]); } } finally { m_cacheDataMutex.unlock(); } reportJoystickUnpluggedWarning( "Joystick POV " + pov + " on port " + stick + " not available, check if controller is plugged in"); return POVDirection.Center; } /** * The state of the buttons on the joystick. * * @param stick The joystick to read. * @return The state of the buttons on the joystick. */ public static long getStickButtons(final int stick) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } m_cacheDataMutex.lock(); try { return m_joystickButtons[stick].m_buttons; } finally { m_cacheDataMutex.unlock(); } } /** * Gets the maximum index of axes on a given joystick port. * * @param stick The joystick port number * @return The maximum index of axes on the indicated joystick */ public static int getStickAxesMaximumIndex(int stick) { return availableToCount(getStickAxesAvailable(stick)); } /** * Returns the available bitmask of axes on a given joystick port. * * @param stick The joystick port number * @return The number of axes available on the indicated joystick */ public static int getStickAxesAvailable(int stick) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } m_cacheDataMutex.lock(); try { return m_joystickAxes[stick].m_available; } finally { m_cacheDataMutex.unlock(); } } /** * Gets the maximum index of povs on a given joystick port. * * @param stick The joystick port number * @return The maximum index of povs on the indicated joystick */ public static int getStickPOVsMaximumIndex(int stick) { return availableToCount(getStickPOVsAvailable(stick)); } /** * Returns the available bitmask of povs on a given joystick port. * * @param stick The joystick port number * @return The number of povs available on the indicated joystick */ public static int getStickPOVsAvailable(int stick) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } m_cacheDataMutex.lock(); try { return m_joystickPOVs[stick].m_available; } finally { m_cacheDataMutex.unlock(); } } /** * Gets the maximum index of buttons on a given joystick port. * * @param stick The joystick port number * @return The maximum index of buttons on the indicated joystick */ public static int getStickButtonsMaximumIndex(int stick) { return availableToCount(getStickButtonsAvailable(stick)); } /** * Gets the bitmask of buttons available. * * @param stick The joystick port number * @return The buttons available on the indicated joystick */ public static long getStickButtonsAvailable(int stick) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } m_cacheDataMutex.lock(); try { return m_joystickButtons[stick].m_available; } finally { m_cacheDataMutex.unlock(); } } /** * Gets the value of isGamepad on a joystick. * * @param stick The joystick port number * @return A boolean that returns the value of isGamepad */ public static boolean getJoystickIsGamepad(int stick) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } return DriverStationJNI.getJoystickIsGamepad((byte) stick) == 1; } /** * Gets the value of type on a joystick. * * @param stick The joystick port number * @return The value of type */ public static int getJoystickType(int stick) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } return DriverStationJNI.getJoystickType((byte) stick); } /** * Gets the name of the joystick at a port. * * @param stick The joystick port number * @return The value of name */ public static String getJoystickName(int stick) { if (stick < 0 || stick >= kJoystickPorts) { throw new IllegalArgumentException("Joystick index is out of range, should be 0-5"); } return DriverStationJNI.getJoystickName((byte) stick); } /** * Returns if a joystick is connected to the Driver Station. * *

This makes a best effort guess by looking at the reported number of axis, buttons, and POVs * attached. * * @param stick The joystick port number * @return true if a joystick is connected */ public static boolean isJoystickConnected(int stick) { return getStickAxesAvailable(stick) != 0 || getStickButtonsAvailable(stick) != 0 || getStickPOVsAvailable(stick) != 0; } /** * Gets a value indicating whether the Driver Station requires the robot to be enabled. * * @return True if the robot is enabled, false otherwise. */ public static boolean isEnabled() { m_cacheDataMutex.lock(); try { return m_controlWord.getEnabled() && m_controlWord.getDSAttached(); } finally { m_cacheDataMutex.unlock(); } } /** * Gets a value indicating whether the Driver Station requires the robot to be disabled. * * @return True if the robot should be disabled, false otherwise. */ public static boolean isDisabled() { return !isEnabled(); } /** * Gets a value indicating whether the Robot is e-stopped. * * @return True if the robot is e-stopped, false otherwise. */ public static boolean isEStopped() { m_cacheDataMutex.lock(); try { return m_controlWord.getEStop(); } finally { m_cacheDataMutex.unlock(); } } /** * Gets a value indicating whether the Driver Station requires the robot to be running in * autonomous mode. * * @return True if autonomous mode should be enabled, false otherwise. */ public static boolean isAutonomous() { m_cacheDataMutex.lock(); try { return m_controlWord.getAutonomous(); } finally { m_cacheDataMutex.unlock(); } } /** * Gets a value indicating whether the Driver Station requires the robot to be running in * autonomous mode and enabled. * * @return True if autonomous should be set and the robot should be enabled. */ public static boolean isAutonomousEnabled() { m_cacheDataMutex.lock(); try { return m_controlWord.getAutonomous() && m_controlWord.getEnabled(); } finally { m_cacheDataMutex.unlock(); } } /** * Gets a value indicating whether the Driver Station requires the robot to be running in * operator-controlled mode. * * @return True if operator-controlled mode should be enabled, false otherwise. */ public static boolean isTeleop() { return !(isAutonomous() || isTest()); } /** * Gets a value indicating whether the Driver Station requires the robot to be running in * operator-controller mode and enabled. * * @return True if operator-controlled mode should be set and the robot should be enabled. */ public static boolean isTeleopEnabled() { m_cacheDataMutex.lock(); try { return !m_controlWord.getAutonomous() && !m_controlWord.getTest() && m_controlWord.getEnabled(); } finally { m_cacheDataMutex.unlock(); } } /** * Gets a value indicating whether the Driver Station requires the robot to be running in Test * mode. * * @return True if test mode should be enabled, false otherwise. */ public static boolean isTest() { m_cacheDataMutex.lock(); try { return m_controlWord.getTest(); } finally { m_cacheDataMutex.unlock(); } } /** * Gets a value indicating whether the Driver Station requires the robot to be running in Test * mode and enabled. * * @return True if test mode should be set and the robot should be enabled. */ public static boolean isTestEnabled() { m_cacheDataMutex.lock(); try { return m_controlWord.getTest() && m_controlWord.getEnabled(); } finally { m_cacheDataMutex.unlock(); } } /** * Gets a value indicating whether the Driver Station is attached. * * @return True if Driver Station is attached, false otherwise. */ public static boolean isDSAttached() { m_cacheDataMutex.lock(); try { return m_controlWord.getDSAttached(); } finally { m_cacheDataMutex.unlock(); } } /** * Gets if the driver station attached to a Field Management System. * * @return true if the robot is competing on a field being controlled by a Field Management System */ public static boolean isFMSAttached() { m_cacheDataMutex.lock(); try { return m_controlWord.getFMSAttached(); } finally { m_cacheDataMutex.unlock(); } } /** * Get the game specific message from the FMS. * *

If the FMS is not connected, it is set from the game data setting on the driver station. * * @return the game specific message */ public static String getGameSpecificMessage() { m_cacheDataMutex.lock(); try { return m_matchInfo.gameSpecificMessage; } finally { m_cacheDataMutex.unlock(); } } /** * Get the event name from the FMS. * * @return the event name */ public static String getEventName() { m_cacheDataMutex.lock(); try { return m_matchInfo.eventName; } finally { m_cacheDataMutex.unlock(); } } /** * Get the match type from the FMS. * * @return the match type */ public static MatchType getMatchType() { int matchType; m_cacheDataMutex.lock(); try { matchType = m_matchInfo.matchType; } finally { m_cacheDataMutex.unlock(); } return switch (matchType) { case 1 -> MatchType.Practice; case 2 -> MatchType.Qualification; case 3 -> MatchType.Elimination; default -> MatchType.None; }; } /** * Get the match number from the FMS. * * @return the match number */ public static int getMatchNumber() { m_cacheDataMutex.lock(); try { return m_matchInfo.matchNumber; } finally { m_cacheDataMutex.unlock(); } } /** * Get the replay number from the FMS. * * @return the replay number */ public static int getReplayNumber() { m_cacheDataMutex.lock(); try { return m_matchInfo.replayNumber; } finally { m_cacheDataMutex.unlock(); } } private static Map> m_allianceMap = Map.of( AllianceStationID.Unknown, Optional.empty(), AllianceStationID.Red1, Optional.of(Alliance.Red), AllianceStationID.Red2, Optional.of(Alliance.Red), AllianceStationID.Red3, Optional.of(Alliance.Red), AllianceStationID.Blue1, Optional.of(Alliance.Blue), AllianceStationID.Blue2, Optional.of(Alliance.Blue), AllianceStationID.Blue3, Optional.of(Alliance.Blue)); private static Map m_stationMap = Map.of( AllianceStationID.Unknown, OptionalInt.empty(), AllianceStationID.Red1, OptionalInt.of(1), AllianceStationID.Red2, OptionalInt.of(2), AllianceStationID.Red3, OptionalInt.of(3), AllianceStationID.Blue1, OptionalInt.of(1), AllianceStationID.Blue2, OptionalInt.of(2), AllianceStationID.Blue3, OptionalInt.of(3)); /** * Get the current alliance from the FMS. * *

If the FMS is not connected, it is set from the team alliance setting on the driver station. * * @return The alliance (red or blue) or an empty optional if the alliance is invalid */ public static Optional getAlliance() { AllianceStationID allianceStationID = DriverStationJNI.getAllianceStation(); if (allianceStationID == null) { allianceStationID = AllianceStationID.Unknown; } return m_allianceMap.get(allianceStationID); } /** * Gets the location of the team's driver station controls from the FMS. * *

If the FMS is not connected, it is set from the team alliance setting on the driver station. * * @return the location of the team's driver station controls: 1, 2, or 3 */ public static OptionalInt getLocation() { AllianceStationID allianceStationID = DriverStationJNI.getAllianceStation(); if (allianceStationID == null) { allianceStationID = AllianceStationID.Unknown; } return m_stationMap.get(allianceStationID); } /** * Gets the raw alliance station of the teams driver station. * *

This returns the raw low level value. Prefer getLocation or getAlliance unless necessary for * performance. * * @return The raw alliance station id. */ public static AllianceStationID getRawAllianceStation() { return DriverStationJNI.getAllianceStation(); } /** * Return the approximate match time. The FMS does not send an official match time to the robots, * but does send an approximate match time. The value will count down the time remaining in the * current period (auto or teleop). Warning: This is not an official time (so it cannot be used to * dispute ref calls or guarantee that a function will trigger before the match ends). * *

When connected to the real field, this number only changes in full integer increments, and * always counts down. * *

When the DS is in practice mode, this number is a floating point number, and counts down. * *

When the DS is in teleop or autonomous mode, this number is a floating point number, and * counts up. * *

Simulation matches DS behavior without an FMS connected. * * @return Time remaining in current match period (auto or teleop) in seconds */ public static double getMatchTime() { return DriverStationJNI.getMatchTime(); } /** * Allows the user to specify whether they want joystick connection warnings to be printed to the * console. This setting is ignored when the FMS is connected -- warnings will always be on in * that scenario. * * @param silence Whether warning messages should be silenced. */ public static void silenceJoystickConnectionWarning(boolean silence) { m_silenceJoystickWarning = silence; } /** * Returns whether joystick connection warnings are silenced. This will always return false when * connected to the FMS. * * @return Whether joystick connection warnings are silenced. */ public static boolean isJoystickConnectionWarningSilenced() { return !isFMSAttached() && m_silenceJoystickWarning; } /** * Refresh the passed in control word to contain the current control word cache. * * @param word Word to refresh. */ public static void refreshControlWordFromCache(ControlWord word) { m_cacheDataMutex.lock(); try { word.update(m_controlWord); } finally { m_cacheDataMutex.unlock(); } } /** * 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. */ public static void refreshData() { DriverStationJNI.refreshDSData(); // Get the status of all the joysticks for (byte stick = 0; stick < kJoystickPorts; stick++) { DriverStationJNI.getAllJoystickData( stick, m_joystickAxesCache[stick].m_axes, m_joystickAxesRawCache[stick].m_axes, m_joystickPOVsCache[stick].m_povs, m_metadataCache); m_joystickAxesCache[stick].m_available = (int) m_metadataCache[0]; m_joystickAxesRawCache[stick].m_available = (int) m_metadataCache[0]; m_joystickPOVsCache[stick].m_available = (int) m_metadataCache[1]; m_joystickButtonsCache[stick].m_available = m_metadataCache[2]; m_joystickButtonsCache[stick].m_buttons = m_metadataCache[3]; } DriverStationJNI.getMatchInfo(m_matchInfoCache); DriverStationJNI.getControlWord(m_controlWordCache); DataLogSender dataLogSender; // lock joystick mutex to swap cache data m_cacheDataMutex.lock(); try { for (int i = 0; i < kJoystickPorts; i++) { // If buttons weren't pressed and are now, set flags in m_buttonsPressed m_joystickButtonsPressed[i] |= ~m_joystickButtons[i].m_buttons & m_joystickButtonsCache[i].m_buttons; // If buttons were pressed and aren't now, set flags in m_buttonsReleased m_joystickButtonsReleased[i] |= m_joystickButtons[i].m_buttons & ~m_joystickButtonsCache[i].m_buttons; } // move cache to actual data HALJoystickAxes[] currentAxes = m_joystickAxes; m_joystickAxes = m_joystickAxesCache; m_joystickAxesCache = currentAxes; HALJoystickAxesRaw[] currentAxesRaw = m_joystickAxesRaw; m_joystickAxesRaw = m_joystickAxesRawCache; m_joystickAxesRawCache = currentAxesRaw; HALJoystickButtons[] currentButtons = m_joystickButtons; m_joystickButtons = m_joystickButtonsCache; m_joystickButtonsCache = currentButtons; HALJoystickPOVs[] currentPOVs = m_joystickPOVs; m_joystickPOVs = m_joystickPOVsCache; m_joystickPOVsCache = currentPOVs; MatchInfoData currentInfo = m_matchInfo; m_matchInfo = m_matchInfoCache; m_matchInfoCache = currentInfo; ControlWord currentWord = m_controlWord; m_controlWord = m_controlWordCache; m_controlWordCache = currentWord; dataLogSender = m_dataLogSender; } finally { m_cacheDataMutex.unlock(); } m_refreshEvents.wakeup(); m_matchDataSender.sendMatchData(); if (dataLogSender != null) { dataLogSender.send(WPIUtilJNI.now()); } } /** * Registers the given handle for DS data refresh notifications. * * @param handle The event handle. */ public static void provideRefreshedDataEventHandle(int handle) { m_refreshEvents.add(handle); } /** * Unregisters the given handle from DS data refresh notifications. * * @param handle The event handle. */ public static void removeRefreshedDataEventHandle(int handle) { m_refreshEvents.remove(handle); } /** * Reports errors related to unplugged joysticks Throttles the errors so that they don't overwhelm * the DS. */ private static void reportJoystickUnpluggedWarning(String message) { if (isFMSAttached() || !m_silenceJoystickWarning) { double currentTime = Timer.getTimestamp(); if (currentTime > m_nextMessageTime) { reportWarning(message, false); m_nextMessageTime = currentTime + JOYSTICK_UNPLUGGED_MESSAGE_INTERVAL; } } } /** * Starts logging DriverStation data to data log. Repeated calls are ignored. * * @param log data log * @param logJoysticks if true, log joystick data */ @SuppressWarnings("PMD.NonThreadSafeSingleton") public static void startDataLog(DataLog log, boolean logJoysticks) { m_cacheDataMutex.lock(); try { if (m_dataLogSender == null) { m_dataLogSender = new DataLogSender(log, logJoysticks, WPIUtilJNI.now()); } } finally { m_cacheDataMutex.unlock(); } } /** * Starts logging DriverStation data to data log, including joystick data. Repeated calls are * ignored. * * @param log data log */ public static void startDataLog(DataLog log) { startDataLog(log, true); } }