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