Skip to content

Commit ba5df90

Browse files
CopilotTaylerUva
andauthored
Add Copilot PR review instructions for FRC robot code (#56)
All PRs lacked FRC-aware review context, leaving Copilot unable to enforce team standards like the state machine architecture, FreeSpin/Positional motor subsystem split, constants discipline, and naming conventions from the [team wiki](https://github.com/FRCTeam3255/Wiki/blob/main/Software/Conventions.md). ## Changes - **`.github/copilot-instructions.md`** — Global baseline applied to every PR: - State machine rules: one `RobotState` enum, gerund state names (`INTAKING` not `INTAKE`), `setRobotState()` must be first in `initialize()` - Motor architecture: `FreeSpin.java` = velocity-controlled, `Positional.java` = position-controlled; no cross-contamination - Hardcoded number flagging: any numeric literal outside `constants/` or `DeviceIDs.java` is a violation - Follower motor naming: `...FollowerAlignedRequest`/`...FollowerOpposedRequest`, no compass directions in request object names - WPILib typed units everywhere (`AngularVelocity`, `Angle`, `Distance`); never raw `double` for physical quantities - `@Logged` required on every subsystem; wildcard imports preferred - **`.github/instructions/*.instructions.md`** — Path-scoped instructions activated via `applyTo` front-matter: | File | Scope | Key additions | |---|---|---| | `constants.instructions.md` | `constants/**` | Every `TalonFX` needs a `_CONFIGURATION` constant; all setup in `static {}` block | | `states.instructions.md` | `commands/states/**` | `setRobotState()` first rule; `BasePreps` `command`/`input` variable prefix pattern | | `subsystems.instructions.md` | `subsystems/**` | FreeSpin/Positional split enforcement; follower request naming; `periodic()` must be lightweight | | `commands.instructions.md` | `commands/**` | No hardware instantiation in commands; `ChoreoTraj` constants not raw strings; `end()` cleanup | | `deviceids.instructions.md` | `DeviceIDs.java` | CAN ID range comments (`0–9`, `10–29`, `30–49`); one constant per motor; no subsystem name repetition | | `robotcontainer.instructions.md` | `RobotContainer.java` | `public static final *Instance` + `private final logged*Instance` pair; `TRY_` prefix for state commands; bindings in separate `config*Bindings()` methods | <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #55 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Tayler Uva <8679670+TaylerUva@users.noreply.github.com>
1 parent 6134daa commit ba5df90

6 files changed

Lines changed: 610 additions & 0 deletions

.github/copilot-instructions.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# GitHub Copilot Instructions — FRCTeam3255 Robot Code
2+
3+
All code in this repository is FRC (FIRST Robotics Competition) robot code written in Java using the WPILib command-based framework. Reviews and suggestions must be evaluated through the lens of FRC best practices and team conventions documented below.
4+
5+
Reference: [FRCTeam3255 Software Conventions](https://github.com/FRCTeam3255/Wiki/blob/main/Software/Conventions.md)
6+
7+
---
8+
9+
## Project Overview
10+
11+
This robot uses a **state machine architecture**. All robot behavior is coordinated through:
12+
- `StateMachine.java` — manages the `RobotState` enum and validates state transitions
13+
- `DriverStateMachine.java` — manages driver input and drivetrain states
14+
- `commands/states/` — one `Command` class per `RobotState`
15+
16+
Motor hardware is split across exactly two generic subsystems:
17+
- `FreeSpin.java` — all velocity-controlled (free-spinning) motors
18+
- `Positional.java` — all position-controlled (MotionMagic) motors
19+
20+
All subsystem instances are declared as `public static` fields in `RobotContainer` so state commands can reference them directly.
21+
22+
---
23+
24+
## Naming Conventions
25+
26+
| Element | Convention | Example |
27+
|---|---|---|
28+
| Classes | `UpperCamelCase` | `StateMachine`, `FreeSpin` |
29+
| Methods | `lowerCamelCase` | `setFlywheelVelocity()` |
30+
| Variables | `lowerCamelCase` | `commandTurretAngle` |
31+
| Constants | `SCREAMING_SNAKE_CASE` | `FLYWHEEL_CORNER_SPEED` |
32+
| Device IDs | `SCREAMING_SNAKE_CASE` | `INTAKE_ROLLERS_WEST_CAN` |
33+
| State commands | `TRY_` prefix + `SCREAMING_SNAKE_CASE` | `TRY_INTAKING` |
34+
| Controller fields | `con` prefix | `conDriver`, `conOperator` |
35+
| Subsystem instances | `*Instance` suffix | `freeSpinInstance`, `drivetrainInstance` |
36+
| Logged subsystem refs | `logged*` | `loggedFreeSpin` |
37+
38+
---
39+
40+
## Hardcoded Numbers — Flag These
41+
42+
**Always flag** numeric literals that appear outside of `constants/` or `DeviceIDs.java`:
43+
- Magic numbers in subsystem methods, commands, or `RobotContainer`
44+
- Motor percent output values (e.g. `motor.set(0.5)`) — must reference a `SCREAMING_SNAKE_CASE` constant like `ConstFreeSpin.INTAKE_PERCENT_OUTPUT`
45+
- PID gains, setpoints, timeouts, tolerances — all belong in the matching `Const*.java` file
46+
- Physical quantities must use WPILib `Units` typed measures (e.g. `Angle`, `AngularVelocity`, `Distance`) — never raw `double` values
47+
48+
The only acceptable raw `double` for motor power is a reference to a constant with the suffix `_PERCENT_OUTPUT`.
49+
50+
---
51+
52+
## State Machine Rules
53+
54+
- There is **one** `RobotState` enum shared across the entire robot — never create per-mechanism enums.
55+
- `RobotState` values must be in gerund form (`INTAKING`, `SHOOTING`, `CLIMBING`) — not imperative (`INTAKE`, `SHOOT`, `CLIMB`).
56+
- Every state must have a corresponding class in `commands/states/` that extends `Command`.
57+
- In any state command's `initialize()`, **`setRobotState()` must be called first** — before any hardware commands.
58+
- State commands only require `stateMachineInstance`; hardware is driven through `RobotContainer.freeSpinInstance` and `RobotContainer.positionalInstance`.
59+
- The `StateMachine.tryState()` method enforces legal transitions — only valid `RobotState` transitions reach hardware.
60+
61+
---
62+
63+
## Subsystem Architecture
64+
65+
- `FreeSpin.java` holds **all** velocity-controlled TalonFX motors. Do not put positional motors here.
66+
- `Positional.java` holds **all** position-controlled TalonFX motors. Do not put free-spin motors here.
67+
- Motor clusters: exactly one `...Leader` motor; all others are `...Follower`.
68+
- `Follower` control request objects must be named with `Follower` in the name and end in `AlignedRequest` or `OpposedRequest`**never** include compass directions (`East`, `West`, `North`, `South`) in the request object name.
69+
- Separate motor clusters with a blank line for readability.
70+
- Every subsystem class must have `@Logged` annotation.
71+
- All motor configurations (TalonFX, CANcoder, Pigeon2) must come from `constants/` — no inline configuration.
72+
73+
---
74+
75+
## Constants Rules
76+
77+
- One `Const*.java` file per subsystem in `constants/` (e.g. `ConstFreeSpin.java`, `ConstDrivetrain.java`).
78+
- All fields are `public static final`.
79+
- Physical quantities use WPILib `Units` typed measures — never raw `double`.
80+
- **Every** TalonFX motor must have a corresponding `TalonFXConfiguration` constant named `<MOTOR_NAME>_CONFIGURATION`, declared at the top of the class and fully configured in a `static {}` block.
81+
- Nested inner classes for logical sub-groups use `SCREAMING_SNAKE_CASE` (e.g. `PRACTICE_BOT`, `AUTO_ALIGN`) — except `constControllers` in `ConstSystem` which uses `lowerCamelCase` by convention.
82+
- Constant names follow `PURPOSE_DESCRIPTION` (e.g. `FLYWHEEL_CORNER_SPEED`, `INTAKE_PERCENT_OUTPUT`). Do not repeat the subsystem name inside the constant name — it's redundant through the class reference.
83+
84+
---
85+
86+
## DeviceIDs Rules
87+
88+
- All hardware port/ID mappings live in a single `DeviceIDs.java` file at the root of `frc/robot/`.
89+
- Each subsystem has a nested inner class with `lowerCamelCase` + `IDs` suffix (e.g. `freeSpinIDs`, `drivetrainIDs`, `positionalIDs`).
90+
- All ID constants are `SCREAMING_SNAKE_CASE`.
91+
- CAN ID naming: `MECHANISM_POSITION_CAN` (e.g. `INTAKE_ROLLERS_WEST_CAN`).
92+
- Non-CAN IDs: `DEVICETYPE_LOCATION_CONNECTIONTYPE` (e.g. `ENCODER_FRONT_LEFT_DIO`).
93+
- Do not specify which subsystem an ID belongs to inside the ID name — the nested inner class already provides that context.
94+
- CAN ID ranges must be documented in comments: drivetrain `0–9`, FreeSpin `10–29`, Positional `30–49`.
95+
96+
---
97+
98+
## Units
99+
100+
- Always use WPILib's `Units` class — never hardcode unit conversions as raw numbers.
101+
- Method parameters and return types for physical quantities must use typed units (e.g. `Angle`, `AngularVelocity`, `Distance`, `LinearVelocity`) not `double`.
102+
103+
```java
104+
// ❌ Bad
105+
public void setFlywheelSpeed(double rpm) { ... }
106+
107+
// ✔ Good
108+
public void setFlywheelVelocity(AngularVelocity velocity) { ... }
109+
```
110+
111+
---
112+
113+
## Logging
114+
115+
- Every subsystem class must be annotated with `@Logged`.
116+
- Fields that should be excluded from logging must be annotated with `@NotLogged`.
117+
- Use `edu.wpi.first.epilogue.Logged` (Epilogue framework), not SmartDashboard/NetworkTables for subsystem state logging.
118+
119+
---
120+
121+
## Imports
122+
123+
- Prefer wildcard imports (`.*`) over listing individual classes from the same package.
124+
- Import `frc.robot.commands.states.*` rather than listing each state command individually.
125+
126+
---
127+
128+
## RobotContainer Conventions
129+
130+
- Controllers: `private final SN_XboxController con<Role> = new SN_XboxController(controllerIDs.<ROLE>_USB);`
131+
- Subsystem declarations: `public static final <Type> <type>Instance = new <Type>();` followed immediately by `private final <Type> logged<Type>Instance = <type>Instance;`
132+
- State-transition commands: `Command TRY_<STATE> = Commands.deferredProxy(() -> stateMachineInstance.tryState(RobotState.<STATE>));`
133+
- Bindings separated into `configDriverBindings()` and `configOperatorBindings()` — each a private method with no parameters.
134+
- Autonomous setup in `configAutonomous()`.
135+
136+
---
137+
138+
## FRC-Specific Considerations
139+
140+
- `periodic()` methods should be lightweight — avoid blocking calls, heavy computation, or I/O in the scheduler loop.
141+
- Never use `Thread.sleep()` in robot code; use WPILib scheduling (`Commands.waitSeconds()`, etc.).
142+
- Brownout voltage is configured in `RobotContainer` constructor via `RobotController.setBrownoutVoltage()`.
143+
- Practice bot vs. competition bot differentiation is handled via RIO serial number in `ConstSystem.PRACTICE_BOT_RIO_SERIAL_NUMBER`.
144+
- Alliance-aware poses must use `ConstField.Pose2dAllianceSet` — never manually mirror coordinates inline.
145+
- Trajectory names must reference `ChoreoTraj` enum constants — never use raw strings for trajectory lookup.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
applyTo: "src/main/java/frc/robot/constants/**"
3+
---
4+
5+
# Constants Folder Instructions
6+
7+
Files in `constants/` define all static configuration values for the robot. Every number that controls hardware behavior — setpoints, gains, speeds, positions, timeouts — must live here.
8+
9+
## File Naming & Structure
10+
11+
- One file per subsystem: `Const<SubsystemName>.java` (e.g. `ConstFreeSpin.java`, `ConstPositional.java`).
12+
- File is a `public class` (not `final`, unless it is `ConstSystem`) with no constructor.
13+
- `ConstSystem.java` is `public final class` and holds cross-subsystem constants (controller deadbands, RIO serial numbers, etc.).
14+
15+
## Field Declarations
16+
17+
- All fields must be `public static final`.
18+
- All field names must be `SCREAMING_SNAKE_CASE`.
19+
- Use WPILib `Units` typed measures for **all** physical quantities — no raw `double` for speeds, angles, distances, or times.
20+
- `AngularVelocity`, `Angle`, `Distance`, `LinearVelocity`, `Time`, `Voltage`, `Current`, etc.
21+
- Raw `double` is only acceptable for dimensionless ratios or motor percent output values (suffix `_PERCENT_OUTPUT`).
22+
23+
## TalonFX Configuration Requirement
24+
25+
- **Every** TalonFX motor in the project **must** have a corresponding `TalonFXConfiguration` constant in its subsystem's constants file.
26+
- Name: `<MOTOR_DESCRIPTIVE_NAME>_CONFIGURATION` (e.g. `FLYWHEEL_WEST_CONFIGURATION`).
27+
- Declare the configuration object at the top of the class.
28+
- All `.apply()` calls must happen in a `static {}` initializer block — never inline in the subsystem constructor.
29+
30+
```java
31+
// ✔ Good
32+
public class ConstFreeSpin {
33+
public static final TalonFXConfiguration FLYWHEEL_WEST_CONFIGURATION = new TalonFXConfiguration();
34+
35+
static {
36+
FLYWHEEL_WEST_CONFIGURATION.MotorOutput.NeutralMode = NeutralModeValue.Coast;
37+
FLYWHEEL_WEST_CONFIGURATION.MotorOutput.Inverted = InvertedValue.CounterClockwise_Positive;
38+
}
39+
}
40+
```
41+
42+
## Grouping with Inner Classes
43+
44+
- Use nested `public static class` blocks to group related constants (e.g. hardware variants, tuning sets).
45+
46+
47+
## Naming Rules
48+
49+
- Constant names follow `PURPOSE_DESCRIPTION` — what it is used for + minimum detail to remove ambiguity.
50+
- Do **not** repeat the subsystem name inside a constant's name — it is already implied by the class context.
51+
- Valid examples: `FLYWHEEL_CORNER_SPEED`, `INTAKE_ROLLER_PERCENT_OUTPUT`, `OUTTAKE_TOLERANCE`, `CURRENT_LIMIT_FLOOR`.
52+
- Avoid: `FREESPIN_FLYWHEEL_CORNER_SPEED` (subsystem name is redundant).
53+
54+
## Unit Conversions
55+
56+
- Never hardcode a pre-computed conversion result. Derive it from other constants using `Units`:
57+
58+
```java
59+
// ❌ Bad
60+
public static final double WHEEL_CIRCUMFERENCE = 0.31742888;
61+
62+
// ✔ Good
63+
public static final double WHEEL_CIRCUMFERENCE = WHEEL_DIAMETER.in(Units.Meters) * Math.PI;
64+
```
65+
66+
## Common Review Flags
67+
68+
- Any raw numeric literal in a subsystem or command that should be in constants.
69+
- Missing `TalonFXConfiguration` for any motor that exists in `DeviceIDs`.
70+
- Physical quantity stored as `double` instead of a WPILib typed measure.
71+
- Configuration logic placed in the subsystem constructor rather than a `static {}` block here.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
applyTo: "src/main/java/frc/robot/DeviceIDs.java"
3+
---
4+
5+
# DeviceIDs.java Instructions
6+
7+
`DeviceIDs.java` is the **single source of truth** for all hardware port and ID mappings on the robot. Every motor, encoder, sensor, and controller ID lives here and nowhere else.
8+
9+
## File Structure
10+
11+
- One top-level `public class DeviceIDs` with no constructor.
12+
- One nested `public static class` per subsystem, named in `lowerCamelCase` with an `IDs` suffix.
13+
- A comment above each inner class indicating its CAN ID range.
14+
15+
```java
16+
public class DeviceIDs {
17+
public static class controllerIDs { ... } // USB ports
18+
19+
// Drivetrain IDs: 0–9
20+
public static class drivetrainIDs { ... }
21+
22+
// FreeSpin IDs: 10–29
23+
public static class freeSpinIDs { ... }
24+
25+
// Positional IDs: 30–49
26+
public static class positionalIDs { ... }
27+
}
28+
```
29+
30+
## ID Constant Naming
31+
32+
- All ID constants are `public static final int` (or `CANBus` for the CAN bus name) and `SCREAMING_SNAKE_CASE`.
33+
- CAN IDs: `MECHANISM_POSITION_CAN` (e.g. `INTAKE_ROLLERS_WEST_CAN`, `FLYWHEEL_WEST_CAN`).
34+
- Non-CAN: `DEVICETYPE_LOCATION_CONNECTIONTYPE` (e.g. `ENCODER_FRONT_LEFT_DIO`, `DRIVER_USB`).
35+
- Do **not** include the subsystem name in the constant name — the inner class already provides that context.
36+
37+
```java
38+
// ❌ Bad — subsystem name is redundant inside freeSpinIDs
39+
public static class freeSpinIDs {
40+
public static final int FREESPIN_INTAKE_ROLLERS_WEST_CAN = 10;
41+
}
42+
43+
// ✔ Good
44+
public static class freeSpinIDs {
45+
public static final int INTAKE_ROLLERS_WEST_CAN = 10;
46+
}
47+
```
48+
49+
## CAN ID Ranges
50+
51+
Maintain the established ID range blocks and keep IDs within their assigned ranges:
52+
- `drivetrainIDs`: `0–9`
53+
- `freeSpinIDs`: `10–29`
54+
- `positionalIDs`: `30–49`
55+
56+
Flag any ID that falls outside its designated range.
57+
58+
## Every Motor Needs an ID
59+
60+
- Every `TalonFX` declared in `FreeSpin.java` or `Positional.java` must have a corresponding constant in `freeSpinIDs` or `positionalIDs` respectively.
61+
- Every `TalonFX` in `Drivetrain.java` must have a corresponding constant in `drivetrainIDs`.
62+
- Flag any motor instantiated with a raw integer literal instead of a reference to `DeviceIDs`.
63+
64+
## CANBus
65+
66+
- The swerve drivetrain CAN bus is declared as a `CANBus` object (not an `int`) in `drivetrainIDs`.
67+
- Name: `CAN_BUS_NAME`.
68+
69+
## Common Review Flags
70+
71+
- New motor added to a subsystem but no corresponding ID added here.
72+
- ID constant name repeats the subsystem name (redundant with the inner class).
73+
- Raw integer literal used in a subsystem motor constructor instead of referencing `DeviceIDs`.
74+
- ID value falls outside the designated range for its inner class.
75+
- New subsystem added but no corresponding inner class created in `DeviceIDs`.
76+
- Inner class name doesn't follow `lowerCamelCase` + `IDs` pattern.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
applyTo: "src/main/java/frc/robot/RobotContainer.java"
3+
---
4+
5+
# RobotContainer.java Instructions
6+
7+
`RobotContainer.java` is the top-level composition class — it instantiates all subsystems, declares all command objects, and configures all button-to-command bindings.
8+
9+
## Subsystem Declarations
10+
11+
Each subsystem must be declared as a `public static final` instance with an `*Instance` suffix, immediately followed by a `private final` logged reference pointing to the same object:
12+
13+
```java
14+
public static final FreeSpin freeSpinInstance = new FreeSpin();
15+
private final FreeSpin loggedFreeSpin = freeSpinInstance;
16+
17+
public static final Positional positionalInstance = new Positional();
18+
private final Positional loggedPositional = positionalInstance;
19+
```
20+
21+
- The `loggedXxx` field enables Epilogue's `@Logged` annotation on `RobotContainer` to automatically log subsystem state.
22+
- Do not add `@NotLogged` to the logged reference fields — that would defeat their purpose.
23+
- Do not instantiate subsystems anywhere else in the codebase.
24+
25+
## Controller Declarations
26+
27+
```java
28+
private final SN_XboxController conDriver = new SN_XboxController(controllerIDs.DRIVER_USB);
29+
private final SN_XboxController conOperator = new SN_XboxController(controllerIDs.OPERATOR_USB);
30+
```
31+
32+
- Controller fields are `private final`.
33+
- Name: `con` + role (e.g. `conDriver`, `conOperator`).
34+
- Always reference `controllerIDs` from `DeviceIDs` — never use a raw integer.
35+
36+
## State Transition Command Declarations
37+
38+
State transition commands must:
39+
- Be `SCREAMING_SNAKE_CASE`.
40+
- Be prefixed with `TRY_`.
41+
- Use `Commands.deferredProxy(() -> stateMachineInstance.tryState(RobotState.<STATE>))`.
42+
43+
```java
44+
Command TRY_INTAKING = Commands.deferredProxy(
45+
() -> stateMachineInstance.tryState(RobotState.INTAKING));
46+
Command TRY_NONE = Commands.deferredProxy(
47+
() -> stateMachineInstance.tryState(RobotState.NONE));
48+
```
49+
50+
Non-state commands (drive modes) are also `SCREAMING_SNAKE_CASE` with a descriptive name (no `TRY_` prefix):
51+
52+
```java
53+
Command MANUAL = new DeferredCommand(
54+
driverStateMachineInstance.tryState(DriverState.MANUAL, ...), Set.of(...));
55+
```
56+
57+
## Constructor
58+
59+
The constructor must call these methods in order:
60+
1. Set controller deadbands (e.g. `conDriver.setLeftDeadband(...)`)
61+
2. Set default commands (e.g. `driverStateMachineInstance.setDefaultCommand(MANUAL)`)
62+
3. `configDriverBindings()`
63+
4. `configOperatorBindings()`
64+
5. `configAutonomous()`
65+
6. Any global hardware setup (e.g. `RobotController.setBrownoutVoltage(ConstSystem.BROWNOUT_VOLTAGE)`)
66+
67+
## Binding Methods
68+
69+
- `configDriverBindings()``private`, no parameters, configures all driver controller bindings.
70+
- `configOperatorBindings()``private`, no parameters, configures all operator controller bindings.
71+
- `configAutonomous()``private` or package-private, configures `AutoFactory`, registers autonomous routines, and sets up `SendableChooser`.
72+
73+
Never define bindings inline in the constructor.
74+
75+
## Autonomous Configuration
76+
77+
- The `AutoFactory` is the single factory for all Choreo trajectory commands.
78+
- Autonomous routines are registered with `autoChooser.addOption()`.
79+
- Starting poses must be set via `autoFactory.resetOdometry(path.name()).ignoringDisable(true)` in an `onChange` listener.
80+
- All trajectory references use `ChoreoTraj` enum constants — never raw strings.
81+
82+
## `isPracticeBot()`
83+
84+
- Practice bot detection uses `RobotController.getSerialNumber().equals(ConstSystem.PRACTICE_BOT_RIO_SERIAL_NUMBER)`.
85+
- The serial number lives in `ConstSystem` — never hardcode it here.
86+
87+
## Common Review Flags
88+
89+
- Subsystem declared without the paired `loggedXxx` field.
90+
- Subsystem declared without `public static final`.
91+
- Controller declared with a raw integer port instead of `controllerIDs.*`.
92+
- State transition command not using `Commands.deferredProxy()`.
93+
- State transition command missing the `TRY_` prefix.
94+
- Bindings defined inline in the constructor instead of in `configDriverBindings()` / `configOperatorBindings()`.
95+
- Trajectory referenced as a raw string instead of a `ChoreoTraj` constant.
96+
- Numeric literal used for brownout voltage or deadband instead of a `ConstSystem` constant.

0 commit comments

Comments
 (0)