def setup(self):
     # empty waypoints are used for trajectories with only two points
     self.start_poses = [
         to_pose(-3.459, -1.7, math.pi),
         to_pose(-8.163, -1.7, math.pi),
         to_pose(-4.168, -1.7, math.pi),
     ]
     self.end_poses = [
         to_pose(-8.163, -1.7, math.pi),
         to_pose(-4.168, -1.7, math.pi),
         to_pose(-6.062 - 1, 0.248, -1.178),
     ]
     self.waypoints = [
         [
             geometry.Translation2d(-7.077, -1.7),
             geometry.Translation2d(-7.992, -1.7),
         ],
         [],
         [],
     ]
     self.expected_balls = [3, 0, 2]
     self.reversed = [False, True, False]
     self.trajectory_config = trajectory.TrajectoryConfig(maxVelocity=2.5,
                                                          maxAcceleration=1)
     self.trajectory_max = 3
     super().setup()
Example #2
0
def get_test_trajectory(velocity=k_max_speed_meters_per_second):
    start_pose = geo.Pose2d(0, 0, geo.Rotation2d(0))
    end_pose = geo.Pose2d(4, 0, geo.Rotation2d(0))
    midpoints = [geo.Translation2d(1.5, 0.5), geo.Translation2d(2.5, -0.5)]
    test_trajectory = wpimath.trajectory.TrajectoryGenerator.generateTrajectory(
        start_pose, midpoints, end_pose, make_config(velocity))
    return test_trajectory
 def setup(self):
     # TODO add correct headings
     # empty waypoints are used for trajectories with only two points
     self.start_poses = [
         to_pose(-3.459, -1.7, math.pi),
         to_pose(-6.165, 0.582, 0),
         to_pose(-4.698, 2.359, 0),
     ]
     self.end_poses = [
         to_pose(-6.179, 2.981, 0),
         to_pose(-4.698, 2.359, 0),
         to_pose(-8.163, 0.705, 0),
     ]
     self.waypoints = [
         [
             geometry.Translation2d(-4.971, 1.154),
             geometry.Translation2d(-5.616, 1.978),
         ],
         [],
         [geometry.Translation2d(-5.668, 0.988)],
     ]
     self.trajectory_config = trajectory.TrajectoryConfig(maxVelocity=1.5,
                                                          maxAcceleration=1)
     self.expected_balls = [5, 0, 3]
     self.reversed = [False, True, False]
     self.trajectory_max = 3
     super().setup()
 def setup(self):
     self.start_poses = [to_pose(-3.459, -1.7, math.pi)]
     self.end_poses = [to_pose(-8.163, -1.7, math.pi)]
     self.waypoints = [[
         geometry.Translation2d(-7.077, -1.7),
         geometry.Translation2d(-7.992, -1.7)
     ]]
     self.expected_balls = [3]
     self.reversed = [False]
     self.trajectory_config = trajectory.TrajectoryConfig(maxVelocity=2.5,
                                                          maxAcceleration=1)
     self.trajectory_max = 1
     super().setup()
 def setup(self):
     self.start_poses = [to_pose(0, 0, math.pi)]
     self.end_poses = [to_pose(-2, 0, math.pi)]
     self.waypoints = [[geometry.Translation2d(-1, math.pi)]]
     self.trajectory_config = trajectory.TrajectoryConfig(maxVelocity=1,
                                                          maxAcceleration=1)
     self.expected_balls = [0]
     self.reversed = [False]
     self.trajectory_max = 1
     super().setup()
