Example #1
0
    def setup(self):
        """
        Called after injection
        """
        # Config
        self.sd_prefix = self.cfg.sd_prefix or 'Module'
        self.encoder_zero = self.cfg.zero or 0
        self.inverted = self.cfg.inverted or False
        self.allow_reverse = self.cfg.allow_reverse or True

        # SmartDashboard
        self.sd = NetworkTables.getTable('SmartDashboard')
        self.debugging = self.sd.getEntry('drive/drive/debugging')

        # Motor
        self.driveMotor.setInverted(self.inverted)

        self._requested_voltage = 0
        self._requested_speed = 0

        # PID Controller
        # kP = 1.5, kI = 0.0, kD = 0.0
        self._pid_controller = PIDController(1.5, 0.0, 0.0)
        self._pid_controller.enableContinuousInput(
            0.0, 5.0)  # Will set the 0 and 5 as the same point
        self._pid_controller.setTolerance(
            0.05, 0.05)  # Tolerance where the PID will be accpeted aligned
Example #2
0
 def setup(self):
     """
     Run setup code on the injected variables (train)
     """
     self.angle_controller = PIDController(self.angle_p, self.angle_i,
                                           self.angle_d)
     self.angle_controller.setTolerance(2, 1)
     self.angle_controller.enableContinuousInput(0, 360)
     self.angle_setpoint = None
     self.calculated_pid = False
Example #3
0
    def __init__(self, robot, distance):
        super().__init__()
        self.robot = robot

        self.requires(self.robot.drivetrain)
        self.pid = PIDController(
            -2,
            0,
            0,
        )
        self.pid.setSetpoint(distance)
Example #4
0
    def __init__(self):
        self.clockwise_limit_switch = DigitalInput(
            TURRET_CLOCKWISE_LIMIT_SWITCH)
        self.counterclockwise_limit_switch = DigitalInput(
            TURRET_COUNTERCLOCKWISE_LIMIT_SWITCH)

        self.turn_motor = SparkMax(TURRET_TURN_MOTOR)
        self.turn_pid = PIDController(0.4, 0.001, 0.02)

        self.shoot_motor_1 = Falcon(TURRET_SHOOT_MOTORS[0])
        self.shoot_motor_2 = Falcon(TURRET_SHOOT_MOTORS[1])
        self.timer = Timer()

        self.limelight = Limelight()
Example #5
0
class SetDistanceToBox(Command):
    """
    Drive until the robot is the given distance away from the box. Uses a local
    PID controller to run a simple PID loop that is only enabled while this
    command is running. The input is the averaged values of the left and right
    encoders.
    """
    def __init__(self, robot, distance):
        super().__init__()
        self.robot = robot

        self.requires(self.robot.drivetrain)
        self.pid = PIDController(
            -2,
            0,
            0,
        )
        self.pid.setSetpoint(distance)

    def initialize(self):
        """Called just before this Command runs the first time"""

        # Get everything in a safe starting state.
        self.robot.drivetrain.reset()
        self.pid.reset()
        self.pid.enable()

    def execute(self):
        """Called repeatedly when this Command is scheduled to run"""

    def isFinished(self):
        """Make this return true when this Command no longer needs to run execute()"""
        return self.pid.onTarget()

    def end(self):
        """Called once after isFinished returns true"""

        # Stop PID and the wheels
        self.pid.disable()
        self.robot.drivetrain.driveManual(0, 0)

    def interrupted(self):
        """Called when another command which requires one or more of the same
        subsystems is scheduled to run"""
        self.end()
Example #6
0
    def robotInit(self):
        self.left_motor = wpilib.Spark(0)
        self.right_motor = wpilib.Spark(1)
        self.drive = wpilib.drive.DifferentialDrive(self.left_motor, self.right_motor)
        self.stick = wpilib.Joystick(0)
        self.ahrs = AHRS.create_spi()
        # self.ahrs = AHRS.create_i2c()

        turnController = PIDController(
            self.kP, self.kI, self.kD, period = 1.0
        )

        self.turnController = turnController
        self.rotateToAngleRate = 0
