Skip to content

Commit 0608bb8

Browse files
authoredDec 8, 2023
Merge pull request #34 from lospugs/pid_and_trapezoidprofile
Adds PIDSubsystem and TrapezoidProfileSubsystem to Commands2.
2 parents 7babb0e + 4ceae13 commit 0608bb8

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed
 

‎commands2/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from .parallelracegroup import ParallelRaceGroup
3232
from .perpetualcommand import PerpetualCommand
3333
from .pidcommand import PIDCommand
34+
from .pidsubsystem import PIDSubsystem
3435
from .printcommand import PrintCommand
3536
from .proxycommand import ProxyCommand
3637
from .proxyschedulecommand import ProxyScheduleCommand
@@ -42,6 +43,7 @@
4243
from .startendcommand import StartEndCommand
4344
from .subsystem import Subsystem
4445
from .timedcommandrobot import TimedCommandRobot
46+
from .trapezoidprofilesubsystem import TrapezoidProfileSubsystem
4547
from .waitcommand import WaitCommand
4648
from .waituntilcommand import WaitUntilCommand
4749
from .wrappercommand import WrapperCommand
@@ -62,6 +64,7 @@
6264
"ParallelRaceGroup",
6365
"PerpetualCommand",
6466
"PIDCommand",
67+
"PIDSubsystem",
6568
"PrintCommand",
6669
"ProxyCommand",
6770
"ProxyScheduleCommand",
@@ -73,6 +76,7 @@
7376
"StartEndCommand",
7477
"Subsystem",
7578
"TimedCommandRobot",
79+
"TrapezoidProfileSubsystem",
7680
"WaitCommand",
7781
"WaitUntilCommand",
7882
"WrapperCommand",

‎commands2/pidsubsystem.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright (c) FIRST and other WPILib contributors.
2+
# Open Source Software; you can modify and/or share it under the terms of
3+
# the WPILib BSD license file in the root directory of this project.
4+
from __future__ import annotations
5+
6+
from wpimath.controller import PIDController
7+
8+
from .subsystem import Subsystem
9+
10+
11+
class PIDSubsystem(Subsystem):
12+
"""
13+
A subsystem that uses a {@link PIDController} to control an output. The
14+
controller is run synchronously from the subsystem's periodic() method.
15+
"""
16+
17+
def __init__(self, controller: PIDController, initial_position: float = 0.0):
18+
"""
19+
Creates a new PIDSubsystem.
20+
21+
:param controller: The PIDController to use.
22+
:param initial_position: The initial setpoint of the subsystem.
23+
"""
24+
super().__init__()
25+
26+
self._controller = controller
27+
self.setSetpoint(initial_position)
28+
self.addChild("PID Controller", self._controller)
29+
self._enabled = False
30+
31+
def periodic(self):
32+
"""
33+
Executes the PID control logic during each periodic update.
34+
35+
This method is called synchronously from the subsystem's periodic() method.
36+
"""
37+
if self._enabled:
38+
self.useOutput(
39+
self._controller.calculate(self.getMeasurement()), self.getSetpoint()
40+
)
41+
42+
def getController(self) -> PIDController:
43+
"""
44+
Returns the PIDController used by the subsystem.
45+
46+
:return: The PIDController.
47+
"""
48+
return self._controller
49+
50+
def setSetpoint(self, setpoint: float):
51+
"""
52+
Sets the setpoint for the subsystem.
53+
54+
:param setpoint: The setpoint for the subsystem.
55+
"""
56+
self._controller.setSetpoint(setpoint)
57+
58+
def getSetpoint(self) -> float:
59+
"""
60+
Returns the current setpoint of the subsystem.
61+
62+
:return: The current setpoint.
63+
"""
64+
return self._controller.getSetpoint()
65+
66+
def useOutput(self, output: float, setpoint: float):
67+
"""
68+
Uses the output from the PIDController.
69+
70+
:param output: The output of the PIDController.
71+
:param setpoint: The setpoint of the PIDController (for feedforward).
72+
"""
73+
raise NotImplementedError("Subclasses must implement this method")
74+
75+
def getMeasurement(self) -> float:
76+
"""
77+
Returns the measurement of the process variable used by the PIDController.
78+
79+
:return: The measurement of the process variable.
80+
"""
81+
raise NotImplementedError("Subclasses must implement this method")
82+
83+
def enable(self):
84+
"""Enables the PID control. Resets the controller."""
85+
self._enabled = True
86+
self._controller.reset()
87+
88+
def disable(self):
89+
"""Disables the PID control. Sets output to zero."""
90+
self._enabled = False
91+
self.useOutput(0, 0)
92+
93+
def isEnabled(self) -> bool:
94+
"""
95+
Returns whether the controller is enabled.
96+
97+
:return: Whether the controller is enabled.
98+
"""
99+
return self._enabled
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Copyright (c) FIRST and other WPILib contributors.
2+
# Open Source Software; you can modify and/or share it under the terms of
3+
# the WPILib BSD license file in the root directory of this project.
4+
from __future__ import annotations
5+
6+
from typing import Union
7+
8+
from .subsystem import Subsystem
9+
from wpimath.trajectory import TrapezoidProfile
10+
11+
12+
class TrapezoidProfileSubsystem(Subsystem):
13+
"""
14+
A subsystem that generates and runs trapezoidal motion profiles automatically. The user specifies
15+
how to use the current state of the motion profile by overriding the `useState` method.
16+
"""
17+
18+
def __init__(
19+
self,
20+
constraints: TrapezoidProfile.Constraints,
21+
initial_position: float = 0.0,
22+
period: float = 0.02,
23+
):
24+
"""
25+
Creates a new TrapezoidProfileSubsystem.
26+
27+
:param constraints: The constraints (maximum velocity and acceleration) for the profiles.
28+
:param initial_position: The initial position of the controlled mechanism when the subsystem is constructed.
29+
:param period: The period of the main robot loop, in seconds.
30+
"""
31+
self._profile = TrapezoidProfile(constraints)
32+
self._state = TrapezoidProfile.State(initial_position, 0)
33+
self.setGoal(initial_position)
34+
self._period = period
35+
self._enabled = True
36+
37+
def periodic(self):
38+
"""
39+
Executes the TrapezoidProfileSubsystem logic during each periodic update.
40+
41+
This method is called synchronously from the subsystem's periodic() method.
42+
"""
43+
self._state = self._profile.calculate(self._period, self._goal, self._state)
44+
if self._enabled:
45+
self.useState(self._state)
46+
47+
def setGoal(self, goal: Union[TrapezoidProfile.State, float]):
48+
"""
49+
Sets the goal state for the subsystem. Goal velocity assumed to be zero.
50+
51+
:param goal: The goal position for the subsystem's motion profile. The goal
52+
can either be a `TrapezoidProfile.State` or `float`. If float is provided,
53+
the assumed velocity for the goal will be 0.
54+
"""
55+
# If we got a float, instantiate the state
56+
if isinstance(goal, (float, int)):
57+
goal = TrapezoidProfile.State(goal, 0)
58+
59+
self._goal = goal
60+
61+
def enable(self):
62+
"""Enable the TrapezoidProfileSubsystem's output."""
63+
self._enabled = True
64+
65+
def disable(self):
66+
"""Disable the TrapezoidProfileSubsystem's output."""
67+
self._enabled = False
68+
69+
def useState(self, state: TrapezoidProfile.State):
70+
"""
71+
Users should override this to consume the current state of the motion profile.
72+
73+
:param state: The current state of the motion profile.
74+
"""
75+
raise NotImplementedError("Subclasses must implement this method")

0 commit comments

Comments
 (0)
Failed to load comments.