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.cameraserver.CameraServerShared;
008import edu.wpi.first.cameraserver.CameraServerSharedStore;
009import edu.wpi.first.cscore.CameraServerJNI;
010import edu.wpi.first.hal.FRCNetComm.tInstances;
011import edu.wpi.first.hal.FRCNetComm.tResourceType;
012import edu.wpi.first.hal.HAL;
013import edu.wpi.first.hal.HALUtil;
014import edu.wpi.first.math.MathShared;
015import edu.wpi.first.math.MathSharedStore;
016import edu.wpi.first.math.MathUsageId;
017import edu.wpi.first.networktables.MultiSubscriber;
018import edu.wpi.first.networktables.NetworkTableInstance;
019import edu.wpi.first.util.WPIUtilJNI;
020import edu.wpi.first.wpilibj.livewindow.LiveWindow;
021import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
022import edu.wpi.first.wpilibj.util.WPILibVersion;
023import java.io.File;
024import java.io.IOException;
025import java.io.OutputStream;
026import java.nio.charset.StandardCharsets;
027import java.nio.file.Files;
028import java.util.concurrent.locks.ReentrantLock;
029import java.util.function.Supplier;
030
031/**
032 * Implement a Robot Program framework. The RobotBase class is intended to be subclassed by a user
033 * creating a robot program. Overridden autonomous() and operatorControl() methods are called at the
034 * appropriate time as the match proceeds. In the current implementation, the Autonomous code will
035 * run to completion before the OperatorControl code could start. In the future the Autonomous code
036 * might be spawned as a task, then killed at the end of the Autonomous period.
037 */
038public abstract class RobotBase implements AutoCloseable {
039  /** The ID of the main Java thread. */
040  // This is usually 1, but it is best to make sure
041  private static long m_threadId = -1;
042
043  private final MultiSubscriber m_suball;
044
045  private static void setupCameraServerShared() {
046    CameraServerShared shared =
047        new CameraServerShared() {
048          @Override
049          public void reportVideoServer(int id) {
050            HAL.report(tResourceType.kResourceType_PCVideoServer, id + 1);
051          }
052
053          @Override
054          public void reportUsbCamera(int id) {
055            HAL.report(tResourceType.kResourceType_UsbCamera, id + 1);
056          }
057
058          @Override
059          public void reportDriverStationError(String error) {
060            DriverStation.reportError(error, true);
061          }
062
063          @Override
064          public void reportAxisCamera(int id) {
065            HAL.report(tResourceType.kResourceType_AxisCamera, id + 1);
066          }
067
068          @Override
069          public Long getRobotMainThreadId() {
070            return RobotBase.getMainThreadId();
071          }
072
073          @Override
074          public boolean isRoboRIO() {
075            return RobotBase.isReal();
076          }
077        };
078
079    CameraServerSharedStore.setCameraServerShared(shared);
080  }
081
082  private static void setupMathShared() {
083    MathSharedStore.setMathShared(
084        new MathShared() {
085          @Override
086          public void reportError(String error, StackTraceElement[] stackTrace) {
087            DriverStation.reportError(error, stackTrace);
088          }
089
090          @Override
091          public void reportUsage(MathUsageId id, int count) {
092            switch (id) {
093              case kKinematics_DifferentialDrive:
094                HAL.report(
095                    tResourceType.kResourceType_Kinematics,
096                    tInstances.kKinematics_DifferentialDrive);
097                break;
098              case kKinematics_MecanumDrive:
099                HAL.report(
100                    tResourceType.kResourceType_Kinematics, tInstances.kKinematics_MecanumDrive);
101                break;
102              case kKinematics_SwerveDrive:
103                HAL.report(
104                    tResourceType.kResourceType_Kinematics, tInstances.kKinematics_SwerveDrive);
105                break;
106              case kTrajectory_TrapezoidProfile:
107                HAL.report(tResourceType.kResourceType_TrapezoidProfile, count);
108                break;
109              case kFilter_Linear:
110                HAL.report(tResourceType.kResourceType_LinearFilter, count);
111                break;
112              case kOdometry_DifferentialDrive:
113                HAL.report(
114                    tResourceType.kResourceType_Odometry, tInstances.kOdometry_DifferentialDrive);
115                break;
116              case kOdometry_SwerveDrive:
117                HAL.report(tResourceType.kResourceType_Odometry, tInstances.kOdometry_SwerveDrive);
118                break;
119              case kOdometry_MecanumDrive:
120                HAL.report(tResourceType.kResourceType_Odometry, tInstances.kOdometry_MecanumDrive);
121                break;
122              case kController_PIDController2:
123                HAL.report(tResourceType.kResourceType_PIDController2, count);
124                break;
125              case kController_ProfiledPIDController:
126                HAL.report(tResourceType.kResourceType_ProfiledPIDController, count);
127                break;
128              default:
129                break;
130            }
131          }
132
133          @Override
134          public double getTimestamp() {
135            return WPIUtilJNI.now() * 1.0e-6;
136          }
137        });
138  }
139
140  /**
141   * Constructor for a generic robot program. User code should be placed in the constructor that
142   * runs before the Autonomous or Operator Control period starts. The constructor will run to
143   * completion before Autonomous is entered.
144   *
145   * <p>This must be used to ensure that the communications code starts. In the future it would be
146   * nice to put this code into its own task that loads on boot so ensure that it runs.
147   */
148  protected RobotBase() {
149    final NetworkTableInstance inst = NetworkTableInstance.getDefault();
150    m_threadId = Thread.currentThread().getId();
151    setupCameraServerShared();
152    setupMathShared();
153    // subscribe to "" to force persistent values to propagate to local
154    m_suball = new MultiSubscriber(inst, new String[] {""});
155    if (isReal()) {
156      inst.startServer("/home/lvuser/networktables.json");
157    } else {
158      inst.startServer();
159    }
160
161    // wait for the NT server to actually start
162    try {
163      int count = 0;
164      while (inst.getNetworkMode().contains(NetworkTableInstance.NetworkMode.kStarting)) {
165        Thread.sleep(10);
166        count++;
167        if (count > 100) {
168          throw new InterruptedException();
169        }
170      }
171    } catch (InterruptedException ex) {
172      System.err.println("timed out while waiting for NT server to start");
173    }
174
175    LiveWindow.setEnabled(false);
176    Shuffleboard.disableActuatorWidgets();
177  }
178
179  public static long getMainThreadId() {
180    return m_threadId;
181  }
182
183  @Override
184  public void close() {
185    m_suball.close();
186  }
187
188  /**
189   * Get the current runtime type.
190   *
191   * @return Current runtime type.
192   */
193  public static RuntimeType getRuntimeType() {
194    return RuntimeType.getValue(HALUtil.getHALRuntimeType());
195  }
196
197  /**
198   * Get if the robot is a simulation.
199   *
200   * @return If the robot is running in simulation.
201   */
202  public static boolean isSimulation() {
203    return !isReal();
204  }
205
206  /**
207   * Get if the robot is real.
208   *
209   * @return If the robot is running in the real world.
210   */
211  public static boolean isReal() {
212    RuntimeType runtimeType = getRuntimeType();
213    return runtimeType == RuntimeType.kRoboRIO || runtimeType == RuntimeType.kRoboRIO2;
214  }
215
216  /**
217   * Determine if the Robot is currently disabled.
218   *
219   * @return True if the Robot is currently disabled by the Driver Station.
220   */
221  public boolean isDisabled() {
222    return DriverStation.isDisabled();
223  }
224
225  /**
226   * Determine if the Robot is currently enabled.
227   *
228   * @return True if the Robot is currently enabled by the Driver Station.
229   */
230  public boolean isEnabled() {
231    return DriverStation.isEnabled();
232  }
233
234  /**
235   * Determine if the robot is currently in Autonomous mode as determined by the Driver Station.
236   *
237   * @return True if the robot is currently operating Autonomously.
238   */
239  public boolean isAutonomous() {
240    return DriverStation.isAutonomous();
241  }
242
243  /**
244   * Determine if the robot is currently in Autonomous mode and enabled as determined by the Driver
245   * Station.
246   *
247   * @return True if the robot is currently operating autonomously while enabled.
248   */
249  public boolean isAutonomousEnabled() {
250    return DriverStation.isAutonomousEnabled();
251  }
252
253  /**
254   * Determine if the robot is currently in Test mode as determined by the Driver Station.
255   *
256   * @return True if the robot is currently operating in Test mode.
257   */
258  public boolean isTest() {
259    return DriverStation.isTest();
260  }
261
262  /**
263   * Determine if the robot is current in Test mode and enabled as determined by the Driver Station.
264   *
265   * @return True if the robot is currently operating in Test mode while enabled.
266   */
267  public boolean isTestEnabled() {
268    return DriverStation.isTestEnabled();
269  }
270
271  /**
272   * Determine if the robot is currently in Operator Control mode as determined by the Driver
273   * Station.
274   *
275   * @return True if the robot is currently operating in Tele-Op mode.
276   */
277  public boolean isTeleop() {
278    return DriverStation.isTeleop();
279  }
280
281  /**
282   * Determine if the robot is currently in Operator Control mode and enabled as determined by the
283   * Driver Station.
284   *
285   * @return True if the robot is currently operating in Tele-Op mode while enabled.
286   */
287  public boolean isTeleopEnabled() {
288    return DriverStation.isTeleopEnabled();
289  }
290
291  /** Provide an alternate "main loop" via startCompetition(). */
292  public abstract void startCompetition();
293
294  /** Ends the main loop in startCompetition(). */
295  public abstract void endCompetition();
296
297  private static final ReentrantLock m_runMutex = new ReentrantLock();
298  private static RobotBase m_robotCopy;
299  private static boolean m_suppressExitWarning;
300
301  /** Run the robot main loop. */
302  private static <T extends RobotBase> void runRobot(Supplier<T> robotSupplier) {
303    System.out.println("********** Robot program starting **********");
304
305    T robot;
306    try {
307      robot = robotSupplier.get();
308    } catch (Throwable throwable) {
309      Throwable cause = throwable.getCause();
310      if (cause != null) {
311        throwable = cause;
312      }
313      String robotName = "Unknown";
314      StackTraceElement[] elements = throwable.getStackTrace();
315      if (elements.length > 0) {
316        robotName = elements[0].getClassName();
317      }
318      DriverStation.reportError(
319          "Unhandled exception instantiating robot " + robotName + " " + throwable.toString(),
320          elements);
321      DriverStation.reportError(
322          "The robot program quit unexpectedly."
323              + " This is usually due to a code error.\n"
324              + "  The above stacktrace can help determine where the error occurred.\n"
325              + "  See https://wpilib.org/stacktrace for more information.\n",
326          false);
327      DriverStation.reportError("Could not instantiate robot " + robotName + "!", false);
328      return;
329    }
330
331    m_runMutex.lock();
332    m_robotCopy = robot;
333    m_runMutex.unlock();
334
335    if (isReal()) {
336      final File file = new File("/tmp/frc_versions/FRC_Lib_Version.ini");
337      try {
338        if (file.exists() && !file.delete()) {
339          throw new IOException("Failed to delete FRC_Lib_Version.ini");
340        }
341
342        if (!file.createNewFile()) {
343          throw new IOException("Failed to create new FRC_Lib_Version.ini");
344        }
345
346        try (OutputStream output = Files.newOutputStream(file.toPath())) {
347          output.write("Java ".getBytes(StandardCharsets.UTF_8));
348          output.write(WPILibVersion.Version.getBytes(StandardCharsets.UTF_8));
349        }
350      } catch (IOException ex) {
351        DriverStation.reportError(
352            "Could not write FRC_Lib_Version.ini: " + ex.toString(), ex.getStackTrace());
353      }
354    }
355
356    boolean errorOnExit = false;
357    try {
358      robot.startCompetition();
359    } catch (Throwable throwable) {
360      Throwable cause = throwable.getCause();
361      if (cause != null) {
362        throwable = cause;
363      }
364      DriverStation.reportError(
365          "Unhandled exception: " + throwable.toString(), throwable.getStackTrace());
366      errorOnExit = true;
367    } finally {
368      m_runMutex.lock();
369      boolean suppressExitWarning = m_suppressExitWarning;
370      m_runMutex.unlock();
371      if (!suppressExitWarning) {
372        // startCompetition never returns unless exception occurs....
373        DriverStation.reportWarning(
374            "The robot program quit unexpectedly."
375                + " This is usually due to a code error.\n"
376                + "  The above stacktrace can help determine where the error occurred.\n"
377                + "  See https://wpilib.org/stacktrace for more information.",
378            false);
379        if (errorOnExit) {
380          DriverStation.reportError(
381              "The startCompetition() method (or methods called by it) should have "
382                  + "handled the exception above.",
383              false);
384        } else {
385          DriverStation.reportError("Unexpected return from startCompetition() method.", false);
386        }
387      }
388    }
389  }
390
391  /**
392   * Suppress the "The robot program quit unexpectedly." message.
393   *
394   * @param value True if exit warning should be suppressed.
395   */
396  public static void suppressExitWarning(boolean value) {
397    m_runMutex.lock();
398    m_suppressExitWarning = value;
399    m_runMutex.unlock();
400  }
401
402  /**
403   * Starting point for the applications.
404   *
405   * @param <T> Robot subclass.
406   * @param robotSupplier Function that returns an instance of the robot subclass.
407   */
408  public static <T extends RobotBase> void startRobot(Supplier<T> robotSupplier) {
409    if (!HAL.initialize(500, 0)) {
410      throw new IllegalStateException("Failed to initialize. Terminating");
411    }
412
413    // Force refresh DS data
414    DriverStation.refreshData();
415
416    // Call a CameraServer JNI function to force OpenCV native library loading
417    // Needed because all the OpenCV JNI functions don't have built in loading
418    CameraServerJNI.enumerateSinks();
419
420    HAL.report(
421        tResourceType.kResourceType_Language, tInstances.kLanguage_Java, 0, WPILibVersion.Version);
422
423    if (!Notifier.setHALThreadPriority(true, 40)) {
424      DriverStation.reportWarning("Setting HAL Notifier RT priority to 40 failed", false);
425    }
426
427    if (HAL.hasMain()) {
428      Thread thread =
429          new Thread(
430              () -> {
431                runRobot(robotSupplier);
432                HAL.exitMain();
433              },
434              "robot main");
435      thread.setDaemon(true);
436      thread.start();
437      HAL.runMain();
438      suppressExitWarning(true);
439      m_runMutex.lock();
440      RobotBase robot = m_robotCopy;
441      m_runMutex.unlock();
442      if (robot != null) {
443        robot.endCompetition();
444      }
445      try {
446        thread.join(1000);
447      } catch (InterruptedException ex) {
448        Thread.currentThread().interrupt();
449      }
450    } else {
451      runRobot(robotSupplier);
452    }
453
454    HAL.shutdown();
455
456    System.exit(0);
457  }
458}