class ShootMoveShootBase(AutonomousStateMachine):

    shooter_controller: ShooterController

    chassis: Chassis
    indexer: Indexer
    shooter: Shooter

    # has_zeroed: bool

    TARGET_POSITION = geometry.Translation2d(0, 0)

    def __init__(self) -> None:
        super().__init__()
        self.controller = controller.RamseteController()
        tolerance = to_pose(0.1, 0.1, math.pi / 18)
        self.controller.setTolerance(tolerance)
        self.trajectory_config = trajectory.TrajectoryConfig(maxVelocity=1,
                                                             maxAcceleration=1)
        self.gen = trajectory.TrajectoryGenerator()
        self.trajectory_num = 0

    def setup(self):
        self.trajectory_config.setKinematics(self.chassis.kinematics)
        self.trajectory_config.addConstraint(
            constraint.DifferentialDriveVoltageConstraint(
                self.chassis.ff_calculator, self.chassis.kinematics, 10))
        self.end_ranges = []
        self.paths = []
        for i in range(len(self.start_poses)):
            self.trajectory_config.setReversed(self.reversed[i])
            path = self.gen.generateTrajectory(
                self.start_poses[i],
                self.waypoints[i],
                self.end_poses[i],
                self.trajectory_config,
            )
            self.paths.append(path)
            self.end_ranges.append((self.TARGET_POSITION -
                                    self.end_poses[i].translation()).norm())
        self.path = self.paths[0]

    def on_enable(self) -> None:
        self.chassis.reset_odometry(self.start_poses[0])
        self.indexer.lower_intake()
        self.indexer.enable_intaking()
        self.trajectory_num = 0
        # self.has_zeroed = True
        super().on_enable()

    @state(first=True)
    def shoot(self, initial_call, state_tm) -> None:
        """
        Shoot all balls that we currently have
        """
        if initial_call:
            if self.trajectory_num == 0:
                self.shooter.set_range(3)
            self.shooter_controller.fired_count = 0
        self.shooter_controller.engage()
        self.shooter_controller.fire_input()
        if self.has_fired_balls():
            if self.trajectory_num >= len(self.paths):
                self.done()
            else:
                self.next_state("move")

    @state
    def move(self, initial_call, state_tm) -> None:
        """
        Follow the trajectory defined by our waypoints
        """
        if initial_call:
            self.path = self.paths[self.trajectory_num]
            self.shooter.set_range(self.end_ranges[self.trajectory_num])
        if (state_tm > self.path.totalTime()
                # or self.has_collected_balls()
            ):
            # this needs to be overidden in the subclasses
            print(f"Calculated path time: {self.path.totalTime()}")
            self.chassis.drive(0, 0)
            self.next_state("shoot")
            self.trajectory_num += 1
            return
        pos = self.chassis.get_pose()
        goal = self.path.sample(state_tm)
        speeds = self.controller.calculate(pos, goal)
        self.chassis.drive(speeds.vx, speeds.omega)

    def has_collected_balls(self) -> bool:
        """
        Has the robot collected all the balls for the current trajectory?
        """
        ball_target = self.expected_balls[self.trajectory_num]
        if ball_target == 0:
            return False
        if self.indexer.balls_loaded() >= ball_target:
            return True
        else:
            return False

    def has_fired_balls(self) -> bool:
        balls_to_fire = self.expected_balls[self.trajectory_num - 1]
        if self.trajectory_num == 0:
            balls_to_fire = 3
        # print(f"Expecting to fire {balls_to_fire} balls")
        return self.shooter_controller.fired_count >= balls_to_fire
