import math
import typing
from ._pathfinder import (
DistanceFollower as _DistanceFollower,
EncoderFollower as _EncoderFollower,
EncoderConfig,
FollowerConfig,
Segment,
pathfinder_follow_distance2,
pathfinder_follow_encoder2,
)
__all__ = [
'DistanceFollower',
'EncoderFollower',
]
[docs]class DistanceFollower(_DistanceFollower):
"""
The DistanceFollower is an object designed to follow a trajectory based on distance covered input. This class can be used
for Tank or Swerve drive implementations.
"""
def __init__(self, trajectory: typing.List[Segment]):
self.trajectory = trajectory
self.cfg = FollowerConfig()
[docs] def setTrajectory(self, trajectory: typing.List[Segment]) -> None:
"""Set a new trajectory to follow, and reset the cumulative errors and segment counts"""
self.trajectory = trajectory
self.reset()
[docs] def reset(self) -> None:
"""Reset the follower to start again. Encoders must be reconfigured."""
self.last_error = 0
self.segment = 0
[docs] def calculate(self, distance_covered: float) -> float:
"""Calculate the desired output for the motors, based on the distance the robot has covered.
This does not account for heading of the robot. To account for heading, add some extra terms in your control
loop for realignment based on gyroscope input and the desired heading given by this object.
:param distance_covered: The distance covered in meters
:returns: The desired output for your motor controller
"""
tlen = len(self.trajectory)
if self.segment >= tlen:
self.finished = 1
self.output = 0
self.heading = self.trajectory[-1].heading
return 0.0
else:
return pathfinder_follow_distance2(self.cfg, self, self.trajectory[self.segment], tlen, distance_covered)
[docs] def getHeading(self) -> float:
""":returns: the desired heading of the current point in the trajectory"""
return self.heading
[docs] def getSegment(self) -> Segment:
""":returns: the current segment being operated on"""
return self.trajectory[self.segment]
[docs] def isFinished(self) -> bool:
""":returns: whether we have finished tracking this trajectory or not."""
return self.segment >= len(self.trajectory)
[docs]class EncoderFollower(_EncoderFollower):
"""
The EncoderFollower is an object designed to follow a trajectory based on encoder input. This class can be used
for Tank or Swerve drive implementations.
"""
def __init__(self, trajectory: typing.List[Segment]):
self.trajectory = trajectory
self.cfg = EncoderConfig()
[docs] def setTrajectory(self, trajectory: typing.List[Segment]) -> None:
"""Set a new trajectory to follow, and reset the cumulative errors and segment counts"""
self.trajectory = trajectory
self.reset()
[docs] def reset(self) -> None:
"""Reset the follower to start again. Encoders must be reconfigured."""
self.last_error = 0
self.segment = 0
[docs] def calculate(self, encoder_tick: int) -> float:
"""Calculate the desired output for the motors, based on the amount of ticks the encoder has gone through.
This does not account for heading of the robot. To account for heading, add some extra terms in your control
loop for realignment based on gyroscope input and the desired heading given by this object.
:param encoder_tick: The amount of ticks the encoder has currently measured.
:returns: The desired output for your motor controller
"""
tlen = len(self.trajectory)
if self.segment >= tlen:
self.finished = 1
self.output = 0
self.heading = self.trajectory[-1].heading
return 0.0
else:
return pathfinder_follow_encoder2(self.cfg, self, self.trajectory[self.segment], tlen, encoder_tick)
[docs] def getHeading(self) -> float:
""":returns: the desired heading of the current point in the trajectory"""
return self.heading
[docs] def getSegment(self) -> Segment:
""":returns: the current segment being operated on"""
return self.trajectory[self.segment]
[docs] def isFinished(self) -> bool:
""":returns: whether we have finished tracking this trajectory or not."""
return self.segment >= len(self.trajectory)