-
Notifications
You must be signed in to change notification settings - Fork 16
Add basic motion profiling #36
base: master
Are you sure you want to change the base?
Changes from all commits
8eb55da
67fb639
fcbd728
0e96fd7
66c7186
d33329b
e63ac19
563a035
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
pyfrc>=2018.0.0 | ||
robotpy-ctre>=2018.0.0 | ||
pynetworktables>=2018.0.0 | ||
pybind11>=2.2.2 | ||
robotpy-pathfinder>=0.2.5 | ||
robotpy-navx>=2018.0.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,20 @@ | ||
from magicbot.state_machine import state, timed_state, AutonomousStateMachine | ||
#from automations import | ||
#from magicbot import tunable | ||
from components import drive | ||
# from automations import | ||
# from magicbot import tunable | ||
from components import drive, trajectory_follower | ||
|
||
|
||
class Charge(AutonomousStateMachine): | ||
MODE_NAME = 'Charge' | ||
DEFAULT = True | ||
|
||
drive = drive.Drive | ||
drive: drive.Drive | ||
follower: trajectory_follower.TrajectoryFollower | ||
|
||
@timed_state(duration=3, first=True) | ||
@state(first=True) | ||
def charge(self, initial_call): | ||
# Move forward | ||
self.drive.move(1, 0) | ||
if initial_call: | ||
self.follower.follow_trajectory('charge') | ||
|
||
if not self.follower.is_following('charge'): | ||
self.done() # If using mutliple states use self.next_state(name) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import pathfinder as pf | ||
import navx | ||
from typing import Tuple, List | ||
import wpilib | ||
from wpilib import drive | ||
|
||
|
||
class TrajectoryFollower: | ||
""" | ||
Move along generated paths for autonomous | ||
""" | ||
# TODO FIND THE REAL VALUES | ||
WHEEL_DIAMETER = 0.5 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fairly sure the diameter is six inches but we can check |
||
KV = 1.101 | ||
KA = 0.164 # 0.102 | ||
|
||
drivetrain: drive.DifferentialDrive | ||
navx: navx.AHRS | ||
l_encoder: wpilib.Encoder | ||
r_encoder: wpilib.Encoder | ||
generated_trajectories: dict | ||
|
||
def on_enable(self): | ||
self._current_trajectory = None | ||
self.last_difference = 0 | ||
|
||
self.left_follower = pf.followers.EncoderFollower(None) | ||
self.right_follower = pf.followers.EncoderFollower(None) | ||
|
||
self.left_follower.configurePIDVA(1.0, 0, 0.1, 1 / 10.903, 1 / 73.220) | ||
self.right_follower.configurePIDVA(1.0, 0, 0.1, 1 / 10.903, 1 / 73.220) | ||
|
||
self._cofigure_encoders() | ||
|
||
def follow_trajectory(self, trajectory_name: str): | ||
self._current_trajectory = trajectory_name | ||
self.left_follower.setTrajectory(self.generated_trajectories[trajectory_name][0]) | ||
self.right_follower.setTrajectory(self.generated_trajectories[trajectory_name][1]) | ||
|
||
self._cofigure_encoders() | ||
|
||
def _cofigure_encoders(self): | ||
self.left_follower.configureEncoder(self.l_encoder.get(), 360, self.WHEEL_DIAMETER) | ||
self.right_follower.configureEncoder(self.r_encoder.get(), 360, self.WHEEL_DIAMETER) | ||
|
||
def is_following(self, trajectory_name): | ||
return self._current_trajectory is not None and self._current_trajectory == trajectory_name | ||
|
||
def execute(self): | ||
if (self.left_follower.trajectory is None or self.right_follower.trajectory is None) or \ | ||
(self.left_follower.isFinished() and self.right_follower.isFinished()): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: also technically necessary parens There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. *un There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lol I just saw he went the wrong way. Darn you autocorrect. |
||
self._current_trajectory = None | ||
return | ||
|
||
left = self.left_follower.calculate(self.l_encoder.get()) | ||
right = self.right_follower.calculate(self.r_encoder.get()) | ||
|
||
gyro_heading = ( | ||
-self.navx.getAngle() | ||
) # Assuming the gyro is giving a value in degrees | ||
desired_heading = pf.r2d( | ||
self.left_follower.getHeading() | ||
) # Should also be in degrees | ||
|
||
# This is a poor man's P controller | ||
angle_difference = pf.boundHalfDegrees(desired_heading - gyro_heading) | ||
turn = (1.1 * (-1.0 / 80.0) * angle_difference) + (0.05 * (angle_difference - self.last_difference)) | ||
# turn = 0.7 * (-1.0 / 80.0) * angle_difference | ||
# turn = 0 | ||
|
||
self.last_difference = angle_difference | ||
|
||
left += turn | ||
right -= turn | ||
|
||
# -1 is forward, so invert both values | ||
self.drivetrain.tankDrive(-left, -right) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import math | ||
from pyfrc.physics import motor_cfgs, tankmodel | ||
from pyfrc.physics.units import units | ||
|
||
|
||
class PhysicsEngine(object): | ||
""" | ||
Simulates a 4-wheel robot using Tank Drive joystick control | ||
""" | ||
|
||
def __init__(self, physics_controller): | ||
""" | ||
:param physics_controller: `pyfrc.physics.core.Physics` object | ||
to communicate simulation effects to | ||
""" | ||
self.physics_controller = physics_controller | ||
|
||
self.drivetrain = tankmodel.TankModel.theory( | ||
motor_cfgs.MOTOR_CFG_CIM, # motor configuration | ||
80 * units.lbs, # robot mass | ||
12.75, # drivetrain gear ratio | ||
2, # motors per side | ||
17.75 * units.inch, # robot wheelbase | ||
21.63 * units.inch, # robot width | ||
28 * units.inch, # robot length | ||
6 * units.inch # wheel diameter | ||
) | ||
|
||
self.kEncoder = 360 / (0.5 * math.pi) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This value seems mighty mighty low. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those are some SMALL wheels. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think they're actually 6 inches, we can check to be sure @AndrewLester There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a good idea to make this a constant at any rate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's in feet. I think I just kept it that way for consistency, but I'm not sure. The 360 is low though, as I think it's the encoder counts per revolution which is normally higher. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that makes more sense, but then the encoder value still seems low I think |
||
|
||
self.l_distance = 0 | ||
self.r_distance = 0 | ||
|
||
def update_sim(self, hal_data, now, tm_diff): | ||
""" | ||
Called when the simulation parameters for the program need to be | ||
updated. | ||
|
||
:param now: The current time as a float | ||
:param tm_diff: The amount of time that has passed since the last | ||
time that this function was called | ||
""" | ||
|
||
# Simulate the drivetrain | ||
lf_motor = hal_data['pwm'][0b00]['value'] | ||
rf_motor = hal_data['pwm'][0b10]['value'] | ||
|
||
# Not needed because front and rear should be in sync | ||
# lr_motor = hal_data['pwm'][0b01]['value'] | ||
# rr_motor = hal_data['pwm'][0b11]['value'] | ||
|
||
x, y, angle = self.drivetrain.get_distance(lf_motor, rf_motor, tm_diff) | ||
self.physics_controller.distance_drive(x, y, angle) | ||
|
||
# Update encoders | ||
self.l_distance += self.drivetrain.l_velocity * tm_diff | ||
self.r_distance += self.drivetrain.r_velocity * tm_diff | ||
|
||
hal_data['encoder'][0]['count'] = int(self.l_distance * self.kEncoder) | ||
hal_data['encoder'][1]['count'] = int(self.r_distance * self.kEncoder) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"pyfrc": { | ||
"robot": { | ||
"w": 2.33, | ||
"h": 1.80, | ||
"starting_x": 1.8, | ||
"starting_y": 14 | ||
}, | ||
"pwm": { | ||
"0": "lf_motor", | ||
"1": "lr_motor", | ||
"2": "rf_motor", | ||
"3": "rr_motor" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import os | ||
import pickle | ||
import pathfinder as pf | ||
import wpilib | ||
|
||
|
||
WHEELBASE_WIDTH = 1.479 # In feet | ||
TRAJECTORY_DIRECTORY = 'trajectories' | ||
PICKLE_FILE = os.path.join(os.path.dirname(__file__), TRAJECTORY_DIRECTORY, 'trajectories.pickle') | ||
|
||
trajectories = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a better way to do this then listing them in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could read them in from a JSON? Pathfinder will also read in directly from a pathfinder binary created by he |
||
"diagonal_higher": [ | ||
pf.Waypoint(0, 0, 0), # Waypoints are relative to first, so start at 0, 0, 0 | ||
pf.Waypoint(15, 8, 0) | ||
], | ||
"diagonal": [ | ||
pf.Waypoint(0, 0, 0), # Waypoints are relative to first, so start at 0, 0, 0 | ||
pf.Waypoint(15, 5, 0) | ||
], | ||
"charge": [ | ||
pf.Waypoint(0, 0, 0), | ||
pf.Waypoint(10, 0, 0) | ||
] | ||
} | ||
|
||
|
||
def load_trajectories(): | ||
if wpilib.RobotBase.isSimulation(): | ||
generated_trajectories = _generate_trajectories() | ||
_write_trajectories(generated_trajectories) | ||
else: | ||
with open(PICKLE_FILE, 'rb') as f: | ||
generated_trajectories = pickle.load(f) | ||
|
||
return generated_trajectories | ||
|
||
|
||
def _write_trajectories(trajectories): | ||
with open(PICKLE_FILE, 'wb') as f: | ||
pickle.dump(trajectories, f) | ||
|
||
|
||
def _generate_trajectories(): | ||
generated_trajectories = {} | ||
|
||
for trajectory_name, trajectory in trajectories.items(): | ||
generated_trajectory = pf.generate( | ||
trajectory, | ||
pf.FIT_HERMITE_CUBIC, | ||
pf.SAMPLES_HIGH, | ||
dt=0.02, # 20ms | ||
max_velocity=10.903, # These are in ft/sec and | ||
max_acceleration=73.220, # set the units for distance to ft. | ||
max_jerk=140 | ||
)[1] # The 0th element is just info | ||
|
||
modifier = pf.modifiers.TankModifier(generated_trajectory).modify(WHEELBASE_WIDTH) | ||
|
||
generated_trajectories[trajectory_name] = ( | ||
modifier.getLeftTrajectory(), | ||
modifier.getRightTrajectory() | ||
) | ||
|
||
if wpilib.RobotBase.isSimulation(): | ||
from pyfrc.sim import get_user_renderer | ||
|
||
renderer = get_user_renderer() | ||
if renderer: | ||
renderer.draw_pathfinder_trajectory(modifier.getLeftTrajectory(), '#0000ff', offset=(-0.9, 0)) | ||
renderer.draw_pathfinder_trajectory(modifier.source, '#00ff00', show_dt=True) | ||
renderer.draw_pathfinder_trajectory(modifier.getRightTrajectory(), '#0000ff', offset=(0.9, 0)) | ||
|
||
return generated_trajectories |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our favorite!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did this give you trouble in the past?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we forgot to install it on the RIO last year and it took us a while to figure out what we'd done wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need to install this on the RIO. If you do, you're doing something wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@virtuald I'm likely thinking of a different package then.