[Progress] GoPiGo3 API Under The Microscope - GoPiGo3 World Speed Record

While the GoPiGo3 API is open-source, available for us to understand to the level of our curiosity, the code that runs inside the “red board” has always required us to use black box testing for any insight.

The Raspberry Pi uses the GoPiGo3 API to send commands across an SPI bus to the red board, and interpret any returned result data. The GET_MOTOR_STATUS_LEFT, and GET_MOTOR_STATUS_RIGHT commands via the gopigo3.get_motor_status() API return:

Returns a list:
            flags -- 8-bits of bit-flags that indicate motor status:
                bit 0 -- LOW_VOLTAGE_FLOAT - The motors are automatically disabled because the battery voltage is too low
                bit 1 -- OVERLOADED - The motors aren't close to the target (applies to position control and dps speed control).
            power -- the raw PWM power in percent (-100 to 100)
            encoder -- The encoder position
            dps -- The current speed in Degrees Per Second

So imagine my surprise when using the gopigo3.reset_motor_encoder() API (which sends an OFFSET_MOTOR_ENCODER SPI command) to see get_motor_status() return non-zero robot speeds, and in fact speeds far exceeding possible GoPiGo3 robot motion.

This is the result when resetting the encoders without moving the robot:

*** started moving
[INFO] [1715963280.121958244] [odometer]: start heading -0.0329 rads
[INFO] [1715963280.122452707] [odometer]: last heading -0.0329 rads
[INFO] [1715963280.123078985] [odometer]: current heading -0.0329 rads
[INFO] [1715963280.123697318] [odometer]: last_point - x: -0.1734 y: 0.0196 z: 0.0000 heading: -1.9
[INFO] [1715963280.124226837] [odometer]: current_point - x: -0.1734 y: 0.0196 z: 0.0000 heading: -1.9
[INFO] [1715963280.124845392] [odometer]: lSpeed,rSpeed: (2823.000,2880.000  lEncoder,rEncoder: (0.0,0.0)  lPwr,rPwr: (-128,-128)   

<<<---- ROBOT IS NOT MOVING BUT REPORTS 2880 DPS or 1.6m/s EQUIVALENT VELOCITY --->>>

[INFO] [1715963280.125430411] [odometer]: start_point - x: -0.173 y:  0.020 z:  0.000 heading:   -2
[INFO] [1715963280.218462322] [odometer]: 
*** stopped moving
[INFO] [1715963280.218914730] [odometer]: start heading -0.0329 rads
[INFO] [1715963280.219304433] [odometer]: last heading 0.0000 rads
[INFO] [1715963280.219889507] [odometer]: current heading 0.0000 rads
[INFO] [1715963280.220388767] [odometer]: start_point - x: -0.1734 y: 0.0196 z: 0.0000 heading: -1.9
[INFO] [1715963280.220965137] [odometer]: lSpeed,rSpeed: (0.000,0.000  lEncoder,rEncoder: (0.0,0.0)  lPwr,rPwr: (-128,-128)
[INFO] [1715963280.221521544] [odometer]: stop_point -  x:  0.000 y:  0.000 z:  0.000 heading:    0 - moved:  0.175 meters in 0.1s


Dave has a maximum measured velocity of 450 DPI which is 0.25 meters per second. There is absolutely no way any GoPiGo3 robot could move at 1.6 m/s. I have seen reported speeds over 5000 DPI returned when resetting the encoders.

The difficulty for Dave comes in trying to correctly identify when Dave is moving. I thought I could use motor_status.dps but the GoPiGo3 redboard is reporting fictional motion when resetting the encoders - bummer.

Tested Carl - reported 0 to 20 MPH in 0.02 seconds! That would beat my new Prius.

2 Likes

That might even beat a Porsche or maybe even a Lambo?

2 Likes

Even dogs twitch when they dream, I can’t fault the GoPiGo for dreaming.

2 Likes

I created a test for motion that protects against the false speed reported during reset_motor_encoder():

# CHECK FOR MOVING
    #   GoPiGo3 issue reset_motor_encoder() will cause motor_status speed and power to report movement.
    #       workaround: ignore wild speeds outside GoPiGo3 ability
    #                   ignore valid non-zero speed when encoder is -1, 0 or 1 value
    #       This will delay detection of movement after encoder reset up to 2 degrees (1 mm maximum loss)
    lSpeed = abs(self.motor_status_msg.left.speed)
    rSpeed = abs(self.motor_status_msg.right.speed)
    lEncoder = abs(self.motor_status_msg.left.encoder)
    rEncoder = abs(self.motor_status_msg.right.encoder)
    moving_now = ((1000 > lSpeed > 0.0)  or \
                  (1000 > rSpeed > 0.0) ) and \
                  (lEncoder > 1)  and \
                  (rEncoder > 1)

and indeed it works, albeit at the cost of 1mm each 170mm dismount from docking:

*** started moving
[INFO] [1716050942.272141004] [odometer]: start heading 0.0000 rads
[INFO] [1716050942.272777004] [odometer]: last heading 0.0000 rads
[INFO] [1716050942.273444725] [odometer]: current heading 0.0000 rads
[INFO] [1716050942.274087650] [odometer]: last_point - x: -0.0000 y: 0.0001 z: 0.0000 heading:  0.0
[INFO] [1716050942.274735279] [odometer]: current_point - x: -0.0000 y: 0.0001 z: 0.0000 heading:  0.0
[INFO] [1716050942.275338464] [odometer]: lSpeed,rSpeed: (126.000,32.000  lEncoder,rEncoder: (2.0,2.0)  lPwr,rPwr: (30,37)
[INFO] [1716050942.275938260] [odometer]: start_point - x: -0.000 y:  0.000 z:  0.000 heading:    0
[INFO] [1716050945.738200463] [odometer]: 
*** stopped moving
[INFO] [1716050945.738864425] [odometer]: start heading 0.0000 rads
[INFO] [1716050945.739761387] [odometer]: last heading 0.0878 rads
[INFO] [1716050945.740530312] [odometer]: current heading 0.0878 rads
[INFO] [1716050945.741312385] [odometer]: start_point - x: -0.0000 y: 0.0001 z: 0.0000 heading:  0.0
[INFO] [1716050945.742012866] [odometer]: lSpeed,rSpeed: (0.000,0.000  lEncoder,rEncoder: (284.0,300.0)  lPwr,rPwr: (-128,-128)
[INFO] [1716050945.742802717] [odometer]: stop_point -  x:  0.168 y:  0.007 z:  0.000 heading:    5 - moved:  0.169 meters in 3.5s

moved: 0.169 meters in 3.5s - so Dave loses 4mm a day, I don’t think he’ll complain.

2 Likes

Interestingly, using the EasyGoPiGo3.reset_encoders() method fictional speeds are reported but the power is set and maintained at 0, so using this method instead of reset_motor_encoder() does not cause the GoPiGo3 to flinch, (and allows my “is_moving” test to ignore the non-zero speeds if the power is 0.

I need to change the GoPiGo3 node reset_odometry() to use the EasyGoPiGo3.reset_encoders() method.

The Create3 had a /stop_status topic that made life real easy - I probably should add this to the GoPiGo3 node rather than making folks make decisions based on the personality of the bot API.

2 Likes

That works well

Calling the new version of the ROS2 GoPiGo3 Node reset_odometry service does not register as moving.


moving_now = ((1000 > lSpeed > 0.0)  and (lPower > 0) ) or \
                 ((1000 > rSpeed > 0.0)  and (rPower > 0) )

Call reset odometry service:

echo -e "\n*** Calling /odom/reset service"
echo "*** ros2 service call /odom/reset std_srvs/srv/Trigger"
echo "*** (note no data required for Trigger type)"
ros2 service call /odom/reset std_srvs/srv/Trigger

No motion reported by odometer, but start point of next move will be {0,0}

And the new odometer “moving_now” method using speed limits and power is working great when the bot actually moves:

*** started moving
[INFO] [1716128396.579502523] [odometer]: start heading 0.0000 rads
[INFO] [1716128396.580052022] [odometer]: last heading 0.0000 rads
[INFO] [1716128396.580565743] [odometer]: current heading 0.0000 rads
[INFO] [1716128396.581066613] [odometer]: last_point - x: 0.0000 y: 0.0000 z: 0.0000 heading:  0.0
[INFO] [1716128396.581560909] [odometer]: current_point - x: 0.0000 y: 0.0000 z: 0.0000 heading:  0.0
[INFO] [1716128396.582052334] [odometer]: lSpeed,rSpeed: (14.000,17.000  lEncoder,rEncoder: (0.0,1.0)  lPwr,rPwr: (35,28)
[INFO] [1716128396.582547074] [odometer]: start_point - x:  0.000 y:  0.000 z:  0.000 heading:    0
[INFO] [1716128400.077184665] [odometer]: 
*** stopped moving
[INFO] [1716128400.078420719] [odometer]: start heading 0.0000 rads
[INFO] [1716128400.080425309] [odometer]: last heading 0.0932 rads
[INFO] [1716128400.080987142] [odometer]: current heading 0.0932 rads
[INFO] [1716128400.081513030] [odometer]: start_point - x: 0.0000 y: 0.0000 z: 0.0000 heading:  0.0
[INFO] [1716128400.082023566] [odometer]: lSpeed,rSpeed: (0.000,0.000  lEncoder,rEncoder: (283.0,300.0)  lPwr,rPwr: (-128,-128)
[INFO] [1716128400.082528028] [odometer]: stop_point -  x:  0.168 y:  0.008 z:  0.000 heading:    5 - moved:  0.168 meters in 3.5s
2 Likes