Skip to content

Commit a8de5bb

Browse files
authored
Merge pull request #40 from lospugs/trapezoidprofilecommand
Adds TrapezoidProfileCommand and TrapezoidProfileCommandRadians
2 parents 421f7f6 + 6d2aa5a commit a8de5bb

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed

commands2/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from .subsystem import Subsystem
2828
from .swervecontrollercommand import SwerveControllerCommand
2929
from .timedcommandrobot import TimedCommandRobot
30+
from .trapezoidprofilecommand import TrapezoidProfileCommand
3031
from .trapezoidprofilesubsystem import TrapezoidProfileSubsystem
3132
from .waitcommand import WaitCommand
3233
from .waituntilcommand import WaitUntilCommand
@@ -63,6 +64,7 @@
6364
"Subsystem",
6465
"SwerveControllerCommand",
6566
"TimedCommandRobot",
67+
"TrapezoidProfileCommand",
6668
"TrapezoidProfileSubsystem",
6769
"WaitCommand",
6870
"WaitUntilCommand",

commands2/trapezoidprofilecommand.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# validated: 2024-01-24 DS 192a28af4731 TrapezoidProfileCommand.java
2+
#
3+
# Copyright (c) FIRST and other WPILib contributors.
4+
# Open Source Software; you can modify and/or share it under the terms of
5+
# the WPILib BSD license file in the root directory of this project.
6+
#
7+
from __future__ import annotations
8+
9+
from typing import Callable, Any
10+
11+
from wpilib import Timer
12+
13+
from .command import Command
14+
from .subsystem import Subsystem
15+
16+
17+
class TrapezoidProfileCommand(Command):
18+
"""
19+
A command that runs a :class:`wpimath.trajectory.TrapezoidProfile`. Useful for smoothly controlling mechanism motion.
20+
"""
21+
22+
def __init__(
23+
self,
24+
profile,
25+
output: Callable[[Any], Any],
26+
goal: Callable[[], Any],
27+
currentState: Callable[[], Any],
28+
*requirements: Subsystem,
29+
):
30+
"""Creates a new TrapezoidProfileCommand that will execute the given :class:`wpimath.trajectory.TrapezoidProfile`.
31+
Output will be piped to the provided consumer function.
32+
33+
:param profile: The motion profile to execute.
34+
:param output: The consumer for the profile output.
35+
:param goal: The supplier for the desired state
36+
:param currentState: The supplier for the current state
37+
:param requirements: The subsystems required by this command.
38+
"""
39+
super().__init__()
40+
self._profile = profile
41+
self._output = output
42+
self._goal = goal
43+
self._currentState = currentState
44+
self._timer = Timer()
45+
46+
self.addRequirements(*requirements)
47+
48+
def initialize(self) -> None:
49+
self._timer.restart()
50+
51+
def execute(self) -> None:
52+
self._output(
53+
self._profile.calculate(
54+
self._timer.get(),
55+
self._currentState(),
56+
self._goal(),
57+
)
58+
)
59+
60+
def end(self, interrupted) -> None:
61+
self._timer.stop()
62+
63+
def isFinished(self) -> bool:
64+
return self._timer.hasElapsed(self._profile.totalTime())