Example #7
0
    def robotInit(self):
        #self.smartDashboard = smart_dashboard
        self.sd = NetworkTables.getTable("SmartDashboard")
        self.left_motor = ctre.WPI_TalonSRX(0)
        self.right_motor = ctre.WPI_TalonSRX(1)
        self.drive = wpilib.drive.DifferentialDrive(self.left_motor,
                                                    self.right_motor)
        self.stick = wpilib.Joystick(0)
        self.navx = navx.AHRS.create_spi()
        # self.ahrs = AHRS.create_i2c()

        turnController = PIDController(self.kP, self.kI, self.kD, period=1.0)

        self.turnController = turnController
        self.rotateToAngleRate = 5
        smart_dashboard.putData("PID COntroller", self.turnController)
Example #8
0
class Drive:
    """
    Handle robot drivetrain.
    All drive interaction must go through this class.
    """

    limelight: Limelight
    train: wpilib.drive.DifferentialDrive
    navx: navx.AHRS
    left_motors: wpilib.SpeedControllerGroup
    right_motors: wpilib.SpeedControllerGroup

    y = will_reset_to(0.0)
    rot = will_reset_to(0.0)
    aligning = will_reset_to(False)
    deadband = will_reset_to(0.1)

    auto = will_reset_to(False)
    left_voltage = will_reset_to(0)
    right_voltage = will_reset_to(0)

    speed_constant = will_reset_to(1.05)
    rotational_constant = will_reset_to(0.8)
    squared_inputs = False

    angle_p = ntproperty('/align/kp', 0.04)
    angle_i = ntproperty('/align/ki', 0)
    angle_d = ntproperty('/align/kd', 0.0)
    angle_reported = ntproperty('/align/angle', 0)
    angle_to = ntproperty('/align/angle_to', 0)

    def setup(self):
        """
        Run setup code on the injected variables (train)
        """
        self.angle_controller = PIDController(self.angle_p, self.angle_i,
                                              self.angle_d)
        self.angle_controller.setTolerance(2, 1)
        self.angle_controller.enableContinuousInput(0, 360)
        self.angle_setpoint = None
        self.calculated_pid = False

    def set_target(self, angle: float, relative=True):
        if relative and angle is not None:
            self.angle_setpoint = (self.angle + angle) % 360
            self.angle_to = self.angle_setpoint
        else:
            self.angle_setpoint = angle

        if angle is not None:
            self.angle_controller.setSetpoint(self.angle_setpoint)
        else:
            self.calculated_pid = False
            self.angle_controller.reset()

    def align(self):
        self.aligning = True
        self.deadband = 0

    def voltageDrive(self, left_voltage, right_voltage):
        self.left_voltage = left_voltage
        self.right_voltage = right_voltage
        self.auto = True
        self.deadband = 0

    def move(self, y: float, rot: float):
        """
        Move robot.
        :param y: Speed of motion in the y direction. [-1..1]
        :param rot: Speed of rotation. [-1..1]
        """
        self.y = y
        self.rot = rot

    @property
    def target_locked(self):
        # self.logger.info(f'SET {self.angle_controller.getSetpoint()} ANG {self.navx.getAngle()}')
        return self.angle_controller.atSetpoint() and self.calculated_pid

    @property
    def angle(self):
        raw = self.navx.getAngle()
        while raw < 0:
            raw += 360
        return raw % 360

    def execute(self):
        """
        Handle driving.
        """
        self.train.setDeadband(self.deadband)
        self.angle_reported = self.angle

        if self.aligning and self.angle_setpoint is not None:
            self.right_motors.setInverted(False)
            if self.angle_controller.atSetpoint() and self.calculated_pid:
                self.train.arcadeDrive(0, 0, squareInputs=False)
                return

            # Use new network tables variables for testing
            self.angle_controller.setP(self.angle_p)
            self.angle_controller.setD(self.angle_d)
            self.angle_controller.setI(self.angle_i)

            output = self.angle_controller.calculate(self.angle)
            self.logger.info(
                f'Output: {output}, Error: {self.angle_controller.getPositionError()}'
            )
            # Manual I-term zone (15 degrees)
            if abs(self.angle_controller.getPositionError()) <= 3:
                self.angle_controller.setI(self.angle_i)
                # Minumum and Maximum effect of integrator on output
                self.angle_controller.setIntegratorRange(-0.05, 0.05)
            else:
                self.angle_controller.setI(0)
                self.angle_controller.setIntegratorRange(0, 0)

            if output < 0:
                output = max(output, -0.35)
            else:
                output = min(output, 0.35)

            self.train.arcadeDrive(0, output, squareInputs=False)
            self.calculated_pid = True
        elif self.auto:
            self.right_motors.setInverted(True)
            self.right_motors.setVoltage(self.right_voltage)
            self.left_motors.setVoltage(self.left_voltage)
        else:
            self.right_motors.setInverted(False)
            self.train.arcadeDrive(
                self.speed_constant * self.y,
                self.rotational_constant * self.rot,
                squareInputs=self.squared_inputs,
            )

        if not self.limelight.targetExists():
            self.limelight.target_state = 0
        elif self.target_locked:
            self.limelight.target_state = 2
        else:
            self.limelight.target_state = 1
