@@ -3,4 +3,144 @@ title: Control Module
33description : PID, feedforward, geometry, and motion profiling.
44---
55
6- Content coming soon.
6+ import { Tabs , TabItem , Aside } from ' @astrojs/starlight/components'
7+
8+ The ` control ` module is a WPILib-style control theory library: feedback controllers, feedforward models,
9+ signal filters, motion profiling, typed 2D geometry, and a type-safe units system. It has no dependency on
10+ the FTC SDK, so it can be used entirely on its own, even outside of a robot project.
11+
12+ ## PID control
13+
14+ ` PIDController ` implements a standard PID loop, with optional continuous input for wrapping angles.
15+
16+ <Tabs syncKey = " language" >
17+ <TabItem label = " Kotlin" >
18+ ``` kotlin
19+ val pid = PIDController (PIDCoefficients (kP = 0.01 , kI = 0.0 , kD = 0.001 ))
20+
21+ val output = pid.calculate(error = setpoint - currentPosition)
22+ ```
23+ </TabItem >
24+ <TabItem label = " Java" >
25+ ``` java
26+ PIDController pid = new PIDController (new PIDCoefficients (0.01 , 0.0 , 0.001 ));
27+
28+ double output = pid. calculate(setpoint - currentPosition);
29+ ```
30+ </TabItem >
31+ </Tabs >
32+
33+ ## Feedforward
34+
35+ ` SimpleFeedforward ` models a mechanism with static friction, velocity, and acceleration terms.
36+ ` ElevatorFeedforward ` and ` ArmFeedforward ` add a gravity term for mechanisms that fight gravity.
37+
38+ <Tabs syncKey = " language" >
39+ <TabItem label = " Kotlin" >
40+ ``` kotlin
41+ val feedforward = ArmFeedforward (GravityFeedforwardParameters (kG = 0.3 , kV = 0.01 ))
42+
43+ val output = feedforward.calculate(position = armAngle, velocity = targetVelocity)
44+ ```
45+ </TabItem >
46+ <TabItem label = " Java" >
47+ ``` java
48+ ArmFeedforward feedforward = new ArmFeedforward (new GravityFeedforwardParameters (0.3 , 0.0 , 0.01 , 0.0 ));
49+
50+ double output = feedforward. calculate(armAngle, targetVelocity, 0.0 );
51+ ```
52+ </TabItem >
53+ </Tabs >
54+
55+ ## Geometry
56+
57+ ` Pose2d ` , ` Vector2d ` , and ` Rotation2d ` model robot position and orientation, adapted from RoadRunner and
58+ WPILib. They support the full range of operators you'd expect: adding a ` Twist2d ` to a ` Pose2d ` , composing
59+ rotations, taking the relative transform between two poses, and more.
60+
61+ <Tabs syncKey = " language" >
62+ <TabItem label = " Kotlin" >
63+ ``` kotlin
64+ val pose = Pose2d (x = 12.0 , y = 24.0 , heading = 90.0 .degrees)
65+ val relative = otherPose.relativeTo(pose)
66+ ```
67+ </TabItem >
68+ <TabItem label = " Java" >
69+ ``` java
70+ Pose2d pose = new Pose2d (12.0 , 24.0 , Units . Degrees . of(90.0 ));
71+ Transform2d relative = otherPose. relativeTo(pose);
72+ ```
73+ </TabItem >
74+ </Tabs >
75+
76+ ## Motion profiling
77+
78+ ` TrapezoidProfile ` generates a smooth acceleration/cruise/deceleration profile between two ` MotionState ` s,
79+ respecting maximum velocity and acceleration constraints.
80+
81+ <Tabs syncKey = " language" >
82+ <TabItem label = " Kotlin" >
83+ ``` kotlin
84+ val profile = TrapezoidProfile (TrapezoidProfileConstraints .linear(maxVelocity = 30.0 , maxAcceleration = 60.0 ))
85+
86+ val state = profile.calculate(current = currentState, goal = goalState)
87+ ```
88+ </TabItem >
89+ <TabItem label = " Java" >
90+ ``` java
91+ TrapezoidProfile profile = new TrapezoidProfile (TrapezoidProfileConstraints . linear(30.0 , 60.0 ));
92+
93+ MotionState state = profile. calculate(currentState, goalState);
94+ ```
95+ </TabItem >
96+ </Tabs >
97+
98+ ## Drive kinematics
99+
100+ ` MecanumKinematics ` and ` TankKinematics ` convert raw driver input (forward, strafe, and rotation) into
101+ per-wheel power for their respective drivetrains.
102+
103+ <Tabs syncKey = " language" >
104+ <TabItem label = " Kotlin" >
105+ ``` kotlin
106+ val kinematics = MecanumKinematics ()
107+ val powers = kinematics.calculate(DriveInput (x = gamepad.leftStickX.value, y = - gamepad.leftStickY.value, rx = gamepad.rightStickX.value))
108+ ```
109+ </TabItem >
110+ <TabItem label = " Java" >
111+ ``` java
112+ MecanumKinematics kinematics = new MecanumKinematics ();
113+ MecanumWheelPowers powers = kinematics. calculate(new DriveInput (gamepad. leftStickX(). getValue(), - gamepad. leftStickY(). getValue(), gamepad. rightStickX(). getValue()));
114+ ```
115+ </TabItem >
116+ </Tabs >
117+
118+ ## Filters
119+
120+ ` EMAFilter ` and ` SlewRateLimiter ` smooth out noisy or jumpy signals, ` Debouncer ` filters chattery boolean
121+ input, and ` KalmanFilter ` fuses multiple noisy measurements into a single best estimate of state.
122+
123+ ## Units
124+
125+ Every measurement in the ` control ` module is typed, using extension properties instead of bare doubles:
126+
127+ <Tabs syncKey = " language" >
128+ <TabItem label = " Kotlin" >
129+ ``` kotlin
130+ val distance = 5.0 .meters
131+ val speed = distance / 2.0 .seconds // a LinearVelocity, not a raw Double
132+ ```
133+ </TabItem >
134+ <TabItem label = " Java" >
135+ ``` java
136+ Distance distance = Units . Meters . of(5.0 );
137+ LinearVelocity speed = distance. div(Units . Seconds . of(2.0 )); // a LinearVelocity, not a raw double
138+ ```
139+ </TabItem >
140+ </Tabs >
141+
142+ <Aside type = " tip" >
143+ Dividing or multiplying two measures always produces a typed result: dividing a ` Distance ` by a
144+ ` Time ` gives you a genuine ` LinearVelocity ` , not a bare number, so unit mistakes get caught by the
145+ compiler instead of on the field.
146+ </Aside >
0 commit comments