Ejemplo n.º 1
0
class Robot(wpilib.TimedRobot):
    def robotInit(self):
        self.tankDrive = DifferentialDrive(
            wpilib.PWMVictorSPX(0), wpilib.PWMVictorSPX(1)
        )
        self.leftEncoder = wpilib.Encoder(0, 1)
        self.rightEncoder = wpilib.Encoder(2, 3)

        self.elevatorMotor = wpilib.PWMVictorSPX(2)
        self.elevatorPot = wpilib.AnalogPotentiometer(0)

        # Add a 'max speed' widget to a tab named 'Configuration', using a number slider
        # The widget will be placed in the second column and row and will be two columns wide
        self.maxSpeed = (
            Shuffleboard.getTab("Configuration")
            .add(title="Max Speed", value=1)
            .withWidget("Number Slider")
            .withPosition(1, 1)
            .withSize(2, 1)
            .getEntry()
        )

        # Add the tank drive and encoders to a 'Drivebase' tab
        driveBaseTab = Shuffleboard.getTab("Drivebase")
        driveBaseTab.add(title="Tank Drive", value=self.tankDrive)
        # Put both encoders in a list layout
        encoders = (
            driveBaseTab.getLayout(type="List Layout", title="Encoders")
            .withPosition(0, 0)
            .withSize(2, 2)
        )
        encoders.add(title="Left Encoder", value=self.leftEncoder)
        encoders.add(title="Right Encoder", value=self.rightEncoder)

        # Add the elevator motor and potentiometer to an 'Elevator' tab
        elevatorTab = Shuffleboard.getTab("Elevator")
        elevatorTab.add(title="Motor", value=self.elevatorMotor)
        elevatorTab.add(title="Potentiometer", value=self.elevatorPot)

    def autonomousInit(self):
        # Read the value of the 'max speed' widget from the dashboard
        self.tankDrive.setMaxOutput(self.maxSpeed.getDouble(1.0))
Ejemplo n.º 2
0
class Drivetrain(SubsystemBase):
    def __init__(self):

        super().__init__()

        # Create the motor controllers and their respective speed controllers.
        self.leftMotors = SpeedControllerGroup(
            PWMSparkMax(constants.kLeftMotor1Port),
            PWMSparkMax(constants.kLeftMotor2Port),
        )

        self.rightMotors = SpeedControllerGroup(
            PWMSparkMax(constants.kRightMotor1Port),
            PWMSparkMax(constants.kRightMotor2Port),
        )

        # Create the differential drivetrain object, allowing for easy motor control.
        self.drive = DifferentialDrive(self.leftMotors, self.rightMotors)

        # Create the encoder objects.
        self.leftEncoder = Encoder(
            constants.kLeftEncoderPorts[0],
            constants.kLeftEncoderPorts[1],
            constants.kLeftEncoderReversed,
        )

        self.rightEncoder = Encoder(
            constants.kRightEncoderPorts[0],
            constants.kRightEncoderPorts[1],
            constants.kRightEncoderReversed,
        )

        # Configure the encoder so it knows how many encoder units are in one rotation.
        self.leftEncoder.setDistancePerPulse(
            constants.kEncoderDistancePerPulse)
        self.rightEncoder.setDistancePerPulse(
            constants.kEncoderDistancePerPulse)

        # Create the gyro, a sensor which can indicate the heading of the robot relative
        # to a customizable position.
        self.gyro = ADXRS450_Gyro()

        # Create the an object for our odometry, which will utilize sensor data to
        # keep a record of our position on the field.
        self.odometry = DifferentialDriveOdometry(self.gyro.getRotation2d())

        # Reset the encoders upon the initilization of the robot.
        self.resetEncoders()

    def periodic(self):
        """
        Called periodically when it can be called. Updates the robot's
        odometry with sensor data.
        """
        self.odometry.update(
            self.gyro.getRotation2d(),
            self.leftEncoder.getDistance(),
            self.rightEncoder.getDistance(),
        )

    def getPose(self):
        """Returns the current position of the robot using it's odometry."""
        return self.odometry.getPose()

    def getWheelSpeeds(self):
        """Return an object which represents the wheel speeds of our drivetrain."""
        speeds = DifferentialDriveWheelSpeeds(self.leftEncoder.getRate(),
                                              self.rightEncoder.getRate())
        return speeds

    def resetOdometry(self, pose):
        """ Resets the robot's odometry to a given position."""
        self.resetEncoders()
        self.odometry.resetPosition(pose, self.gyro.getRotation2d())

    def arcadeDrive(self, fwd, rot):
        """Drive the robot with standard arcade controls."""
        self.drive.arcadeDrive(fwd, rot)

    def tankDriveVolts(self, leftVolts, rightVolts):
        """Control the robot's drivetrain with voltage inputs for each side."""
        # Set the voltage of the left side.
        self.leftMotors.setVoltage(leftVolts)

        # Set the voltage of the right side. It's
        # inverted with a negative sign because it's motors need to spin in the negative direction
        # to move forward.
        self.rightMotors.setVoltage(-rightVolts)

        # Resets the timer for this motor's MotorSafety
        self.drive.feed()

    def resetEncoders(self):
        """Resets the encoders of the drivetrain."""
        self.leftEncoder.reset()
        self.rightEncoder.reset()

    def getAverageEncoderDistance(self):
        """
        Take the sum of each encoder's traversed distance and divide it by two,
        since we have two encoder values, to find the average value of the two.
        """
        return (self.leftEncoder.getDistance() +
                self.rightEncoder.getDistance()) / 2

    def getLeftEncoder(self):
        """Returns the left encoder object."""
        return self.leftEncoder

    def getRightEncoder(self):
        """Returns the right encoder object."""
        return self.rightEncoder

    def setMaxOutput(self, maxOutput):
        """Set the max percent output of the drivetrain, allowing for slower control."""
        self.drive.setMaxOutput(maxOutput)

    def zeroHeading(self):
        """Zeroes the gyro's heading."""
        self.gyro.reset()

    def getHeading(self):
        """Return the current heading of the robot."""
        return self.gyro.getRotation2d().getDegrees()

    def getTurnRate(self):
        """Returns the turning rate of the robot using the gyro."""

        # The minus sign negates the value.
        return -self.gyro.getRate()