Example #9
0
class SwerveModule:
    # Get the motors, encoder and config from injection
    driveMotor: ctre.WPI_VictorSPX
    rotateMotor: ctre.WPI_VictorSPX

    encoder: wpilib.AnalogInput

    cfg: ModuleConfig

    def setup(self):
        """
        Called after injection
        """
        # Config
        self.sd_prefix = self.cfg.sd_prefix or 'Module'
        self.encoder_zero = self.cfg.zero or 0
        self.inverted = self.cfg.inverted or False
        self.allow_reverse = self.cfg.allow_reverse or True

        # SmartDashboard
        self.sd = NetworkTables.getTable('SmartDashboard')
        self.debugging = self.sd.getEntry('drive/drive/debugging')

        # Motor
        self.driveMotor.setInverted(self.inverted)

        self._requested_voltage = 0
        self._requested_speed = 0

        # PID Controller
        # kP = 1.5, kI = 0.0, kD = 0.0
        self._pid_controller = PIDController(1.5, 0.0, 0.0)
        self._pid_controller.enableContinuousInput(
            0.0, 5.0)  # Will set the 0 and 5 as the same point
        self._pid_controller.setTolerance(
            0.05, 0.05)  # Tolerance where the PID will be accpeted aligned

    def get_voltage(self):
        """
        :returns: the voltage position after the zero
        """
        return self.encoder.getAverageVoltage() - self.encoder_zero

    def flush(self):
        """
        Flush the modules requested speed and voltage.
        Resets the PID controller.
        """
        self._requested_voltage = self.encoder_zero
        self._requested_speed = 0
        self._pid_controller.reset()

    @staticmethod
    def voltage_to_degrees(voltage):
        """
        Convert a given voltage value to degrees.

        :param voltage: a voltage value between 0 and 5
        :returns: the degree value between 0 and 359
        """
        deg = (voltage / 5) * 360

        if deg < 0:
            deg += 360

        return deg

    @staticmethod
    def voltage_to_rad(voltage):
        """
        Convert a given voltage value to rad.

        :param voltage: a voltage value between 0 and 5
        :returns: the radian value betwen 0 and 2pi
        """
        return (voltage / 5) * 2 * math.pi

    @staticmethod
    def degree_to_voltage(degree):
        """
        Convert a given degree to voltage.

        :param degree: a degree value between 0 and 360
        :returns" the voltage value between 0 and 5
        """
        return (degree / 360) * 5

    def _set_deg(self, value):
        """
        Round the value to within 360. Set the requested rotate position (requested voltage).
        Intended to be used only by the move function.
        """
        self._requested_voltage = (
            (self.degree_to_voltage(value) + self.encoder_zero) % 5)

    def move(self, speed, deg):
        """
        Set the requested speed and rotation of passed.

        :param speed: requested speed of wheel from -1 to 1
        :param deg: requested angle of wheel from 0 to 359 (Will wrap if over or under)
        """
        deg %= 360  # Prevent values past 360

        if self.allow_reverse:
            """
            If the difference between the requested degree and the current degree is
            more than 90 degrees, don't turn the wheel 180 degrees. Instead reverse the speed.
            """
            if abs(deg - self.voltage_to_degrees(self.get_voltage())) > 90:
                speed *= -1
                deg += 180
                deg %= 360

        self._requested_speed = speed
        self._set_deg(deg)

    def debug(self):
        """
        Print debugging information about the module to the log.
        """
        print(self.sd_prefix, '; requested_speed: ', self._requested_speed,
              ' requested_voltage: ', self._requested_voltage)

    def execute(self):
        """
        Use the PID controller to get closer to the requested position.
        Set the speed requested of the drive motor.

        Called every robot iteration/loop.
        """
        # Calculate the error using the current voltage and the requested voltage.
        # DO NOT use the #self.get_voltage function here. It has to be the raw voltage.
        error = self._pid_controller.calculate(self.encoder.getVoltage(),
                                               self._requested_voltage)

        # Set the output 0 as the default value
        output = 0
        # If the error is not tolerable, set the output to the error.
        # Else, the output will stay at zero.
        if not self._pid_controller.atSetpoint():
            # Use max-min to clamped the output between -1 and 1.
            output = max(min(error, 1), -1)

        # Put the output to the dashboard
        self.sd.putNumber('drive/%s/output' % self.sd_prefix, output)
        # Set the output as the rotateMotor's voltage
        self.rotateMotor.set(output)

        # Set the requested speed as the driveMotor's voltage
        self.driveMotor.set(self._requested_speed)

        self.update_smartdash()

    def update_smartdash(self):
        """
        Output a bunch on internal variables for debugging purposes.
        """
        self.sd.putNumber('drive/%s/degrees' % self.sd_prefix,
                          self.voltage_to_degrees(self.get_voltage()))

        if self.debugging.getBoolean(False):
            self.sd.putNumber('drive/%s/requested_voltage' % self.sd_prefix,
                              self._requested_voltage)
            self.sd.putNumber('drive/%s/requested_speed' % self.sd_prefix,
                              self._requested_speed)
            self.sd.putNumber('drive/%s/raw voltage' % self.sd_prefix,
                              self.encoder.getVoltage()
                              )  # DO NOT USE self.get_voltage() here
            self.sd.putNumber('drive/%s/average voltage' % self.sd_prefix,
                              self.encoder.getAverageVoltage())
            self.sd.putNumber('drive/%s/encoder_zero' % self.sd_prefix,
                              self.encoder_zero)

            self.sd.putNumber('drive/%s/PID Setpoint' % self.sd_prefix,
                              self._pid_controller.getSetpoint())
            self.sd.putNumber('drive/%s/PID Error' % self.sd_prefix,
                              self._pid_controller.getPositionError())
            self.sd.putBoolean('drive/%s/PID isAligned' % self.sd_prefix,
                               self._pid_controller.atSetpoint())

            self.sd.putBoolean('drive/%s/allow_reverse' % self.sd_prefix,
                               self.allow_reverse)