tests/test_trapezoidprofilecommand.py

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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+
5+
from typing import TYPE_CHECKING, List, Tuple
6+
import math
7+
8+
import wpimath.controller as controller
9+
import wpimath.trajectory as trajectory
10+
import wpimath.geometry as geometry
11+
import wpimath.kinematics as kinematics
12+
from wpimath.trajectory import TrapezoidProfile as DimensionlessProfile
13+
from wpimath.trajectory import TrapezoidProfileRadians as RadiansProfile
14+
15+
from wpilib import Timer
16+
17+
from util import * # type: ignore
18+
19+
if TYPE_CHECKING:
20+
from .util import *
21+
22+
import pytest
23+
24+
import commands2
25+
26+
27+
class TrapezoidProfileRadiansFixture:
28+
def __init__(self):
29+
constraints: RadiansProfile.Constraints = RadiansProfile.Constraints(
30+
3 * math.pi, math.pi
31+
)
32+
self._profile: RadiansProfile = RadiansProfile(constraints)
33+
self._goal_state = RadiansProfile.State(3, 0)
34+
35+
self._state = self._profile.calculate(
36+
0, self._goal_state, RadiansProfile.State(0, 0)
37+
)
38+
39+
self._timer = Timer()
40+
41+
def profileOutput(self, state: RadiansProfile.State) -> None:
42+
self._state = state
43+
44+
def currentState(self) -> RadiansProfile.State:
45+
return self._state
46+
47+
def getGoal(self) -> RadiansProfile.State:
48+
return self._goal_state
49+
50+
51+
@pytest.fixture()
52+
def get_trapezoid_profile_radians() -> TrapezoidProfileRadiansFixture:
53+
return TrapezoidProfileRadiansFixture()
54+
55+
56+
class TrapezoidProfileFixture:
57+
def __init__(self):
58+
constraints: DimensionlessProfile.Constraints = (
59+
DimensionlessProfile.Constraints(3 * math.pi, math.pi)
60+
)
61+
self._profile: DimensionlessProfile = DimensionlessProfile(constraints)
62+
self._goal_state = DimensionlessProfile.State(3, 0)
63+
64+
self._state = self._profile.calculate(
65+
0, self._goal_state, DimensionlessProfile.State(0, 0)
66+
)
67+
68+
self._timer = Timer()
69+
70+
def profileOutput(self, state: DimensionlessProfile.State) -> None:
71+
self._state = state
72+
73+
def currentState(self) -> DimensionlessProfile.State:
74+
return self._state
75+
76+
def getGoal(self) -> DimensionlessProfile.State:
77+
return self._goal_state
78+
79+
80+
@pytest.fixture()
81+
def get_trapezoid_profile_dimensionless() -> TrapezoidProfileFixture:
82+
return TrapezoidProfileFixture()
83+
84+
85+
def test_trapezoidProfileDimensionless(
86+
scheduler: commands2.CommandScheduler, get_trapezoid_profile_dimensionless
87+
):
88+
with ManualSimTime() as sim:
89+
subsystem = commands2.Subsystem()
90+
91+
fixture_data = get_trapezoid_profile_dimensionless
92+
93+
command = commands2.TrapezoidProfileCommand(
94+
fixture_data._profile,
95+
fixture_data.profileOutput,
96+
fixture_data.getGoal,
97+
fixture_data.currentState,
98+
subsystem,
99+
)
100+
101+
fixture_data._timer.restart()
102+
103+
command.initialize()
104+
105+
count = 0
106+
while not command.isFinished():
107+
command.execute()
108+
count += 1
109+
sim.step(0.005)
110+
111+
fixture_data._timer.stop()
112+
command.end(True)
113+
114+
115+
def test_trapezoidProfileRadians(
116+
scheduler: commands2.CommandScheduler, get_trapezoid_profile_radians
117+
):
118+
with ManualSimTime() as sim:
119+
subsystem = commands2.Subsystem()
120+
121+
fixture_data = get_trapezoid_profile_radians
122+
123+
command = commands2.TrapezoidProfileCommand(
124+
fixture_data._profile,
125+
fixture_data.profileOutput,
126+
fixture_data.getGoal,
127+
fixture_data.currentState,
128+
subsystem,
129+
)
130+
131+
fixture_data._timer.restart()
132+
133+
command.initialize()
134+
135+
count = 0
136+
while not command.isFinished():
137+
command.execute()
138+
count += 1
139+
sim.step(0.005)
140+
141+
fixture_data._timer.stop()
142+
command.end(True)

0 commit comments

Comments
 (0)