Skip to content

Commit 4ca730a

Browse files
authored
Add SysId example (#111)
1 parent d89b058 commit 4ca730a

File tree

5 files changed

+264
-0
lines changed

5 files changed

+264
-0
lines changed

SysId/constants.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Copyright (c) FIRST and other WPILib contributors.
3+
# Open Source Software; you can modify and/or share it under the terms of
4+
# the WPILib BSD license file in the root directory of this project.
5+
#
6+
7+
import math
8+
9+
10+
class DriveConstants:
11+
# The PWM IDs for the drivetrain motor controllers.
12+
kLeftMotor1Port = 0
13+
kLeftMotor2Port = 1
14+
kRightMotor1Port = 2
15+
kRightMotor2Port = 3
16+
17+
# Encoders and their respective motor controllers.
18+
kLeftEncoderPorts = (0, 1)
19+
kRightEncoderPorts = (2, 3)
20+
kLeftEncoderReversed = False
21+
kRightEncoderReversed = True
22+
23+
# Encoder counts per revolution/rotation.
24+
kEncoderCPR = 1024.0
25+
kWheelDiameterInches = 6.0
26+
27+
# Assumes the encoders are directly mounted on the wheel shafts
28+
kEncoderDistancePerPulse = (kWheelDiameterInches * math.pi) / kEncoderCPR
29+
30+
31+
# autonomous
32+
class AutonomousConstants:
33+
kTimeoutSeconds = 3.0
34+
kDriveDistanceMetres = 2.0
35+
kDriveSpeed = 0.5
36+
37+
38+
class OIConstants:
39+
kDriverControllerPort = 0

SysId/robot.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python3
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+
8+
from commands2 import CommandScheduler, TimedCommandRobot
9+
10+
from sysidroutinebot import SysIdRoutineBot
11+
12+
13+
class MyRobot(TimedCommandRobot):
14+
"""The VM is configured to automatically run this class, and to call the functions corresponding to
15+
each mode, as described in the TimedRobot documentation. If you change the name of this class or
16+
the package after creating this project, you must also update the build.gradle file in the
17+
project.
18+
"""
19+
20+
def robotInit(self) -> None:
21+
"""This function is run when the robot is first started up and should be used for any
22+
initialization code.
23+
"""
24+
self.robot = SysIdRoutineBot()
25+
26+
self.robot.configureBindings()
27+
28+
self.autonomous_command = self.robot.getAutonomousCommand()
29+
30+
def disabledInit(self) -> None:
31+
"""This function is called once each time the robot enters Disabled mode."""
32+
pass
33+
34+
def disabledPeriodic(self) -> None:
35+
pass
36+
37+
def autonomousInit(self) -> None:
38+
self.autonomous_command.schedule()
39+
40+
def teleopInit(self) -> None:
41+
# This makes sure that the autonomous stops running when
42+
# teleop starts running. If you want the autonomous to
43+
# continue until interrupted by another command, remove
44+
# this line or comment it out.
45+
self.autonomous_command.cancel()
46+
47+
def testInit(self) -> None:
48+
# Cancels all running commands at the start of test mode.
49+
CommandScheduler.getInstance().cancelAll()
50+
51+
def testPeriodic(self) -> None:
52+
"""This function is called periodically during test mode."""
53+
pass

SysId/subsystems/drive.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#
2+
# Copyright (c) FIRST and other WPILib contributors.
3+
# Open Source Software; you can modify and/or share it under the terms of
4+
# the WPILib BSD license file in the root directory of this project.
5+
#
6+
7+
from commands2 import Command, Subsystem
8+
from commands2.sysid import SysIdRoutine
9+
from wpilib.sysid import SysIdRoutineLog
10+
11+
from wpilib import Encoder, PWMSparkMax, RobotController
12+
from wpilib.drive import DifferentialDrive
13+
14+
from wpimath.units import volts
15+
16+
from constants import DriveConstants
17+
18+
from typing import Callable
19+
20+
21+
class Drive(Subsystem):
22+
def __init__(self) -> None:
23+
# The motors on the left side of the drive
24+
self.left_motor = PWMSparkMax(DriveConstants.kLeftMotor1Port)
25+
self.left_motor.addFollower(PWMSparkMax(DriveConstants.kLeftMotor2Port))
26+
27+
# The motors on the right side of the drive
28+
self.right_motor = PWMSparkMax(DriveConstants.kRightMotor1Port)
29+
self.right_motor.addFollower(PWMSparkMax(DriveConstants.kRightMotor2Port))
30+
31+
# At least one side of the drive train needs to be inverted. This ensures that positive voltages sent to each motor group result in forward motion.
32+
self.right_motor.setInverted(True)
33+
34+
# The robot's drive
35+
self.drive = DifferentialDrive(self.left_motor, self.right_motor)
36+
37+
# The left-side drive encoder
38+
self.left_encoder = Encoder(
39+
DriveConstants.kLeftEncoderPorts[0],
40+
DriveConstants.kLeftEncoderPorts[1],
41+
DriveConstants.kLeftEncoderReversed,
42+
)
43+
44+
# The right-side drive encoder
45+
self.right_encoder = Encoder(
46+
DriveConstants.kRightEncoderPorts[0],
47+
DriveConstants.kRightEncoderPorts[1],
48+
DriveConstants.kRightEncoderReversed,
49+
)
50+
51+
# Sets the distance per pulse for the encoders
52+
self.left_encoder.setDistancePerPulse(DriveConstants.kEncoderDistancePerPulse)
53+
self.right_encoder.setDistancePerPulse(DriveConstants.kEncoderDistancePerPulse)
54+
55+
# Tell SysId how to plumb the driving voltage to the motors.
56+
def drive(voltage: volts) -> None:
57+
self.left_motor.setVoltage(voltage)
58+
self.right_motor.setVoltage(voltage)
59+
60+
# Tell SysId to make generated commands require this subsystem, suffix test state in
61+
# WPILog with this subsystem's name ("drive")
62+
self.sys_id_routine = SysIdRoutine(
63+
SysIdRoutine.Config(),
64+
SysIdRoutine.Mechanism(drive, self.log, self),
65+
)
66+
67+
# Tell SysId how to record a frame of data for each motor on the mechanism being
68+
# characterized.
69+
def log(self, sys_id_routine: SysIdRoutineLog) -> None:
70+
# Record a frame for the left motors. Since these share an encoder, we consider
71+
# the entire group to be one motor.
72+
sys_id_routine.motor("drive-left").voltage(
73+
self.left_motor.get() * RobotController.getBatteryVoltage()
74+
).position(self.left_encoder.getDistance()).velocity(
75+
self.left_encoder.getRate()
76+
)
77+
# Record a frame for the right motors. Since these share an encoder, we consider
78+
# the entire group to be one motor.
79+
sys_id_routine.motor("drive-right").voltage(
80+
self.right_motor.get() * RobotController.getBatteryVoltage()
81+
).position(self.right_encoder.getDistance()).velocity(
82+
self.right_encoder.getRate()
83+
)
84+
85+
def arcadeDriveCommand(
86+
self, fwd: Callable[[], float], rot: Callable[[], float]
87+
) -> Command:
88+
"""Returns a command that drives the robot with arcade controls.
89+
90+
:param fwd: the commanded forward movement
91+
:param rot: the commanded rotation
92+
"""
93+
94+
# A split-stick arcade command, with forward/backward controlled by the left
95+
# hand, and turning controlled by the right.
96+
return self.run(lambda: self.drive.arcadeDrive(fwd(), rot()))
97+
98+
def sysIdQuasistatic(self, direction: SysIdRoutine.Direction) -> Command:
99+
return self.sys_id_routine.quasistatic(direction)
100+
101+
def sysIdDynamic(self, direction: SysIdRoutine.Direction) -> Command:
102+
return self.sys_id_routine.dynamic(direction)

SysId/sysidroutinebot.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#
2+
# Copyright (c) FIRST and other WPILib contributors.
3+
# Open Source Software; you can modify and/or share it under the terms of
4+
# the WPILib BSD license file in the root directory of this project.
5+
#
6+
7+
from commands2 import Command
8+
from commands2.button import CommandXboxController
9+
from commands2.sysid import SysIdRoutine
10+
11+
from subsystems.drive import Drive
12+
13+
from constants import OIConstants
14+
15+
16+
class SysIdRoutineBot:
17+
"""This class is where the bulk of the robot should be declared. Since Command-based is a
18+
"declarative" paradigm, very little robot logic should actually be handled in the :class:`.Robot`
19+
periodic methods (other than the scheduler calls). Instead, the structure of the robot (including
20+
subsystems, commands, and button mappings) should be declared here.
21+
"""
22+
23+
def __init__(self) -> None:
24+
# The robot's subsystems
25+
self.drive = Drive()
26+
27+
# The driver's controller
28+
self.controller = CommandXboxController(OIConstants.kDriverControllerPort)
29+
30+
def configureBindings(self) -> None:
31+
"""Use this method to define bindings between conditions and commands. These are useful for
32+
automating robot behaviors based on button and sensor input.
33+
34+
Should be called during :meth:`.Robot.robotInit`.
35+
36+
Event binding methods are available on the :class:`.Trigger` class.
37+
"""
38+
39+
# Control the drive with split-stick arcade controls
40+
self.drive.setDefaultCommand(
41+
self.drive.arcadeDriveCommand(
42+
lambda: -self.controller.getLeftY(),
43+
lambda: -self.controller.getRightX(),
44+
)
45+
)
46+
47+
# Bind full set of SysId routine tests to buttons; a complete routine should run each of these
48+
# once.
49+
self.controller.a().whileTrue(
50+
self.drive.sysIdQuasistatic(SysIdRoutine.Direction.kForward)
51+
)
52+
self.controller.b().whileTrue(
53+
self.drive.sysIdQuasistatic(SysIdRoutine.Direction.kReverse)
54+
)
55+
self.controller.x().whileTrue(
56+
self.drive.sysIdDynamic(SysIdRoutine.Direction.kForward)
57+
)
58+
self.controller.y().whileTrue(
59+
self.drive.sysIdDynamic(SysIdRoutine.Direction.kReverse)
60+
)
61+
62+
def getAutonomousCommand(self) -> Command:
63+
"""Use this to define the command that runs during autonomous.
64+
65+
Scheduled during :meth:`.Robot.autonomousInit`.
66+
"""
67+
68+
# Do nothing
69+
return self.drive.run(lambda: None)

run_tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ BASE_TESTS="
5959
StateSpaceFlywheel
6060
StateSpaceFlywheelSysId
6161
SwerveBot
62+
SysId
6263
TankDrive
6364
TankDriveXboxController
6465
Timed/src

0 commit comments

Comments
 (0)