-
-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug] Stop.NONE can have unexpected kinematics in curve() due to dynamics constraint #972
Comments
I can reproduce it with try:
from pybricks.pupdevices import Motor
except ImportError:
from pybricks.ev3devices import Motor
from pybricks.tools import wait
from pybricks.parameters import Port, Direction, Stop
from pybricks.robotics import DriveBase
from pybricks import version
print(version)
# Initialize default "Driving Base" with medium motors and wheels.
left_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE)
right_motor = Motor(Port.B)
drive_base = DriveBase(left_motor, right_motor, wheel_diameter=43, axle_track=86)
drive_base.settings(straight_speed=600, straight_acceleration=200, turn_acceleration=400)
# Allocate logs for motors and controller signals.
DURATION = 20000
DIV = 4
left_motor.log.start(DURATION, DIV)
right_motor.log.start(DURATION, DIV)
drive_base.distance_control.log.start(DURATION, DIV)
drive_base.heading_control.log.start(DURATION, DIV)
# Drive straight forward and back again.
for x in range(4):
drive_base.straight(100, Stop.NONE)
drive_base.curve(43, 90, Stop.NONE)
# Wait so we can also log hold capability, then turn off the motor completely.
wait(100)
drive_base.stop()
# Transfer data logs.
print("Transferring data...")
left_motor.log.save("servo_left.txt")
right_motor.log.save("servo_right.txt")
drive_base.distance_control.log.save("control_distance.txt")
drive_base.heading_control.log.save("control_heading.txt")
print("Done") |
This is a tricky issue. A partial fix committed above solves the main bug: with multiple synchronized trajectories we can't time-shift a trajectory (which we sometimes do for better performance in tight loops). This commit disables that optimization for drivebases. But as I was digging into this, I realized there is another subtle more conceptual issue. Between calling any two commands there is always a bit of time. During that time, a drive base with We could do the same trick as we use for Smart Coast/Brake, but that still leaves the robot having to get rid of its excess speed left over from the previous maneuver as discussed above. So maybe we should leave it at the fix proposed here, and revisit proper splines/beziers or absolute turns for a future API. |
With two patches underway, this script drives a smooth, proper square, which would be even better with gyro included: from pybricks.hubs import EssentialHub
from pybricks.pupdevices import Motor, ColorSensor, ColorLightMatrix
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
from pybricks.robotics import DriveBase
from pybricks.tools import wait, StopWatch
hub = EssentialHub()
bal_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE)
jobb_motor = Motor(Port.B)
robot_bazis = DriveBase(bal_motor, jobb_motor, wheel_diameter=43, axle_track=87.4)
print(robot_bazis.heading_control.scale)
robot_bazis.settings(straight_speed=500, straight_acceleration=200, turn_acceleration=400)
for i in range(1, 5):
robot_bazis.straight(100, Stop.NONE)
robot_bazis.curve(43, i * 90 - robot_bazis.angle(), Stop.NONE)
robot_bazis.straight(100) |
Many thanks! I have retried the same with an InventorHub and standard medium motors -- unfortunately anything above straight_speed=100 results in a wacky and unpredictable movement. from pybricks.hubs import InventorHub
from pybricks.pupdevices import Motor, ColorSensor, ColorLightMatrix
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
from pybricks.robotics import DriveBase
from pybricks.tools import wait, StopWatch
hub = InventorHub()
left_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE)
right_motor = Motor(Port.B)
drive_base = DriveBase(
left_motor,
right_motor,
wheel_diameter=56,
axle_track=114)
print(drive_base.heading_control.scale, drive_base.settings())
# drive_base.settings(straight_speed=500, straight_acceleration=200, turn_acceleration=400)
drive_base.settings(straight_speed=300, straight_acceleration=100)
for i in range(1, 5):
drive_base.straight(200, Stop.NONE)
# print(drive_base.angle(), i * 90 - drive_base.angle())
drive_base.curve(43, (i * 90 - drive_base.angle()), Stop.NONE)
drive_base.straight(200) |
Which firmware is that on? The fixes introduced here are not in the main release yet. To find out, run a script with: from pybricks import version
print(version) To try the fixes introduced in this thread:
|
I have tried with two different hubs/versions with same results. ('primehub', '3.3.0b2', 'v3.3.0b2 on 2023-03-08') |
Yes, and this issue was fixed on March 20 :) So you could try it with the file linked above if you like. |
You are right, sorry for my ignorace.
from pybricks.hubs import InventorHub
from pybricks.pupdevices import Motor, ColorSensor, ColorLightMatrix
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
from pybricks.robotics import DriveBase
from pybricks.tools import wait, StopWatch
from pybricks import version
print(version)
hub = InventorHub()
left_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE)
right_motor = Motor(Port.B)
drive_base = DriveBase(
left_motor,
right_motor,
wheel_diameter=56,
axle_track=114)
print(drive_base.heading_control.scale, drive_base.settings())
drive_base.settings(straight_speed=500, straight_acceleration=200, turn_acceleration=400)
for i in range(1, 5):
drive_base.straight(200, Stop.NONE)
# drive_base.curve(43, i * 90 - drive_base.angle(), Stop.NONE) # video 1
# drive_base.turn(i * 90 - drive_base.angle(), Stop.NONE) # video 2
drive_base.curve(56, i * 90 - drive_base.angle(), Stop.NONE) # video 3
drive_base.straight(200, Stop.HOLD)
wait(5000) version1 - curve with smaller axle track then drivebase version2 - turn version3 - curve with same axle track as drivebase Probably this might even be a neverending kinematics journey, so from my perspective code v3 seems to bring the expected results perfectly. |
One thing I've noticed is that your acceleration values are very low. A limitation of Can you run the program with higher accelerations or just using the defaults? |
After reviewing your three cases in some detail with the help of the graphs above, I think this is the expected behavior. Let's start with version 2, where the code is: for i in range(1, 5):
drive_base.straight(200, Stop.NONE)
drive_base.turn(i * 90 - drive_base.angle(), Stop.NONE) This is asking the robot to go straight and not stop at the end, and then immediately do an in-place turn. An in-place turn means driving forward by 0 mm. But it has all this excess speed coming in to this move, so this manifests as an abrupt stop. If you are curious, you can see this happen in the control_distance graph of this experiment: The target speed does indeed drop to 0 during the curve, as intended. Now for version 3 With a large enough radius, more distance is driven throughout the curve. This means enough time is available to obtain and maintain the desired turn and drive speed. Now for version 1 This is somewhere in between the two cases; you can still see the drive speed in the curve having to go to almost zero to achieve the small-radius turn. This curve is so small that one wheel still has to go backwards to achieve it. All-in-all, the robot drives the expected result in all cases, and comes out in a nice square. It's basically just that Does that make sense? Perhaps the analogy is driving on the road. You can keep driving fast along a slowly-curved highway, but you probably want to slow down and possibly stop at a small junction on a city street. |
This does make sense, I think the issue could be closed based on the changes - if you agree. |
Describe the bug
Stop.NONE
makes consecutive motor movements continuous. In case ofcurve()
, it makes both the rotary and forward motion continuous.This means that when you exit a curve into a straight, the angular velocity is maintained until it decelerates to zero, and vice versa.
This is intentional to reduce slip, but it means the
straight()
command does not produce a straight line (although it does recover to a straight path after absorbing the excess angular velocity).To reproduce
This was reported by @afarago
Expected behavior
Kinematically correct curves and straights.
An easy solution would be to continue only one of angular or forward velocity. This allows the user to get kinematically correct paths, perhaps at the price of some slip due to the instantaneous discontinuity in speed reference. This could be done by accepting a tuple
then=(Stop.NONE, Stop.HOLD)
or variations thereof, which can be passed topbio
as is.The proper solution might be to produce a spline or bezier that has the correct endpoints equivalent to the kinematic curve, but is allowed to deviate from the curve in between to accommodate for excess angular velocity. This is out of scope for now 🤓
The text was updated successfully, but these errors were encountered: