Closed devinbreise closed 2 years ago
Wow, thank you so much for taking the time to write this and also for investigating so thoroughly!
These are exactly the intended use cases, so I really appreciate seeing this being used well.
You've got everything exactly right, and I would pretty much say "yes-to-all" your suggestions.
In fact, some of the requested features are planned but didn't make the cut for the 2.0 release. Now I've got a good reason to bump them higher up the priority list for 2.1.
To make sure I answer this in detail, I'll probably go over your points one by one in the next couple of days (I'm on UTC+2). Feel free to nudge if I failed to respond to some of your comments in while.
But I'm going to respond to one point now because it is in fact on the list for next week, and maybe you're interested to help:
I haven't tested this yet, but I am wondering about how the acceleration curve will play with a higher level PID (or just P) controller calling drive(). For example, to follow a line (or go straight with a gyro)
And related to this, I'll also answer your question about settings
and limits
being the same or not. You hit the issue spot on: I realized it is kind of impossible to find one set of control
parameters that covers the use cases 1 and 2. For 1 we want precision (slow acceleration) and for 2 we want responsiveness (high acceleration).
And so that's why the separate settings
function was introduced. This will let us set low accelerations just for straight
and turn
(and one day, you guessed it, arc
), while setting much higher defaults for everything else.
The action point for next week is selecting those faster defaults, to ensure that indeed we are responsive enough to follow lines well by default. Right now, since I didn't get to this yet, the DriveBase heading and distance control adopt the settings dynamically from their respective motors, so it behaves just about the same as two single Motor
class objects would.
So I'm kind of expecting that we will have to adjust them to accelerate faster, because for single motors we set the default acceleration to be rather slow. All other settings are up for change as well. For example, we may need to reduce the aggressiveness of the PID controller as we increase the magnitude of acceleration.
I would like to ensure that by default, a simple loop like:
db.drive(drive_speed=some_user_constant, turn_rate=light_error*another_user_constant)
works well enough to make a decent proportional line follower, for small drive bases (42 mm wheels) and the educator bot (56 mm wheels, at the top of my head).
So if you have a robot on which you're willing to test this or even experiment with the control parameter values yourself, that would be really great.
If you are going to try other values, don't worry about reviewing the current defaults in great detail, as they are somewhat arbitrary so far. Feel free to focus on what works best for your robot. If you want to share those along with the robot's approximate dimensions, we'll have one extra data point to work with, which would be really great.
Thanks again. - I look forward to responding to your other points as well later.
Oh, please do try the latest beta release on April 10th. It has a few minor improvements like more verbose messages for error codes like EPERM
.
I'll switch over to the latest beta...are you keeping a log of release notes lying around somewhere?
At the moment, I'm testing with my older team's robot from last season. It uses the Lego "znap" wheels which are about 50mm diameter but excellent for this sort of thing as they have no air gap between the tread and rim...very precise. On their robot the center to center wheel base (axle_track) is 95mm. I'll try some line follows tomorrow AM and see how aggressive I can get the parameters before it is no longer reliable. Videos of this robot in action are on their YouTube channel
First off, let me say a big THANK YOU for doing this. This is exactly what I have been looking for for years. We start kids in FLL as early as 3rd grade and by the time they have been doing it for 3+ years, they often outgrow the MindStorms/LabView environment. Its not that the next lessons can't be taught there, its just not optimal. So having this level of control over things like acceleration (both straight and angular!) is really great. The kids will be able to dip their toes in the water of actual physics computations and, for their efforts, have a much improved level of control over their robot's odometry.
Let me start by saying I'm really happy to hear this perspective from an experienced teacher and FLL coach.
I say that because we have also heard the opposite, where some insist that we should not provide this. They argue that it takes away the learning.
I would argue that useful and transparent tools encourage learning. Indeed there's the physics principles you mention. But on an even more basic level, I'd argue it encourages kids to get a ruler and start measuring, as opposed to endlessly trying to guess the "right" the numbers for the conceptually opaque green blocks.
And also, I would argue that not every FLL or STEM student needs to be or become an engineer. There's a lot to learn about application design, problem solving, mission planning, team work and beyond, but it can be a very discouraging hurdle if everyone must learn about wheel geometry just to get there.
I agree with your perspective here. Here are a few additions:
FIRST is a big place, with many diverse opinions. Many of those opinions come from engineers who have made a career at a particular level of the "universal engineering stack", ranging from physics and material sciences, up through ME, into Comp Sci, and then up into project/program management. The wonderful news is that FIRST has room for all of them and the lessons their careers have embodied. The mixed blessing is that people tend to overvalue that which they know. So the Physicists will argue that the kids should learn about momentum and torque and shy away from platforms that hide those details. The MEs will argue that kids should learn about simple machines and be displeased when Lego introduces new parts that fully encapsulate linear actuators. The OS people might bristle at the conversation on the other thread here about hiding vagaries of device drivers, the Apps people will argue that the kids should work out PID control on their own, and so on.
Experienced coaches know that there is a universe of practical learning to be done in FIRST and they tailor the experience to each team, each season. For one team, they may hand them a platform component that neatly encapsulates a tough problem, for another team, they neglect to mention that component exists and coach the kids through recreating it.
They also know that FIRST is foremost, an educational enterprise, and the competitive aspect (like the robots) is just the "hook" to keep the kids interested and engaged. Platforms always gain capability over time, both in the "real world" and in contrived worlds like FIRST competitions. Its called progress. Its also essential to keeping FIRST relevant in a rapidly evolving world. This last season, 50% of more of FTC teams used real time computer vision with AI based object recognition. Virtually none of them understand how that works, they just use the code the platform provides. But, they did learn many of the problems associated with adapting and utilizing those technologies and, importantly, the FIRST ecosystem is keeping up with the state of the art.
FLL made a bold and committing move last season to open up the competition to other coding platforms. That move will change the competition forever. With the introduction of text based programming languages, source code control will take root in FLL, which will drive many teams to github, which will open the door to code sharing. Very strong teams will see the "core values" opportunity to create and share code libraries for other teams to use. This has been standard in FTC and FRC for years, now it will come to FLL.
So your work is not only healthy and of great merit, it is inevitable. If you (with Lego Education and FLL) don't provide it, teams will. And in fact, whatever level of platform you do provide, that will be extended further by teams, "platformized" and shared broadly.
I think it is wonderful that this work is being done and even more wonderful that it is sanctioned and promoted by Lego Education and FLL. Please keep up the good work!
Thanks for your follow-up there as well. We're certainly looking forward to seeing students use this in FLL. As you've found, many of the Motor
and DriveBase
functionality is designed to improve accuracy and simplicity for core robot tasks, used in FLL and beyond.
In the next couple of posts, I'll try and go through some of your specific comments and questions.
---I LOVE how the robot can be "configured" with its wheel_diameter and axle_track. This is a great opportunity to teach some basic geometry to the kids and then let them move on to higher order problems.
And there's room for further experimentation too. In practice, for example, rubber tires compress a bit, leading to a smaller "effective" diameter. And the motor shaft and axle are slightly flexible, usually leading to a smaller "effective" axle track than just the midpoint of the wheels.
------I typically talk to the kids about straight movement, turns (radius turns) and spins (spinning in place). As I understand it, radius turns are missing in this part of the API. Perhaps those could be added?
This is something we might add later (2.1 or later).
I wonder if the straight_speed and turn_rate parameters could be included as inputs on the movement methods themselves (e.g. straight(distance, speed))
Yeah, this is the pattern we use for Motor
.
We've decided to make straight
and turn
really basic on purpose, making the DriveBase
a good entry point for learning programming in general too (not necessarily robotics).
Done this way, there's a lot less ambiguity regarding double negatives. For example, doing straight(-100)
intuitively makes the DriveBase go backward.
Even if we won't encourage it, in practice some programs might end up looking like this, comparable to many green blocks in sequence:
robot.turn(90)
robot.straight(200)
robot.turn(-90)
robot.straight(100)
This is much easier to read and debug compared to:
robot.turn(90, -90)
robot.straight(200, 100)
robot.turn(-90, 1000)
robot.straight(100, -200)
It wasn't initially apparent to me that subsequent calls to drive() would "automatically" enact the specified acceleration curve to handle differences in velocity, but once that came clear, I do like it. Perhaps some notes in the doc.
There's no difference between the first or subsequent calls. It just accelerates to and stays at the requested speed in run(speed)
. Likewise, it accelerates and stays at the requested drive speed and turn rate in drive(drive_speed, turn_rate)
.
As a curiosity, what did you initially expect before seeing this result? Faster acceleration?
I haven't tested this yet, but I am wondering about how the acceleration curve will play with a higher level PID (or just P) controller calling drive(). For example, to follow a line
Indeed, we've started to zoom in on this in #18 .
Provide an explicit way to disable the acceleration curves. Perhaps this is just setting the acceleration numbers to arbitrarily large values?
Indeed, picking a higher value is what you could do. A mechanical system is always going to have to accelerate. To get a sense of how fast that is, perhaps set dc(100)
and see how fast your motor accelerates. As a helping hand to see what is arbitrarily large, consider trying to reaching the maximum speed in one control step (0.01 seconds).
I'm not sure how to stop. I haven't tried calling straight() after a call to drive(speed,0). Perhaps that is the plan?
Before getting into details, it is quite intriguing why people seem to assume that "0" is different and should not be used (This is quite a common assumption, but I'm not sure why.)
My workaround is to call drive() with a very slow speed, let it decelerate for a specific set of encoder clicks, and then call stop() and brake().
So in this case, why a very slow speed, and not just drive(0, 0)
? Like any other drive
command, it accelerates/decelerates smoothly to the target speed, so 0 in this case.
One thing we considered but did not add yet, is a decelerate()
method, which would essentially just call drive(0, 0)
and then conveniently wait until the speed reaches 0 like you described. Would you find this useful?
Or, maybe instead an optional argument wait
(True
/False
) on drive
could capture both this, as well as blending multiple drive
commands the way I think you wanted.
But yes, straight(0)
and turn(0)
would be an abrupt stop, and stop()
just coasts the motor.
I'm not sure how far you want to go with this, but it occurs to me that you could potentially expand the straight() and turn() API to enable "optional" stops.
Indeed, an optional end_speed
could be a next step. The good news is that the controller concept and implementation would allow for this. But it would still be a pretty major change. We're hoping that the existing turn
and straight
will still be quite useful for FLL teams.
Combined with the posts elsewhere, I think this covers most of your questions or comments.
Thanks again for your detailed testing and sharing your insights.
Thanks for being so responsive and tolerating all this "input"!!
less ambiguity regarding double negative
Excellent point! This is a perennial thorn in my side teaching kids...and I take your point about teaching basic programming. And it will be a fine exercise for teams to extend this API to forward(), backward(), etc.
As I understand it, radius turns are missing in this part of the API. Perhaps those could be added? This is something we might add later (2.1 or later)
Yes please! Before next season if possible?
There's no difference between the first or subsequent calls. It just accelerates to and stays at the requested speed in run(speed). Likewise, it accelerates and stays at the requested drive speed and turn rate in drive(drive_speed, turn_rate). As a curiosity, what did you initially expect before seeing this result? Faster acceleration?
My particular thought process: When I first looked at drive() it was right after trying straight() and turn() which seem to assume that the robot will be at rest before and after the movement. So I was looking for the "missing" arc() and thinking about that sort of thing. I wrote code that used drive() along with distance() and angle() to try that out. That was when it occurred to me that It wasn't clear how to stop with a deceleration curve. Not sure why drive(0,0) did not occur to me, but it does "read" as a bit of a contradiction until you grok that "drive" means "accelerate/decelerate to the commanded speed and hold it" (as you say, people are indeed intriguing...). There were also a couple of words in the doc that probably threw me off:
Drive Forever Use drive() to begin driving at a desired speed and steering. It keeps going until you use stop() or change course by using drive() again.
A few well chosen words and something about changing velocity on the fly (as well as heading) might go a long way here.
why people seem to assume that "0" is different and should not be used
In my experience, when people don't see something like that, it is often the name of the method. I'm not suggesting you change anything here, but for illustration, consider the difference in understanding created by "drive()" versus something like "change_velocity_to()" or even "accelerate_to()"
One thing we considered but did not add yet, is a decelerate() method,. Would you find this useful? Or, maybe instead an optional argument wait (True/False) on drive could capture both this, as well as blending multiple drive commands the way I think you wanted. Indeed, an optional end_speed could be a next step We're hoping that the existing turn and straight will still be quite useful for FLL teams.
I have the impression that you have a design goal to provide an API that can be approached at multiple levels of understanding. For example, doing some simple configuration (or accepting defaults) and then using a sequence of calls like straight(500) and turn(90) is a wonderfully simple way of starting. For kids new to programming, learning Python and robots all at the same time, this level of simplicity is just what you need. At the other end, you also seem to be targeting a level of configuration and control here that is rarely seen at much higher levels of youth robotics (integral_range? Bravo!). Designing an API that is useful from one end of that scale to the other is no easy task! As far as specific recommendations go, I'll offer these:
drive_straight(distance, cruise_speed, end_speed=None)
drive_arc(radius, degrees, cruise_speed, end_speed=None)
drive_turn(degrees, cruise_speed, end_speed=None)
end_speed = None indicates no change of velocity at the end, just returns control. end_speed = 0 means decelerate to 0
Thanks for sharing your thought process on some of your points.
Yes, we aim to make it simple for beginners and useful for advanced users at the same time. I think this can work well if the core principles work well and reliably.
Then, by picking sensible defaults but keeping options meaningful and configurable, advanced users will be able to extend it in many ways. Essentially we aim to make it simple, but not simplistic.
Now that most implementation is done, we'll turn our attention to continuing filling the gaps in the docs as you correctly point out. And eventually, hopefully we'll add some more samples to illustrate the various use cases like discussed in this thread, and how they can be useful for teams.
The following features are now available :tada: :robot:
This seems to cover most of the feature requests here, so I think we can close this issue.
Please do not hesitate to share your findings if you try any of these features :)
Disclaimer: My perspective on this is that of an FLL Coach. While I'll admit to a bit of tinkering with an EV3, 99% of my effort in this space is through the work of the 30+ FLL teams I have coached from 3rd graders to middle school world champions. So apologies in advance for the skewed perspective.
First off, let me say a big THANK YOU for doing this. This is exactly what I have been looking for for years. We start kids in FLL as early as 3rd grade and by the time they have been doing it for 3+ years, they often outgrow the MindStorms/LabView environment. Its not that the next lessons can't be taught there, its just not optimal. So having this level of control over things like acceleration (both straight and angular!) is really great. The kids will be able to dip their toes in the water of actual physics computations and, for their efforts, have a much improved level of control over their robot's odometry. In addition, Python is a great choice to transition them into the world of text-based programming, and get them ready to move to Java in FTC after that. So please keep up the great work. I have at least one team that is learning Python as I write this and counting on competing with this library next season.
There are 3 typical use cases I see in FLL for DriveBase:
1) Basic Movement: Driving a robot around a board using a series of discrete movements where the robot comes to a stop between each movement. This is the starting point for most teams and most of what you see in FLL. The kids think: "I need to move my robot forward this much, then I need to turn it that much, then I need to move forward again". In MindStorms, they code that as 3 blocks and behind the scenes, a PID controller uses some very aggressive accel/decel settings to try and keep the wheels from slipping too much, but success is heavily dependent on inertia.
2) Sensor Controlled Movement: Driving a robot using real time feedback from sensors. This is "line follow", "closing to a certain distance from an object" using an ultrasonic sensor, controlling heading using a gyro sensor, driving to a line, etc. In MindStorms, anything involving proportional control gets out of hand very quickly as they kids must use the looping constructs and "data wires" to make it work. Only the teams with devoted programmers who have a few years under their belt pull this off.
3) Smooth Movement: Sequencing a series of movements with smooth accel/decel curves between movements as needed. Currently, this is world-class level functionality in FLL. While it can be done in the the MindStorms environment, it is either highly imprecise (sequencing blocks without the "brake" value set) or highly advanced (teams that essentially write their own PID level controllers and master acceleration curves).
Given those use cases, here are my initial thoughts on DriveBase:
---I LOVE how the robot can be "configured" with its wheel_diameter and axle_track. This is a great opportunity to teach some basic geometry to the kids and then let them move on to higher order problems.
---Use Case 1 : Basic movement ------straight(), turn(), and settings() seems squarely aimed at this. I love that both straight_acceleration and turn_acceleration can be specified. This is a great opportunity to teach some basic physics and have the kids experiment with robot designs to discover what sort of acceleration their robot can tolerate before wheel slippage becomes a factor. ------I typically talk to the kids about straight movement, turns (radius turns) and spins (spinning in place). As I understand it, radius turns are missing in this part of the API. Perhaps those could be added? I get that such a turn can be specified with a combination of forward and angular velocity, but this is pretty advanced for FLL teams. We could try to coach them through the relationship between forward velocity on an arc, angular velocity, and radius, but I'd much prefer to see this math encapsulated in this class. Perhaps a method in this section could take radius measurement as an input? ------It's typical for teams to vary velocity from one movement to another depending on the level of consistency they need out of the specific movement. I wonder if the straight_speed and turn_rate parameters could be included as inputs on the movement methods themselves (e.g. straight(distance, speed))
---Use Case 2: Sensor Controlled Movement ------drive(), stop(), distance(), angle(), state(), and reset() seem to be aimed at this use case. It wasn't initially apparent to me that subsequent calls to drive() would "automatically" enact the specified acceleration curve to handle differences in velocity, but once that came clear, I do like it. Perhaps some notes in the doc. ------I haven't tested this yet, but I am wondering about how the acceleration curve will play with a higher level PID (or just P) controller calling drive(). For example, to follow a line (or go straight with a gyro), we will typically teach the kids the idea of real-time proportional control based on a sensor reading. Using this API, I presume they would code up a tight loop that will call drive() and adjust the turn_rate parameter proportionally to the amount of deviation from their intended course. In MindStorms, there is no software controlled angular acceleration curve to my knowledge, so the motors will simply be instantly commanded to the new rate of angular velocity. Here, depending on the turn_acceleration set, it might not. Not sure if that would be a problem. Similar issue for approaching an object and slowing down proportionally based on distance from the object. Some options: ---------Provide an explicit way to disable the acceleration curves. Perhaps this is just setting the acceleration numbers to arbitrarily large values? ---------Find a way for the kids to "plug in" their sensor reading directly to the drive control. Not sure exactly what this would look like, but imagine if they could provide a sensor as a parameter to a drive() function in way that would control velocity and/or angular velocity with a "built in" PID. Essentially the same thing you are now doing with the motor encoders.
---Use Case 3: Smooth Movement ------I've tried some experiments using sequential calls to drive() while polling distance() and angle() in between to try to generate a series of precise movements that do not including stopping between them. The first "oops" here was realizing that if I want to roll the robot forward at a certain velocity (e.g. drive(250,0)) and then make a radius turn while maintaining velocity, I have an acceleration problem. It appears that the next call to drive (perhaps drive(250,50)) detects that my "forward velocity" is already 250 so no acceleration curve there, but my current angular velocity is 0, so it must "ramp up" the angular velocity, resulting in an arc that looks more elliptical than circular. My workaround is to set the angular acceleration limit to a very high number. ------I'm not sure how to stop. I haven't tried calling straight() after a call to drive(speed,0). Perhaps that is the plan? My workaround is to call drive() with a very slow speed, let it decelerate for a specific set of encoder clicks, and then call stop() and brake(). ------I'm not sure how far you want to go with this, but it occurs to me that you could potentially expand the straight() and turn() API to enable "optional" stops. My FTC teams tend to code something like drive(max_velocity, end_velocity, distance). In this implementation, the initial velocity is sensed, the end_velocity can be zero or non-zero and the distance is used to compute an initial acceleration phase (if needed), a cruise phase (maximized, but none if there is no room for it) and a deceleration phase (if needed). For example:
If you added an ability to specify a radius, I think it would be relatively complete. Given something like drive_radius(max_velocity, end_velocity, degrees, radius), I could see code like:
---Misc ------It wasn't clear if the acceleration parameters passed to settings() are the same as the acceleration parameters for distance_control.limits() and heading_control.limits(). Does setting one change the other? ------I was a bit mystified by the EPERM runtime errors until I clued in to the "You can only change the settings while the robot is stopped. This is either before you begin driving or after you call stop()." statement in the docs. I presume that was what that was about. ------I have not played around the PIDF coefficients yet, but in my initial tests, the defaults seem good so far. Most importantly, a balanced robot drives straight. :) ------I think I have seen some intermittent situations where even after calling stop() one of the motors continued to trundle along. This could easily be explained by bus issues (poor connections on the EV3 are a perpetual issue) but I'll keep an eye out and see if I can reproduce