Ejemplo n.º 3
0
class DriveTrain(Subsystem):
    """
    The DriveTrain subsystem controls the robot's chassis and reads in
    information about its speed and position.
    """
    def __init__(self, robot):
        super().__init__("drivetrain")
        #Subsystem.__init__("drivetrain")
        self.robot = robot
        self.error_dict = {}
        # Add constants and helper variables
        self.twist_power_maximum = 0.5
        self.strafe_power_maximum = 0.5
        self.thrust_power_maximum = 0.5
        self.mecanum_power_limit = 1.0
        self.max_velocity = 500  # rpm for mecanum velocity

        self.current_thrust = 0
        self.current_twist = 0
        self.current_strafe = 0
        self.acceleration_limit = 0.05
        self.counter = 0

        # due to limitations in displaying digits in the Shuffleboard, we'll multiply these by 1000 and divide when updating the controllers
        self.PID_multiplier = 1000.
        self.PID_dict_pos = {
            'kP': 0.010,
            'kI': 5.0e-7,
            'kD': 0.40,
            'kIz': 0,
            'kFF': 0.002,
            'kMaxOutput': 0.99,
            'kMinOutput': -0.99
        }
        self.PID_dict_vel = {
            'kP': 2 * 0.00015,
            'kI': 1.5 * 8.0e-7,
            'kD': 0.00,
            'kIz': 0,
            'kFF': 0.00022,
            'kMaxOutput': 0.99,
            'kMinOutput': -0.99
        }
        self.encoder_offsets = [
            0, 0, 0, 0
        ]  # added because the encoders do not reset fast enough for autonomous

        # Smart Motion Coefficients - these don't seem to be writing for some reason... python is old?  just set with rev's program for now
        self.smartmotion_maxvel = 1000  # rpm
        self.smartmotion_maxacc = 500
        self.current_limit = 100
        self.ramp_rate = 0.0
        # tracking the robot across the field... easier with WCD
        self.x = 0
        self.y = 0
        self.previous_distance = 0
        self.is_limited = False
        self.deadband = 0.05

        # Configure drive motors
        #if True:  # or could be if self.robot.isReal():
        if self.robot.isReal():
            motor_type = rev.MotorType.kBrushless
            self.spark_neo_right_front = rev.CANSparkMax(1, motor_type)
            self.spark_neo_right_rear = rev.CANSparkMax(2, motor_type)
            self.spark_neo_left_front = rev.CANSparkMax(3, motor_type)
            self.spark_neo_left_rear = rev.CANSparkMax(4, motor_type)
            self.controllers = [
                self.spark_neo_left_front, self.spark_neo_left_rear,
                self.spark_neo_right_front, self.spark_neo_right_rear
            ]

            self.spark_PID_controller_right_front = self.spark_neo_right_front.getPIDController(
            )
            self.spark_PID_controller_right_rear = self.spark_neo_right_rear.getPIDController(
            )
            self.spark_PID_controller_left_front = self.spark_neo_left_front.getPIDController(
            )
            self.spark_PID_controller_left_rear = self.spark_neo_left_rear.getPIDController(
            )
            self.pid_controllers = [
                self.spark_PID_controller_left_front,
                self.spark_PID_controller_left_rear,
                self.spark_PID_controller_right_front,
                self.spark_PID_controller_right_rear
            ]
            # wpilib.Timer.delay(0.02)

            # swap encoders to get sign right
            # changing them up for mechanum vs WCD
            self.sparkneo_encoder_1 = rev.CANSparkMax.getEncoder(
                self.spark_neo_left_front)
            self.sparkneo_encoder_2 = rev.CANSparkMax.getEncoder(
                self.spark_neo_left_rear)
            self.sparkneo_encoder_3 = rev.CANSparkMax.getEncoder(
                self.spark_neo_right_front)
            self.sparkneo_encoder_4 = rev.CANSparkMax.getEncoder(
                self.spark_neo_right_rear)
            self.encoders = [
                self.sparkneo_encoder_1, self.sparkneo_encoder_2,
                self.sparkneo_encoder_3, self.sparkneo_encoder_4
            ]

            # Configure encoders and controllers
            # should be wheel_diameter * pi / gear_ratio - and for the old double reduction gear box
            # the gear ratio was 4.17:1.  With the shifter (low gear) I think it was a 12.26.
            # then new 2020 gearbox is 9.52
            gear_ratio = 9.52
            #gear_ratio = 12.75
            conversion_factor = 8.0 * 3.141 / gear_ratio
            for ix, encoder in enumerate(self.encoders):
                self.error_dict.update({
                    'conv_' + str(ix):
                    encoder.setPositionConversionFactor(conversion_factor)
                })

            # wpilib.Timer.delay(0.02)
            # TODO - figure out if I want to invert the motors or the encoders
            inverted = False  # needs this to be True for the toughbox
            self.spark_neo_left_front.setInverted(inverted)
            self.spark_neo_left_rear.setInverted(inverted)
            self.spark_neo_right_front.setInverted(inverted)
            self.spark_neo_right_rear.setInverted(inverted)

            self.configure_controllers()
            #self.display_PIDs()

        else:  # for simulation only, but the CANSpark is getting closer to behaving in sim
            # get a pretend drivetrain for the simulator
            self.spark_neo_left_front = wpilib.Talon(1)
            self.spark_neo_left_rear = wpilib.Talon(2)
            self.spark_neo_right_front = wpilib.Talon(3)
            self.spark_neo_right_rear = wpilib.Talon(4)

        # Not sure if speedcontrollergroups work with the single sparkmax in python - seems to complain
        # drive_type = 'mechanum'  # comp bot
        drive_type = 'wcd'  # practice bot, sim as of 1/16/2021

        if drive_type == 'wcd':
            # WCD
            print("Enabling WCD drive!")
            if robot.isReal():
                err_1 = self.spark_neo_left_rear.follow(
                    self.spark_neo_left_front)
                err_2 = self.spark_neo_right_rear.follow(
                    self.spark_neo_right_front)
                if err_1 != rev.CANError.kOk or err_2 != rev.CANError.kOk:
                    print(
                        f"Warning: drivetrain follower issue with neo2 returning {err_1} and neo4 returning {err_2}"
                    )
            self.speedgroup_left = SpeedControllerGroup(
                self.spark_neo_left_front)
            self.speedgroup_right = SpeedControllerGroup(
                self.spark_neo_right_front)
            self.differential_drive = DifferentialDrive(
                self.speedgroup_left, self.speedgroup_right)
            self.drive = self.differential_drive
            self.differential_drive.setMaxOutput(1.0)
        if drive_type == 'mechanum':
            # Mechanum
            # TODO: Reset followers in software
            print("Enabling mechanum drive!")
            self.speedgroup_lfront = SpeedControllerGroup(
                self.spark_neo_left_front)
            self.speedgroup_lrear = SpeedControllerGroup(
                self.spark_neo_left_rear)
            self.speedgroup_rfront = SpeedControllerGroup(
                self.spark_neo_right_front)
            self.speedgroup_rrear = SpeedControllerGroup(
                self.spark_neo_right_rear)
            self.mechanum_drive = MecanumDrive(self.speedgroup_lfront,
                                               self.speedgroup_lrear,
                                               self.speedgroup_rfront,
                                               self.speedgroup_rrear)
            # self.mechanum_drive = MecanumDrive(self.spark_neo_left_front, self.spark_neo_left_rear, self.spark_neo_right_front, self.spark_neo_right_rear)
            self.mechanum_drive.setMaxOutput(self.mecanum_power_limit)
            self.drive = self.mechanum_drive

        self.drive.setSafetyEnabled(True)
        self.drive.setExpiration(0.1)
        # self.differential_drive.setSensitivity(0.5)

    def initDefaultCommand(self):
        """ When other commands aren't using the drivetrain, allow arcade drive with the joystick. """
        self.setDefaultCommand(DriveByJoystick(self.robot))

    def spark_with_stick(self, thrust=0, strafe=0, z_rotation=0, gyroAngle=0):
        """Simplest way to drive with a joystick"""
        # self.differential_drive.arcadeDrive(x_speed, self.twist_sensitivity * z_rotation, False)
        if self.robot.isReal():
            self.drive.driveCartesian(
                xSpeed=thrust * self.thrust_power_maximum,
                ySpeed=strafe * self.strafe_power_maximum,
                zRotation=self.twist_power_maximum * z_rotation)
        else:
            self.arcade_drive(thrust * self.thrust_power_maximum,
                              self.twist_power_maximum * z_rotation)

    def mecanum_velocity_cartesian(self,
                                   thrust: float,
                                   strafe: float,
                                   z_rotation: float,
                                   gyroAngle: float = 0.0) -> None:
        """ CJH Trying to implement a velocity control loop on the mecanum vs. % output"""

        # Compensate for gyro angle
        input = Vector2d(strafe, thrust)
        input.rotate(gyroAngle)

        wheel_speeds = [
            # Front Left
            input.x + input.y + z_rotation,
            # Rear Left
            -input.x + input.y + z_rotation,
            # Front Right
            -input.x + input.y - z_rotation,
            # Rear Right
            input.x + input.y - z_rotation,
        ]

        #RobotDriveBase.normalize(wheelSpeeds)
        maxMagnitude = max(abs(x) for x in wheel_speeds)
        if maxMagnitude > 1.0:
            for i in range(len(wheel_speeds)):
                wheel_speeds[i] = wheel_speeds[i] / maxMagnitude
        #wheel_speeds = [speed * self.maxOutput for speed in wheelSpeeds]

        # put all this in the zip below
        #self.spark_neo_left_front.set(wheel_speeds[0])
        #self.spark_neo_left_rear.set(wheel_speeds[1])
        #self.spark_neo_right_front.set(wheel_speeds[2] * self.rightSideInvertMultiplier)
        #self.spark_neo_right_rear.set(wheel_speeds[3] * self.rightSideInvertMultiplier)

        multipliers = [1.0, 1.0, -1.0, -1.0]
        debug_speed = []
        if math.sqrt(input.x**2 + input.y**2 + z_rotation**2) < self.deadband:
            self.mechanum_drive.driveCartesian(0, 0, 0)
        else:
            for speed, multiplier, controller in zip(wheel_speeds, multipliers,
                                                     self.pid_controllers):
                controller.setReference(multiplier * speed * self.max_velocity,
                                        rev.ControlType.kVelocity, 1)
                debug_speed.append(multiplier * speed * self.max_velocity)
        if self.counter % 20 == 0:
            pass
            # print(f"Wheel speeds set to {wheel_speeds} from thrust {thrust:2.4f} strafe {strafe:2.4f} rot {z_rotation:2.4f}")
        self.drive.feed()

    def stop(self):
        self.differential_drive.arcadeDrive(0, 0)

    def smooth_drive(self, thrust, strafe, twist):
        """A less jittery way to drive with a joystick
        TODO: See if this can be implemented in hardware - seems like the acceleration limit can be set there
        TODO: Should be ok to slow down quickly, but not speed up - check this code
        TODO: Set a smartdash to see if we are in software limit mode - like with a boolean
        """
        deadzone = 0.05
        self.is_limited = False
        # thrust section
        if math.fabs(thrust) < deadzone:
            self.current_thrust = 0
        else:
            if math.fabs(thrust -
                         self.current_thrust) < self.acceleration_limit:
                self.current_thrust = thrust
            else:
                if thrust - self.current_thrust > 0:
                    # accelerating forward
                    self.current_thrust = self.current_thrust + self.acceleration_limit
                    self.is_limited = True
                elif (thrust - self.current_thrust) < 0 and thrust > 0:
                    # ok to slow down quickly, although really the deadzone should catch this
                    self.current_thrust = thrust
                elif (thrust - self.current_thrust) > 0 and thrust < 0:
                    # ok to slow down quickly, although really the deadzone should catch this
                    self.current_thrust = thrust
                else:
                    # accelerating backward
                    self.current_thrust = self.current_thrust - self.acceleration_limit
                    self.is_limited = True
        # strafe section
        if math.fabs(strafe) < deadzone:
            self.current_strafe = 0
        else:
            if math.fabs(strafe -
                         self.current_strafe) < self.acceleration_limit:
                self.current_strafe = strafe
            else:
                if strafe - self.current_strafe > 0:
                    self.current_strafe = self.current_strafe + self.acceleration_limit
                else:
                    self.current_strafe = self.current_strafe - self.acceleration_limit
        # twist section
        if math.fabs(twist) < deadzone:
            self.current_twist = 0
        else:
            if math.fabs(twist - self.current_twist) < self.acceleration_limit:
                self.current_twist = twist
            else:
                if twist - self.current_twist > 0:
                    self.current_twist = self.current_twist + self.acceleration_limit
                else:
                    self.current_twist = self.current_twist - self.acceleration_limit
        # self.differential_drive.arcadeDrive(self.current_thrust, self.current_twist, True)
        # TODO - fix this for mechanum x and y
        self.mechanum_drive.driveCartesian(
            xSpeed=self.thrust_power_maximum * self.current_thrust,
            ySpeed=self.strafe_power_maximum * self.current_strafe,
            zRotation=self.twist_power_maximum * self.current_twist)

    def tank_drive(self, left, right):
        """This is thrown in for simulation"""
        self.differential_drive.tankDrive(left, right)

    def arcade_drive(self, speed, rotation):
        """This is thrown in for simulation or WCD"""
        self.differential_drive.arcadeDrive(speed, rotation, False)

    def get_position(self):
        """:returns: The encoder position of one of the Neos"""
        return self.sparkneo_encoder_1.getPosition()

    def get_positions(self):
        """:returns: The encoder position of both of the Neos"""
        return self.sparkneo_encoder_1.getPosition(
        ), self.sparkneo_encoder_3.getPosition()

    def set_velocity(self, velocity):
        multipliers = [1.0, 1.0, -1.0, -1.0]
        for multiplier, controller in zip(multipliers, self.pid_controllers):
            controller.setReference(multiplier * velocity,
                                    rev.ControlType.kVelocity, 1)

    def goToSetPoint(self, set_point, reset=True):
        self.reset()
        multipliers = [1.0, 1.0, -1.0, -1.0]
        for multiplier, controller in zip(multipliers, self.pid_controllers):
            # controller.setReference(multiplier * set_point, rev.ControlType.kPosition)
            controller.setReference(multiplier * set_point,
                                    rev.ControlType.kSmartMotion,
                                    pidSlot=1)

    def reset(self, hard=True):
        # There is an issue with the lag in encoder resets for autonomous.
        # TODO: Need to see if it is a CAN lag issue
        if hard:
            self.encoder_offsets = [0, 0, 0, 0]
            if self.robot.isReal():
                for ix, encoder in enumerate(self.encoders):
                    can_error = encoder.setPosition(0)
                    self.error_dict.update({'ResetPos_' + str(ix): can_error})
                    if can_error != rev.CANError.kOk:
                        print(
                            f"Warning: drivetrain reset issue with {encoder} returning {can_error}"
                        )
        else:
            for ix, encoder in enumerate(self.encoders):
                self.encoder_offsets[ix] = encoder.getPosition()
        self.x = 0
        self.y = 0
        # wpilib.Timer.delay(0.02)

    def configure_controllers(self, pid_only=False):
        """Set the PIDs, etc for the controllers, slot 0 is position and slot 1 is velocity"""
        if not pid_only:
            for i, controller in enumerate(self.controllers):
                # error_dict.append(controller.restoreFactoryDefaults())
                self.error_dict.update({
                    'OpenRamp_' + str(i):
                    controller.setOpenLoopRampRate(self.ramp_rate)
                })
                self.error_dict.update({
                    'ClosedRamp_' + str(i):
                    controller.setClosedLoopRampRate(self.ramp_rate)
                })
                self.error_dict.update({
                    'Idle_' + str(i):
                    controller.setIdleMode(rev.IdleMode.kBrake)
                })
                self.error_dict.update({
                    'CurLimit_' + str(i):
                    controller.setSmartCurrentLimit(self.current_limit)
                })
                self.error_dict.update({
                    'VoltComp_' + str(i):
                    controller.enableVoltageCompensation(12)
                })
                # defaults are 10, 20, 20 on the frame rates - trying to cut down a bit on CAN bandwidth
                #self.error_dict.update({'PeriodicStatus0_'+str(i):controller.setPeriodicFramePeriod(rev.CANSparkMax.PeriodicFrame.kStatus0, 20)})
                #self.error_dict.update({'PeriodicStatus1_'+str(i):controller.setPeriodicFramePeriod(rev.CANSparkMax.PeriodicFrame.kStatus1, 40)})
                #self.error_dict.update({'PeriodicStatus2_'+str(i):controller.setPeriodicFramePeriod(rev.CANSparkMax.PeriodicFrame.kStatus2, 20)})

        for i, controller in enumerate(self.pid_controllers):
            self.error_dict.update(
                {'kP0_' + str(i): controller.setP(self.PID_dict_pos['kP'], 0)})
            self.error_dict.update(
                {'kP1_' + str(i): controller.setP(self.PID_dict_vel['kP'], 1)})
            self.error_dict.update(
                {'kI0_' + str(i): controller.setI(self.PID_dict_pos['kI'], 0)})
            self.error_dict.update(
                {'kI1_' + str(i): controller.setI(self.PID_dict_vel['kI'], 1)})
            self.error_dict.update(
                {'kD0_' + str(i): controller.setD(self.PID_dict_pos['kD'], 0)})
            self.error_dict.update(
                {'kD_1' + str(i): controller.setD(self.PID_dict_vel['kD'], 1)})
            self.error_dict.update({
                'kFF_0' + str(i):
                controller.setFF(self.PID_dict_pos['kFF'], 0)
            })
            self.error_dict.update({
                'kFF_1' + str(i):
                controller.setFF(self.PID_dict_vel['kFF'], 1)
            })
            self.error_dict.update({
                'kIZ_0' + str(i):
                controller.setIZone(self.PID_dict_pos['kIz'], 0)
            })
            self.error_dict.update({
                'kIZ_1' + str(i):
                controller.setIZone(self.PID_dict_vel['kIz'], 1)
            })
            self.error_dict.update({
                'MinMax0_' + str(i):
                controller.setOutputRange(self.PID_dict_pos['kMinOutput'],
                                          self.PID_dict_pos['kMaxOutput'], 0)
            })
            self.error_dict.update({
                'MinMax0_' + str(i):
                controller.setOutputRange(self.PID_dict_vel['kMinOutput'],
                                          self.PID_dict_vel['kMaxOutput'], 1)
            })
            self.error_dict.update({
                'Accel0_' + str(i):
                controller.setSmartMotionMaxVelocity(self.smartmotion_maxvel,
                                                     0)
            })
            self.error_dict.update({
                'Accel0_' + str(i):
                controller.setSmartMotionMaxVelocity(self.smartmotion_maxvel,
                                                     1)
            })
            self.error_dict.update({
                'Vel0_' + str(i):
                controller.setSmartMotionMaxAccel(self.smartmotion_maxacc, 0)
            })
            self.error_dict.update({
                'Vel1_' + str(i):
                controller.setSmartMotionMaxAccel(self.smartmotion_maxacc, 1)
            })

        # if 1 in error_dict or 2 in error_dict:
        #    print(f'Issue in configuring controllers: {error_dict}')
        # else:
        #print(f'Results of configuring controllers: {self.error_dict}')
        if len(set(self.error_dict)) > 1:
            print('\n*Sparkmax setting*     *Response*')
            for key in sorted(self.error_dict.keys()):
                print(f'     {key:15} \t {self.error_dict[key]}')
        else:
            print(f'\n *All SparkMax report {list(set(self.error_dict))[0]}')
        burn_flash = False
        if burn_flash:
            for i, controller in enumerate(self.controllers):
                can_error = controller.burnFlash()
                print(f'Burn flash on controller {i}: {can_error}')

    def change_PIDs(self, factor=1, dict_0=None, dict_1=None):
        """Pass a value to the and update the PIDs, probably use 1.5 and 0.67 to see how they change
        can also pass it a dictionary {'kP': 0.06, 'kI': 0.0, 'kD': 0, 'kIz': 0, 'kFF': 0} to set
        slot 0 (position) or slot 1 (velocity) """
        keys = ['kP', 'kI', 'kD', 'kIz', 'kFF']
        for key in keys:
            if dict_0 == None:
                self.PID_dict_pos[key] *= factor
            else:
                self.PID_dict_pos[key] = dict_0[key] / self.PID_multiplier
                SmartDashboard.putString(
                    "alert",
                    f"Pos: kP: {self.PID_dict_pos['kP']}  kI: {self.PID_dict_pos['kI']}  kD: {self.PID_dict_pos['kD']}  kIz: {self.PID_dict_pos['kIz']}  kFF: {self.PID_dict_pos['kFF']}"
                )
            if dict_1 == None:
                self.PID_dict_vel[key] *= factor
            else:
                self.PID_dict_vel[key] = dict_1[key] / self.PID_multiplier
                SmartDashboard.putString(
                    "alert",
                    f"Vel: kP: {self.PID_dict_vel['kP']}  kI: {self.PID_dict_vel['kI']}  kD: {self.PID_dict_vel['kD']}  kIz: {self.PID_dict_vel['kIz']} kFF: {self.PID_dict_vel['kFF']}"
                )

        self.configure_controllers(pid_only=True)
        self.display_PIDs()

    def display_PIDs(self):
        keys = ['kP', 'kI', 'kD', 'kIz', 'kFF']
        for key in keys:
            SmartDashboard.putNumber(
                key + '_0', self.PID_multiplier * self.PID_dict_pos[key])
            SmartDashboard.putNumber(
                key + '_1', self.PID_multiplier * self.PID_dict_vel[key])

    def print_PIDs(self):
        print(
            f"Pos: kP: {self.PID_dict_pos['kP']}  kI: {self.PID_dict_pos['kI']}  kD: {self.PID_dict_pos['kD']}  kIz: {self.PID_dict_pos['kIz']}  kFF: {self.PID_dict_pos['kFF']}"
        )
        print(
            f"Vel: kP: {self.PID_dict_vel['kP']}  kI: {self.PID_dict_vel['kI']}  kD: {self.PID_dict_vel['kD']}  kIz: {self.PID_dict_vel['kIz']} kFF: {self.PID_dict_vel['kFF']}"
        )

    def square(self, value):
        """Return the signed square value of a number - for drive sensitivity
        :return: the squared value of the input retaining the sign
        """
        # sq = lambda x: x**2 if (x > 0) else -1.0 * x**2

        sign = 1.0  # could do this all with a copysign but better to be explicit
        if value < 0:
            sign = -1.0
        return sign * value**2

    def log(self):
        self.counter += 1
        if self.counter % 10 == 0:
            # start keeping track of where the robot is with an x and y position (only good for WCD)
            try:
                distance = 0.5 * (self.sparkneo_encoder_1.getPosition() -
                                  self.sparkneo_encoder_3.getPosition())
            except:
                print(
                    f"Problem: encoder position returns {self.sparkneo_encoder_1.getPosition()}"
                )
                distance = 0
            self.x = self.x + (distance - self.previous_distance) * math.sin(
                math.radians(self.robot.navigation.get_angle()))
            self.y = self.y + (distance - self.previous_distance) * math.cos(
                math.radians(self.robot.navigation.get_angle()))
            self.previous_distance = distance
            # send values to the dash to make sure encoders are working well
            #SmartDashboard.putNumber("Robot X", round(self.x, 2))
            #SmartDashboard.putNumber("Robot Y", round(self.y, 2))
            for ix, encoder in enumerate(self.encoders):
                SmartDashboard.putNumber(
                    f"Position Enc{str(int(1+ix))}",
                    round(encoder.getPosition() - self.encoder_offsets[ix], 2))
                SmartDashboard.putNumber(f"Velocity Enc{str(int(1+ix))}",
                                         round(encoder.getVelocity(), 2))
            SmartDashboard.putNumber(
                "Current M1",
                round(self.spark_neo_left_front.getOutputCurrent(), 2))
            SmartDashboard.putNumber(
                "Current M3",
                round(self.spark_neo_right_front.getOutputCurrent(), 2))
            SmartDashboard.putBoolean('AccLimit', self.is_limited)

        if self.counter % 1000 == 0:
            # self.display_PIDs()
            SmartDashboard.putString(
                "alert",
                f"Position: ({round(self.x, 1)},{round(self.y, 1)})  Time: {round(Timer.getFPGATimestamp() - self.robot.enabled_time, 1)}"
            )
