Skip to content

Commit 5ad9583

Browse files
authored
Merge pull request #124 from robotpy/2025
Update for 2025
2 parents 79d6a79 + 0dc53cd commit 5ad9583

File tree

10 files changed

+94
-60
lines changed

10 files changed

+94
-60
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,20 @@ jobs:
3333
matrix:
3434
os: ["ubuntu-22.04", "macos-13", "windows-2022"]
3535
python_version:
36-
- '3.8'
3736
- '3.9'
3837
- '3.10'
3938
- '3.11'
4039
- '3.12'
40+
- '3.13'
4141

4242
steps:
4343
- uses: actions/checkout@v4
44-
- uses: actions/setup-python@v4
44+
- uses: actions/setup-python@v5
4545
with:
4646
python-version: ${{ matrix.python_version }}
47-
architecture: ${{ matrix.architecture }}
4847
- name: Install deps
4948
run: |
50-
pip install -U pip
51-
pip install 'robotpy[commands2,romi]<2025.0.0,>=2024.2.1.1' 'numpy<2' pytest
49+
pip install 'robotpy[commands2,romi]<2026.0.0,>=2025.0.0b3' numpy pytest
5250
- name: Run tests
5351
run: bash run_tests.sh
5452
shell: bash

DutyCycleEncoder/robot.py

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,42 @@
55
# the WPILib BSD license file in the root directory of this project.
66
#
77

8+
"""
9+
This example shows how to use a duty cycle encoder for devices such as
10+
an arm or elevator.
11+
"""
12+
813
import wpilib
14+
import wpimath
15+
16+
FULL_RANGE = 1.3
17+
EXPECTED_ZERO = 0.0
918

1019

1120
class MyRobot(wpilib.TimedRobot):
1221
def robotInit(self):
13-
"""Robot initialization function"""
22+
"""Called once at the beginning of the robot program."""
1423

15-
self.dutyCycleEncoder = wpilib.DutyCycleEncoder(0)
24+
# 2nd parameter is the range of values. This sensor will output between
25+
# 0 and the passed in value.
26+
# 3rd parameter is the the physical value where you want "0" to be. How
27+
# to measure this is fairly easy. Set the value to 0, place the mechanism
28+
# where you want "0" to be, and observe the value on the dashboard, That
29+
# is the value to enter for the 3rd parameter.
30+
self.dutyCycleEncoder = wpilib.DutyCycleEncoder(0, FULL_RANGE, EXPECTED_ZERO)
1631

17-
self.dutyCycleEncoder.setDistancePerRotation(0.5)
32+
# If you know the frequency of your sensor, uncomment the following
33+
# method, and set the method to the frequency of your sensor.
34+
# This will result in more stable readings from the sensor.
35+
# Do note that occasionally the datasheet cannot be trusted
36+
# and you should measure this value. You can do so with either
37+
# an oscilloscope, or by observing the "Frequency" output
38+
# on the dashboard while running this sample. If you find
39+
# the value jumping between the 2 values, enter halfway between
40+
# those values. This number doesn't have to be perfect,
41+
# just having a fairly close value will make the output readings
42+
# much more stable.
43+
self.dutyCycleEncoder.setAssumedFrequency(967.8)
1844

1945
def robotPeriodic(self):
2046
# Connected can be checked, and uses the frequency of the encoder
@@ -26,10 +52,22 @@ def robotPeriodic(self):
2652
# Output of encoder
2753
output = self.dutyCycleEncoder.get()
2854