Example #10
0
class Turret:
    '''
    The object thats responsible for managing the shooter
    '''
    def __init__(self):
        self.clockwise_limit_switch = DigitalInput(
            TURRET_CLOCKWISE_LIMIT_SWITCH)
        self.counterclockwise_limit_switch = DigitalInput(
            TURRET_COUNTERCLOCKWISE_LIMIT_SWITCH)

        self.turn_motor = SparkMax(TURRET_TURN_MOTOR)
        self.turn_pid = PIDController(0.4, 0.001, 0.02)

        self.shoot_motor_1 = Falcon(TURRET_SHOOT_MOTORS[0])
        self.shoot_motor_2 = Falcon(TURRET_SHOOT_MOTORS[1])
        self.timer = Timer()

        self.limelight = Limelight()

    def set_target_angle(self, angle):
        '''
        Sets the target angle of the turret. This will use a PID to turn the
        turret to the target angle.
        '''

        target_encoder = angle_to_encoder(angle)
        self.turn_pid.setSetpoint(target_encoder)

    def update(self):
        '''
        This is used to continuously update the turret's event loop.

        All this manages as of now is the turrets PID controller.

        The shoot motor is constantly running at a low percentage until we need it.
        '''

        motor_speed = self.turn_pid.calculate(
            self.limelight.get_target_screen_x())

        if self.clockwise_limit_switch.get() and motor_speed < 0:
            self.turn_motor.set_percent_output(motor_speed)

        elif self.counterclockwise_limit_switch.get() and motor_speed > 0:
            self.turn_motor.set_percent_output(motor_speed)

    def shoot(self):
        '''
        The wheel to shoot will rev up completely before balls start feeding
        in from the singulator.
        '''
        # One of the motors will be reversed, so make sure the shoot motor has the correct ID!
        speed = self.shoot_motor_1.get_percent_output()
        if speed < 1:
            speed += 0.02
        elif speed > 1:
            speed -= 0.02

        self.shoot_motor_1.set_percent_output(speed)
        self.shoot_motor_2.set_percent_output(-speed)

    def idle(self):
        '''
        Resets the motors back to their default state.
        '''
        speed = self.shoot_motor_1.get_percent_output()
        if speed < 0.5:
            speed += 0.02
        elif speed > 0.5:
            speed -= 0.02

        self.shoot_motor_1.set_percent_output(speed)
        self.shoot_motor_2.set_percent_output(-speed)

    def is_full_speed(self):
        '''
        Returns if the motor is at full speed or not.
        '''
        self.timer.get() > 0.4
