FIRST-Tech-Challenge / FIRST-Tech-Challenge/FtcRobotController
Expose Driver Station heartbeat age to OpModes (ms + official period)
- Dominant language
- Java
- Stars
- 1.3k
- Forks
- 9.9k
- Avg merge
- 18m
- Merged PRs (30d)
- 1
Description
## Summary
This is a field problem, not a theoretical API request.
Driver Station radio loss while TeleOp is still running (last stick command kept driving the robot until the official ~2 s disconnect stopped the OpMode) is something we have seen at events, and we just hit it in practice. After talking it through as a team, the best option we could identify that does not fight the official watchdog is: give OpModes the age of the last Driver Station heartbeat, and let teams scale then zero drive commands on thresholds they choose. Do not stop the OpMode early. Do not replace FIRST's disconnect handling.
Today that age is not on a supported OpMode API, so we cannot implement the policy.
## What happened
When the DS/RC link drops (or looks dropped), gamepad fields do not go to zero. They sit at the last axes. `loop()` keeps applying that as if the driver were still holding the stick. The robot can keep moving toward a wall or alliance partner until the RC notices official disconnect and kills the OpMode.
That leftover-motion window is the hazard. We are not asking to shorten FIRST's two-second timeout. We want a way to stop using stale input as soon as heartbeats are late, while the OpMode is still legally running.
## Why existing public APIs are not enough
- **`Gamepad` axes** are last delivered state, not "a packet arrived this cycle."
- **`Gamepad.timestamp`** is last **state change**, not last heartbeat. A held stick may not update it; an update is not proof of Wi-Fi.
- **`isStopRequested()` / OpMode stop** only fire after official disconnect. That is too late for this policy; it is also the correct official stop.
- **`EventLoopManager.getHeartbeat()`** exists in RobotCore, but OpModes do not receive that manager. Using it from TeamCode would be unsupported.
A running loop is not a fresh driver command.
## What we would do if the API existed (team policy, not SDK defaults)
We would not call `requestOpModeStop()`. We would scale then zero `setPower` (or ignore drive intents) using **our** thresholds. Example at the documented ~10 Hz heartbeat:
| Intervals since last beat | Approx. age | Drive command |
| --- | --- | --- |
| 0-1 | 0-100 ms | Full (jitter) |
| 2-4 | 200-400 ms | Scale down |
| 5+ | >=500 ms | Zero until a new heartbeat |
Other teams should pick different numbers. The SDK should not hardcode 5.
Official ~2 s OpMode stop and Hub keepalive stay FIRST's. Default OpModes that never call the new methods stay unchanged.
## Proposed API
On `OpMode` (or another documented type every OpMode already has):
```java
/**
* Milliseconds since the last Driver Station heartbeat was received.
* @return age in ms, or -1 if no heartbeat has been received yet
*/
public long getDriverStationHeartbeatAgeMs() { ... }
/**
* Official expected spacing between Driver Station heartbeats, in ms.
* Must be the same period the RC uses internally (wiki: on the order of 100 ms).
*/
public long getDriverStationHeartbeatPeriodMs() { ... }
```
Optional convenience, **only** as `floor(ageMs / periodMs)` from those two values (or `-1` if age is `-1`):
```java
/** Elapsed expected heartbeat intervals since the last received beat. */
public long getDriverStationHeartbeatIntervalsElapsed() { ... }
```
Please do not add `isDriverStationPeerConnected()`. After the RC considers the DS disconnected, it already stops the OpMode. During the stale-command window the peer is still considered connected, so a boolean would not help.
## Semantics (please document)
- **Heartbeat** = Robocol keep-alive for the DS/RC link, not `Gamepad.timestamp`.
- **Age** grows while packets are missing and resets when a heartbeat arrives (stick values may be unchanged).
- **Period** is the official internal spacing, published, so teams do not guess.
- Intervals = integer division, not remainder (`%`).
- One late packet looks like N elapsed intervals until the next packet; that is the freshness signal we need.
- Time base: `uptimeMillis` or equivalent, documented.
- Safe to read from the OpMode loop thread.
- No extra Wi-Fi, packet injection, or second DS client (E301, E302, R704).
## Non-goals
- Changing or shortening the official disconnect / OpMode-stop timeout.
- Weakening Hub keepalive fail-safe.
- Forcing any team to zero motors.
- Detecting a gamepad USB unplug (different failure).
- A TeamCode-only workaround in the public `FtcRobotController` tree (`OpMode` / `EventLoopManager` live in RobotCore).
## Why an issue rather than a PR
FIRST does not accept pull requests on this repository (one-way SDK export). The implementation belongs in RobotCore. We are proposing the contract so the Tech Team can implement it internally.
## Acceptance
- Java TeleOp can read age every loop.
- Age stays small while heartbeats arrive and grows if the DS is silenced (or a test the Tech Team prefers).
- Age is **not** tied to stick motion.
- Period matches internal heartbeat spacing.
- `getRuntime()`, gamepads, and official stop behavior are unchanged if the new methods are unused.
## References
- [FTC SDK Troubleshooting](https://github.com/FIRST-Tech-Challenge/FtcRobotController/wiki/Troubleshooting) (heartbeats about every 0.1 s; more than two seconds without communication treated as disconnect)
- RobotCore `EventLoopManager.getHeartbeat()` (exists; not a supported OpMode surface)
Contributor guide
Research direction
Start by reading RobotCore's EventLoopManager.getHeartbeat() and the supported OpMode API surface. Trace how Driver Station keep-alive timing is recorded, then define how age and official period can be exposed safely to Java OpModes. Done means the documented methods report heartbeat freshness independently of stick motion without changing existing runtime, gamepad, or official stop behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api, robotics
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100