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 org.wpilib.framework; 006 007import java.util.Optional; 008import java.util.concurrent.locks.ReentrantLock; 009import org.wpilib.driverstation.DriverStationErrors; 010import org.wpilib.driverstation.RobotState; 011import org.wpilib.driverstation.UserControls; 012import org.wpilib.driverstation.UserControlsInstance; 013import org.wpilib.driverstation.internal.DriverStationBackend; 014import org.wpilib.hardware.hal.HAL; 015import org.wpilib.hardware.hal.HALUtil; 016import org.wpilib.math.util.MathShared; 017import org.wpilib.math.util.MathSharedStore; 018import org.wpilib.networktables.MultiSubscriber; 019import org.wpilib.networktables.NetworkTableEvent; 020import org.wpilib.networktables.NetworkTableInstance; 021import org.wpilib.system.RuntimeType; 022import org.wpilib.system.Timer; 023import org.wpilib.system.WPILibVersion; 024import org.wpilib.util.ConstructorMatch; 025import org.wpilib.util.WPIUtilJNI; 026import org.wpilib.vision.stream.CameraServerShared; 027import org.wpilib.vision.stream.CameraServerSharedStore; 028 029/** 030 * Implement a Robot Program framework. The RobotBase class is intended to be subclassed to create a 031 * robot program. The user must implement {@link #startCompetition()}, which will be called once and 032 * is not expected to exit. The user must also implement {@link #endCompetition()}, which signals to 033 * the code in {@link #startCompetition()} that it should exit. 034 * 035 * <p>It is not recommended to subclass this class directly - instead subclass IterativeRobotBase or 036 * TimedRobot. 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 final int m_connListenerHandle; 046 047 private static void setupCameraServerShared() { 048 CameraServerShared shared = 049 new CameraServerShared() { 050 @Override 051 public void reportUsage(String resource, String data) { 052 HAL.reportUsage(resource, data); 053 } 054 055 @Override 056 public void reportDriverStationError(String error) { 057 DriverStationErrors.reportError(error, true); 058 } 059 060 @Override 061 public Long getRobotMainThreadId() { 062 return RobotBase.getMainThreadId(); 063 } 064 065 @Override 066 public boolean isRoboRIO() { 067 return !RobotBase.isSimulation(); 068 } 069 }; 070 071 CameraServerSharedStore.setCameraServerShared(shared); 072 } 073 074 private static void setupMathShared() { 075 MathSharedStore.setMathShared( 076 new MathShared() { 077 @Override 078 public void reportError(String error, StackTraceElement[] stackTrace) { 079 DriverStationErrors.reportError(error, stackTrace); 080 } 081 082 @Override 083 public void reportUsage(String resource, String data) { 084 HAL.reportUsage(resource, data); 085 } 086 087 @Override 088 public double getTimestamp() { 089 return Timer.getTimestamp(); 090 } 091 }); 092 } 093 094 /** 095 * Constructor for a generic robot program. User code can be placed in the constructor that runs 096 * before the Autonomous or Operator Control period starts. The constructor will run to completion 097 * before Autonomous is entered. 098 * 099 * <p>This must be used to ensure that the communications code starts. In the future it would be 100 * nice to put this code into its own task that loads on boot so ensure that it runs. 101 */ 102 protected RobotBase() { 103 final NetworkTableInstance inst = NetworkTableInstance.getDefault(); 104 m_threadId = Thread.currentThread().threadId(); 105 setupCameraServerShared(); 106 setupMathShared(); 107 // subscribe to "" to force persistent values to propagate to local 108 m_suball = new MultiSubscriber(inst, new String[] {""}); 109 if (!isSimulation()) { 110 inst.startServer("/home/systemcore/networktables.json", "", "robot"); 111 } else { 112 inst.startServer("networktables.json", "", "robot"); 113 } 114 115 // wait for the NT server to actually start 116 try { 117 int count = 0; 118 while (inst.getNetworkMode().contains(NetworkTableInstance.NetworkMode.STARTING)) { 119 Thread.sleep(10); 120 count++; 121 if (count > 100) { 122 throw new InterruptedException(); 123 } 124 } 125 } catch (InterruptedException ex) { 126 System.err.println("timed out while waiting for NT server to start"); 127 } 128 129 m_connListenerHandle = 130 inst.addConnectionListener( 131 false, 132 event -> { 133 if (event.is(NetworkTableEvent.Kind.CONNECTED)) { 134 HAL.reportUsage("NT/" + event.connInfo.remoteId, ""); 135 } 136 }); 137 } 138 139 /** 140 * Returns the main thread ID. 141 * 142 * @return The main thread ID. 143 */ 144 public static long getMainThreadId() { 145 return m_threadId; 146 } 147 148 @Override 149 public void close() { 150 m_suball.close(); 151 NetworkTableInstance.getDefault().removeListener(m_connListenerHandle); 152 } 153 154 /** 155 * Get the current runtime type. 156 * 157 * @return Current runtime type. 158 */ 159 public static RuntimeType getRuntimeType() { 160 return RuntimeType.getValue(HALUtil.getHALRuntimeType()); 161 } 162 163 /** 164 * Get if the robot is a simulation. 165 * 166 * @return If the robot is running in simulation. 167 */ 168 public static boolean isSimulation() { 169 return getRuntimeType() == RuntimeType.SIMULATION; 170 } 171 172 /** 173 * Get if the robot is real. 174 * 175 * @return If the robot is running in the real world. 176 */ 177 public static boolean isReal() { 178 RuntimeType runtimeType = getRuntimeType(); 179 return runtimeType == RuntimeType.SYSTEMCORE; 180 } 181 182 /** 183 * Determine if the Robot is currently disabled. 184 * 185 * @return True if the Robot is currently disabled by the Driver Station. 186 */ 187 public static boolean isDisabled() { 188 return RobotState.isDisabled(); 189 } 190 191 /** 192 * Determine if the Robot is currently enabled. 193 * 194 * @return True if the Robot is currently enabled by the Driver Station. 195 */ 196 public static boolean isEnabled() { 197 return RobotState.isEnabled(); 198 } 199 200 /** 201 * Determine if the robot is currently in Autonomous mode as determined by the Driver Station. 202 * 203 * @return True if the robot is currently operating Autonomously. 204 */ 205 public static boolean isAutonomous() { 206 return RobotState.isAutonomous(); 207 } 208 209 /** 210 * Determine if the robot is currently in Autonomous mode and enabled as determined by the Driver 211 * Station. 212 * 213 * @return True if the robot is currently operating autonomously while enabled. 214 */ 215 public static boolean isAutonomousEnabled() { 216 return RobotState.isAutonomousEnabled(); 217 } 218 219 /** 220 * Determine if the robot is currently in Utility mode as determined by the Driver Station. 221 * 222 * @return True if the robot is currently operating in Utility mode. 223 */ 224 public static boolean isUtility() { 225 return RobotState.isUtility(); 226 } 227 228 /** 229 * Determine if the robot is current in Utility mode and enabled as determined by the Driver 230 * Station. 231 * 232 * @return True if the robot is currently operating in Utility mode while enabled. 233 */ 234 public static boolean isUtilityEnabled() { 235 return RobotState.isUtilityEnabled(); 236 } 237 238 /** 239 * Determine if the robot is currently in Operator Control mode as determined by the Driver 240 * Station. 241 * 242 * @return True if the robot is currently operating in Tele-Op mode. 243 */ 244 public static boolean isTeleop() { 245 return RobotState.isTeleop(); 246 } 247 248 /** 249 * Determine if the robot is currently in Operator Control mode and enabled as determined by the 250 * Driver Station. 251 * 252 * @return True if the robot is currently operating in Tele-Op mode while enabled. 253 */ 254 public static boolean isTeleopEnabled() { 255 return RobotState.isTeleopEnabled(); 256 } 257 258 /** 259 * Gets the currently selected operating mode of the driver station. Note this does not mean the 260 * robot is enabled; use isEnabled() for that. 261 * 262 * @return the unique ID provided by the DriverStation.addOpMode() function; may return 0 or a 263 * unique ID not added, so callers should be prepared to handle that case 264 */ 265 public static long getOpModeId() { 266 return RobotState.getOpModeId(); 267 } 268 269 /** 270 * Gets the currently selected operating mode of the driver station. Note this does not mean the 271 * robot is enabled; use isEnabled() for that. 272 * 273 * @return Operating mode string; may return a string not in the list of options, so callers 274 * should be prepared to handle that case 275 */ 276 public static String getOpMode() { 277 return RobotState.getOpMode(); 278 } 279 280 /** 281 * Start the main robot code. This function will be called once and should not exit until 282 * signalled by {@link #endCompetition()} 283 */ 284 public abstract void startCompetition(); 285 286 /** Ends the main loop in {@link #startCompetition()}. */ 287 public abstract void endCompetition(); 288 289 private static final ReentrantLock m_runMutex = new ReentrantLock(); 290 private static RobotBase m_robotCopy; 291 private static boolean m_suppressExitWarning; 292 293 private static <T extends RobotBase> T constructRobot(Class<T> robotClass) throws Throwable { 294 UserControlsInstance userControlsAttribute = 295 robotClass.getDeclaredAnnotation(UserControlsInstance.class); 296 UserControls userControlsInstance = null; 297 Optional<ConstructorMatch<T>> constructorMatch = Optional.empty(); 298 if (userControlsAttribute != null) { 299 var userControlsClass = userControlsAttribute.value(); 300 userControlsInstance = userControlsClass.getDeclaredConstructor().newInstance(); 301 constructorMatch = ConstructorMatch.findBestConstructor(robotClass, userControlsClass); 302 } 303 304 if (constructorMatch.isEmpty()) { 305 // Try to find a constructor with no parameters if there is no UserControls constructor 306 constructorMatch = ConstructorMatch.findBestConstructor(robotClass); 307 } 308 309 if (constructorMatch.isEmpty()) { 310 throw new IllegalArgumentException( 311 "No valid constructor found in robot class " + robotClass.getName()); 312 } 313 314 T robot = constructorMatch.get().newInstance(userControlsInstance); 315 316 if (userControlsInstance != null && robot instanceof OpModeRobot opModeRobot) { 317 // Insert the UserControls instance into the opModeRobot for use when constructing opmodes 318 opModeRobot.setUserControlsInstance(userControlsInstance); 319 } 320 return robot; 321 } 322 323 /** Run the robot main loop. */ 324 @SuppressWarnings("PMD.AvoidCatchingGenericException") 325 private static <T extends RobotBase> void runRobot(Class<T> robotClass) { 326 System.out.println("********** Robot program starting **********"); 327 328 T robot; 329 try { 330 robot = constructRobot(robotClass); 331 } catch (Throwable throwable) { 332 Throwable cause = throwable.getCause(); 333 if (cause != null) { 334 throwable = cause; 335 } 336 String robotName = "Unknown"; 337 StackTraceElement[] elements = throwable.getStackTrace(); 338 if (elements.length > 0) { 339 robotName = elements[0].getClassName(); 340 } 341 DriverStationErrors.reportError( 342 "Unhandled exception instantiating robot " + robotName + " " + throwable, elements); 343 DriverStationErrors.reportError( 344 "The robot program quit unexpectedly." 345 + " This is usually due to a code error.\n" 346 + " The above stacktrace can help determine where the error occurred.\n" 347 + " See https://wpilib.org/stacktrace for more information.\n", 348 false); 349 DriverStationErrors.reportError("Could not instantiate robot " + robotName + "!", false); 350 return; 351 } 352 353 m_runMutex.lock(); 354 m_robotCopy = robot; 355 m_runMutex.unlock(); 356 357 boolean errorOnExit = false; 358 try { 359 robot.startCompetition(); 360 } catch (Throwable throwable) { 361 Throwable cause = throwable.getCause(); 362 if (cause != null) { 363 throwable = cause; 364 } 365 DriverStationErrors.reportError( 366 "Unhandled exception: " + throwable, throwable.getStackTrace()); 367 errorOnExit = true; 368 } finally { 369 m_runMutex.lock(); 370 boolean suppressExitWarning = m_suppressExitWarning; 371 m_runMutex.unlock(); 372 if (!suppressExitWarning) { 373 // startCompetition never returns unless exception occurs.... 374 DriverStationErrors.reportWarning( 375 "The robot program quit unexpectedly." 376 + " This is usually due to a code error.\n" 377 + " The above stacktrace can help determine where the error occurred.\n" 378 + " See https://wpilib.org/stacktrace for more information.", 379 false); 380 if (errorOnExit) { 381 DriverStationErrors.reportError( 382 "The startCompetition() method (or methods called by it) should have " 383 + "handled the exception above.", 384 false); 385 } else { 386 DriverStationErrors.reportError( 387 "Unexpected return from startCompetition() method.", false); 388 } 389 } 390 } 391 } 392 393 /** 394 * Suppress the "The robot program quit unexpectedly." message. 395 * 396 * @param value True if exit warning should be suppressed. 397 */ 398 public static void suppressExitWarning(boolean value) { 399 m_runMutex.lock(); 400 m_suppressExitWarning = value; 401 m_runMutex.unlock(); 402 } 403 404 /** 405 * Starting point for the applications. 406 * 407 * @param <T> Robot subclass. 408 * @param robotClass Robot subclass type. 409 */ 410 public static <T extends RobotBase> void startRobot(Class<T> robotClass) { 411 // Check that the MSVC runtime is valid. 412 WPIUtilJNI.checkMsvcRuntime(); 413 414 if (!HAL.initialize(500, 0)) { 415 throw new IllegalStateException("Failed to initialize. Terminating"); 416 } 417 418 // Force refresh DS data 419 DriverStationBackend.refreshData(); 420 421 HAL.reportUsage("Language", "Java"); 422 HAL.reportUsage("WPILibVersion", WPILibVersion.Version); 423 424 if (HAL.hasMain()) { 425 Thread thread = 426 new Thread( 427 () -> { 428 runRobot(robotClass); 429 HAL.exitMain(); 430 }, 431 "robot main"); 432 thread.setDaemon(true); 433 thread.start(); 434 HAL.runMain(); 435 suppressExitWarning(true); 436 m_runMutex.lock(); 437 RobotBase robot = m_robotCopy; 438 m_runMutex.unlock(); 439 if (robot != null) { 440 robot.endCompetition(); 441 } 442 try { 443 thread.join(1000); 444 } catch (InterruptedException ex) { 445 Thread.currentThread().interrupt(); 446 } 447 } else { 448 runRobot(robotClass); 449 } 450 451 // On RIO, this will just terminate rather than shutting down cleanly (it's a no-op in sim). 452 // It's not worth the risk of hanging on shutdown when we want the code to restart as quickly 453 // as possible. 454 HAL.terminate(); 455 456 HAL.shutdown(); 457 458 System.exit(0); 459 } 460}