Skip to content

Commit 126e7ef

Browse files
committed
Update docs
1 parent d9b76fc commit 126e7ef

2 files changed

Lines changed: 113 additions & 50 deletions

File tree

doc/DEFENSIVE_GUARDS.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,27 +166,29 @@ if (denominator <= 0) {
166166
// Guard: denominator <= 0 means target is unreachable with this impact angle (would require
167167
// negative velocity or infinite speed). Using < 1e-6 threshold also catches near-zero values
168168
// that would cause numerical instability in sqrt(g / denominator).
169-
if (denominator < 1e-6) {
169+
// Use !(x >= threshold) instead of (x < threshold) to catch NaN
170+
if (!(denominator >= 1e-6)) {
170171
Logger.recordOutput("Launcher/" + key + "/Reachable", false);
171172
// log(d, v0, key);
172173
return v0nominalLast;
173174
}
174175
```
175176

176-
**Change:** Threshold changed from `<= 0` to `< 1e-6` and explanatory comment added above the guard.
177+
**Change:** Threshold changed from `<= 0` to `>= 1e-6` (inverted with `!`) and explanatory comment added above the guard. The inverted syntax `!(x >= threshold)` catches both values below the threshold AND NaN values (since NaN comparisons always return false).
177178

178179
### Launcher.java - v_0r Guard (NEW)
179180

180181
**After the denominator check:**
181182
```java
182183
double v_0r = dr * Math.sqrt(g / denominator);
183-
if (v_0r < 1e-6) {
184+
// Use !(x >= threshold) instead of (x < threshold) to catch NaN
185+
if (!(v_0r >= 1e-6)) {
184186
Logger.recordOutput("Launcher/" + key + "/Reachable", false);
185187
return v0nominalLast;
186188
}
187189
```
188190

189-
**Impact:** Prevents division by near-zero v_0r in subsequent calculation.
191+
**Impact:** Prevents division by near-zero v_0r in subsequent calculation. Also catches NaN propagation.
190192

191193
### Launcher.java - Discriminant (REFINED)
192194

@@ -205,15 +207,16 @@ if (discriminant < 0) {
205207
// Guard: discriminant < 0 means target is beyond maximum range for current flywheel speed.
206208
// Using < 1e-6 threshold adds safety margin against sqrt of tiny negative values from
207209
// floating-point errors at the edge of reachable range.
208-
if (discriminant < 1e-6) {
210+
// Use !(x >= threshold) instead of (x < threshold) to catch NaN
211+
if (!(discriminant >= 1e-6)) {
209212
// Unreachable target at this speed
210213
Logger.recordOutput("Launcher/" + key + "/Reachable", false);
211214
// log(d, v0, key);
212215
return v0replannedLast;
213216
}
214217
```
215218

216-
**Change:** Threshold changed from `< 0` to `< 1e-6` and explanatory comment added above the guard.
219+
**Change:** Threshold changed from `< 0` to `>= 1e-6` (inverted with `!`) and explanatory comment added above the guard. The inverted syntax catches NaN values as well.
217220

218221
---
219222

doc/LED.md

Lines changed: 104 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Series can span multiple physical portions. By implementing WPILib's `LEDReader`
3838
3939
┌─────────────────────────────────────────────────────────────┐
4040
│ LEDSeries │
41-
│ - Enum of logical series (Y_AXIS, X_AXIS, ALL, etc.)
41+
│ - Enum of logical series (Y_AXIS, X_AXIS_FULL, ALL, etc.) │
4242
│ - Implements LEDReader + LEDWriter for unified buffer │
4343
│ - applyPattern() applies any LEDPattern │
4444
└─────────────────────────────────────────────────────────────┘
@@ -58,35 +58,41 @@ The robot currently has one LED strip:
5858

5959
| Strip | PWM Port | LED Count |
6060
|-------|----------|-----------|
61-
| MAIN | 0 | 24 |
61+
| MAIN | 0 | 36 |
6262

6363
### Series Configuration
6464

6565
The strip is divided into two primary segments by robot axis:
6666

6767
| Series | LEDs | Description |
6868
|--------|------|-------------|
69-
| `ALL` | 0-23 | All LEDs as one series |
70-
| `Y_AXIS` | 0-11 | Along robot Y axis (pose-seek, hub countdown) |
71-
| `X_AXIS` | 12-23 | Along robot X axis (auto selection, robot state) |
69+
| `ALL` | 0-35 | All LEDs as one series |
70+
| `Y_AXIS` | 0-11 | Along robot Y axis (pose-seek feedback, robot state) |
71+
| `X_AXIS_FULL` | 12-35 | Full X-axis range (hub countdown when FMS attached) |
72+
| `X_AXIS_BODY` | 15-35 | Main X-axis display area (hub countdown when FMS not attached) |
73+
| `AUTO_SELECTION` | 24-35 | Auto selection counting blocks |
7274

73-
#### Pose-Seek Segments (within Y_AXIS)
75+
#### Warning Indicators
7476

75-
The Y_AXIS segment is further divided for pose-seek feedback:
77+
Reserved LEDs for status warnings:
7678

77-
```
78-
[Y_LEFT] [ROT_LEFT] [X_CENTER] [ROT_RIGHT] [Y_RIGHT]
79-
[0-1] [2-3] [4-7] [8-9] [10-11]
80-
```
79+
| Series | LEDs | Description |
80+
|--------|------|-------------|
81+
| `WARNING_STORAGE` | 0-1 | USB storage low warning (orange-red when <2GB free) |
82+
| `WARNING_COMPRESSOR` | 12-13 | Compressor state (green when running) |
83+
| `WARNING_ALLIANCE` | 22-23 | Alliance mismatch warning (yellow when DS/selected disagree) |
84+
85+
#### Pose-Seek Segments (within Y_AXIS)
86+
87+
The Y_AXIS segment includes pose-seek feedback for manual alignment:
8188

8289
| Series | LEDs | Description |
8390
|--------|------|-------------|
84-
| `POSE_Y_LEFT` | 0-1 | Left side Y translation feedback |
85-
| `POSE_ROTATION_LEFT` | 2-3 | Left rotation feedback |
86-
| `POSE_X_CENTER` | 4-7 | X translation feedback (center) |
87-
| `POSE_ROTATION_RIGHT` | 8-9 | Right rotation feedback |
88-
| `POSE_Y_RIGHT` | 10-11 | Right side Y translation feedback |
89-
| `POSE_ROTATION` | 2-3, 8-9 | Combined rotation (both sides) |
91+
| `POSE_Y` | 3-6 | Y translation feedback |
92+
| `POSE_ROTATION_Y` | 8-11 | Rotation feedback (Y axis side) |
93+
| `POSE_ROTATION_X` | 12-15 | Rotation feedback (X axis side) |
94+
| `POSE_X` | 17-20 | X translation feedback |
95+
| `POSE_ROTATION` | 8-11, 12-15 | Combined rotation (composite series) |
9096

9197
## Display Functions
9298

@@ -99,28 +105,27 @@ Displays the currently selected autonomous routine during disabled mode.
99105
- Shows counting blocks in alliance color (red or blue)
100106
- Number of blocks corresponds to the auto option number (1, 2, 3, etc.)
101107
- Blinks yellow if no auto is selected
102-
- Sets the last LED to yellow if there's a mismatch between driver station alliance and selected alliance
108+
- Shows yellow warning on `WARNING_ALLIANCE` if there's a mismatch between driver station alliance and selected alliance
109+
- Shows orange-red warning on `WARNING_STORAGE` if USB storage is below 2GB free
103110

104-
**Used on:** `X_AXIS` (LEDs 12-23)
111+
**Used on:** `AUTO_SELECTION` (LEDs 24-35)
105112

106113
### displayPoseSeek(currentPose, targetPose)
107114

108-
Provides visual feedback for manually aligning the robot to a target pose. Useful during disabled mode to help drivers position the robot for autonomous.
109-
110-
**Layout:** `[Y_LEFT][ROT_LEFT][X_CENTER][ROT_RIGHT][Y_RIGHT]`
115+
Provides visual feedback for manually aligning the robot to a target pose. Useful during disabled mode to help drivers position the robot for autonomous. Coordinates are transformed to robot-relative.
111116

112117
| Segment | Meaning |
113118
|---------|---------|
114-
| **X (center)** | Green = drive forward, Red = drive backward, White = correct |
115-
| **Rotation (inner)** | Magenta = rotate CCW, Cyan = rotate CW, White = correct |
116-
| **Y (ends)** | Green on side to move toward, Red on opposite side, White = correct |
119+
| **X (POSE_X)** | Green = drive forward, Red = drive backward, White = correct |
120+
| **Rotation (POSE_ROTATION_X/Y)** | Green on one side, Red on other side depending on rotation direction; White = correct |
121+
| **Y (POSE_Y)** | Red = move left needed, Green = move right needed, White = correct |
117122

118123
Tolerances are defined in `LEDConstants`:
119124
- X: 5 cm
120125
- Y: 6 cm
121126
- Heading: 3°
122127

123-
**Used on:** `Y_AXIS` segments (LEDs 0-11)
128+
**Used on:** `POSE_X`, `POSE_Y`, `POSE_ROTATION_X`, `POSE_ROTATION_Y` segments
124129

125130
### displayRobotState(isOnTarget, isSpindexing)
126131

@@ -133,16 +138,27 @@ Shows launcher and spindexer state during teleop and autonomous.
133138
| On target, spindexer inactive | Green | Solid |
134139
| On target, spindexer active | Green | Bounce ripple |
135140

136-
**Used on:** `X_AXIS` (LEDs 12-23)
141+
**Used on:** `Y_AXIS` (LEDs 0-11)
137142

138143
### displayHubCountdown()
139144

140-
Shows remaining time in the current match phase as a progress bar.
145+
Shows remaining time in the current match phase as an "urgent countdown" progress bar.
141146

142147
- Bar fills in alliance color (red or blue based on hub state)
143148
- Empties as time runs out
149+
- In the final 10 seconds, resets to full and counts down while flashing (4Hz)
150+
- When FMS is attached, uses full X-axis range for maximum visibility
144151

145-
**Used on:** `Y_AXIS` (LEDs 0-11)
152+
**Used on:** `X_AXIS_FULL` (LEDs 12-35) when FMS attached, `X_AXIS_BODY` (LEDs 15-35) otherwise
153+
154+
### displayCompressorState(isRunning)
155+
156+
Shows compressor state on the warning indicator.
157+
158+
- Green when compressor is actively running
159+
- Off (black) when compressor is idle
160+
161+
**Used on:** `WARNING_COMPRESSOR` (LEDs 12-13)
146162

147163
### clear() / clear(series, ...)
148164

@@ -171,17 +187,21 @@ The `LEDController` provides pre-built display methods:
171187
LEDController leds = LEDController.getInstance();
172188

173189
// Display auto selection (counting blocks in alliance color)
190+
// Also shows warning indicators for alliance mismatch and low USB storage
174191
leds.displayAutoSelection();
175192

176193
// Display pose-seek feedback
177194
leds.displayPoseSeek(drive.getPose(), targetPose);
178195

179-
// Display robot state
196+
// Display robot state (on Y_AXIS)
180197
leds.displayRobotState(() -> launcher.isOnTarget(), () -> feeder.isSpinning());
181198

182-
// Display hub countdown (progress bar)
199+
// Display hub countdown (urgent countdown bar on X_AXIS)
183200
leds.displayHubCountdown();
184201

202+
// Display compressor state
203+
leds.displayCompressorState(compressor.isEnabled());
204+
185205
// Clear all LEDs
186206
leds.clear();
187207
```
@@ -194,7 +214,7 @@ In `LEDStrip.java`, define each physical LED strip:
194214

195215
```java
196216
public enum LEDStrip {
197-
MAIN(0, 24); // PWM port 0, 24 LEDs
217+
MAIN(0, 36); // PWM port 0, 36 LEDs
198218
// FRONT(8, 60), // Uncomment to add more strips
199219
// BACK(7, 30);
200220
...
@@ -208,15 +228,26 @@ In `LEDSeries.java`, the `P` class defines portions - the single source of truth
208228
```java
209229
private static final class P {
210230
// (strip, startIndex, endIndex, reversed)
211-
static final Portion ALL = new Portion(LEDStrip.MAIN, 0, 23, false);
231+
static final Portion ALL = new Portion(LEDStrip.MAIN, 0, 35, false);
232+
233+
// Primary segments
212234
static final Portion Y_AXIS = new Portion(LEDStrip.MAIN, 0, 11, false);
213-
static final Portion X_AXIS = new Portion(LEDStrip.MAIN, 12, 23, false);
235+
static final Portion X_AXIS_FULL = new Portion(LEDStrip.MAIN, 12, 35, false);
236+
static final Portion X_AXIS_BODY = new Portion(LEDStrip.MAIN, 15, 35, false);
237+
238+
// Warning indicators
239+
static final Portion WARNING_STORAGE = new Portion(LEDStrip.MAIN, 0, 1, false);
240+
static final Portion WARNING_COMPRESSOR = new Portion(LEDStrip.MAIN, 12, 13, false);
241+
static final Portion WARNING_ALLIANCE = new Portion(LEDStrip.MAIN, 22, 23, false);
242+
243+
// Auto selection
244+
static final Portion AUTO_SELECTION = new Portion(LEDStrip.MAIN, 24, 35, false);
245+
214246
// Pose-seek portions
215-
static final Portion POSE_Y_LEFT = new Portion(LEDStrip.MAIN, 0, 1, false);
216-
static final Portion POSE_ROT_LEFT = new Portion(LEDStrip.MAIN, 2, 3, false);
217-
static final Portion POSE_X_CENTER = new Portion(LEDStrip.MAIN, 4, 7, false);
218-
static final Portion POSE_ROT_RIGHT = new Portion(LEDStrip.MAIN, 8, 9, false);
219-
static final Portion POSE_Y_RIGHT = new Portion(LEDStrip.MAIN, 10, 11, false);
247+
static final Portion POSE_Y = new Portion(LEDStrip.MAIN, 3, 6, false);
248+
static final Portion POSE_ROTATION_Y = new Portion(LEDStrip.MAIN, 8, 11, false);
249+
static final Portion POSE_ROTATION_X = new Portion(LEDStrip.MAIN, 12, 15, false);
250+
static final Portion POSE_X = new Portion(LEDStrip.MAIN, 17, 20, false);
220251
}
221252
```
222253

@@ -230,9 +261,14 @@ Series reference portions from `P`:
230261
public enum LEDSeries implements LEDReader, LEDWriter {
231262
ALL(P.ALL),
232263
Y_AXIS(P.Y_AXIS),
233-
X_AXIS(P.X_AXIS),
234-
POSE_Y_LEFT(P.POSE_Y_LEFT),
235-
POSE_ROTATION(P.POSE_ROT_LEFT, P.POSE_ROT_RIGHT), // Composite series
264+
X_AXIS_FULL(P.X_AXIS_FULL),
265+
X_AXIS_BODY(P.X_AXIS_BODY),
266+
WARNING_ALLIANCE(P.WARNING_ALLIANCE),
267+
WARNING_STORAGE(P.WARNING_STORAGE),
268+
WARNING_COMPRESSOR(P.WARNING_COMPRESSOR),
269+
AUTO_SELECTION(P.AUTO_SELECTION),
270+
POSE_Y(P.POSE_Y),
271+
POSE_ROTATION(P.POSE_ROTATION_X, P.POSE_ROTATION_Y), // Composite series
236272
...
237273
}
238274
```
@@ -291,6 +327,22 @@ public static LEDPattern progressBar(
291327
}
292328
```
293329

330+
### Urgent Countdown Pattern
331+
332+
The `urgentCountdown` pattern is designed for match timers with an "urgent" final phase:
333+
334+
```java
335+
public static LEDPattern urgentCountdown(
336+
Supplier<Double> remainingSecondsSupplier,
337+
Supplier<Double> totalDurationSupplier,
338+
double urgencyThresholdSeconds, // e.g., 10.0
339+
Supplier<Color> colorSupplier,
340+
Color backgroundColor,
341+
double blinkPeriodSeconds) { // e.g., 0.25 for 4Hz flash
342+
...
343+
}
344+
```
345+
294346
## Available Patterns
295347

296348
### WPILib Built-in Patterns
@@ -311,6 +363,7 @@ LEDPattern.gradient(LEDPattern.GradientType.kContinuous, Color.kRed, Color.kBlue
311363
| `scrollingBlocks(color, blockSize, gapSize)` | Configurable scrolling blocks |
312364
| `solidIf(condition, trueColor, falseColor)` | Conditional solid color |
313365
| `progressBar(progress, colorSupplier, bgColor)` | Fill bar based on 0.0-1.0 value |
366+
| `urgentCountdown(remaining, total, threshold, color, bg, blinkPeriod)` | Countdown bar with urgent flashing mode in final seconds |
314367
| `statusGradient(value, lowColor, highColor)` | Blend between colors |
315368
| `countingBlocks(countSupplier, colorSupplier, blockSize, gapSize)` | Display N blocks |
316369
| `bounceRipple(color)` | Expanding/contracting ripple with comet tail |
@@ -324,11 +377,15 @@ LEDPattern.gradient(LEDPattern.GradientType.kContinuous, Color.kRed, Color.kBlue
324377
|---------|-------------|
325378
| `solidBlackPattern` | Solid black (off) |
326379
| `solidYellowPattern` | Solid yellow |
380+
| `solidRedPattern` | Solid red |
327381
| `solidGreenPattern` | Solid green |
382+
| `solidWhitePattern` | Solid white |
383+
| `solidOrangeRedPattern` | Solid orange-red (for storage warning) |
384+
| `blinkingYellowPattern` | Blinking yellow (0.5s period, for missing auto) |
328385
| `bounceRippleYellowPattern` | Yellow bounce ripple (spindexing, not on target) |
329386
| `bounceRippleGreenPattern` | Green bounce ripple (spindexing, on target) |
330387
| `autoSelectionPattern` | Counting blocks in alliance color |
331-
| `hubCountdownPattern` | Progress bar for match phase |
388+
| `hubCountdownPattern` | Urgent countdown bar for match phase (flashes in final 10s) |
332389

333390
## Simulation
334391

@@ -359,4 +416,7 @@ Check the start/end indices in your `Portion` definitions. Indices are inclusive
359416
WPILib uses RGB order. Some LED strips expect GRB. Check your strip's data format.
360417

361418
### Same LEDs lighting up for different series
362-
Verify that portion definitions in the `P` class have non-overlapping indices for the segments you're using independently.
419+
Some series intentionally overlap (e.g., `WARNING_STORAGE` overlaps with `Y_AXIS`). This is by design - warning indicators share space with other displays. Make sure your code doesn't apply conflicting patterns to overlapping series simultaneously.
420+
421+
### Warning indicators not showing
422+
Warning indicators (`WARNING_ALLIANCE`, `WARNING_STORAGE`, `WARNING_COMPRESSOR`) are controlled by `displayAutoSelection()` and `displayCompressorState()`. Ensure these methods are being called in the appropriate robot modes.

0 commit comments

Comments
 (0)