Example #7
0
    def __init__(self, physics_controller: PhysicsInterface):

        self.physics_controller = physics_controller  # mandatory

        self.counter = 0
        self.x, self.y = 0, 0
        self.obstacles = 'slalom'  # initialize only, this is read from the dash periodically
        self.hood_position = 30

        # --------  INITIALIZE HARDWARE  ---------------

        if self.sparkmax:  # use the sparkmax drivetrain objects, access their position and velocity attributes
            self.l_spark = simlib.SimDeviceSim('SPARK MAX [3]')
            self.r_spark = simlib.SimDeviceSim('SPARK MAX [1]')
            print(
                f'** SparkMax allows access to: **\n{self.r_spark.enumerateValues()} '
            )
            self.l_spark_position = self.l_spark.getDouble('Position')
            self.r_spark_position = self.r_spark.getDouble('Position')
            self.l_spark_velocity = self.l_spark.getDouble('Velocity')
            self.r_spark_velocity = self.r_spark.getDouble('Velocity')
            self.l_spark_output = self.l_spark.getDouble('Applied Output')
            self.r_spark_output = self.r_spark.getDouble('Applied Output')

        else:  # use the generic wpilib motors and encoders
            self.l_motor = simlib.PWMSim(1)
            self.r_motor = simlib.PWMSim(3)
            self.l_encoder = simlib.EncoderSim.createForChannel(0)
            self.r_encoder = simlib.EncoderSim.createForChannel(2)

            # update units from drive constants
            self.l_encoder.setDistancePerPulse(
                drive_constants.k_encoder_distance_per_pulse_m)
            self.r_encoder.setDistancePerPulse(
                drive_constants.k_encoder_distance_per_pulse_m)

        # hood IO
        self.hood_encoder = simlib.EncoderSim.createForChannel(4)
        self.hood_encoder.setDistancePerPulse(1 / 1024)
        self.limit_low = simlib.DIOSim(6)
        self.limit_hi = simlib.DIOSim(7)
        self.hood_motor = simlib.PWMSim(5)
        self.shooter_feed = simlib.PWMSim(6)
        #self.shooter_hood = simlib.PWMSim(5)

        # NavX (SPI interface) - no idea why the "4" is there, seems to be the default name generated by the navx code
        self.navx = simlib.SimDeviceSim("navX-Sensor[4]")
        self.navx_yaw = self.navx.getDouble("Yaw")

        # set up two simulated encoders to see how they effect the robot
        self.l_distance, self.r_distance = 0, 0
        self.l_distance_old, self.r_distance_old = 0, 0  # used for calculating encoder rates

        # --------  INITIALIZE FIELD SETTINGS  ---------------

        # keep us on the field - set x,y limits for driving
        field_size = 'competition'
        if field_size == 'competition':
            self.x_limit, self.y_limit = 15.97 + 0.5, 8.21 + 0.5  # meters for a 52.4x26.9' field PLUS 0.5 meter border
            self.x, self.y = 4, 8.21 / 2
        else:  # at home autonomous challenge for 2021
            self.x_limit, self.y_limit = 9.114, 4.572  # meters for a 30x15' DO NOT INCLUDE BORDER.  To that with the imgui.ini.
            # Set our position on the field
            position = 'slalom'  # set this to put the robot on the field
            if position == 'slalom':
                self.x, self.y = 1.1, 0.68
            elif position == 'barrel':
                self.x, self.y = 1.1, 2.3 - .25
            elif position == 'bounce':
                self.x, self.y = 1.1, 2.5 - .25
            else:
                self.x, self.y = 0, 0

        self.field_offset = geo.Transform2d(geo.Translation2d(0.25, 0.25),
                                            geo.Rotation2d(0))

        # is all this necessary?
        initial_pose = geo.Pose2d(0, 0, geo.Rotation2d(0))
        final_pose = geo.Pose2d(self.x, self.y, geo.Rotation2d(0))
        initial_position_transform = geo.Transform2d(initial_pose, final_pose)
        self.physics_controller.move_robot(initial_position_transform)

        # --------  INITIALIZE ROBOT MODEL  ---------------
        # Change these parameters to fit your robot!
        practice_weight = 20  # lightweight practice bot, WCD

        bumper_width = 3.25 * units.inch
        """        # this is the theory version - when you don't have a drivetrain to emulate
        self.drivetrain = tankmodel.TankModel.theory(
            motor_cfgs.MOTOR_CFG_FALCON_500,           # motor configuration
            practice_weight * units.lbs,          # robot mass
            drive_constants.k_gear_ratio,         # drivetrain gear ratio
            2,                                  # motors per side
            18/2 * units.inch,              # robot wheelbase, 27 in = 0.69 meters, but in half for WCD!
            27 * units.inch + bumper_width * 2, # robot width
            29 * units.inch + bumper_width * 2, # robot length
            drive_constants.k_wheel_diameter_in * units.inch,    # wheel diameter
        )"""

        # characterized values of the drivetrain - 2021 0306 CJH
        self.drivetrain = tankmodel.TankModel(
            motor_config=motor_cfgs.MOTOR_CFG_775PRO,
            robot_mass=practice_weight * units.lbs,
            x_wheelbase=drive_constants.k_robot_wheelbase *
            units.meters,  # need to cut this in half for WCD
            robot_width=drive_constants.k_robot_width *
            units.meters,  # measured wheel to wheel
            robot_length=drive_constants.k_robot_length *
            units.meters,  # measured across entire frame
            l_kv=drive_constants.kv_volt_seconds_per_meter * units.volts *
            units.seconds / units.meter,
            l_ka=drive_constants.ka_volt_seconds_squared_per_meter *
            units.volts * units.seconds * units.seconds / units.meter,
            l_vi=drive_constants.ks_volts * units.volts,
            r_kv=drive_constants.kv_volt_seconds_per_meter * units.volts *
            units.seconds / units.meter,
            r_ka=drive_constants.ka_volt_seconds_squared_per_meter *
            units.volts * units.seconds * units.seconds / units.meter,
            r_vi=drive_constants.ks_volts * units.volts,
            timestep=5 * units.ms)