29-
# Output scaled by DistancePerPulse
30-
distance = self.dutyCycleEncoder.getDistance()
55+
# By default, the output will wrap around to the full range value
56+
# when the sensor goes below 0. However, for moving mechanisms this
57+
# is not usually ideal, as if 0 is set to a hard stop, its still
58+
# possible for the sensor to move slightly past. If this happens
59+
# The sensor will assume its now at the furthest away position,
60+
# which control algorithms might not handle correctly. Therefore
61+
# it can be a good idea to slightly shift the output so the sensor
62+
# can go a bit negative before wrapping. Usually 10% or so is fine.
63+
# This does not change where "0" is, so no calibration numbers need
64+
# to be changed.
65+
percentOfRange = FULL_RANGE * 0.1
66+
shiftedOutput = wpimath.inputModulus(
67+
output, 0 - percentOfRange, FULL_RANGE - percentOfRange
68+
)
3169

3270
wpilib.SmartDashboard.putBoolean("Connected", connected)
3371
wpilib.SmartDashboard.putNumber("Frequency", frequency)
3472
wpilib.SmartDashboard.putNumber("Output", output)
35-
wpilib.SmartDashboard.putNumber("Distance", distance)
73+
wpilib.SmartDashboard.putNumber("ShiftedOutput", shiftedOutput)

ElevatorSimulation/physics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(self, physics_controller: PhysicsInterface, robot: "MyRobot"):
5353
robot.kMaxElevatorHeight,
5454
True,
5555
0,
56-
[0.01],
56+
[0.01, 0.0],
5757
)
5858
self.encoderSim = wpilib.simulation.EncoderSim(robot.encoder)
5959
self.motorSim = wpilib.simulation.PWMSim(robot.motor.getChannel())

ElevatorTrapezoidProfile/robot.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
import wpilib
99
import wpimath.controller
10-
import wpimath.trajectory
10+
from wpimath.trajectory import TrapezoidProfile
11+
1112
import examplesmartmotorcontroller
1213

1314

@@ -20,30 +21,25 @@ def robotInit(self):
2021
# Note: These gains are fake, and will have to be tuned for your robot.
2122
self.feedforward = wpimath.controller.SimpleMotorFeedforwardMeters(1, 1.5)
2223

23-
self.constraints = wpimath.trajectory.TrapezoidProfile.Constraints(1.75, 0.75)
24+
# Create a motion profile with the given maximum velocity and maximum
25+
# acceleration constraints for the next setpoint.
26+
self.profile = TrapezoidProfile(TrapezoidProfile.Constraints(1.75, 0.75))
2427

25-
self.goal = wpimath.trajectory.TrapezoidProfile.State()
26-
self.setpoint = wpimath.trajectory.TrapezoidProfile.State()
28+
self.goal = TrapezoidProfile.State()
29+
self.setpoint = TrapezoidProfile.State()
2730

2831
# Note: These gains are fake, and will have to be tuned for your robot.
2932
self.motor.setPID(1.3, 0.0, 0.7)
3033

3134
def teleopPeriodic(self):
3235
if self.joystick.getRawButtonPressed(2):
33-
self.goal = wpimath.trajectory.TrapezoidProfile.State(5, 0)
36+
self.goal = TrapezoidProfile.State(5, 0)
3437
elif self.joystick.getRawButtonPressed(3):
35-
self.goal = wpimath.trajectory.TrapezoidProfile.State(0, 0)
36-
37-
# Create a motion profile with the given maximum velocity and maximum
38-
# acceleration constraints for the next setpoint, the desired goal, and the
39-
# current setpoint.
40-
profile = wpimath.trajectory.TrapezoidProfile(
41-
self.constraints, self.goal, self.setpoint
42-
)
38+
self.goal = TrapezoidProfile.State(0, 0)
4339

4440
# Retrieve the profiled setpoint for the next timestep. This setpoint moves
4541
# toward the goal while obeying the constraints.
46-
self.setpoint = profile.calculate(self.kDt)
42+
self.setpoint = self.profile.calculate(self.kDt, self.setpoint, self.goal)
4743