Example #11
0
 def update_controller(self, controller: PIDController) -> None:
     controller.setPID(self.p, self.i, self.d)
Example #12
0
 def setup(self):
     self.trajectory_name = None
     self.left_controller = PIDController(0.07, 0, 0)
     self.right_controller = PIDController(0.07, 0, 0)
Example #13
0
class Follower:
    kinematics: DifferentialDriveKinematics
    drive_feedforward: SimpleMotorFeedforwardMeters
    trajectories: dict
    odometry: Odometry
    drive: Drive
    use_pid = tunable(True)
    trajectory = will_reset_to(None)
    sample_time = will_reset_to(0.0)

    def setup(self):
        self.trajectory_name = None
        self.left_controller = PIDController(0.07, 0, 0)
        self.right_controller = PIDController(0.07, 0, 0)

    def on_disable(self):
        self.trajectory_name = None
        self.left_controller.reset()
        self.right_controller.reset()

    def setup_trajectory(self, trajectory: Trajectory):
        self.controller = RamseteController()
        self.left_controller.reset()
        self.right_controller.reset()
        self.prev_time = 0
        self.speed = DifferentialDriveWheelSpeeds()

        self.controller.setTolerance(
            Pose2d(0.05, 0.05, Rotation2d(math.radians(5))))
        self.initial_state = self.trajectory.sample(0)
        speeds = ChassisSpeeds()
        speeds.vx = self.initial_state.velocity
        speeds.omega = self.initial_state.velocity * self.initial_state.curvature
        self.prevSpeeds = self.kinematics.toWheelSpeeds(speeds)

    def follow_trajectory(self, trajectory_name: str, sample_time: float):
        try:
            self.trajectory = self.trajectories[trajectory_name]
        except KeyError:
            self.logger.error(
                f'The trajectory with name "{trajectory_name}" could not be found.'
            )
            return
        self.sample_time = sample_time

        # Check if trajectory was restarted or if a new one has begun
        if sample_time <= self.prev_time or trajectory_name != self.trajectory_name:
            self.setup_trajectory(self.trajectory)

        self.trajectory_name = trajectory_name

    def is_finished(self, trajectory_name: str):
        if self.trajectory_name != trajectory_name or self.trajectory is None:
            self.logger.warning(
                'Method "is_finished" called with no trajectory running.')
            return True

        return self.prev_time >= self.trajectory.totalTime() + 0.04

    def execute(self):
        if self.trajectory is None:
            return

        if self.use_pid:
            self.dt = self.sample_time - self.prev_time

            if self.dt == 0:
                self.dt = 0.02

            targetWheelSpeeds = self.kinematics.toWheelSpeeds(
                self.controller.calculate(
                    self.odometry.get_pose(),
                    self.trajectory.sample(self.sample_time)))

            leftSpeedSetpoint = targetWheelSpeeds.left
            rightSpeedSetpoint = targetWheelSpeeds.right

            leftFeedforward = self.drive_feedforward.calculate(
                leftSpeedSetpoint,
                (leftSpeedSetpoint - self.prevSpeeds.left) / self.dt)

            rightFeedforward = self.drive_feedforward.calculate(
                rightSpeedSetpoint,
                (rightSpeedSetpoint - self.prevSpeeds.right) / self.dt)

            leftOutput = leftFeedforward + self.left_controller.calculate(
                self.odometry.left_rate, leftSpeedSetpoint)

            rightOutput = rightFeedforward + self.right_controller.calculate(
                self.odometry.right_rate, rightSpeedSetpoint)
        else:
            leftOutput = leftSpeedSetpoint
            rightOutput = rightSpeedSetpoint

        self.drive.voltageDrive(leftOutput, rightOutput)

        self.prev_time = self.sample_time
        self.prevSpeeds = targetWheelSpeeds
