Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d6d1345

Browse files
author
NewtonCrosby
committedDec 12, 2023
Adds TrapezoidProfileCommand and TrapezoidProfileCommandRadians to Commands2. robotpy/robbotpy-commands-v2#28
1 parent 0608bb8 commit d6d1345

File tree

3 files changed

+216
-0
lines changed

3 files changed

+216
-0
lines changed
 

‎commands2/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from .startendcommand import StartEndCommand
4444
from .subsystem import Subsystem
4545
from .timedcommandrobot import TimedCommandRobot
46+
from .trapezoidprofilecommand import TrapezoidProfileCommand
4647
from .trapezoidprofilesubsystem import TrapezoidProfileSubsystem
4748
from .waitcommand import WaitCommand
4849
from .waituntilcommand import WaitUntilCommand
@@ -76,6 +77,7 @@
7677
"StartEndCommand",
7778
"Subsystem",
7879
"TimedCommandRobot",
80+
"TrapezoidProfileCommand",
7981
"TrapezoidProfileSubsystem",
8082
"WaitCommand",
8183
"WaitUntilCommand",

‎commands2/trapezoidprofilecommand.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 TypeVar, Callable, Any, Generic
7+
8+
from wpilib import Timer
9+
from wpimath.trajectory import TrapezoidProfile, TrapezoidProfileRadians
10+
11+
from .command import Command
12+
from .subsystem import Subsystem
13+
14+
# Defined two generic types for the Profile and ProfileState variables.
15+
# This allows an implementation for both dimensionless and Radians
16+
# instances of TrapezoidProfiles (or any future dimension as well)
17+
TP = TypeVar("TP") # Generic[TrapezoidProfile]
18+
TS = TypeVar("TS") # Generic[TrapezoidProfile.State]
19+
20+
21+
class TrapezoidProfileCommand(Command, Generic[TP, TS]):
22+
"""
23+
A command that runs a :class:`.TrapezoidProfile`. Useful for smoothly controlling mechanism motion.
24+
25+
This class is provided by the NewCommands VendorDep
26+
"""
27+
28+
def __init__(
29+
self,
30+
profile: TP,
31+
output: Callable[[TS], Any],
32+
getGoal: Callable[[], TS],
33+
getCurrent: Callable[[], TS],
34+
*requirements: Subsystem,
35+
):
36+
"""Creates a new TrapezoidProfileCommand that will execute the given :class:`.TrapezoidProfile`.
37+
Output will be piped to the provided consumer function.
38+
39+
:param profile: The motion profile to execute.
40+
:param output: The consumer for the profile output.
41+
:param getGoal: The supplier for the desired state
42+
:param getCurrent: The supplier for the current state
43+
:param requirements: The subsystems required by this command.
44+
"""
45+
super().__init__()
46+
self._profile = profile
47+
self._output = output
48+
self._getGoal = getGoal
49+
self._getCurrent = getCurrent
50+
self._timer = Timer()
51+
52+
self.addRequirements(*requirements)
53+
54+
def initialize(self) -> None:
55+
self._timer.restart()
56+
57+
def execute(self) -> None:
58+
self._output(
59+
self._profile.calculate( # type: ignore[attr-defined]
60+
self._timer.get(), self._getGoal(), self._getCurrent()
61+
)
62+
)
63+
64+
def end(self, interrupted) -> None:
65+
self._timer.stop()
66+
67+
def isFinished(self) -> bool:
68+
return self._timer.hasElapsed(self._profile.totalTime()) # type: ignore[attr-defined]

‎tests/test_trapezoidprofilecommand.py

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
DimensionlessProfile, DimensionlessProfile.State
95+
](
96+
fixture_data._profile,
97+
fixture_data.profileOutput,
98+
fixture_data.getGoal,
99+
fixture_data.currentState,
100+
subsystem,
101+
)
102+
103+
fixture_data._timer.restart()
104+
105+
command.initialize()
106+
107+
count = 0
108+
while not command.isFinished():
109+
command.execute()
110+
count += 1
111+
sim.step(0.005)
112+
113+
fixture_data._timer.stop()
114+
command.end(True)
115+
116+
117+
def test_trapezoidProfileRadians(
118+
scheduler: commands2.CommandScheduler, get_trapezoid_profile_radians
119+
):
120+
with ManualSimTime() as sim:
121+
subsystem = commands2.Subsystem()
122+
123+
fixture_data = get_trapezoid_profile_radians
124+
125+
command = commands2.TrapezoidProfileCommand[
126+
RadiansProfile, RadiansProfile.State
127+
](
128+
fixture_data._profile,
129+
fixture_data.profileOutput,
130+
fixture_data.getGoal,
131+
fixture_data.currentState,
132+
subsystem,
133+
)
134+
135+
fixture_data._timer.restart()
136+
137+
command.initialize()
138+
139+
count = 0
140+
while not command.isFinished():
141+
command.execute()
142+
count += 1
143+
sim.step(0.005)
144+
145+
fixture_data._timer.stop()
146+
command.end(True)

0 commit comments

Comments
 (0)
Please sign in to comment.