-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Timers in FTC Blocks
This tutorial shows FTC Blocks programmers how to use Timers. If you're in a hurry (no time), skip to the examples below.
Java programmers may benefit from learning here how FTC timers work.
Many new Blocks programmers use the Sleep command (MyOpMode.sleep) as their only control of time.
This is a very basic and common use of timed action in a linear OpMode used for Autonomous. It tells the robot to process no new instructions for the specified duration.
But what if other activities need to happen while that motor runs? In Autonomous, for example, you might need to collect sensor data while the robot moves forward for 3 seconds. In TeleOp, you might need to continue driving with gamepads while a CR servo rotates for 2 seconds. The Sleep command will not allow this; "no new instructions".
Timers provide a more flexible solution.
In FTC Blocks, timer commands are found in the menu at the left side, under Utilities, Time, ElapsedTime.
In general you will create your own timer, then use pre-made commands or methods to access the contents of that timer. This is not as simple as 'look at the clock' in real life. There are more steps, based on the structure and protections of Object-Oriented Programming (OOP). Java is an OOP language, and Blocks is based on Java.
The first step is to create a new Variable, called "myTimer" (or any other name that suits its purpose in your OpMode).
But "myTimer" will not be an ordinary variable that stores a number, or text, or Boolean value.
From the Variables menu, and from the ElapsedTime menu, create this Blocks command:
Now "myTimer" is a timer object, which you can think of as "My New Stopwatch" (although Blocks timers don't actually stop).
Programming note: In Java, an Object is an instance or example of a Class. FTC Blocks provides a class called ElapsedTime. A class can contain variables and methods (commands). Your new timer is an instance of that Java class, inheriting its characteristics.
This is not the usual Blocks "Set" command. Instead of assigning a number to a variable, this Block creates or instantiates a new object called myTimer. The Java keyword new causes myTimer to be created as an instance of the class ElapsedTime.
The default unit of reporting time is Seconds, for a timer created with the simple Block shown above. An option for Milliseconds is discussed far below.
A new timer begins running as soon as it's created, even in the Initialize section, starting with the current time on the system clock. However you will probably not rely on that starting time value, because you will use the timer later in your OpMode when the system time has changed. Pressing the DS phone's Start button at the beginning of Autonomous or TeleOp does not restart your timer.
Consider the point where your OpMode is ready to use myTimer, probably someplace after 'waitForStart'. As a programmer you don't know in advance how much time has passed, so you should set the time value of myTimer to a definite fresh starting point. That starting point will again be the current time on the system clock.
This Block uses a method called "reset", from the class called ElapsedTime. It resets your timer to the current system time; it will start counting immediately and keep running. Timers don't stop; all you can do is read their changing time value at a single instant.
Note: this tutorial will reserve the term clock to mean only the high-level system clock that runs on the RC phone or Control Hub, independent of the FTC apps and OpMode. It cannot be reset by the OpMode.
It is important to understand that "myTimer" is the name of the timer or stopwatch, not the time value itself. The Block is purple like other Variables, but trying to read "myTimer" as a number will not give you the time.
Instead you must use a built-in method or command, to get or retrieve the contents of myTimer. Here is the simplest way.
The green Block uses an Elapsed Time method named "Time"; you can consider it as "Get A Timer's Current Elapsed Time Value". Elapsed time means the time that has passed since the timer was created or last reset, whichever was later. You might prefer to ignore the system time and think of timers as counting from zero. That outlook or model does work, but keep in mind the actual process is to subtract the starting system time from the current system time. This tutorial will often refer to elapsed time as simply "time".
We can read the above Blocks from right to left. From myTimer, the "Time" method gets the time value and passes, or returns, the calculated Elapsed Time to the left. On the left could be a variable to hold that number, or it could be another Block that accepts number input.
The units will be Seconds or Milliseconds, based on how the timer was created.
Here the variable "currentTime" receives and holds the number of seconds (or milliseconds) that myTimer has been running, effectively starting from zero as noted above. Finally you have the number you want, but remember that this number is fixed, not changing. It was the time value at the exact moment you checked myTimer.
New programmers often try to store or display time values directly from myTimer. Again, this doesn't work; myTimer is just the name of your timer object.
Here is a basic, correct way to display the current elapsed-time value in your timer.
You may find that a constantly changing time value causes programming or calculation problems. You could store the time value once, then use it various places.
Here the Telemetry Block builds a message showing the variable currentTime, containing a value retrieved from your timer. Although its value may be updating constantly in a Repeat loop, currentTime is not actually the running stopwatch. Again, keep in mind that the variable holds a fixed, saved value.
Telemetry can give a final time report from variables that recorded the duration of actions or time between actions. When developing new Autonomous OpModes, this tool can help you optimize performance and identify wasted time.
Telemetry can be used inside a Repeat loop to monitor changing time values, as shown in several Examples below. Telemetry can also be used after a loop to give a final time report, from variables that recorded times inside the loop.
When testing your OpMode, make sure the final Telemetry message stays on the screen long enough to read it. If this is not possible, or you are studying a sequence of very close events, consider the logging option described far below.
Programming tip: Consider the user. Telemetry can display timer values in units of Seconds or Milliseconds, but always with its native Nanosecond accuracy. The extra 5 to 9 digits make it hard to interpret the data on the DS screen. Use the 'roundDecimal' Block from the Miscellaneous menu and choose a suitable rounding level such as 1 or 2 decimals for Seconds, or zero decimals for Milliseconds.
The 'round' Block from the Math menu is a suitable alternate for Milliseconds.
Here are five common uses of timers in FTC:
-
Loop until a time limit is reached. Think of this as an alarm clock.
-
Measure the length of time until an action occurs or stops. Think of this as a stopwatch.
-
Safety timeout. Don't get stuck in a loop!
-
Match timer or countdown timer.
- Device timer in TeleOp.
More advanced applications are mentioned at the end of this tutorial.
This is the most common use. In the shortest version, create and start the timer immediately before use.
In the more formal recommended version, create the timer in the Initialize section of your OpMode (before waitForStart). Then reset the timer before each use. This example also shows the Telemetry command.
Create the 'stopwatch' timer in the Initialize section, then reset that timer when a robot action begins. When the robot action is finished (not based on time), store the elapsed-time value in a Variable. Here is a simple Autonomous example.
Again, timers don't stop; all you can do is read the time value at one moment. Here the stored post-action timer value is displayed immediately as Telemetry. For testing only, a long Sleep command can keep your Telemetry on the DS screen, before the OpMode ends.
This is similar to 'Loop for Time', except here the timer is not the primary basis for looping, it's the last resort. Reaching the time limit typically means the loop did not achieve its primary goal; it's time to give up and move on. This can help prevent: motor/servo burnout, mechanism breaking, field damage, or simply wasted time. Action loops should have a safety timeout.
Here is a simple example that might be used in Autonomous or an automated section of TeleOp.
What might cause a timeout? Sensors failed, unexpected field condition, low battery, robot meets robot, etc. If the OpMode remains stuck in a Repeat Loop, the timeout can allow the OpMode to continue safely.
As noted by the blue comments, inside the loop you could set a variable indicating the cause of exiting the loop. In this example, the variable is simply a Boolean (true/false) "flag" to indicate whether or not the robot action finished as intended. This Boolean flag can be used immediately after the Loop to stop the action if needed, and carry out the best next action. The next action might be to back up, try again, move on with other scoring attempts, re-establish the robot's location, or simply take no action until the end of Autonomous or Endgame.
Test to make sure the safety time limit is not shorter than the intended action, under various conditions.
Your OpMode can monitor the elapsed time since the start of Autonomous or TeleOp, and give warnings or take action accordingly. It can also can 'lock out' or prevent certain actions until a specific time in TeleOp, to reduce driver error. Example from Rover Ruckus: don't begin the "hang" before Endgame. Or, from SKYSTONE: don't release the spring-powered Loading Zone extender before Endgame.
It's recommended to instantiate a timer in the Initialize section, allowing re-use of that timer with simple Resets at each place needed in the OpMode. This example does not do that, to illustrate that it's optional.
You might prefer to instantiate a timer only where needed. It makes sense in this example, since a Match Timer will not be used more than once. You get a Reset for free, saving one line of code.
Programming Tip: Boolean (true/false) Variables can be read directly by IF blocks, as shown in the above example. The following two Blocks are equivalent.
Likewise, these two Blocks are equivalent.
Carefully select the names of your Boolean variables, so your OpModes are simple and easy to read.
Programming Tip: Similarly, the Logic Compare Block returns a Boolean value. In the above example, the first IF-DO Blocks could be replaced with:
Caution: this replacement is not exactly the same. It can also assign False, where the original IF-DO did not handle the False scenario. Programmers must consider these differences.
For TeleOp, an OpMode typically runs all commands in a single overall Repeat Loop. How can you run a device for time, within that loop? Example: when button B pressed, run a CR servo for 2 seconds.
The timer here is instantiated with time units or resolution of Milliseconds, rather than the default Seconds. This affects only the programming and display aspects for convenience; the actual timer accuracy is in Nanoseconds.
Timers can be used in this way also for Autonomous OpModes written in a State Machine style, where an iterative (looping) OpMode progresses sequentially through program steps, or states.
This example also shows how to capture only the first moment of the button being pressed. The continued pressing of the button is ignored, when the Boolean (true/false) variable CRservoIsRunning is set to True. After 2 seconds of running, this Boolean "flag" is reset to False, which stops checking the timer and allows a fresh press of button B if desired.
Programming tip: The pressing of a gamepad button lasts for some time: many full cycles of a Repeat Loop. Programmers should beware of unintended effects of continued pressing. Without the flag in the above example, the CR servo could run much longer than 2 seconds.
Programming note: An advanced refinement would require button B's release before allowing a restart of the CR servo. This would protect against a double-activation caused by a very long button press.
Here are the other timer-related Blocks not yet discussed. First, above the ElapsedTime menu is the Time menu, containing only one Block.
This returns the system-level time in Nanoseconds, to be used for elapsed-time calculations. The system timer is a high-level clock that runs independently from the OpMode and the FTC apps, and cannot be reset in the OpMode. A millisecond is 1/1000 of a second, a microsecond is 1/1000 of a millisecond, and a nanosecond is 1/1000 of a microsecond.
From the ElapsedTime menu, the above Block creates a new timer using Seconds, initialized to the input value. When using the indicated default system input, this Block is the same as the common "new ElapsedTime" Block used throughout this tutorial. In either case, the timer will later return a correct elapsed time. The faint/ghosted default input works the same as the bright purple input from the Time menu above ElapsedTime.
This Block does allow plugging in a different starting time as a raw number in Nanoseconds, but take great care. This will not give a usable elapsed-time result later, since each new run of the OpMode would start at a different time relative to the constantly running system clock. But the StartTime method shown below would reflect the raw number input. For example if the input is zero, the start time always shows as zero, and an elapsed-time getter method (e.g. ElapsedTime.Time) returns the current system clock time.
The above Block also creates a new timer, allowing you to specify Seconds or Milliseconds as the unit provided by the new timer. Most of the examples above use the basic version with a default resolution of Seconds. Again, this choice affects only programming and display; the actual underlying timer accuracy is in Nanoseconds. This command starts the timer at the current system time.
The above Block normally returns or provides this timer's starting time on the system clock. This is not a constantly running time value, it's the saved time value of the timer's instantiation or last Reset. In other words, it gives the time of the start, not the time since the start. The units will be Seconds or Milliseconds, based on the original setting for this timer.
The above Block returns the current elapsed time in Seconds, even if the timer normally provides Milliseconds.
The above Block returns the current elapsed time in Milliseconds, even if the timer normally provides Seconds.
The above Block returns a text field indicating whether the timer was created for SECONDS or MILLISECONDS.
The above Block writes a message to the system log file, for later analysis. The message shows the timer's current elapsed time in its specified units, preceded by "TIMER:" and a custom text label for identification in the (very detailed) log file. Logging can be a useful substitute for Telemetry, when studying a sequence of very close events, disconnected events, or rare events. Elsewhere this FTC wiki describes downloading system logs for Robot Controller (RC) phones and for REV Control Hubs.
The above Block returns a text string, converted from the timer's numeric value of elapsed time. This allows combining the value with other text; here is an example:
Lastly, there is a time-related Block in the LinearOpMode menu (top of list at left side). This method, called 'getRuntime', returns the time in Seconds since the OpMode was started by pressing the INIT button.
Think of Runtime as a special timer instantiated as the first action in the initialize section of your OpMode -- except it can only be read, and cannot be reset. Runtime starts from zero and is not affected by pressing the START button. None of the above ElapsedTime methods can operate on Runtime; it's not an instance of that class. However the getRuntime Block can be used directly in Math, Logic, Telemetry and DbgLog operations without needing an ElapsedTime method to retrieve its contents.
You can create and use multiple timers, resetting and reading them at various points in your OpMode. They can overlap, and have no effect on each other. It's best to give them meaningful names, like LifterLoopTimer or TimeSinceStart or GrabberSafetyTimer.
This tutorial described basic use of Timers in Blocks programming. Timers also offer many advanced capabilities in FTC robotics, including:
- stall detection
- using gamepad buttons as analog inputs
- complex task automation in Auto and TeleOp
- custom PI and PID control algorithms
Questions, comments and corrections to: [email protected]
-
TensorFlow 2023-2024