// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. package org.wpilib.opmode; import static org.wpilib.units.Units.Seconds; import java.util.PriorityQueue; import org.wpilib.driverstation.DriverStation; import org.wpilib.hardware.hal.ControlWord; import org.wpilib.hardware.hal.DriverStationJNI; import org.wpilib.hardware.hal.HAL; import org.wpilib.hardware.hal.NotifierJNI; import org.wpilib.networktables.NetworkTableInstance; import org.wpilib.smartdashboard.SmartDashboard; import org.wpilib.system.RobotController; import org.wpilib.system.Watchdog; import org.wpilib.units.measure.Time; import org.wpilib.util.WPIUtilJNI; /** * An opmode structure for periodic operation. This base class implements a loop that runs one or * more functions periodically (on a set time interval aka loop period). The primary periodic * callback function is the abstract periodic() function; the time interval for this callback is 20 * ms by default, but may be changed via passing a different time interval to the constructor. * Additional periodic callbacks with different intervals can be added using the addPeriodic() set * of functions. * *
Lifecycle: * *
This is scheduled on the same Notifier as periodic(), so periodic() and the callback run * synchronously. Interactions between them are thread-safe. * * @param callback The callback to run. * @param period The period at which to run the callback in seconds. */ public final void addPeriodic(Runnable callback, double period) { m_callbacks.add(new Callback(callback, m_startTimeUs, (long) (period * 1e6), 0)); } /** * Add a callback to run at a specific period with a starting time offset. * *
This is scheduled on the same Notifier as periodic(), so periodic() and the callback run * synchronously. Interactions between them are thread-safe. * * @param callback The callback to run. * @param period The period at which to run the callback in seconds. * @param offset The offset from the common starting time in seconds. This is useful for * scheduling a callback in a different timeslot relative to PeriodicOpMode. */ public final void addPeriodic(Runnable callback, double period, double offset) { m_callbacks.add( new Callback(callback, m_startTimeUs, (long) (period * 1e6), (long) (offset * 1e6))); } /** * Add a callback to run at a specific period. * *
This is scheduled on the same Notifier as periodic(), so periodic() and the callback run * synchronously. Interactions between them are thread-safe. * * @param callback The callback to run. * @param period The period at which to run the callback. */ public final void addPeriodic(Runnable callback, Time period) { addPeriodic(callback, period.in(Seconds)); } /** * Add a callback to run at a specific period with a starting time offset. * *
This is scheduled on the same Notifier as periodic(), so periodic() and the callback run * synchronously. Interactions between them are thread-safe. * * @param callback The callback to run. * @param period The period at which to run the callback. * @param offset The offset from the common starting time. This is useful for scheduling a * callback in a different timeslot relative to PeriodicOpMode. */ public final void addPeriodic(Runnable callback, Time period, Time offset) { addPeriodic(callback, period.in(Seconds), offset.in(Seconds)); } /** * Gets time period between calls to Periodic() functions. * * @return The time period between calls to Periodic() functions. */ public double getPeriod() { return m_period; } /** Loop function. */ protected void loopFunc() { DriverStation.refreshData(); DriverStation.refreshControlWordFromCache(m_word); m_word.setOpModeId(m_opModeId); DriverStationJNI.observeUserProgram(m_word.getNative()); if (!DriverStation.isEnabled() || DriverStation.getOpModeId() != m_opModeId) { m_running = false; return; } m_watchdog.reset(); periodic(); m_watchdog.addEpoch("periodic()"); SmartDashboard.updateValues(); m_watchdog.addEpoch("SmartDashboard.updateValues()"); // if (isSimulation()) { // HAL.simPeriodicBefore(); // simulationPeriodic(); // HAL.simPeriodicAfter(); // m_watchdog.addEpoch("simulationPeriodic()"); // } m_watchdog.disable(); // Flush NetworkTables NetworkTableInstance.getDefault().flushLocal(); // Warn on loop time overruns if (m_watchdog.isExpired()) { m_watchdog.printEpochs(); } } // implements OpMode interface @Override public final void opModeRun(long opModeId) { m_opModeId = opModeId; start(); while (m_running) { // We don't have to check there's an element in the queue first because // there's always at least one (the constructor adds one). It's reenqueued // at the end of the loop. var callback = m_callbacks.poll(); NotifierJNI.setNotifierAlarm(m_notifier, callback.expirationTime, 0, true, true); try { WPIUtilJNI.waitForObject(m_notifier); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); break; } long currentTime = RobotController.getMonotonicTime(); m_loopStartTimeUs = RobotController.getMonotonicTime(); callback.func.run(); // Increment the expiration time by the number of full periods it's behind // plus one to avoid rapid repeat fires from a large loop overrun. We // assume currentTime ≥ expirationTime rather than checking for it since // the callback wouldn't be running otherwise. callback.expirationTime += callback.period + (currentTime - callback.expirationTime) / callback.period * callback.period; m_callbacks.add(callback); // Process all other callbacks that are ready to run while (m_callbacks.peek().expirationTime <= currentTime) { callback = m_callbacks.poll(); callback.func.run(); callback.expirationTime += callback.period + (currentTime - callback.expirationTime) / callback.period * callback.period; m_callbacks.add(callback); } } end(); } @Override public final void opModeStop() { NotifierJNI.destroyNotifier(m_notifier); m_notifier = 0; } @Override public final void opModeClose() { if (m_notifier != 0) { NotifierJNI.destroyNotifier(m_notifier); } close(); } /** Prints list of epochs added so far and their times. */ public void printWatchdogEpochs() { m_watchdog.printEpochs(); } private void printLoopOverrunMessage() { DriverStation.reportWarning("Loop time of " + m_period + "s overrun\n", false); } }