Source code for seamonsters.pathFollower

import math, sys
import seamonsters as sea

class PathFollower:
    """
    Controls a SuperHolonomicDrive to follow paths on the field.
    """

    NAVX_LAG = 7 # frames
    NAVX_ERROR_CORRECTION = 0.1 # out of 1

[docs] def __init__(self, drive, ahrs=None): """ :param drive: a SuperHolonomicDrive :param x: starting x position of the robot, in feet :param y: starting y position of the robot, in feet :param angle: starting angle of the robot, radians. 0 means the robot's local XY coordinates line up with the field XY coordinates. :param ahrs: an optional AHRS (NavX) instance. If provided, this will be used to track the robot's rotation; if not, the rotation will be calculated based on the movement of the motors. """ self.drive = drive self._drivePositionState = None self.ahrs = ahrs self._ahrsOrigin = 0 if ahrs is not None: self._ahrsOrigin = self._getAHRSAngle() self.robotX = 0 self.robotY = 0 self.robotAngle = 0 self._robotAngleHistory = []
[docs] def setPosition(self, x, y, angle): self.robotX = x self.robotY = y if angle is not None: self.robotAngle = angle if self.ahrs is not None: self._ahrsOrigin = 0 self._ahrsOrigin = self._getAHRSAngle() - angle self._robotAngleHistory.clear() self._drivePositionState = None
def _getAHRSAngle(self): return -math.radians(self.ahrs.getAngle()) - self._ahrsOrigin
[docs] def updateRobotPosition(self): moveDist, moveDir, moveTurn, self._drivePositionState = \ self.drive.getRobotPositionOffset(self._drivePositionState, target=False) # set the target to False because it is more accurate self.robotAngle += moveTurn self._robotAngleHistory.append(self.robotAngle) # pretty sure this isn't off by 1 if len(self._robotAngleHistory) >= PathFollower.NAVX_LAG: laggedAngle = self._robotAngleHistory.pop(0) if self.ahrs is not None: navxAngle = self._getAHRSAngle() error = (navxAngle - laggedAngle) * PathFollower.NAVX_ERROR_CORRECTION self.robotAngle += error for i in range(0, len(self._robotAngleHistory)): self._robotAngleHistory[i] += error self.robotX += math.cos(moveDir + self.robotAngle) * moveDist self.robotY += math.sin(moveDir + self.robotAngle) * moveDist
[docs] def driveToPointGenerator(self, x, y, finalAngle=None, speed=1, robotPositionTolerance=0, robotAngleTolerance=0): """ A generator to drive to a location on the field while simultaneously pointing the robot in a new direction. This will attempt to move the robot so it reaches the target position at a given speed and ends by rotating to the inputted angle. This generator never exits, but yields ``True`` or ``False`` if the robot is close enough to its target position, within tolerance. """ dist, aDiff = self._robotVectorToPoint(x, y) for wheel in self.drive.wheels: wheel.resetPosition() if dist < 0.1: # TODO: constant dist = 0 if abs(aDiff) < math.radians(1): # TODO aDiff = 0 # if the robot has reached the angle or position hasReachedInitialAngle = False hasReachedPosition = False hasReachedFinalAngle = True if finalAngle is not None: hasReachedFinalAngle = False # if the robot should go backwards backwards = False accel = 0 while True: accel += 0.1 if accel > 1: accel = 1 self.updateRobotPosition() dist, aDiff = self._robotVectorToPoint(x, y) # when it reaches the initial angle, # work towards the final angle if hasReachedPosition and not hasReachedFinalAngle: aDiff = finalAngle - self.robotAngle # make robot turn the shorter distance # and not always go clockwise aDiff %= math.pi * 2 if aDiff > math.pi: aDiff -= math.pi * 2 elif aDiff < -math.pi: aDiff += math.pi * 2 if not hasReachedPosition: # decide if the robot should go # backwards and adjust the angle if aDiff > math.pi / 2: aDiff -= math.pi backwards = True elif aDiff < -math.pi / 2: aDiff += math.pi backwards = True # is the robot close enough to call it good? atPosition = abs(dist) <= robotPositionTolerance atAngle = abs(aDiff) <= robotAngleTolerance # updates the angle and position reached variables if not hasReachedInitialAngle: hasReachedInitialAngle = atAngle elif not hasReachedPosition: hasReachedPosition = atPosition elif not hasReachedFinalAngle: hasReachedFinalAngle = atAngle mag = dist * accel * speed aMag = -aDiff * accel * speed * 2 # the robot is a simultaion if sys.argv[1] == 'sim': aMag *= -1 if backwards: mag *= -1 # once the robot reaches the initial angle, # it will drive forward until it reaches the # position and then face the final angle # turn to face the target first, then drive forward if not hasReachedInitialAngle or (hasReachedPosition and not hasReachedFinalAngle): self.drive.drive(0, 0, aMag) elif not hasReachedPosition: if abs(aDiff) > robotAngleTolerance: # so it will correct if the robot is off course aMag *= 1.5 self.drive.drive(mag, math.pi/2, aMag) yield hasReachedPosition and hasReachedFinalAngle
# return magnitude, angle def _robotVectorToPoint(self, x, y): xDiff = x - self.robotX yDiff = y - self.robotY return (math.sqrt(xDiff ** 2 + yDiff ** 2), math.atan2(yDiff, xDiff) - self.robotAngle - math.pi/2)