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 static org.wpilib.units.Units.Seconds; 008 009import java.io.File; 010import java.io.IOException; 011import java.lang.reflect.Modifier; 012import java.net.JarURLConnection; 013import java.net.URISyntaxException; 014import java.net.URL; 015import java.util.Enumeration; 016import java.util.HashMap; 017import java.util.HashSet; 018import java.util.Map; 019import java.util.Optional; 020import java.util.Set; 021import java.util.function.Supplier; 022import java.util.jar.JarEntry; 023import java.util.jar.JarFile; 024import org.wpilib.driverstation.Alert; 025import org.wpilib.driverstation.DriverStationErrors; 026import org.wpilib.driverstation.RobotState; 027import org.wpilib.driverstation.UserControls; 028import org.wpilib.driverstation.UserControlsInstance; 029import org.wpilib.driverstation.internal.DriverStationBackend; 030import org.wpilib.hardware.hal.ControlWord; 031import org.wpilib.hardware.hal.DriverStationJNI; 032import org.wpilib.hardware.hal.HAL; 033import org.wpilib.hardware.hal.NotifierJNI; 034import org.wpilib.hardware.hal.RobotMode; 035import org.wpilib.internal.PeriodicPriorityQueue; 036import org.wpilib.internal.PeriodicPriorityQueue.Callback; 037import org.wpilib.networktables.NetworkTableInstance; 038import org.wpilib.opmode.Autonomous; 039import org.wpilib.opmode.OpMode; 040import org.wpilib.opmode.PeriodicOpMode; 041import org.wpilib.opmode.Teleop; 042import org.wpilib.opmode.Utility; 043import org.wpilib.smartdashboard.SmartDashboard; 044import org.wpilib.system.RobotController; 045import org.wpilib.system.Watchdog; 046import org.wpilib.util.Color; 047import org.wpilib.util.ConstructorMatch; 048 049/** 050 * OpModeRobot implements the opmode-based robot program framework. 051 * 052 * <p>The OpModeRobot class is intended to be subclassed by a user creating a robot program. 053 * 054 * <p>Classes annotated with {@link Autonomous}, {@link Teleop}, and {@link Utility} in the same 055 * package or subpackages as the user's subclass are automatically registered as autonomous, teleop, 056 * and utility opmodes respectively. 057 * 058 * <p>Opmodes are constructed when selected on the driver station. While selected and disabled, 059 * {@link PeriodicOpMode#disabledPeriodic()} is called. When enabled, {@link PeriodicOpMode#start()} 060 * is called once and {@link PeriodicOpMode#periodic()} runs at the rate from {@link #getPeriod()}. 061 * On disable or mode switch while enabled, {@link PeriodicOpMode#end()} is called asynchronously 062 * and the opmode is then closed and discarded. When no opmode is selected, {@link #nonePeriodic()} 063 * is called. {@link #driverStationConnected()} is called once when the DS first connects. 064 */ 065public abstract class OpModeRobot extends RobotBase { 066 private final ControlWord m_word = new ControlWord(); 067 068 private record OpModeFactory(String name, Supplier<OpMode> supplier) {} 069 070 private final Map<Long, OpModeFactory> m_opModes = new HashMap<>(); 071 072 // Callback system fields (match C++ architecture) 073 private final PeriodicPriorityQueue m_callbacks = new PeriodicPriorityQueue(); 074 private int m_notifier; 075 private final double m_period; 076 private final long m_startTimeUs; 077 078 // OpMode lifecycle state 079 private long m_lastModeId = -1; 080 private boolean m_calledDriverStationConnected = false; 081 private boolean m_lastEnabledState = false; 082 private OpMode m_currentOpMode; 083 private Callback m_currentOpModePeriodic; 084 private final Set<Callback> m_activeOpModeCallbacks = new HashSet<>(); 085 private final Watchdog m_watchdog; 086 private final Alert m_loopOverrunAlert; 087 088 private static void reportAddOpModeError(Class<?> cls, String message) { 089 DriverStationErrors.reportError( 090 "Error adding OpMode " + cls.getSimpleName() + ": " + message, false); 091 } 092 093 private final Optional<Class<? extends UserControls>> m_userControlsBaseClass; 094 private UserControls m_userControlsInstance; 095 096 void setUserControlsInstance(UserControls userControlsInstance) { 097 if (m_userControlsBaseClass.isEmpty()) { 098 throw new IllegalStateException("No UserControls class specified"); 099 } 100 101 if (!m_userControlsBaseClass.get().isAssignableFrom(userControlsInstance.getClass())) { 102 throw new IllegalArgumentException( 103 userControlsInstance.getClass().getSimpleName() 104 + " is not assignable to " 105 + m_userControlsBaseClass.get().getSimpleName()); 106 } 107 m_userControlsInstance = userControlsInstance; 108 } 109 110 /** 111 * Find a public constructor to instantiate the opmode. This constructor can have up to 2 112 * parameters. The first parameter (if present) must be assignable from this.getClass(). The 113 * second parameter (if present) must be assignable from DriverStationBase. If multiple, first 114 * sort by most parameters, then by most specific first, then by most specific second. 115 */ 116 private <T> Optional<ConstructorMatch<T>> findOpModeConstructor(Class<T> cls) { 117 Optional<ConstructorMatch<T>> ctor; 118 119 // try 2-parameter constructor 120 if (m_userControlsBaseClass.isPresent()) { 121 ctor = ConstructorMatch.findBestConstructor(cls, getClass(), m_userControlsBaseClass.get()); 122 if (ctor.isPresent()) { 123 return ctor; 124 } 125 } 126 127 // try 1-parameter constructor with RobotBase parameter 128 ctor = ConstructorMatch.findBestConstructor(cls, getClass()); 129 if (ctor.isPresent()) { 130 return ctor; 131 } 132 133 // try 1-parameter constructor with UserControls parameter 134 if (m_userControlsBaseClass.isPresent()) { 135 ctor = ConstructorMatch.findBestConstructor(cls, m_userControlsBaseClass.get()); 136 if (ctor.isPresent()) { 137 return ctor; 138 } 139 } 140 141 // try no-parameter constructor 142 ctor = ConstructorMatch.findBestConstructor(cls); 143 return ctor; 144 } 145 146 private <T extends OpMode> T constructOpModeClass(Class<T> cls) { 147 Optional<ConstructorMatch<T>> constructor = findOpModeConstructor(cls); 148 if (constructor.isEmpty()) { 149 DriverStationErrors.reportError( 150 "No suitable constructor to instantiate OpMode " + cls.getSimpleName(), true); 151 return null; 152 } 153 try { 154 if (m_userControlsInstance != null) { 155 return constructor.get().newInstance(this, m_userControlsInstance); 156 } else { 157 return constructor.get().newInstance(this); 158 } 159 } catch (ReflectiveOperationException e) { 160 DriverStationErrors.reportError( 161 "Could not instantiate OpMode " + cls.getSimpleName(), e.getStackTrace()); 162 return null; 163 } 164 } 165 166 private void checkOpModeClass(Class<?> cls) { 167 // the class must be a subclass of OpMode 168 if (!OpMode.class.isAssignableFrom(cls)) { 169 throw new IllegalArgumentException("not a subclass of OpMode"); 170 } 171 int modifiers = cls.getModifiers(); 172 // it cannot be abstract 173 if (Modifier.isAbstract(modifiers)) { 174 throw new IllegalArgumentException("is abstract"); 175 } 176 // it must be public 177 if (!Modifier.isPublic(modifiers)) { 178 throw new IllegalArgumentException("not public"); 179 } 180 // it must not be a non-static inner class 181 if (cls.getEnclosingClass() != null && !Modifier.isStatic(modifiers)) { 182 throw new IllegalArgumentException("is a non-static inner class"); 183 } 184 // it must have a public no-arg constructor or a public constructor that accepts this class 185 // (or a superclass/interface) as an argument 186 if (findOpModeConstructor(cls).isEmpty()) { 187 throw new IllegalArgumentException( 188 "missing public no-arg constructor or constructor accepting " 189 + getClass().getSimpleName()); 190 } 191 } 192 193 /** 194 * Adds an opmode using a factory function that creates the opmode. It's necessary to call 195 * publishOpModes() to make the added mode visible to the driver station. 196 * 197 * @param factory factory function to create the opmode 198 * @param mode robot mode 199 * @param name name of the operating mode 200 * @param group group of the operating mode 201 * @param description description of the operating mode 202 * @param textColor text color, or null for default 203 * @param backgroundColor background color, or null for default 204 * @throws IllegalArgumentException if class does not meet criteria 205 */ 206 public void addOpModeFactory( 207 Supplier<OpMode> factory, 208 RobotMode mode, 209 String name, 210 String group, 211 String description, 212 Color textColor, 213 Color backgroundColor) { 214 long id = RobotState.addOpMode(mode, name, group, description, textColor, backgroundColor); 215 m_opModes.put(id, new OpModeFactory(name, factory)); 216 } 217 218 /** 219 * Adds an opmode using a factory function that creates the opmode. It's necessary to call 220 * publishOpModes() to make the added mode visible to the driver station. 221 * 222 * @param factory factory function to create the opmode 223 * @param mode robot mode 224 * @param name name of the operating mode 225 * @param group group of the operating mode 226 * @param description description of the operating mode 227 * @throws IllegalArgumentException if class does not meet criteria 228 */ 229 public void addOpModeFactory( 230 Supplier<OpMode> factory, RobotMode mode, String name, String group, String description) { 231 addOpModeFactory(factory, mode, name, group, description, null, null); 232 } 233 234 /** 235 * Adds an opmode using a factory function that creates the opmode. It's necessary to call 236 * publishOpModes() to make the added mode visible to the driver station. 237 * 238 * @param factory factory function to create the opmode 239 * @param mode robot mode 240 * @param name name of the operating mode 241 * @param group group of the operating mode 242 * @throws IllegalArgumentException if class does not meet criteria 243 */ 244 public void addOpModeFactory( 245 Supplier<OpMode> factory, RobotMode mode, String name, String group) { 246 addOpModeFactory(factory, mode, name, group, ""); 247 } 248 249 /** 250 * Adds an opmode using a factory function that creates the opmode. It's necessary to call 251 * publishOpModes() to make the added mode visible to the driver station. 252 * 253 * @param factory factory function to create the opmode 254 * @param mode robot mode 255 * @param name name of the operating mode 256 * @throws IllegalArgumentException if class does not meet criteria 257 */ 258 public void addOpModeFactory(Supplier<OpMode> factory, RobotMode mode, String name) { 259 addOpModeFactory(factory, mode, name, ""); 260 } 261 262 /** 263 * Adds an opmode for an opmode class. The class must be a public, non-abstract subclass of OpMode 264 * with a public constructor that either takes no arguments or accepts a single argument 265 * assignable from this robot class type (the latter is preferred; if multiple match, the most 266 * specific parameter type is used). It's necessary to call publishOpModes() to make the added 267 * mode visible to the driver station. 268 * 269 * @param cls class to add 270 * @param mode robot mode 271 * @param name name of the operating mode 272 * @param group group of the operating mode 273 * @param description description of the operating mode 274 * @param textColor text color, or null for default 275 * @param backgroundColor background color, or null for default 276 * @throws IllegalArgumentException if class does not meet criteria 277 */ 278 public void addOpMode( 279 Class<? extends OpMode> cls, 280 RobotMode mode, 281 String name, 282 String group, 283 String description, 284 Color textColor, 285 Color backgroundColor) { 286 checkOpModeClass(cls); 287 addOpModeFactory( 288 () -> constructOpModeClass(cls), 289 mode, 290 name, 291 group, 292 description, 293 textColor, 294 backgroundColor); 295 } 296 297 /** 298 * Adds an opmode for an opmode class. The class must be a public, non-abstract subclass of OpMode 299 * with a public constructor that either takes no arguments or accepts a single argument 300 * assignable from this robot class type (the latter is preferred; if multiple match, the most 301 * specific parameter type is used). It's necessary to call publishOpModes() to make the added 302 * mode visible to the driver station. 303 * 304 * @param cls class to add 305 * @param mode robot mode 306 * @param name name of the operating mode 307 * @param group group of the operating mode 308 * @param description description of the operating mode 309 * @throws IllegalArgumentException if class does not meet criteria 310 */ 311 public void addOpMode( 312 Class<? extends OpMode> cls, RobotMode mode, String name, String group, String description) { 313 addOpMode(cls, mode, name, group, description, null, null); 314 } 315 316 /** 317 * Adds an opmode for an opmode class. The class must be a public, non-abstract subclass of OpMode 318 * with a public constructor that either takes no arguments or accepts a single argument 319 * assignable from this robot class type (the latter is preferred; if multiple match, the most 320 * specific parameter type is used). It's necessary to call publishOpModes() to make the added 321 * mode visible to the driver station. 322 * 323 * @param cls class to add 324 * @param mode robot mode 325 * @param name name of the operating mode 326 * @param group group of the operating mode 327 * @throws IllegalArgumentException if class does not meet criteria 328 */ 329 public void addOpMode(Class<? extends OpMode> cls, RobotMode mode, String name, String group) { 330 addOpMode(cls, mode, name, group, ""); 331 } 332 333 /** 334 * Adds an opmode for an opmode class. The class must be a public, non-abstract subclass of OpMode 335 * with a public constructor that either takes no arguments or accepts a single argument 336 * assignable from this robot class type (the latter is preferred; if multiple match, the most 337 * specific parameter type is used). It's necessary to call publishOpModes() to make the added 338 * mode visible to the driver station. 339 * 340 * @param cls class to add 341 * @param mode robot mode 342 * @param name name of the operating mode 343 * @throws IllegalArgumentException if class does not meet criteria 344 */ 345 public void addOpMode(Class<? extends OpMode> cls, RobotMode mode, String name) { 346 addOpMode(cls, mode, name, ""); 347 } 348 349 private void addOpModeClassImpl( 350 Class<? extends OpMode> cls, 351 RobotMode mode, 352 String name, 353 String group, 354 String description, 355 String textColor, 356 String backgroundColor) { 357 if (name == null || name.isBlank()) { 358 name = cls.getSimpleName(); 359 } 360 Color tColor = textColor.isBlank() ? null : Color.fromString(textColor); 361 Color bColor = backgroundColor.isBlank() ? null : Color.fromString(backgroundColor); 362 long id = RobotState.addOpMode(mode, name, group, description, tColor, bColor); 363 m_opModes.put(id, new OpModeFactory(name, () -> constructOpModeClass(cls))); 364 } 365 366 private void addAnnotatedOpModeImpl( 367 Class<? extends OpMode> cls, Autonomous auto, Teleop teleop, Utility utility) { 368 checkOpModeClass(cls); 369 370 // add an opmode for each annotation 371 if (auto != null) { 372 addOpModeClassImpl( 373 cls, 374 RobotMode.AUTONOMOUS, 375 auto.name(), 376 auto.group(), 377 auto.description(), 378 auto.textColor(), 379 auto.backgroundColor()); 380 } 381 if (teleop != null) { 382 addOpModeClassImpl( 383 cls, 384 RobotMode.TELEOPERATED, 385 teleop.name(), 386 teleop.group(), 387 teleop.description(), 388 teleop.textColor(), 389 teleop.backgroundColor()); 390 } 391 if (utility != null) { 392 addOpModeClassImpl( 393 cls, 394 RobotMode.UTILITY, 395 utility.name(), 396 utility.group(), 397 utility.description(), 398 utility.textColor(), 399 utility.backgroundColor()); 400 } 401 } 402 403 /** 404 * Adds an opmode for an opmode class annotated with {@link Autonomous}, {@link Teleop}, or {@link 405 * Utility}. The class must be a public, non-abstract subclass of OpMode with a public constructor 406 * that either takes no arguments or accepts a single argument assignable from this robot class 407 * type (if multiple match, the most specific parameter type is used). It's necessary to call 408 * publishOpModes() to make the added mode visible to the driver station. 409 * 410 * @param cls class to add 411 * @throws IllegalArgumentException if class does not meet criteria 412 */ 413 public void addAnnotatedOpMode(Class<? extends OpMode> cls) { 414 Autonomous auto = cls.getAnnotation(Autonomous.class); 415 Teleop teleop = cls.getAnnotation(Teleop.class); 416 Utility utility = cls.getAnnotation(Utility.class); 417 if (auto == null && teleop == null && utility == null) { 418 throw new IllegalArgumentException("must be annotated with Autonomous, Teleop, or Utility"); 419 } 420 addAnnotatedOpModeImpl(cls, auto, teleop, utility); 421 } 422 423 private void addAnnotatedOpModeClass(String name) { 424 // trim ".class" from end 425 String className = name.replace('/', '.').substring(0, name.length() - 6); 426 Class<? extends OpMode> cls; 427 try { 428 cls = Class.forName(className).asSubclass(OpMode.class); 429 } catch (ClassNotFoundException | ClassCastException e) { 430 return; 431 } 432 Autonomous auto = cls.getAnnotation(Autonomous.class); 433 Teleop teleop = cls.getAnnotation(Teleop.class); 434 Utility utility = cls.getAnnotation(Utility.class); 435 if (auto == null && teleop == null && utility == null) { 436 return; 437 } 438 try { 439 addAnnotatedOpModeImpl(cls, auto, teleop, utility); 440 } catch (IllegalArgumentException e) { 441 reportAddOpModeError(cls, e.getMessage()); 442 } 443 } 444 445 private void addAnnotatedOpModeClassesDir(File root, File dir, String packagePath) { 446 File[] files = dir.listFiles(); 447 if (files == null) { 448 return; 449 } 450 for (File file : files) { 451 if (file.isDirectory()) { 452 addAnnotatedOpModeClassesDir(root, file, packagePath); 453 } else if (file.getName().endsWith(".class")) { 454 String relPath = root.toPath().relativize(file.toPath()).toString().replace('\\', '/'); 455 addAnnotatedOpModeClass(packagePath + "." + relPath); 456 } 457 } 458 } 459 460 /** 461 * Scans for classes in the specified package and all nested packages that are annotated with 462 * {@link Autonomous}, {@link Teleop}, or {@link Utility} and registers them. It's necessary to 463 * call publishOpModes() to make the added modes visible to the driver station. 464 * 465 * @param pkg package to scan 466 */ 467 public void addAnnotatedOpModeClasses(Package pkg) { 468 String packageName = pkg.getName(); 469 String packagePath = packageName.replace('.', '/'); 470 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 471 472 try { 473 Enumeration<URL> resources = classLoader.getResources(packagePath); 474 while (resources.hasMoreElements()) { 475 URL resource = resources.nextElement(); 476 if ("jar".equals(resource.getProtocol())) { 477 var connection = resource.openConnection(); 478 if (!(connection instanceof JarURLConnection jarConnection)) { 479 DriverStationErrors.reportError( 480 "Error scanning OpModes from " 481 + resource 482 + ": expected JarURLConnection, got " 483 + connection.getClass().getSimpleName(), 484 false); 485 continue; 486 } 487 jarConnection.setUseCaches(false); 488 try (JarFile jar = jarConnection.getJarFile()) { 489 Enumeration<JarEntry> entries = jar.entries(); 490 while (entries.hasMoreElements()) { 491 String name = entries.nextElement().getName(); 492 if (!name.startsWith(packagePath) || !name.endsWith(".class")) { 493 continue; 494 } 495 addAnnotatedOpModeClass(name); 496 } 497 } 498 } else if ("file".equals(resource.getProtocol())) { 499 // Handle .class files in directories 500 File dir = new File(resource.toURI()); 501 if (dir.exists() && dir.isDirectory()) { 502 addAnnotatedOpModeClassesDir(dir, dir, packagePath); 503 } 504 } 505 } 506 } catch (IOException | URISyntaxException e) { 507 e.printStackTrace(); 508 } 509 } 510 511 /** 512 * Removes an operating mode option. It's necessary to call publishOpModes() to make the removed 513 * mode no longer visible to the driver station. 514 * 515 * @param mode robot mode 516 * @param name name of the operating mode 517 */ 518 public void removeOpMode(RobotMode mode, String name) { 519 long id = RobotState.removeOpMode(mode, name); 520 if (id != 0) { 521 m_opModes.remove(id); 522 } 523 } 524 525 /** Publishes the operating mode options to the driver station. */ 526 public void publishOpModes() { 527 RobotState.publishOpModes(); 528 } 529 530 /** Clears all operating mode options and publishes an empty list to the driver station. */ 531 public void clearOpModes() { 532 RobotState.clearOpModes(); 533 m_opModes.clear(); 534 } 535 536 /** Default loop period. */ 537 public static final double DEFAULT_PERIOD = 0.02; 538 539 /** Constructor with default period. */ 540 @SuppressWarnings("this-escape") 541 public OpModeRobot() { 542 this(DEFAULT_PERIOD); 543 } 544 545 /** 546 * Constructor with specified period. 547 * 548 * @param period the period at which to run the robot and opmode periodic callbacks. 549 */ 550 @SuppressWarnings("this-escape") 551 public OpModeRobot(double period) { 552 m_period = period; 553 554 // Create our own notifier and callback queue (match C++) 555 m_notifier = NotifierJNI.createNotifier(); 556 NotifierJNI.setNotifierName(m_notifier, "OpModeRobot"); 557 558 m_startTimeUs = RobotController.getMonotonicTime(); 559 560 m_loopOverrunAlert = 561 new Alert("Loop time of \"" + m_period + "\"s overrun", Alert.Level.MEDIUM); 562 m_watchdog = new Watchdog(Seconds.of(m_period), () -> m_loopOverrunAlert.set(true)); 563 564 // Add LoopFunc as periodic callback (match C++) 565 addPeriodic(this::loopFunc, period); 566 567 // Check to see if we have a DS annotation 568 UserControlsInstance userControlsAnnotation = 569 getClass().getAnnotation(UserControlsInstance.class); 570 if (userControlsAnnotation != null) { 571 m_userControlsBaseClass = Optional.of(userControlsAnnotation.value()); 572 } else { 573 m_userControlsBaseClass = Optional.empty(); 574 } 575 // Scan for annotated opmode classes within the derived class's package and subpackages 576 addAnnotatedOpModeClasses(getClass().getPackage()); 577 RobotState.publishOpModes(); 578 579 HAL.reportUsage("Framework", "OpModeRobot"); 580 } 581 582 /** 583 * Add a callback to run at a specific period. 584 * 585 * @param callback The callback to run. 586 * @param period The period at which to run the callback. 587 */ 588 public void addPeriodic(Runnable callback, double period) { 589 m_callbacks.add(callback, m_startTimeUs, period); 590 } 591 592 /** 593 * Get the period at which robot and opmode periodic callbacks are run. 594 * 595 * @return The period at which robot and opmode periodic callbacks are run. 596 */ 597 public double getPeriod() { 598 return m_period; 599 } 600 601 /** 602 * Code that needs to know the DS state should go here. 603 * 604 * <p>Users should override this method for initialization that needs to occur after the DS is 605 * connected, such as needing the alliance information. 606 */ 607 public void driverStationConnected() {} 608 609 /** Function called periodically every loop, regardless of enabled state or OpMode selection. */ 610 public void robotPeriodic() {} 611 612 /** Function called once during robot initialization in simulation. */ 613 public void simulationInit() {} 614 615 /** Function called periodically in simulation. */ 616 public void simulationPeriodic() {} 617 618 /** Function called once when the robot becomes disabled. */ 619 public void disabledInit() {} 620 621 /** Function called periodically while the robot is disabled. */ 622 public void disabledPeriodic() {} 623 624 /** Function called once when the robot exits disabled state. */ 625 public void disabledExit() {} 626 627 /** 628 * Function called periodically anytime when no opmode is selected, including when the Driver 629 * Station is disconnected. 630 */ 631 public void nonePeriodic() {} 632 633 /** 634 * Return the system clock time in microseconds for the start of the current periodic loop. This 635 * is in the same time base as Timer.getMonotonicTimestamp(), but is stable through a loop. It is 636 * updated at the beginning of every periodic callback (including the normal periodic loop). 637 * 638 * @return Robot running time in microseconds, as of the start of the current periodic function. 639 */ 640 public long getLoopStartTime() { 641 return m_callbacks.getLoopStartTime(); 642 } 643 644 /** Main robot loop function. Handles disabled state logic and opmode management. */ 645 private void loopFunc() { 646 DriverStationBackend.refreshData(); 647 648 // Get current enabled state and opmode 649 DriverStationBackend.refreshControlWordFromCache(m_word); 650 m_watchdog.reset(); 651 boolean enabled = m_word.isEnabled(); 652 long modeId = m_word.isDSAttached() ? m_word.getOpModeId() : 0; 653 654 if (!m_calledDriverStationConnected && m_word.isDSAttached()) { 655 m_calledDriverStationConnected = true; 656 driverStationConnected(); 657 m_watchdog.addEpoch("driverStationConnected()"); 658 } 659 660 // Handle opmode changes 661 if (modeId != m_lastModeId) { 662 // Clean up current opmode 663 if (m_currentOpMode != null) { 664 // Remove opmode callbacks 665 m_callbacks.remove(m_currentOpModePeriodic); 666 m_callbacks.removeAll(m_activeOpModeCallbacks); 667 m_activeOpModeCallbacks.clear(); 668 m_currentOpMode.end(); 669 m_currentOpMode.close(); 670 m_currentOpMode = null; 671 } 672 673 // Set up new opmode 674 if (modeId != 0) { 675 OpModeFactory factory = m_opModes.get(modeId); 676 if (factory != null) { 677 // Instantiate the new opmode 678 System.out.println("********** Starting OpMode " + factory.name() + " **********"); 679 m_currentOpMode = factory.supplier().get(); 680 if (m_currentOpMode != null) { 681 // Ensure disabledPeriodic is called at least once 682 m_currentOpMode.disabledPeriodic(); 683 m_watchdog.addEpoch("opMode.disabledPeriodic()"); 684 // Register the opmode's periodic callbacks 685 m_currentOpModePeriodic = 686 m_callbacks.add(m_currentOpMode::periodic, m_startTimeUs, m_period); 687 m_activeOpModeCallbacks.addAll(m_currentOpMode.getCallbacks()); 688 m_callbacks.addAll(m_activeOpModeCallbacks); 689 } 690 } else { 691 DriverStationErrors.reportError("No OpMode found for mode " + modeId, false); 692 } 693 } 694 m_lastModeId = modeId; 695 } 696 697 // Handle enabled state changes 698 boolean justCalledDisabledInit = false; 699 if (m_lastEnabledState != enabled) { 700 if (enabled) { 701 // Transitioning to enabled 702 disabledExit(); 703 m_watchdog.addEpoch("disabledExit()"); 704 if (m_currentOpMode != null) { 705 m_currentOpMode.start(); 706 m_watchdog.addEpoch("opMode.start()"); 707 } 708 } else { 709 // Transitioning to disabled 710 if (m_currentOpMode != null && m_lastEnabledState) { 711 // Was enabled, now disabled 712 m_currentOpMode.end(); 713 m_watchdog.addEpoch("opMode.end()"); 714 } 715 disabledInit(); 716 m_watchdog.addEpoch("disabledInit()"); 717 justCalledDisabledInit = true; 718 } 719 m_lastEnabledState = enabled; 720 } 721 722 // Call periodic functions based on current state 723 if (!enabled) { 724 // Only call disabledPeriodic if we didn't just call disabledInit 725 if (!justCalledDisabledInit) { 726 disabledPeriodic(); 727 m_watchdog.addEpoch("disabledPeriodic()"); 728 } 729 730 // Call opmode disabledPeriodic if we have one 731 if (m_currentOpMode != null) { 732 m_currentOpMode.disabledPeriodic(); 733 m_watchdog.addEpoch("opMode.disabledPeriodic()"); 734 } 735 } 736 737 // Call nonePeriodic when no opmode is selected 738 if (RobotState.getOpModeId() == 0) { 739 nonePeriodic(); 740 m_watchdog.addEpoch("nonePeriodic()"); 741 } 742 743 // Always call robotPeriodic 744 robotPeriodic(); 745 m_watchdog.addEpoch("robotPeriodic()"); 746 747 // Always observe user program state 748 DriverStationJNI.observeUserProgram(m_word.getNative()); 749 750 SmartDashboard.updateValues(); 751 m_watchdog.addEpoch("SmartDashboard.updateValues()"); 752 753 // Call simulationPeriodic if in simulation 754 if (isSimulation()) { 755 HAL.simPeriodicBefore(); 756 simulationPeriodic(); 757 HAL.simPeriodicAfter(); 758 m_watchdog.addEpoch("simulationPeriodic()"); 759 } 760 761 m_watchdog.disable(); 762 763 // Flush NetworkTables 764 NetworkTableInstance.getDefault().flushLocal(); 765 766 // Warn on loop time overruns 767 if (m_watchdog.isExpired()) { 768 m_watchdog.printEpochs(); 769 } 770 } 771 772 /** Provide an alternate "main loop" via startCompetition(). */ 773 @Override 774 public final void startCompetition() { 775 System.out.println("********** Robot program startup complete **********"); 776 777 if (isSimulation()) { 778 simulationInit(); 779 } 780 781 // Tell the DS that the robot is ready to be enabled 782 DriverStationBackend.observeUserProgramStarting(); 783 784 // Loop forever, calling the callback system which handles periodic functions 785 while (true) { 786 if (!m_callbacks.runCallbacks(m_notifier)) { 787 break; 788 } 789 } 790 } 791 792 @Override 793 public void close() { 794 NotifierJNI.destroyNotifier(m_notifier); 795 } 796 797 /** Ends the main loop in startCompetition(). */ 798 @Override 799 public final void endCompetition() { 800 NotifierJNI.destroyNotifier(m_notifier); 801 } 802 803 /** Prints list of epochs added so far and their times. */ 804 public void printWatchdogEpochs() { 805 m_watchdog.printEpochs(); 806 } 807}