Example #8
0
    def update_sim(self, now: float, tm_diff: float) -> None:
        """
            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 and update encoders
        if self.sparkmax:
            #self.l_spark_output.set(0.333) # need the -1 to 1 motor value, and I have to fake it
            #self.r_spark_output.set(0.999)
            l_motor = self.l_spark_output.get()
            r_motor = self.r_spark_output.get()
        else:
            l_motor = self.l_motor.getSpeed(
            )  # these are PWM speeds of -1 to 1
            r_motor = self.r_motor.getSpeed(
            )  #  going forward, left should be positive and right is negative

        # get a new location based on motor movement
        transform = self.drivetrain.calculate(l_motor, r_motor, tm_diff)
        pose = self.physics_controller.move_robot(
            transform)  # includes inertia

        # keep us on the simulated field - reverse the transform if we try to go out of bounds
        sim_padding = 0.25  # 0.25  # let us go a bit outside but not get lost
        bad_move = False  # see if we are out of bounds or hitting a barrier
        if (pose.translation().x < -sim_padding
                or pose.translation().x > self.x_limit + sim_padding
                or pose.translation().y < -sim_padding
                or pose.translation().y > self.y_limit + sim_padding):
            bad_move = True

        # allowing the user to change the obstacles
        pylon_points = []
        if 'slalom' in self.obstacles:
            pylon_points = drive_constants.slalom_points
        if 'barrel' in self.obstacles:
            pylon_points = drive_constants.barrel_points
        if 'bounce' in self.obstacles:
            pylon_points = drive_constants.bounce_points
        if any([
                drive_constants.distance(pose, i) <
                drive_constants.k_track_width_meters / 2 for i in pylon_points
        ]):
            bad_move = True

        # this resets the move if we are hitting a barrier
        # ToDo: stop the motion as well.  Perhaps in addition to moving back we should redo transform w/ no motor speed
        if bad_move:
            curr_x, curr_y = transform.translation().x, transform.translation(
            ).y
            # in 2021 library they added an inverse() to transforms so this could all be one line
            new_transform = geo.Transform2d(geo.Translation2d(-curr_x, curr_y),
                                            transform.rotation())
            pose = self.physics_controller.move_robot(new_transform)

        # Update encoders - need use the pose so it updates properly for coasting to a stop
        # ToDo: get the actual values, probably from wheelspeeds?
        # have to only work with deltas otherwise we can't reset the encoder from the real code
        # damn it, the SparkMax and the standard encoders have different calls

        if self.sparkmax:
            self.l_spark_position.set(
                self.l_spark_position.get() +
                (self.drivetrain.l_position - self.l_distance_old) * 0.3105)
            self.r_spark_position.set(
                self.r_spark_position.get() +
                (self.drivetrain.r_position - self.r_distance_old) * 0.3105)
            self.l_spark_velocity.set(
                0.31 * (self.drivetrain.l_position - self.l_distance_old) /
                tm_diff)
            self.r_spark_velocity.set(
                0.31 * (self.drivetrain.r_position - self.r_distance_old) /
                tm_diff)
        else:
            self.l_encoder.setDistance(
                self.l_encoder.getDistance() +
                (self.drivetrain.l_position - self.l_distance_old) * 0.3105)
            self.r_encoder.setDistance(
                self.r_encoder.getDistance() -
                (self.drivetrain.r_position - self.r_distance_old) *
                0.3105)  # negative going forward
            self.l_encoder.setRate(
                0.31 * (self.drivetrain.l_position - self.l_distance_old) /
                tm_diff)
            self.r_encoder.setRate(
                -0.31 * (self.drivetrain.r_position - self.r_distance_old) /
                tm_diff)  # needs to be negitive going fwd

        self.l_distance_old = self.drivetrain.l_position
        self.r_distance_old = self.drivetrain.r_position

        self.x, self.y = pose.translation().x, pose.translation().y
        # Update the navx gyro simulation
        # -> FRC gyros like NavX are positive clockwise, but the returned pose is positive counter-clockwise
        self.navx_yaw.set(-pose.rotation().degrees())

        # ---------------- HOOD ELEVATOR UPDATES ---------------------------
        # update 'position' (use tm_diff so the rate is constant) - this is for simulating an elevator, arm etc w/ limit switches
        self.hood_position += self.hood_motor.getSpeed(
        ) * tm_diff * 30 - 0.05  # inserting gravity!

        # update limit switches based on position
        if self.hood_position <= 30:
            switch1 = True
            switch2 = False
            if self.hood_position < 29.5:  # don't let gravity sag too much
                self.hood_position = 29.5
        elif self.hood_position > 50:
            switch1 = False
            switch2 = True

        else:
            switch1 = False
            switch2 = False

        # set values here - nice way to emulate the robot's state
        self.limit_low.setValue(switch1)
        self.limit_hi.setValue(switch2)
        self.hood_encoder.setDistance(round(self.hood_position, 2))

        self.counter += 1
        if self.counter % 5 == 0:
            SmartDashboard.putNumber('field_x', round(self.x, 2))
            SmartDashboard.putNumber('field_y', round(self.y, 2))
            SmartDashboard.putNumber('hood', round(self.hood_position, 1))

        if self.counter % 50 == 0:
            self.obstacles = SmartDashboard.getData(
                'obstacles').getSelected()  # read the obstacles from the dash
Example #9
0
def get_point_trajectory(velocity=k_max_speed_meters_per_second):
    start_pose = geo.Pose2d(0, 0, geo.Rotation2d(0))
    end_pose = geo.Pose2d(0, 1.76, geo.Rotation2d(0))
    midpoints = [
        geo.Translation2d(0.32, 0.01),
        geo.Translation2d(1.25, 0.47),
        geo.Translation2d(2.16, 1.71),
        geo.Translation2d(4.74, 1.91),
        geo.Translation2d(5.74, 1.06),
        geo.Translation2d(6.06, 0.31),
        geo.Translation2d(7.41, 0.21),
        geo.Translation2d(7.52, 1.50),
        geo.Translation2d(6.97, 1.82),
        geo.Translation2d(6.10, 1.48),
        geo.Translation2d(5.87, 0.84),
        geo.Translation2d(5.30, 0.17),
        geo.Translation2d(3.77, -0.05),
        geo.Translation2d(1.81, 0.33),
        geo.Translation2d(1.12, 1.36),
        geo.Translation2d(0.22, 1.69),
        geo.Translation2d(-0.15, 1.76)
    ]
    slalom_point_trajectory = wpimath.trajectory.TrajectoryGenerator.generateTrajectory(
        start_pose, midpoints, end_pose, make_config(velocity))
    return slalom_point_trajectory