Ejemplo n.º 4
0
class DriveTrain(Subsystem):
    """
    The DriveTrain subsystem controls the robot's chassis and reads in
    information about its speed and position.
    """

    def __init__(self, robot):
        super().__init__()
        self.robot = robot

        # Add constants and helper variables
        self.twist_sensitivity = 0.5
        self.current_thrust = 0
        self.current_twist = 0
        self.current_strafe = 0
        self.acceleration_limit = 0.05
        self.counter = 0
        self.mecanum_power_limit = 0.6
        # due to limitations in displaying digits in the Shuffleboard, we'll multiply these by 1000 and divide when updating the controllers
        self.PID_multiplier = 1000.
        self.PID_dict_pos = {'kP': 0.010, 'kI': 5.0e-7, 'kD': 0.40, 'kIz': 0, 'kFF': 0.002, 'kMaxOutput': 0.99,
                             'kMinOutput': -0.99}
        self.PID_dict_vel = {'kP': 0.00015, 'kI': 8.0e-7, 'kD': 0.00, 'kIz': 0, 'kFF': 0.00022, 'kMaxOutput': 0.99,
                             'kMinOutput': -0.99}
        # Smart Motion Coefficients - these don't seem to be writing for some reason... python is old?  just set with rev's program for now
        self.maxvel = 500 # rpm
        self.maxacc = 500
        self.current_limit = 40
        self.x = 0
        self.y = 0
        self.previous_distance = 0
        self.is_limited = False
        self.deadband = 0.05

        # Configure drive motors
        try:
            if robot.isReal():
                self.spark_neo_right_front = rev.CANSparkMax(1, rev.MotorType.kBrushless)
                self.spark_neo_right_rear = rev.CANSparkMax(2, rev.MotorType.kBrushless)
                self.spark_neo_left_front = rev.CANSparkMax(3, rev.MotorType.kBrushless)
                self.spark_neo_left_rear = rev.CANSparkMax(4, rev.MotorType.kBrushless)
                self.spark_PID_controller_right_front = self.spark_neo_right_front.getPIDController()
                self.spark_PID_controller_right_rear = self.spark_neo_right_rear.getPIDController()
                self.spark_PID_controller_left_front = self.spark_neo_left_front.getPIDController()
                self.spark_PID_controller_left_rear = self.spark_neo_left_rear.getPIDController()
                wpilib.Timer.delay(0.02)

                # swap encoders to get sign right
                # changing them up for mechanum vs WCD
                self.sparkneo_encoder_1 = rev.CANSparkMax.getEncoder(self.spark_neo_left_front)
                self.sparkneo_encoder_2 = rev.CANSparkMax.getEncoder(self.spark_neo_left_rear)
                self.sparkneo_encoder_3 = rev.CANSparkMax.getEncoder(self.spark_neo_right_front)
                self.sparkneo_encoder_4 = rev.CANSparkMax.getEncoder(self.spark_neo_right_rear)
                wpilib.Timer.delay(0.02)

                # Configure encoders and controllers
                # should be wheel_diameter * pi / gear_ratio - and for the old double reduction gear box
                # the gear ratio was either  5.67:1 or 4.17:1.  With the shifter (low gear) I think it was a 12.26.
                conversion_factor = 8.0 * 3.141 / 4.17
                err_1 = self.sparkneo_encoder_1.setPositionConversionFactor(conversion_factor)
                err_2 = self.sparkneo_encoder_2.setPositionConversionFactor(conversion_factor)
                err_3 = self.sparkneo_encoder_3.setPositionConversionFactor(conversion_factor)
                err_4 = self.sparkneo_encoder_4.setPositionConversionFactor(conversion_factor)

                wpilib.Timer.delay(0.02)
                # TODO - figure out if I want to invert the motors or the encoders
                self.spark_neo_left_front.setInverted(False)
                self.spark_neo_left_rear.setInverted(False)
                self.spark_neo_right_front.setInverted(False)
                self.spark_neo_right_rear.setInverted(False)

                if err_1 != rev.CANError.kOK or err_2 != rev.CANError.kOK:
                    print(f"Warning: drivetrain encoder issue with neo1 returning {err_1} and neo3 returning {err_2}")
                self.configure_controllers()
                self.display_PIDs()

            else:
                # get a pretend drivetrain for the simulator
                self.spark_neo_left_front = wpilib.Talon(1)
                self.spark_neo_left_rear = wpilib.Talon(2)
                self.spark_neo_right_front = wpilib.Talon(3)
                self.spark_neo_right_rear = wpilib.Talon(4)

            # Not sure if speedcontrollergroups work with the single sparkmax in python - seems to complain
            drive_type = 'mechanum'
            if drive_type == 'wcd':
                # WCD
                err_1 = self.spark_neo_left_rear.follow(self.spark_neo_left_front)
                err_2 = self.spark_neo_right_rear.follow(self.spark_neo_right_front)
                if err_1 != rev.CANError.kOK or err_2 != rev.CANError.kOK:
                    print(f"Warning: drivetrain follower issue with neo2 returning {err_1} and neo4 returning {err_2}")
                self.speedgroup_left = SpeedControllerGroup(self.spark_neo_left_front)
                self.speedgroup_right = SpeedControllerGroup(self.spark_neo_right_front)
                self.differential_drive = DifferentialDrive(self.speedgroup_left, self.speedgroup_right)
                self.drive = self.differential_drive
                self.differential_drive.setMaxOutput(1.0)
            if drive_type == 'mechanum':
                # Mechanum
                #TODO: Reset followers in software
                self.speedgroup_lfront = SpeedControllerGroup(self.spark_neo_left_front)
                self.speedgroup_lrear = SpeedControllerGroup(self.spark_neo_left_rear)
                self.speedgroup_rfront = SpeedControllerGroup(self.spark_neo_right_front)
                self.speedgroup_rrear = SpeedControllerGroup(self.spark_neo_right_rear)
                self.mechanum_drive = MecanumDrive(self.speedgroup_lfront, self.speedgroup_lrear, self.speedgroup_rfront, self.speedgroup_rrear)
                #self.mechanum_drive = MecanumDrive(self.spark_neo_left_front, self.spark_neo_left_rear, self.spark_neo_right_front, self.spark_neo_right_rear)
                self.mechanum_drive.setMaxOutput(self.mecanum_power_limit)
                self.drive = self.mechanum_drive

            self.drive.setSafetyEnabled(True)
            self.drive.setExpiration(0.1)
            # self.differential_drive.setSensitivity(0.5)

            # wpilib.LiveWindow.addActuator("DriveTrain", "spark_neo_l1", self.spark_neo_l1)
            # wpilib.LiveWindow.addActuator("DriveTrain", "spark_neo_r3", self.spark_neo_r3)
            # wpilib.LiveWindow.addActuator("DriveTrain", "spark_neo_l2", self.spark_neo_l2)
            # wpilib.LiveWindow.addActuator("DriveTrain", "spark_neo_r4", self.spark_neo_r4)

        except rev.CANError:
            print('Buncha CAN errors)')


    def initDefaultCommand(self):
        """
        When other commands aren't using the drivetrain, allow arcade drive with the joystick.
        """
        self.setDefaultCommand(DriveByJoystick(self.robot))

    def spark_with_stick(self, thrust=0, strafe=0, z_rotation=0, gyroAngle=0):
        '''Simplest way to drive with a joystick'''
        #self.differential_drive.arcadeDrive(x_speed, self.twist_sensitivity * z_rotation, False)
        self.mechanum_drive.driveCartesian(xSpeed=thrust, ySpeed=strafe, zRotation=self.twist_sensitivity*z_rotation)

    def stop(self):
        #self.differential_drive.arcadeDrive(0, 0)
        self.mechanum_drive.driveCartesian(0, 0, 0)

    def smooth_drive(self, thrust, strafe, twist):
        '''A less jittery way to drive with a joystick
        TODO: See if this can be implemented in hardware - seems like the acceleration limit can be set there
        TODO: Should be ok to slow down quickly, but not speed up - check this code
        TODO: Set a smartdash to see if we are in software limit mode - like with a boolean
        '''
        deadzone = 0.05
        self.is_limited = False
        # thrust section
        if math.fabs(thrust) < deadzone:
            self.current_thrust = 0
        else:
            if math.fabs(thrust - self.current_thrust) < self.acceleration_limit:
                self.current_thrust = thrust
            else:
                if thrust - self.current_thrust > 0:
                    # accelerating forward
                    self.current_thrust = self.current_thrust + self.acceleration_limit
                    self.is_limited = True
                elif (thrust - self.current_thrust) < 0 and thrust > 0:
                    # ok to slow down quickly, although really the deadzone should catch this
                    self.current_thrust = thrust
                elif (thrust - self.current_thrust) > 0 and thrust < 0:
                    # ok to slow down quickly, although really the deadzone should catch this
                    self.current_thrust = thrust
                else:
                    # accelerating backward
                    self.current_thrust = self.current_thrust - self.acceleration_limit
                    self.is_limited = True
        #strafe section
        if math.fabs(strafe) < deadzone:
            self.current_strafe = 0
        else:
            if math.fabs(strafe - self.current_strafe) < self.acceleration_limit:
                self.current_strafe = strafe
            else:
                if strafe - self.current_strafe > 0:
                    self.current_strafe = self.current_strafe + self.acceleration_limit
                else:
                    self.current_strafe = self.current_strafe - self.acceleration_limit
        # twist section
        if math.fabs(twist) < deadzone:
            self.current_twist = 0
        else:
            if math.fabs(twist - self.current_twist) < self.acceleration_limit:
                self.current_twist = twist
            else:
                if twist - self.current_twist > 0:
                    self.current_twist = self.current_twist + self.acceleration_limit
                else:
                    self.current_twist = self.current_twist - self.acceleration_limit
        #self.differential_drive.arcadeDrive(self.current_thrust, self.current_twist, True)
        # TODO - fix this for mechanum x and y
        self.mechanum_drive.driveCartesian(xSpeed=self.current_thrust, ySpeed=self.current_strafe, zRotation=self.current_twist)

    def tank_drive(self, left, right):
        '''Not sure why we would ever need this, but it's here if we do'''
        pass
        #self.differential_drive.tankDrive(left, right)

    def get_position(self):
        ''':returns: The encoder position of one of the Neos'''
        return self.sparkneo_encoder_1.getPosition()

    def set_velocity(self, velocity):
        controllers = [self.spark_PID_controller_left_front, self.spark_PID_controller_left_rear ,
                       self.spark_PID_controller_right_front, self.spark_PID_controller_right_rear]
        multipliers = [1.0, 1.0, -1.0, -1.0]
        for multiplier, controller in zip(multipliers, controllers):
            controller.setReference(multiplier * velocity, rev.ControlType.kVelocity, 1)

    def goToSetPoint(self, set_point):
        self.reset()
        controllers = [self.spark_PID_controller_left_front, self.spark_PID_controller_left_rear ,
                       self.spark_PID_controller_right_front, self.spark_PID_controller_right_rear]
        multipliers = [1.0, 1.0, -1.0, -1.0]
        for multiplier, controller in zip(multipliers, controllers):
            # controller.setReference(multiplier * set_point, rev.ControlType.kPosition)
            controller.setReference(multiplier * set_point, rev.ControlType.kSmartMotion, pidSlot=1)

    def reset(self):
        if self.robot.isReal():
            err_1 = self.sparkneo_encoder_1.setPosition(0)
            err_2 = self.sparkneo_encoder_2.setPosition(0)
            err_3 = self.sparkneo_encoder_3.setPosition(0)
            err_4 = self.sparkneo_encoder_4.setPosition(0)
            if err_1 != rev.CANError.kOK or err_2 != rev.CANError.kOK or err_3 != rev.CANError.kOK or err_4 != rev.CANError.kOK:
                print(f"Warning: drivetrain reset issue with neo1 returning {err_1} and neo3 returning {err_2}")
        self.x = 0
        self.y = 0
        wpilib.Timer.delay(0.02)

    def configure_controllers(self, pid_only=False):
        '''Set the PIDs, etc for the controllers, slot 0 is position and slot 1 is velocity'''
        error_list = []
        if not pid_only:
            controllers = [self.spark_neo_left_front, self.spark_neo_left_rear, self.spark_neo_right_front, self.spark_neo_right_rear]
            for controller in controllers:
                #error_list.append(controller.restoreFactoryDefaults())
                #Timer.delay(0.01)
                #looks like they orphaned the setIdleMode - it doesn't work.  Try ConfigParameter
                #error_list.append(controller.setIdleMode(rev.IdleMode.kBrake))
                controller.setParameter(rev.ConfigParameter.kIdleMode, rev.IdleMode.kBrake)
                error_list.append(controller.setSmartCurrentLimit(self.current_limit))
                controller.setParameter(rev.ConfigParameter.kSmartMotionMaxAccel_0, self.maxacc)
                controller.setParameter(rev.ConfigParameter.kSmartMotionMaxAccel_1, self.maxacc)
                controller.setParameter(rev.ConfigParameter.kSmartMotionMaxVelocity_0, self.maxvel)
                controller.setParameter(rev.ConfigParameter.kSmartMotionMaxVelocity_1, self.maxvel)
                Timer.delay(0.01)
                #controller.burnFlash()

        controllers = [self.spark_neo_left_front, self.spark_neo_left_rear, self.spark_neo_right_front, self.spark_neo_right_rear]
        for controller in controllers:
            error_list.append(controller.setParameter(rev.ConfigParameter.kP_0, self.PID_dict_pos['kP']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kP_1, self.PID_dict_vel['kP']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kI_0, self.PID_dict_pos['kI']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kI_1, self.PID_dict_vel['kI']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kD_0, self.PID_dict_pos['kD']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kD_1, self.PID_dict_vel['kD']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kIZone_0, self.PID_dict_pos['kIz']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kIZone_1, self.PID_dict_vel['kIz']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kF_0, self.PID_dict_pos['kFF']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kF_1, self.PID_dict_vel['kFF']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kOutputMax_0, self.PID_dict_pos['kMaxOutput']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kOutputMax_1, self.PID_dict_vel['kMaxOutput']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kOutputMin_0, self.PID_dict_pos['kMinOutput']))
            error_list.append(controller.setParameter(rev.ConfigParameter.kOutputMin_1, self.PID_dict_vel['kMinOutput']))
            #controller.burnFlash()
            Timer.delay(0.02)

        # if 1 in error_list or 2 in error_list:
        #    print(f'Issue in configuring controllers: {error_list}')
        # else:
        print(f'Results of configuring controllers: {error_list}')

    def change_PIDs(self, factor=1, dict_0=None, dict_1=None):
        '''Pass a value to the and update the PIDs, probably use 1.5 and 0.67 to see how they change
        can also pass it a dictionary {'kP': 0.06, 'kI': 0.0, 'kD': 0, 'kIz': 0, 'kFF': 0} to set
        slot 0 (position) or slot 1 (velocity) '''
        keys = ['kP', 'kI', 'kD', 'kIz', 'kFF']
        for key in keys:
            if dict_0 == None:
                self.PID_dict_pos[key] *= factor
            else:
                self.PID_dict_pos[key] = dict_0[key] / self.PID_multiplier
                SmartDashboard.putString("alert",
                                         f"Pos: kP: {self.PID_dict_pos['kP']}  kI: {self.PID_dict_pos['kI']}  kD: {self.PID_dict_pos['kD']}  kIz: {self.PID_dict_pos['kIz']}  kFF: {self.PID_dict_pos['kFF']}")
            if dict_1 == None:
                self.PID_dict_vel[key] *= factor
            else:
                self.PID_dict_vel[key] = dict_1[key] / self.PID_multiplier
                SmartDashboard.putString("alert",
                                         f"Vel: kP: {self.PID_dict_vel['kP']}  kI: {self.PID_dict_vel['kI']}  kD: {self.PID_dict_vel['kD']}  kIz: {self.PID_dict_vel['kIz']} kFF: {self.PID_dict_vel['kFF']}")

        self.configure_controllers(pid_only=True)
        self.display_PIDs()

    def display_PIDs(self):
        keys = ['kP', 'kI', 'kD', 'kIz', 'kFF']
        for key in keys:
            SmartDashboard.putNumber(key + '_0', self.PID_multiplier * self.PID_dict_pos[key])
            SmartDashboard.putNumber(key + '_1', self.PID_multiplier * self.PID_dict_vel[key])

    def print_PIDs(self):
        print(f"Pos: kP: {self.PID_dict_pos['kP']}  kI: {self.PID_dict_pos['kI']}  kD: {self.PID_dict_pos['kD']}  kIz: {self.PID_dict_pos['kIz']}  kFF: {self.PID_dict_pos['kFF']}")
        print(f"Vel: kP: {self.PID_dict_vel['kP']}  kI: {self.PID_dict_vel['kI']}  kD: {self.PID_dict_vel['kD']}  kIz: {self.PID_dict_vel['kIz']} kFF: {self.PID_dict_vel['kFF']}")

    def log(self):
        self.counter += 1
        if self.counter % 10 == 0:
            # start keeping track of where the robot is with an x and y position
            try:
                distance = 0.5 * (self.sparkneo_encoder_1.getPosition() - self.sparkneo_encoder_3.getPosition())
            except:
                print(f"Problem: encoder position returns {self.sparkneo_encoder_1.getPosition()}")
                distance = 0
            self.x = self.x + (distance - self.previous_distance) * math.sin(
                math.radians(self.robot.navigation.get_angle()))
            self.y = self.y + (distance - self.previous_distance) * math.cos(
                math.radians(self.robot.navigation.get_angle()))
            self.previous_distance = distance
            # send values to the dash to make sure encoders are working well
            SmartDashboard.putNumber("Robot X", round(self.x, 2))
            SmartDashboard.putNumber("Robot Y", round(self.y, 2))
            SmartDashboard.putNumber("Position Enc1", round(self.sparkneo_encoder_1.getPosition(), 2))
            SmartDashboard.putNumber("Position Enc2", round(self.sparkneo_encoder_2.getPosition(), 2))
            SmartDashboard.putNumber("Position Enc3", round(self.sparkneo_encoder_3.getPosition(), 2))
            SmartDashboard.putNumber("Position Enc4", round(self.sparkneo_encoder_4.getPosition(), 2))
            SmartDashboard.putNumber("Velocity Enc1", round(self.sparkneo_encoder_1.getVelocity(), 2))
            SmartDashboard.putNumber("Velocity Enc2", round(self.sparkneo_encoder_2.getVelocity(), 2))
            SmartDashboard.putNumber("Velocity Enc3", round(self.sparkneo_encoder_3.getVelocity(), 2))
            SmartDashboard.putNumber("Velocity Enc4", round(self.sparkneo_encoder_4.getVelocity(), 2))
            #SmartDashboard.putNumber("Power M1", round(self.spark_neo_left_front.getAppliedOutput(), 2))
            #SmartDashboard.putNumber("Power M3", round(self.spark_neo_right_front.getAppliedOutput(), 2))
            SmartDashboard.putNumber("Current M1", round(self.spark_neo_left_front.getOutputCurrent(), 2))
            SmartDashboard.putNumber("Current M3", round(self.spark_neo_right_front.getOutputCurrent(), 2))
            SmartDashboard.putBoolean('AccLimit', self.is_limited)


        if self.counter % 1000 == 0:
            self.display_PIDs()
            SmartDashboard.putString("alert",
                                     f"Position: ({round(self.x, 1)},{round(self.y, 1)})  Time: {round(Timer.getFPGATimestamp() - self.robot.enabled_time, 1)}")
            SmartDashboard.putString("Controller1 Idle", str(self.spark_neo_left_front.getIdleMode()))
            SmartDashboard.putNumber("Enc1 Conversion", self.sparkneo_encoder_1.getPositionConversionFactor())