001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj;
006
007import edu.wpi.first.hal.AllianceStationID;
008import edu.wpi.first.hal.ControlWord;
009import edu.wpi.first.hal.DriverStationJNI;
010import edu.wpi.first.hal.HAL;
011import edu.wpi.first.hal.MatchInfoData;
012import edu.wpi.first.networktables.BooleanPublisher;
013import edu.wpi.first.networktables.IntegerPublisher;
014import edu.wpi.first.networktables.NetworkTable;
015import edu.wpi.first.networktables.NetworkTableInstance;
016import edu.wpi.first.networktables.StringPublisher;
017import edu.wpi.first.util.EventVector;
018import edu.wpi.first.util.WPIUtilJNI;
019import edu.wpi.first.util.datalog.BooleanArrayLogEntry;
020import edu.wpi.first.util.datalog.BooleanLogEntry;
021import edu.wpi.first.util.datalog.DataLog;
022import edu.wpi.first.util.datalog.FloatArrayLogEntry;
023import edu.wpi.first.util.datalog.IntegerArrayLogEntry;
024import java.nio.ByteBuffer;
025import java.util.concurrent.locks.ReentrantLock;
026
027/** Provide access to the network communication data to / from the Driver Station. */
028public final class DriverStation {
029  /** Number of Joystick Ports. */
030  public static final int kJoystickPorts = 6;
031
032  private static class HALJoystickButtons {
033    public int m_buttons;
034    public byte m_count;
035  }
036
037  private static class HALJoystickAxes {
038    public float[] m_axes;
039    public int m_count;
040
041    HALJoystickAxes(int count) {
042      m_axes = new float[count];
043    }
044  }
045
046  private static class HALJoystickAxesRaw {
047    public int[] m_axes;
048    public int m_count;
049
050    HALJoystickAxesRaw(int count) {
051      m_axes = new int[count];
052    }
053  }
054
055  private static class HALJoystickPOVs {
056    public short[] m_povs;
057    public int m_count;
058
059    HALJoystickPOVs(int count) {
060      m_povs = new short[count];
061      for (int i = 0; i < count; i++) {
062        m_povs[i] = -1;
063      }
064    }
065  }
066
067  /** The robot alliance that the robot is a part of. */
068  public enum Alliance {
069    Red,
070    Blue,
071    Invalid
072  }
073
074  public enum MatchType {
075    None,
076    Practice,
077    Qualification,
078    Elimination
079  }
080
081  private static final double JOYSTICK_UNPLUGGED_MESSAGE_INTERVAL = 1.0;
082  private static double m_nextMessageTime;
083
084  @SuppressWarnings("MemberName")
085  private static class MatchDataSender {
086    NetworkTable table;
087    StringPublisher typeMetadata;
088    StringPublisher gameSpecificMessage;
089    StringPublisher eventName;
090    IntegerPublisher matchNumber;
091    IntegerPublisher replayNumber;
092    IntegerPublisher matchType;
093    BooleanPublisher alliance;
094    IntegerPublisher station;
095    IntegerPublisher controlWord;
096    boolean oldIsRedAlliance = true;
097    int oldStationNumber = 1;
098    String oldEventName = "";
099    String oldGameSpecificMessage = "";
100    int oldMatchNumber;
101    int oldReplayNumber;
102    int oldMatchType;
103    int oldControlWord;
104
105    MatchDataSender() {
106      table = NetworkTableInstance.getDefault().getTable("FMSInfo");
107      typeMetadata = table.getStringTopic(".type").publish();
108      typeMetadata.set("FMSInfo");
109      gameSpecificMessage = table.getStringTopic("GameSpecificMessage").publish();
110      gameSpecificMessage.set("");
111      eventName = table.getStringTopic("EventName").publish();
112      eventName.set("");
113      matchNumber = table.getIntegerTopic("MatchNumber").publish();
114      matchNumber.set(0);
115      replayNumber = table.getIntegerTopic("ReplayNumber").publish();
116      replayNumber.set(0);
117      matchType = table.getIntegerTopic("MatchType").publish();
118      matchType.set(0);
119      alliance = table.getBooleanTopic("IsRedAlliance").publish();
120      alliance.set(true);
121      station = table.getIntegerTopic("StationNumber").publish();
122      station.set(1);
123      controlWord = table.getIntegerTopic("FMSControlData").publish();
124      controlWord.set(0);
125    }
126
127    private void sendMatchData() {
128      AllianceStationID allianceID = DriverStationJNI.getAllianceStation();
129      boolean isRedAlliance = false;
130      int stationNumber = 1;
131      switch (allianceID) {
132        case Blue1:
133          isRedAlliance = false;
134          stationNumber = 1;
135          break;
136        case Blue2:
137          isRedAlliance = false;
138          stationNumber = 2;
139          break;
140        case Blue3:
141          isRedAlliance = false;
142          stationNumber = 3;
143          break;
144        case Red1:
145          isRedAlliance = true;
146          stationNumber = 1;
147          break;
148        case Red2:
149          isRedAlliance = true;
150          stationNumber = 2;
151          break;
152        default:
153          isRedAlliance = true;
154          stationNumber = 3;
155          break;
156      }
157
158      String currentEventName;
159      String currentGameSpecificMessage;
160      int currentMatchNumber;
161      int currentReplayNumber;
162      int currentMatchType;
163      int currentControlWord;
164      m_cacheDataMutex.lock();
165      try {
166        currentEventName = DriverStation.m_matchInfo.eventName;
167        currentGameSpecificMessage = DriverStation.m_matchInfo.gameSpecificMessage;
168        currentMatchNumber = DriverStation.m_matchInfo.matchNumber;
169        currentReplayNumber = DriverStation.m_matchInfo.replayNumber;
170        currentMatchType = DriverStation.m_matchInfo.matchType;
171      } finally {
172        m_cacheDataMutex.unlock();
173      }
174      currentControlWord = DriverStationJNI.nativeGetControlWord();
175
176      if (oldIsRedAlliance != isRedAlliance) {
177        alliance.set(isRedAlliance);
178        oldIsRedAlliance = isRedAlliance;
179      }
180      if (oldStationNumber != stationNumber) {
181        station.set(stationNumber);
182        oldStationNumber = stationNumber;
183      }
184      if (!oldEventName.equals(currentEventName)) {
185        eventName.set(currentEventName);
186        oldEventName = currentEventName;
187      }
188      if (!oldGameSpecificMessage.equals(currentGameSpecificMessage)) {
189        gameSpecificMessage.set(currentGameSpecificMessage);
190        oldGameSpecificMessage = currentGameSpecificMessage;
191      }
192      if (currentMatchNumber != oldMatchNumber) {
193        matchNumber.set(currentMatchNumber);
194        oldMatchNumber = currentMatchNumber;
195      }
196      if (currentReplayNumber != oldReplayNumber) {
197        replayNumber.set(currentReplayNumber);
198        oldReplayNumber = currentReplayNumber;
199      }
200      if (currentMatchType != oldMatchType) {
201        matchType.set(currentMatchType);
202        oldMatchType = currentMatchType;
203      }
204      if (currentControlWord != oldControlWord) {
205        controlWord.set(currentControlWord);
206        oldControlWord = currentControlWord;
207      }
208    }
209  }
210
211  private static class JoystickLogSender {
212    JoystickLogSender(DataLog log, int stick, long timestamp) {
213      m_stick = stick;
214
215      m_logButtons = new BooleanArrayLogEntry(log, "DS:joystick" + stick + "/buttons", timestamp);
216      m_logAxes = new FloatArrayLogEntry(log, "DS:joystick" + stick + "/axes", timestamp);
217      m_logPOVs = new IntegerArrayLogEntry(log, "DS:joystick" + stick + "/povs", timestamp);
218
219      appendButtons(m_joystickButtons[m_stick], timestamp);
220      appendAxes(m_joystickAxes[m_stick], timestamp);
221      appendPOVs(m_joystickPOVs[m_stick], timestamp);
222    }
223
224    public void send(long timestamp) {
225      HALJoystickButtons buttons = m_joystickButtons[m_stick];
226      if (buttons.m_count != m_prevButtons.m_count
227          || buttons.m_buttons != m_prevButtons.m_buttons) {
228        appendButtons(buttons, timestamp);
229      }
230
231      HALJoystickAxes axes = m_joystickAxes[m_stick];
232      int count = axes.m_count;
233      boolean needToLog = false;
234      if (count != m_prevAxes.m_count) {
235        needToLog = true;
236      } else {
237        for (int i = 0; i < count; i++) {
238          if (axes.m_axes[i] != m_prevAxes.m_axes[i]) {
239            needToLog = true;
240          }
241        }
242      }
243      if (needToLog) {
244        appendAxes(axes, timestamp);
245      }
246
247      HALJoystickPOVs povs = m_joystickPOVs[m_stick];
248      count = m_joystickPOVs[m_stick].m_count;
249      needToLog = false;
250      if (count != m_prevPOVs.m_count) {
251        needToLog = true;
252      } else {
253        for (int i = 0; i < count; i++) {
254          if (povs.m_povs[i] != m_prevPOVs.m_povs[i]) {
255            needToLog = true;
256          }
257        }
258      }
259      if (needToLog) {
260        appendPOVs(povs, timestamp);
261      }
262    }
263
264    void appendButtons(HALJoystickButtons buttons, long timestamp) {
265      byte count = buttons.m_count;
266      if (m_sizedButtons == null || m_sizedButtons.length != count) {
267        m_sizedButtons = new boolean[count];
268      }
269      int buttonsValue = buttons.m_buttons;
270      for (int i = 0; i < count; i++) {
271        m_sizedButtons[i] = (buttonsValue & (1 << i)) != 0;
272      }
273      m_logButtons.append(m_sizedButtons, timestamp);
274      m_prevButtons.m_count = count;
275      m_prevButtons.m_buttons = buttons.m_buttons;
276    }
277
278    void appendAxes(HALJoystickAxes axes, long timestamp) {
279      int count = axes.m_count;
280      if (m_sizedAxes == null || m_sizedAxes.length != count) {
281        m_sizedAxes = new float[count];
282      }
283      System.arraycopy(axes.m_axes, 0, m_sizedAxes, 0, count);
284      m_logAxes.append(m_sizedAxes, timestamp);
285      m_prevAxes.m_count = count;
286      System.arraycopy(axes.m_axes, 0, m_prevAxes.m_axes, 0, count);
287    }
288
289    @SuppressWarnings("PMD.AvoidArrayLoops")
290    void appendPOVs(HALJoystickPOVs povs, long timestamp) {
291      int count = povs.m_count;
292      if (m_sizedPOVs == null || m_sizedPOVs.length != count) {
293        m_sizedPOVs = new long[count];
294      }
295      for (int i = 0; i < count; i++) {
296        m_sizedPOVs[i] = povs.m_povs[i];
297      }
298      m_logPOVs.append(m_sizedPOVs, timestamp);
299      m_prevPOVs.m_count = count;
300      System.arraycopy(povs.m_povs, 0, m_prevPOVs.m_povs, 0, count);
301    }
302
303    final int m_stick;
304    boolean[] m_sizedButtons;
305    float[] m_sizedAxes;
306    long[] m_sizedPOVs;
307    final HALJoystickButtons m_prevButtons = new HALJoystickButtons();
308    final HALJoystickAxes m_prevAxes = new HALJoystickAxes(DriverStationJNI.kMaxJoystickAxes);
309    final HALJoystickPOVs m_prevPOVs = new HALJoystickPOVs(DriverStationJNI.kMaxJoystickPOVs);
310    final BooleanArrayLogEntry m_logButtons;
311    final FloatArrayLogEntry m_logAxes;
312    final IntegerArrayLogEntry m_logPOVs;
313  }
314
315  private static class DataLogSender {
316    DataLogSender(DataLog log, boolean logJoysticks, long timestamp) {
317      m_logEnabled = new BooleanLogEntry(log, "DS:enabled", timestamp);
318      m_logAutonomous = new BooleanLogEntry(log, "DS:autonomous", timestamp);
319      m_logTest = new BooleanLogEntry(log, "DS:test", timestamp);
320      m_logEstop = new BooleanLogEntry(log, "DS:estop", timestamp);
321
322      // append initial control word values
323      m_wasEnabled = m_controlWordCache.getEnabled();
324      m_wasAutonomous = m_controlWordCache.getAutonomous();
325      m_wasTest = m_controlWordCache.getTest();
326      m_wasEstop = m_controlWordCache.getEStop();
327
328      m_logEnabled.append(m_wasEnabled, timestamp);
329      m_logAutonomous.append(m_wasAutonomous, timestamp);
330      m_logTest.append(m_wasTest, timestamp);
331      m_logEstop.append(m_wasEstop, timestamp);
332
333      if (logJoysticks) {
334        m_joysticks = new JoystickLogSender[kJoystickPorts];
335        for (int i = 0; i < kJoystickPorts; i++) {
336          m_joysticks[i] = new JoystickLogSender(log, i, timestamp);
337        }
338      } else {
339        m_joysticks = new JoystickLogSender[0];
340      }
341    }
342
343    public void send(long timestamp) {
344      // append control word value changes
345      boolean enabled = m_controlWordCache.getEnabled();
346      if (enabled != m_wasEnabled) {
347        m_logEnabled.append(enabled, timestamp);
348      }
349      m_wasEnabled = enabled;
350
351      boolean autonomous = m_controlWordCache.getAutonomous();
352      if (autonomous != m_wasAutonomous) {
353        m_logAutonomous.append(autonomous, timestamp);
354      }
355      m_wasAutonomous = autonomous;
356
357      boolean test = m_controlWordCache.getTest();
358      if (test != m_wasTest) {
359        m_logTest.append(test, timestamp);
360      }
361      m_wasTest = test;
362
363      boolean estop = m_controlWordCache.getEStop();
364      if (estop != m_wasEstop) {
365        m_logEstop.append(estop, timestamp);
366      }
367      m_wasEstop = estop;
368
369      // append joystick value changes
370      for (JoystickLogSender joystick : m_joysticks) {
371        joystick.send(timestamp);
372      }
373    }
374
375    boolean m_wasEnabled;
376    boolean m_wasAutonomous;
377    boolean m_wasTest;
378    boolean m_wasEstop;
379    final BooleanLogEntry m_logEnabled;
380    final BooleanLogEntry m_logAutonomous;
381    final BooleanLogEntry m_logTest;
382    final BooleanLogEntry m_logEstop;
383
384    final JoystickLogSender[] m_joysticks;
385  }
386
387  // Joystick User Data
388  private static HALJoystickAxes[] m_joystickAxes = new HALJoystickAxes[kJoystickPorts];
389  private static HALJoystickAxesRaw[] m_joystickAxesRaw = new HALJoystickAxesRaw[kJoystickPorts];
390  private static HALJoystickPOVs[] m_joystickPOVs = new HALJoystickPOVs[kJoystickPorts];
391  private static HALJoystickButtons[] m_joystickButtons = new HALJoystickButtons[kJoystickPorts];
392  private static MatchInfoData m_matchInfo = new MatchInfoData();
393  private static ControlWord m_controlWord = new ControlWord();
394  private static EventVector m_refreshEvents = new EventVector();
395
396  // Joystick Cached Data
397  private static HALJoystickAxes[] m_joystickAxesCache = new HALJoystickAxes[kJoystickPorts];
398  private static HALJoystickAxesRaw[] m_joystickAxesRawCache =
399      new HALJoystickAxesRaw[kJoystickPorts];
400  private static HALJoystickPOVs[] m_joystickPOVsCache = new HALJoystickPOVs[kJoystickPorts];
401  private static HALJoystickButtons[] m_joystickButtonsCache =
402      new HALJoystickButtons[kJoystickPorts];
403  private static MatchInfoData m_matchInfoCache = new MatchInfoData();
404  private static ControlWord m_controlWordCache = new ControlWord();
405
406  // Joystick button rising/falling edge flags
407  private static int[] m_joystickButtonsPressed = new int[kJoystickPorts];
408  private static int[] m_joystickButtonsReleased = new int[kJoystickPorts];
409
410  // preallocated byte buffer for button count
411  private static final ByteBuffer m_buttonCountBuffer = ByteBuffer.allocateDirect(1);
412
413  private static final MatchDataSender m_matchDataSender;
414  private static DataLogSender m_dataLogSender;
415
416  private static final ReentrantLock m_cacheDataMutex = new ReentrantLock();
417
418  private static boolean m_silenceJoystickWarning;
419
420  /**
421   * DriverStation constructor.
422   *
423   * <p>The single DriverStation instance is created statically with the instance static member
424   * variable.
425   */
426  private DriverStation() {}
427
428  static {
429    HAL.initialize(500, 0);
430
431    for (int i = 0; i < kJoystickPorts; i++) {
432      m_joystickButtons[i] = new HALJoystickButtons();
433      m_joystickAxes[i] = new HALJoystickAxes(DriverStationJNI.kMaxJoystickAxes);
434      m_joystickAxesRaw[i] = new HALJoystickAxesRaw(DriverStationJNI.kMaxJoystickAxes);
435      m_joystickPOVs[i] = new HALJoystickPOVs(DriverStationJNI.kMaxJoystickPOVs);
436
437      m_joystickButtonsCache[i] = new HALJoystickButtons();
438      m_joystickAxesCache[i] = new HALJoystickAxes(DriverStationJNI.kMaxJoystickAxes);
439      m_joystickAxesRawCache[i] = new HALJoystickAxesRaw(DriverStationJNI.kMaxJoystickAxes);
440      m_joystickPOVsCache[i] = new HALJoystickPOVs(DriverStationJNI.kMaxJoystickPOVs);
441    }
442
443    m_matchDataSender = new MatchDataSender();
444  }
445
446  /**
447   * Report error to Driver Station. Optionally appends Stack trace to error message.
448   *
449   * @param error The error to report.
450   * @param printTrace If true, append stack trace to error string
451   */
452  public static void reportError(String error, boolean printTrace) {
453    reportErrorImpl(true, 1, error, printTrace);
454  }
455
456  /**
457   * Report error to Driver Station. Appends provided stack trace to error message.
458   *
459   * @param error The error to report.
460   * @param stackTrace The stack trace to append
461   */
462  public static void reportError(String error, StackTraceElement[] stackTrace) {
463    reportErrorImpl(true, 1, error, stackTrace);
464  }
465
466  /**
467   * Report warning to Driver Station. Optionally appends Stack trace to warning message.
468   *
469   * @param warning The warning to report.
470   * @param printTrace If true, append stack trace to warning string
471   */
472  public static void reportWarning(String warning, boolean printTrace) {
473    reportErrorImpl(false, 1, warning, printTrace);
474  }
475
476  /**
477   * Report warning to Driver Station. Appends provided stack trace to warning message.
478   *
479   * @param warning The warning to report.
480   * @param stackTrace The stack trace to append
481   */
482  public static void reportWarning(String warning, StackTraceElement[] stackTrace) {
483    reportErrorImpl(false, 1, warning, stackTrace);
484  }
485
486  private static void reportErrorImpl(boolean isError, int code, String error, boolean printTrace) {
487    reportErrorImpl(isError, code, error, printTrace, Thread.currentThread().getStackTrace(), 3);
488  }
489
490  private static void reportErrorImpl(
491      boolean isError, int code, String error, StackTraceElement[] stackTrace) {
492    reportErrorImpl(isError, code, error, true, stackTrace, 0);
493  }
494
495  private static void reportErrorImpl(
496      boolean isError,
497      int code,
498      String error,
499      boolean printTrace,
500      StackTraceElement[] stackTrace,
501      int stackTraceFirst) {
502    String locString;
503    if (stackTrace.length >= stackTraceFirst + 1) {
504      locString = stackTrace[stackTraceFirst].toString();
505    } else {
506      locString = "";
507    }
508    StringBuilder traceString = new StringBuilder();
509    if (printTrace) {
510      boolean haveLoc = false;
511      for (int i = stackTraceFirst; i < stackTrace.length; i++) {
512        String loc = stackTrace[i].toString();
513        traceString.append("\tat ").append(loc).append('\n');
514        // get first user function
515        if (!haveLoc && !loc.startsWith("edu.wpi.first")) {
516          locString = loc;
517          haveLoc = true;
518        }
519      }
520    }
521    DriverStationJNI.sendError(
522        isError, code, false, error, locString, traceString.toString(), true);
523  }
524
525  /**
526   * The state of one joystick button. Button indexes begin at 1.
527   *
528   * @param stick The joystick to read.
529   * @param button The button index, beginning at 1.
530   * @return The state of the joystick button.
531   */
532  public static boolean getStickButton(final int stick, final int button) {
533    if (stick < 0 || stick >= kJoystickPorts) {
534      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
535    }
536    if (button <= 0) {
537      reportJoystickUnpluggedError("Button indexes begin at 1 in WPILib for C++ and Java\n");
538      return false;
539    }
540
541    m_cacheDataMutex.lock();
542    try {
543      if (button <= m_joystickButtons[stick].m_count) {
544        return (m_joystickButtons[stick].m_buttons & 1 << (button - 1)) != 0;
545      }
546    } finally {
547      m_cacheDataMutex.unlock();
548    }
549
550    reportJoystickUnpluggedWarning(
551        "Joystick Button "
552            + button
553            + " on port "
554            + stick
555            + " not available, check if controller is plugged in");
556    return false;
557  }
558
559  /**
560   * Whether one joystick button was pressed since the last check. Button indexes begin at 1.
561   *
562   * @param stick The joystick to read.
563   * @param button The button index, beginning at 1.
564   * @return Whether the joystick button was pressed since the last check.
565   */
566  public static boolean getStickButtonPressed(final int stick, final int button) {
567    if (button <= 0) {
568      reportJoystickUnpluggedError("Button indexes begin at 1 in WPILib for C++ and Java\n");
569      return false;
570    }
571    if (stick < 0 || stick >= kJoystickPorts) {
572      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
573    }
574
575    m_cacheDataMutex.lock();
576    try {
577      if (button <= m_joystickButtons[stick].m_count) {
578        // If button was pressed, clear flag and return true
579        if ((m_joystickButtonsPressed[stick] & 1 << (button - 1)) != 0) {
580          m_joystickButtonsPressed[stick] &= ~(1 << (button - 1));
581          return true;
582        } else {
583          return false;
584        }
585      }
586    } finally {
587      m_cacheDataMutex.unlock();
588    }
589
590    reportJoystickUnpluggedWarning(
591        "Joystick Button "
592            + button
593            + " on port "
594            + stick
595            + " not available, check if controller is plugged in");
596    return false;
597  }
598
599  /**
600   * Whether one joystick button was released since the last check. Button indexes begin at 1.
601   *
602   * @param stick The joystick to read.
603   * @param button The button index, beginning at 1.
604   * @return Whether the joystick button was released since the last check.
605   */
606  public static boolean getStickButtonReleased(final int stick, final int button) {
607    if (button <= 0) {
608      reportJoystickUnpluggedError("Button indexes begin at 1 in WPILib for C++ and Java\n");
609      return false;
610    }
611    if (stick < 0 || stick >= kJoystickPorts) {
612      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
613    }
614
615    m_cacheDataMutex.lock();
616    try {
617      if (button <= m_joystickButtons[stick].m_count) {
618        // If button was released, clear flag and return true
619        if ((m_joystickButtonsReleased[stick] & 1 << (button - 1)) != 0) {
620          m_joystickButtonsReleased[stick] &= ~(1 << (button - 1));
621          return true;
622        } else {
623          return false;
624        }
625      }
626    } finally {
627      m_cacheDataMutex.unlock();
628    }
629
630    reportJoystickUnpluggedWarning(
631        "Joystick Button "
632            + button
633            + " on port "
634            + stick
635            + " not available, check if controller is plugged in");
636    return false;
637  }
638
639  /**
640   * Get the value of the axis on a joystick. This depends on the mapping of the joystick connected
641   * to the specified port.
642   *
643   * @param stick The joystick to read.
644   * @param axis The analog axis value to read from the joystick.
645   * @return The value of the axis on the joystick.
646   */
647  public static double getStickAxis(int stick, int axis) {
648    if (stick < 0 || stick >= kJoystickPorts) {
649      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
650    }
651    if (axis < 0 || axis >= DriverStationJNI.kMaxJoystickAxes) {
652      throw new IllegalArgumentException("Joystick axis is out of range");
653    }
654
655    m_cacheDataMutex.lock();
656    try {
657      if (axis < m_joystickAxes[stick].m_count) {
658        return m_joystickAxes[stick].m_axes[axis];
659      }
660    } finally {
661      m_cacheDataMutex.unlock();
662    }
663
664    reportJoystickUnpluggedWarning(
665        "Joystick axis "
666            + axis
667            + " on port "
668            + stick
669            + " not available, check if controller is plugged in");
670    return 0.0;
671  }
672
673  /**
674   * Get the state of a POV on the joystick.
675   *
676   * @param stick The joystick to read.
677   * @param pov The POV to read.
678   * @return the angle of the POV in degrees, or -1 if the POV is not pressed.
679   */
680  public static int getStickPOV(int stick, int pov) {
681    if (stick < 0 || stick >= kJoystickPorts) {
682      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
683    }
684    if (pov < 0 || pov >= DriverStationJNI.kMaxJoystickPOVs) {
685      throw new IllegalArgumentException("Joystick POV is out of range");
686    }
687
688    m_cacheDataMutex.lock();
689    try {
690      if (pov < m_joystickPOVs[stick].m_count) {
691        return m_joystickPOVs[stick].m_povs[pov];
692      }
693    } finally {
694      m_cacheDataMutex.unlock();
695    }
696
697    reportJoystickUnpluggedWarning(
698        "Joystick POV "
699            + pov
700            + " on port "
701            + stick
702            + " not available, check if controller is plugged in");
703    return -1;
704  }
705
706  /**
707   * The state of the buttons on the joystick.
708   *
709   * @param stick The joystick to read.
710   * @return The state of the buttons on the joystick.
711   */
712  public static int getStickButtons(final int stick) {
713    if (stick < 0 || stick >= kJoystickPorts) {
714      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
715    }
716
717    m_cacheDataMutex.lock();
718    try {
719      return m_joystickButtons[stick].m_buttons;
720    } finally {
721      m_cacheDataMutex.unlock();
722    }
723  }
724
725  /**
726   * Returns the number of axes on a given joystick port.
727   *
728   * @param stick The joystick port number
729   * @return The number of axes on the indicated joystick
730   */
731  public static int getStickAxisCount(int stick) {
732    if (stick < 0 || stick >= kJoystickPorts) {
733      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
734    }
735
736    m_cacheDataMutex.lock();
737    try {
738      return m_joystickAxes[stick].m_count;
739    } finally {
740      m_cacheDataMutex.unlock();
741    }
742  }
743
744  /**
745   * Returns the number of povs on a given joystick port.
746   *
747   * @param stick The joystick port number
748   * @return The number of povs on the indicated joystick
749   */
750  public static int getStickPOVCount(int stick) {
751    if (stick < 0 || stick >= kJoystickPorts) {
752      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
753    }
754
755    m_cacheDataMutex.lock();
756    try {
757      return m_joystickPOVs[stick].m_count;
758    } finally {
759      m_cacheDataMutex.unlock();
760    }
761  }
762
763  /**
764   * Gets the number of buttons on a joystick.
765   *
766   * @param stick The joystick port number
767   * @return The number of buttons on the indicated joystick
768   */
769  public static int getStickButtonCount(int stick) {
770    if (stick < 0 || stick >= kJoystickPorts) {
771      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
772    }
773
774    m_cacheDataMutex.lock();
775    try {
776      return m_joystickButtons[stick].m_count;
777    } finally {
778      m_cacheDataMutex.unlock();
779    }
780  }
781
782  /**
783   * Gets the value of isXbox on a joystick.
784   *
785   * @param stick The joystick port number
786   * @return A boolean that returns the value of isXbox
787   */
788  public static boolean getJoystickIsXbox(int stick) {
789    if (stick < 0 || stick >= kJoystickPorts) {
790      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
791    }
792
793    return DriverStationJNI.getJoystickIsXbox((byte) stick) == 1;
794  }
795
796  /**
797   * Gets the value of type on a joystick.
798   *
799   * @param stick The joystick port number
800   * @return The value of type
801   */
802  public static int getJoystickType(int stick) {
803    if (stick < 0 || stick >= kJoystickPorts) {
804      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
805    }
806
807    return DriverStationJNI.getJoystickType((byte) stick);
808  }
809
810  /**
811   * Gets the name of the joystick at a port.
812   *
813   * @param stick The joystick port number
814   * @return The value of name
815   */
816  public static String getJoystickName(int stick) {
817    if (stick < 0 || stick >= kJoystickPorts) {
818      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
819    }
820
821    return DriverStationJNI.getJoystickName((byte) stick);
822  }
823
824  /**
825   * Returns the types of Axes on a given joystick port.
826   *
827   * @param stick The joystick port number
828   * @param axis The target axis
829   * @return What type of axis the axis is reporting to be
830   */
831  public static int getJoystickAxisType(int stick, int axis) {
832    if (stick < 0 || stick >= kJoystickPorts) {
833      throw new IllegalArgumentException("Joystick index is out of range, should be 0-5");
834    }
835
836    return DriverStationJNI.getJoystickAxisType((byte) stick, (byte) axis);
837  }
838
839  /**
840   * Returns if a joystick is connected to the Driver Station.
841   *
842   * <p>This makes a best effort guess by looking at the reported number of axis, buttons, and POVs
843   * attached.
844   *
845   * @param stick The joystick port number
846   * @return true if a joystick is connected
847   */
848  public static boolean isJoystickConnected(int stick) {
849    return getStickAxisCount(stick) > 0
850        || getStickButtonCount(stick) > 0
851        || getStickPOVCount(stick) > 0;
852  }
853
854  /**
855   * Gets a value indicating whether the Driver Station requires the robot to be enabled.
856   *
857   * @return True if the robot is enabled, false otherwise.
858   */
859  public static boolean isEnabled() {
860    m_cacheDataMutex.lock();
861    try {
862      return m_controlWord.getEnabled() && m_controlWord.getDSAttached();
863    } finally {
864      m_cacheDataMutex.unlock();
865    }
866  }
867
868  /**
869   * Gets a value indicating whether the Driver Station requires the robot to be disabled.
870   *
871   * @return True if the robot should be disabled, false otherwise.
872   */
873  public static boolean isDisabled() {
874    return !isEnabled();
875  }
876
877  /**
878   * Gets a value indicating whether the Robot is e-stopped.
879   *
880   * @return True if the robot is e-stopped, false otherwise.
881   */
882  public static boolean isEStopped() {
883    m_cacheDataMutex.lock();
884    try {
885      return m_controlWord.getEStop();
886    } finally {
887      m_cacheDataMutex.unlock();
888    }
889  }
890
891  /**
892   * Gets a value indicating whether the Driver Station requires the robot to be running in
893   * autonomous mode.
894   *
895   * @return True if autonomous mode should be enabled, false otherwise.
896   */
897  public static boolean isAutonomous() {
898    m_cacheDataMutex.lock();
899    try {
900      return m_controlWord.getAutonomous();
901    } finally {
902      m_cacheDataMutex.unlock();
903    }
904  }
905
906  /**
907   * Gets a value indicating whether the Driver Station requires the robot to be running in
908   * autonomous mode and enabled.
909   *
910   * @return True if autonomous should be set and the robot should be enabled.
911   */
912  public static boolean isAutonomousEnabled() {
913    m_cacheDataMutex.lock();
914    try {
915      return m_controlWord.getAutonomous() && m_controlWord.getEnabled();
916    } finally {
917      m_cacheDataMutex.unlock();
918    }
919  }
920
921  /**
922   * Gets a value indicating whether the Driver Station requires the robot to be running in
923   * operator-controlled mode.
924   *
925   * @return True if operator-controlled mode should be enabled, false otherwise.
926   */
927  public static boolean isTeleop() {
928    return !(isAutonomous() || isTest());
929  }
930
931  /**
932   * Gets a value indicating whether the Driver Station requires the robot to be running in
933   * operator-controller mode and enabled.
934   *
935   * @return True if operator-controlled mode should be set and the robot should be enabled.
936   */
937  public static boolean isTeleopEnabled() {
938    m_cacheDataMutex.lock();
939    try {
940      return !m_controlWord.getAutonomous()
941          && !m_controlWord.getTest()
942          && m_controlWord.getEnabled();
943    } finally {
944      m_cacheDataMutex.unlock();
945    }
946  }
947
948  /**
949   * Gets a value indicating whether the Driver Station requires the robot to be running in Test
950   * mode.
951   *
952   * @return True if test mode should be enabled, false otherwise.
953   */
954  public static boolean isTest() {
955    m_cacheDataMutex.lock();
956    try {
957      return m_controlWord.getTest();
958    } finally {
959      m_cacheDataMutex.unlock();
960    }
961  }
962
963  /**
964   * Gets a value indicating whether the Driver Station requires the robot to be running in Test
965   * mode and enabled.
966   *
967   * @return True if test mode should be set and the robot should be enabled.
968   */
969  public static boolean isTestEnabled() {
970    m_cacheDataMutex.lock();
971    try {
972      return m_controlWord.getTest() && m_controlWord.getEnabled();
973    } finally {
974      m_cacheDataMutex.unlock();
975    }
976  }
977
978  /**
979   * Gets a value indicating whether the Driver Station is attached.
980   *
981   * @return True if Driver Station is attached, false otherwise.
982   */
983  public static boolean isDSAttached() {
984    m_cacheDataMutex.lock();
985    try {
986      return m_controlWord.getDSAttached();
987    } finally {
988      m_cacheDataMutex.unlock();
989    }
990  }
991
992  /**
993   * Gets if the driver station attached to a Field Management System.
994   *
995   * @return true if the robot is competing on a field being controlled by a Field Management System
996   */
997  public static boolean isFMSAttached() {
998    m_cacheDataMutex.lock();
999    try {
1000      return m_controlWord.getFMSAttached();
1001    } finally {
1002      m_cacheDataMutex.unlock();
1003    }
1004  }
1005
1006  /**
1007   * Get the game specific message from the FMS.
1008   *
1009   * <p>If the FMS is not connected, it is set from the game data setting on the driver station.
1010   *
1011   * @return the game specific message
1012   */
1013  public static String getGameSpecificMessage() {
1014    m_cacheDataMutex.lock();
1015    try {
1016      return m_matchInfo.gameSpecificMessage;
1017    } finally {
1018      m_cacheDataMutex.unlock();
1019    }
1020  }
1021
1022  /**
1023   * Get the event name from the FMS.
1024   *
1025   * @return the event name
1026   */
1027  public static String getEventName() {
1028    m_cacheDataMutex.lock();
1029    try {
1030      return m_matchInfo.eventName;
1031    } finally {
1032      m_cacheDataMutex.unlock();
1033    }
1034  }
1035
1036  /**
1037   * Get the match type from the FMS.
1038   *
1039   * @return the match type
1040   */
1041  public static MatchType getMatchType() {
1042    int matchType;
1043    m_cacheDataMutex.lock();
1044    try {
1045      matchType = m_matchInfo.matchType;
1046    } finally {
1047      m_cacheDataMutex.unlock();
1048    }
1049    switch (matchType) {
1050      case 1:
1051        return MatchType.Practice;
1052      case 2:
1053        return MatchType.Qualification;
1054      case 3:
1055        return MatchType.Elimination;
1056      default:
1057        return MatchType.None;
1058    }
1059  }
1060
1061  /**
1062   * Get the match number from the FMS.
1063   *
1064   * @return the match number
1065   */
1066  public static int getMatchNumber() {
1067    m_cacheDataMutex.lock();
1068    try {
1069      return m_matchInfo.matchNumber;
1070    } finally {
1071      m_cacheDataMutex.unlock();
1072    }
1073  }
1074
1075  /**
1076   * Get the replay number from the FMS.
1077   *
1078   * @return the replay number
1079   */
1080  public static int getReplayNumber() {
1081    m_cacheDataMutex.lock();
1082    try {
1083      return m_matchInfo.replayNumber;
1084    } finally {
1085      m_cacheDataMutex.unlock();
1086    }
1087  }
1088
1089  /**
1090   * Get the current alliance from the FMS.
1091   *
1092   * <p>If the FMS is not connected, it is set from the team alliance setting on the driver station.
1093   *
1094   * @return the current alliance
1095   */
1096  public static Alliance getAlliance() {
1097    AllianceStationID allianceStationID = DriverStationJNI.getAllianceStation();
1098    if (allianceStationID == null) {
1099      return Alliance.Invalid;
1100    }
1101
1102    switch (allianceStationID) {
1103      case Red1:
1104      case Red2:
1105      case Red3:
1106        return Alliance.Red;
1107
1108      case Blue1:
1109      case Blue2:
1110      case Blue3:
1111        return Alliance.Blue;
1112
1113      default:
1114        return Alliance.Invalid;
1115    }
1116  }
1117
1118  /**
1119   * Gets the location of the team's driver station controls from the FMS.
1120   *
1121   * <p>If the FMS is not connected, it is set from the team alliance setting on the driver station.
1122   *
1123   * @return the location of the team's driver station controls: 1, 2, or 3
1124   */
1125  public static int getLocation() {
1126    AllianceStationID allianceStationID = DriverStationJNI.getAllianceStation();
1127    if (allianceStationID == null) {
1128      return 0;
1129    }
1130    switch (allianceStationID) {
1131      case Red1:
1132      case Blue1:
1133        return 1;
1134
1135      case Red2:
1136      case Blue2:
1137        return 2;
1138
1139      case Blue3:
1140      case Red3:
1141        return 3;
1142
1143      default:
1144        return 0;
1145    }
1146  }
1147
1148  /**
1149   * Wait for a DS connection.
1150   *
1151   * @param timeoutSeconds timeout in seconds. 0 for infinite.
1152   * @return true if connected, false if timeout
1153   */
1154  public static boolean waitForDsConnection(double timeoutSeconds) {
1155    int event = WPIUtilJNI.createEvent(true, false);
1156    DriverStationJNI.provideNewDataEventHandle(event);
1157    boolean result;
1158    try {
1159      if (timeoutSeconds == 0) {
1160        WPIUtilJNI.waitForObject(event);
1161        result = true;
1162      } else {
1163        result = !WPIUtilJNI.waitForObjectTimeout(event, timeoutSeconds);
1164      }
1165    } catch (InterruptedException ex) {
1166      Thread.currentThread().interrupt();
1167      result = false;
1168    } finally {
1169      DriverStationJNI.removeNewDataEventHandle(event);
1170      WPIUtilJNI.destroyEvent(event);
1171    }
1172    return result;
1173  }
1174
1175  /**
1176   * Return the approximate match time. The FMS does not send an official match time to the robots,
1177   * but does send an approximate match time. The value will count down the time remaining in the
1178   * current period (auto or teleop). Warning: This is not an official time (so it cannot be used to
1179   * dispute ref calls or guarantee that a function will trigger before the match ends) The Practice
1180   * Match function of the DS approximates the behavior seen on the field.
1181   *
1182   * @return Time remaining in current match period (auto or teleop) in seconds
1183   */
1184  public static double getMatchTime() {
1185    return DriverStationJNI.getMatchTime();
1186  }
1187
1188  /**
1189   * Allows the user to specify whether they want joystick connection warnings to be printed to the
1190   * console. This setting is ignored when the FMS is connected -- warnings will always be on in
1191   * that scenario.
1192   *
1193   * @param silence Whether warning messages should be silenced.
1194   */
1195  public static void silenceJoystickConnectionWarning(boolean silence) {
1196    m_silenceJoystickWarning = silence;
1197  }
1198
1199  /**
1200   * Returns whether joystick connection warnings are silenced. This will always return false when
1201   * connected to the FMS.
1202   *
1203   * @return Whether joystick connection warnings are silenced.
1204   */
1205  public static boolean isJoystickConnectionWarningSilenced() {
1206    return !isFMSAttached() && m_silenceJoystickWarning;
1207  }
1208
1209  /**
1210   * Refresh the passed in control word to contain the current control word cache.
1211   *
1212   * @param word Word to refresh.
1213   */
1214  public static void refreshControlWordFromCache(ControlWord word) {
1215    m_cacheDataMutex.lock();
1216    try {
1217      word.update(m_controlWord);
1218    } finally {
1219      m_cacheDataMutex.unlock();
1220    }
1221  }
1222
1223  /**
1224   * Copy data from the DS task for the user. If no new data exists, it will just be returned,
1225   * otherwise the data will be copied from the DS polling loop.
1226   */
1227  public static void refreshData() {
1228    DriverStationJNI.refreshDSData();
1229
1230    // Get the status of all the joysticks
1231    for (byte stick = 0; stick < kJoystickPorts; stick++) {
1232      m_joystickAxesCache[stick].m_count =
1233          DriverStationJNI.getJoystickAxes(stick, m_joystickAxesCache[stick].m_axes);
1234      m_joystickAxesRawCache[stick].m_count =
1235          DriverStationJNI.getJoystickAxesRaw(stick, m_joystickAxesRawCache[stick].m_axes);
1236      m_joystickPOVsCache[stick].m_count =
1237          DriverStationJNI.getJoystickPOVs(stick, m_joystickPOVsCache[stick].m_povs);
1238      m_joystickButtonsCache[stick].m_buttons =
1239          DriverStationJNI.getJoystickButtons(stick, m_buttonCountBuffer);
1240      m_joystickButtonsCache[stick].m_count = m_buttonCountBuffer.get(0);
1241    }
1242
1243    DriverStationJNI.getMatchInfo(m_matchInfoCache);
1244
1245    DriverStationJNI.getControlWord(m_controlWordCache);
1246
1247    DataLogSender dataLogSender;
1248    // lock joystick mutex to swap cache data
1249    m_cacheDataMutex.lock();
1250    try {
1251      for (int i = 0; i < kJoystickPorts; i++) {
1252        // If buttons weren't pressed and are now, set flags in m_buttonsPressed
1253        m_joystickButtonsPressed[i] |=
1254            ~m_joystickButtons[i].m_buttons & m_joystickButtonsCache[i].m_buttons;
1255
1256        // If buttons were pressed and aren't now, set flags in m_buttonsReleased
1257        m_joystickButtonsReleased[i] |=
1258            m_joystickButtons[i].m_buttons & ~m_joystickButtonsCache[i].m_buttons;
1259      }
1260
1261      // move cache to actual data
1262      HALJoystickAxes[] currentAxes = m_joystickAxes;
1263      m_joystickAxes = m_joystickAxesCache;
1264      m_joystickAxesCache = currentAxes;
1265
1266      HALJoystickAxesRaw[] currentAxesRaw = m_joystickAxesRaw;
1267      m_joystickAxesRaw = m_joystickAxesRawCache;
1268      m_joystickAxesRawCache = currentAxesRaw;
1269
1270      HALJoystickButtons[] currentButtons = m_joystickButtons;
1271      m_joystickButtons = m_joystickButtonsCache;
1272      m_joystickButtonsCache = currentButtons;
1273
1274      HALJoystickPOVs[] currentPOVs = m_joystickPOVs;
1275      m_joystickPOVs = m_joystickPOVsCache;
1276      m_joystickPOVsCache = currentPOVs;
1277
1278      MatchInfoData currentInfo = m_matchInfo;
1279      m_matchInfo = m_matchInfoCache;
1280      m_matchInfoCache = currentInfo;
1281
1282      ControlWord currentWord = m_controlWord;
1283      m_controlWord = m_controlWordCache;
1284      m_controlWordCache = currentWord;
1285
1286      dataLogSender = m_dataLogSender;
1287    } finally {
1288      m_cacheDataMutex.unlock();
1289    }
1290
1291    m_refreshEvents.wakeup();
1292
1293    m_matchDataSender.sendMatchData();
1294    if (dataLogSender != null) {
1295      dataLogSender.send(WPIUtilJNI.now());
1296    }
1297  }
1298
1299  public static void provideRefreshedDataEventHandle(int handle) {
1300    m_refreshEvents.add(handle);
1301  }
1302
1303  public static void removeRefreshedDataEventHandle(int handle) {
1304    m_refreshEvents.remove(handle);
1305  }
1306
1307  /**
1308   * Reports errors related to unplugged joysticks Throttles the errors so that they don't overwhelm
1309   * the DS.
1310   */
1311  private static void reportJoystickUnpluggedError(String message) {
1312    double currentTime = Timer.getFPGATimestamp();
1313    if (currentTime > m_nextMessageTime) {
1314      reportError(message, false);
1315      m_nextMessageTime = currentTime + JOYSTICK_UNPLUGGED_MESSAGE_INTERVAL;
1316    }
1317  }
1318
1319  /**
1320   * Reports errors related to unplugged joysticks Throttles the errors so that they don't overwhelm
1321   * the DS.
1322   */
1323  private static void reportJoystickUnpluggedWarning(String message) {
1324    if (isFMSAttached() || !m_silenceJoystickWarning) {
1325      double currentTime = Timer.getFPGATimestamp();
1326      if (currentTime > m_nextMessageTime) {
1327        reportWarning(message, false);
1328        m_nextMessageTime = currentTime + JOYSTICK_UNPLUGGED_MESSAGE_INTERVAL;
1329      }
1330    }
1331  }
1332
1333  /**
1334   * Starts logging DriverStation data to data log. Repeated calls are ignored.
1335   *
1336   * @param log data log
1337   * @param logJoysticks if true, log joystick data
1338   */
1339  @SuppressWarnings("PMD.NonThreadSafeSingleton")
1340  public static void startDataLog(DataLog log, boolean logJoysticks) {
1341    m_cacheDataMutex.lock();
1342    try {
1343      if (m_dataLogSender == null) {
1344        m_dataLogSender = new DataLogSender(log, logJoysticks, WPIUtilJNI.now());
1345      }
1346    } finally {
1347      m_cacheDataMutex.unlock();
1348    }
1349  }
1350
1351  /**
1352   * Starts logging DriverStation data to data log, including joystick data. Repeated calls are
1353   * ignored.
1354   *
1355   * @param log data log
1356   */
1357  public static void startDataLog(DataLog log) {
1358    startDataLog(log, true);
1359  }
1360}