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