4844
# Send setpoint to offboard controller PID
4945
self.motor.setSetPoint(

FlywheelBangBangController/physics.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
# the WPILib BSD license file in the root directory of this project.
55
#
66

7+
import typing
8+
79
import wpilib.simulation
8-
from wpimath.system.plant import DCMotor
10+
from wpimath.system import plant
911

1012
from pyfrc.physics.core import PhysicsInterface
1113

12-
import typing
13-
1414
if typing.TYPE_CHECKING:
1515
from robot import MyRobot
1616

@@ -35,9 +35,11 @@ def __init__(self, physics_controller: PhysicsInterface, robot: "MyRobot"):
3535
self.encoder = wpilib.simulation.EncoderSim(robot.encoder)
3636

3737
# Flywheel
38-
self.flywheel = wpilib.simulation.FlywheelSim(
39-
DCMotor.NEO(1), robot.kFlywheelGearing, robot.kFlywheelMomentOfInertia
38+
self.gearbox = plant.DCMotor.NEO(1)
39+
self.plant = plant.LinearSystemId.flywheelSystem(
40+
self.gearbox, robot.kFlywheelGearing, robot.kFlywheelMomentOfInertia
4041
)
42+
self.flywheel = wpilib.simulation.FlywheelSim(self.plant, self.gearbox)
4143

4244
def update_sim(self, now: float, tm_diff: float) -> None:
4345
"""
@@ -54,5 +56,5 @@ def update_sim(self, now: float, tm_diff: float) -> None:
5456
self.flywheel.setInputVoltage(
5557
self.flywheelMotor.getSpeed() * wpilib.RobotController.getInputVoltage()
5658
)
57-
self.flywheel.update(0.02)
59+
self.flywheel.update(tm_diff)
5860
self.encoder.setRate(self.flywheel.getAngularVelocity())

StateSpaceArm/robot.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ def robotInit(self) -> None:
5757
# The observer fuses our encoder data and voltage inputs to reject noise.
5858
self.observer = wpimath.estimator.KalmanFilter_2_1_1(
5959
self.armPlant,
60-
[
60+
(
6161
0.015,
6262
0.17,
63-
], # How accurate we think our model is, in radians and radians/sec.
64-
[
65-
0.01
66-
], # How accurate we think our encoder position data is. In this case we very highly trust our encoder position reading.
63+
), # How accurate we think our model is, in radians and radians/sec.
64+
(
65+
0.01,
66+
), # How accurate we think our encoder position data is. In this case we very highly trust our encoder position reading.
6767
0.020,
6868
)
6969

StateSpaceElevator/robot.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ def robotInit(self) -> None:
6464
# The observer fuses our encoder data and voltage inputs to reject noise.
6565
self.observer = wpimath.estimator.KalmanFilter_2_1_1(
6666
self.elevatorPlant,
67-
[
67+
(
6868
wpimath.units.inchesToMeters(2),
6969
wpimath.units.inchesToMeters(40),
70-
], # How accurate we think our model is, in meters and meters/second.
71-
[
72-
0.001
73-
], # How accurate we think our encoder position data is. In this case we very highly trust our encoder position reading.
70+
), # How accurate we think our model is, in meters and meters/second.
71+
(
72+
0.001,
73+
), # How accurate we think our encoder position data is. In this case we very highly trust our encoder position reading.
7474
0.020,
7575
)
7676

StateSpaceFlywheel/robot.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
#
77

88
import math
9+
910
import wpilib
10-
import wpimath.units
1111
import wpimath.controller
12+
import wpimath.estimator
1213
import wpimath.system
1314
import wpimath.system.plant
14-
import wpimath.estimator
15+
import wpimath.units
1516

1617
kMotorPort = 0
1718
kEncoderAChannel = 0
@@ -48,18 +49,18 @@ def robotInit(self) -> None:
4849
# The observer fuses our encoder data and voltage inputs to reject noise.
4950
self.observer = wpimath.estimator.KalmanFilter_1_1_1(
5051
self.flywheelPlant,
51-
[3], # How accurate we think our model is
52-
[0.01], # How accurate we think our encoder data is
52+
(3,), # How accurate we think our model is
53+
(0.01,), # How accurate we think our encoder data is
5354
0.020,
5455
)
5556

5657
# A LQR uses feedback to create voltage commands.
5758
self.controller = wpimath.controller.LinearQuadraticRegulator_1_1(
5859
self.flywheelPlant,
59-
[8], # qelms. Velocity error tolerance, in radians per second. Decrease
60+
(8,), # qelms. Velocity error tolerance, in radians per second. Decrease
6061
# this to more heavily penalize state excursion, or make the controller behave more
6162
# aggressively.
62-
[12], # relms. Control effort (voltage) tolerance. Decrease this to more
63+
(12,), # relms. Control effort (voltage) tolerance. Decrease this to more
6364
# heavily penalize control effort, or make the controller less aggressive. 12 is a good
6465
# starting point because that is the (approximate) maximum voltage of a battery.
6566
0.020, # Nominal time between loops. 0.020 for TimedRobot, but can be lower if using notifiers.
@@ -105,5 +106,5 @@ def teleopPeriodic(self) -> None:
105106
# Send the new calculated voltage to the motors.
106107
# voltage = duty cycle * battery voltage, so
107108
# duty cycle = voltage / battery voltage
108-
nextVoltage = self.loop.U()
109+
nextVoltage = self.loop.U(0)
109110
self.motor.setVoltage(nextVoltage)

SwerveBot/swervemodule.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
#
66

77
import math
8+
89
import wpilib
9-
import wpimath.kinematics
10-
import wpimath.geometry
1110
import wpimath.controller
11+
import wpimath.geometry
12+
import wpimath.kinematics
1213
import wpimath.trajectory
1314

1415
kWheelRadius = 0.0508
@@ -109,25 +110,23 @@ def setDesiredState(
109110
encoderRotation = wpimath.geometry.Rotation2d(self.turningEncoder.getDistance())
110111

111112
# Optimize the reference state to avoid spinning further than 90 degrees
112-
state = wpimath.kinematics.SwerveModuleState.optimize(
113-
desiredState, encoderRotation
114-
)
113+
desiredState.optimize(encoderRotation)
115114

116115
# Scale speed by cosine of angle error. This scales down movement perpendicular to the desired
117116
# direction of travel that can occur when modules change directions. This results in smoother
118117
# driving.
119-
state.speed *= (state.angle - encoderRotation).cos()
118+
desiredState.cosineScale(encoderRotation)
120119

121120
# Calculate the drive output from the drive PID controller.
122121
driveOutput = self.drivePIDController.calculate(
123-
self.driveEncoder.getRate(), state.speed
122+
self.driveEncoder.getRate(), desiredState.speed
124123
)
125124

126-
driveFeedforward = self.driveFeedforward.calculate(state.speed)
125+
driveFeedforward = self.driveFeedforward.calculate(desiredState.speed)
127126

128127
# Calculate the turning motor output from the turning PID controller.
129128
turnOutput = self.turningPIDController.calculate(
130-
self.turningEncoder.getDistance(), state.angle.radians()
129+
self.turningEncoder.getDistance(), desiredState.angle.radians()
131130
)
132131

133132
turnFeedforward = self.turnFeedforward.calculate(

run_tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ BASE_TESTS="
5454
ShuffleBoard
5555
Solenoid
5656
StatefulAutonomous
57-
StateSpaceArm
58-
StateSpaceElevator
5957
StateSpaceFlywheel
6058
StateSpaceFlywheelSysId
6159
SwerveBot
@@ -70,6 +68,8 @@ BASE_TESTS="
7068
IGNORED_TESTS="
7169
RamseteCommand
7270
PhysicsCamSim/src
71+
StateSpaceArm
72+
StateSpaceElevator
7373
"
7474

7575
ALL_TESTS="${BASE_TESTS}"

0 commit comments

Comments
 (0)