Example #14
0
    def getAutonomousCommand(self):
        """Returns the command to be ran during the autonomous period."""

        # Create a voltage constraint to ensure we don't accelerate too fast.

        autoVoltageConstraint = DifferentialDriveVoltageConstraint(
            SimpleMotorFeedforwardMeters(
                constants.ksVolts,
                constants.kvVoltSecondsPerMeter,
                constants.kaVoltSecondsSquaredPerMeter,
            ),
            constants.kDriveKinematics,
            maxVoltage=10,  # 10 volts max.
        )

        # Below will generate the trajectory using a set of programmed configurations

        # Create a configuration for the trajctory. This tells the trajectory its constraints
        # as well as its resources, such as the kinematics object.
        config = TrajectoryConfig(
            constants.kMaxSpeedMetersPerSecond,
            constants.kMaxAccelerationMetersPerSecondSquared,
        )

        # Ensures that the max speed is actually obeyed.
        config.setKinematics(constants.kDriveKinematics)

        # Apply the previously defined voltage constraint.
        config.addConstraint(autoVoltageConstraint)

        # Start at the origin facing the +x direction.
        initialPosition = Pose2d(0, 0, Rotation2d(0))

        # Here are the movements we also want to make during this command.
        # These movements should make an "S" like curve.
        movements = [Translation2d(1, 1), Translation2d(2, -1)]

        # End at this position, three meters straight ahead of us, facing forward.
        finalPosition = Pose2d(3, 0, Rotation2d(0))

        # An example trajectory to follow. All of these units are in meters.
        self.exampleTrajectory = TrajectoryGenerator.generateTrajectory(
            initialPosition,
            movements,
            finalPosition,
            config,
        )

        # Below creates the RAMSETE command

        ramseteCommand = RamseteCommand(
            # The trajectory to follow.
            self.exampleTrajectory,
            # A reference to a method that will return our position.
            self.robotDrive.getPose,
            # Our RAMSETE controller.
            RamseteController(constants.kRamseteB, constants.kRamseteZeta),
            # A feedforward object for the robot.
            SimpleMotorFeedforwardMeters(
                constants.ksVolts,
                constants.kvVoltSecondsPerMeter,
                constants.kaVoltSecondsSquaredPerMeter,
            ),
            # Our drive kinematics.
            constants.kDriveKinematics,
            # A reference to a method which will return a DifferentialDriveWheelSpeeds object.
            self.robotDrive.getWheelSpeeds,
            # The turn controller for the left side of the drivetrain.
            PIDController(constants.kPDriveVel, 0, 0),
            # The turn controller for the right side of the drivetrain.
            PIDController(constants.kPDriveVel, 0, 0),
            # A reference to a method which will set a specified
            # voltage to each motor. The command will pass the two parameters.
            self.robotDrive.tankDriveVolts,
            # The subsystems the command should require.
            [self.robotDrive],
        )

        # Reset the robot's position to the starting position of the trajectory.
        self.robotDrive.resetOdometry(self.exampleTrajectory.initialPose())

        # Return the command to schedule. The "andThen()" will halt the robot after
        # the command finishes.
        return ramseteCommand.andThen(
            lambda: self.robotDrive.tankDriveVolts(0, 0))