Should Calling Motor.dc() after DriveBase.straight() be an error? #266
Replies: 9 comments
-
This is the intended implementation right now, but I agree that it should be improved.
Technically the motors are still actively being held in place. Try moving them manually. The current implementation is hierarchical: the higher level drive base "claims" access to the motors and "releases" it when you call stop. In such a hierarchy, neither of the individual motors is aware of the other. What should the left motor do when you call Besides being technically simpler (and more future-proof), I kind of like it didactically too. Especially when you'd get to multi-tasking, this provides an easy way to catch if something is still busy. But I agree that it would probably surprise users coming from procedural programming languages. There's a few ways we could go about that. Interested to hear your thoughts:
Technically both could be done. Option (1) is kind of nice even aside from this permission issue. But maybe we don't need (2) if we properly document how it works and if we ensure to make it more convenient and obvious. |
Beta Was this translation helpful? Give feedback.
-
I think I get it now, essentially "activating" the DriveBase by calling a method on it that powers the motors establishes a mode that must be "stop()ed" before trying to access the underlying motors (or adjust settings). I can see some benefits in that, but the doc will need to be super clear about this to avoid wide spread confusion I feel. Also, that does seem to suggest a bit of a design goal which might be articulated as: The DriveBase API should be sufficient to accommodate any type of two motor based driving (e.g. One doesn't need to utilize the Motor API unless you are using motors for something other than a two motor drive). As an aside, it seems like the MindStorms API has a similar pattern. I've never tried this, but I'm now wondering what would happen if I used the MoveSteering block to turn a couple of drive motors on (and leave them on) at a specific power and then subsequently used a Large Motor Block to set the duty cycle on one of those motors to a new power...Would the other motor continue along at the original power? Would the PID continue to track the relationship between the two wheels? Would I get an EPERM exception? ;) Regarding your questions, the use case that brought this up is illustrative. Here is a snippet from the "square on line" algorithm. The robot is built with a color sensor directly in front of each drive wheel. while self.lightL.color() != Color.BLACK or self.lightR.color() != Color.BLACK:
if self.lightL.color() != Color.BLACK :
self.motor_B.dc(20)
else:
self.motor_B.hold()
if self.lightR.color() != Color.BLACK :
self.motor_C.dc(20)
else:
self.motor_C.hold() This pattern of controlling the two drive wheels independently based on two corresponding inputs is perhaps not highly common, but not atypical either. I'm not sure either of the ideas above would provide functionality that would allow for it without "leaving" the DriveBase class? Maybe something like DriveBase.dc(left_duty, right_duty, behavior_at_zero) where the last parameter governs the motors behavior when set to 0? I also very much like the idea of getting to the duty cycle directly for PID loops. Going a bit further, while the ability to specify forward and angular velocities independently on a single drive() command is very interesting (reminds me of holonomic drives), I'm not sure it will be super intuitive for younger students. The MoveSteering block in MindStorms has something like that, but I believe the turn radius specified by the "steering" parameter is independent of the forward velocity. I don't think the kids have any concept of what the units on that "steering" parameter might be, but they do come to understand that it means "how sharp the robot turns". And then of course there is the MoveTank block which they seem to readily grasp. As I understand it, in Pybricks, since both parameters to drive() are time based, the combination of the two values sets the turn radius. I suspect I will be trying to explain over and over why their robot's turning radius changed when "all they did" was alter the forward velocity parameter. This is what led to the suggestion of "arc(speed, radius)". Maybe a mm/s version of tank drive control on the DriveBase as well? e.g. DriveBase.tank_drive(left_speed, right_speed, behavior_at_zero) I'd be nervous about 2, I think your use of the word "magically" speaks volumes about how this may be non-intuitive. ;) |
Beta Was this translation helpful? Give feedback.
-
Exactly. So that's where Thinking out loud here... Maybe the latter would be better suited for a totally separate And while we are at it, make
Indeed. And so to avoid all ambiguity, we currently don't allow it. In case you haven't seen it, check out the Zen of Python. By no means I'll claim we follow it strictly, but it is good inspiration.
I meant instead that you can read (not set) these state variables, in response to why staying within the concept of a DriveBase is useful, even when you do your own
I might have phrased it in a way to make option 2 look bad 😉 This is roughly what the graphical EV3 programming languages do, so it isn't entirely exotic. Generally, these languages don't raise any errors at all (they "fail silently"). That's a good thing, because errors might scare off beginning programmers, but it is not helpful when debugging bigger scripts. When users make the step to Python, we think it's time to be explicit about errors too.
We've been at this in another context 😄, but why should 0 be any different than simply "in between -1 and +1"? If special behavior is needed, I would encourage the user to add |
Beta Was this translation helpful? Give feedback.
-
I love this idea, which is why I would be in favor of providing multiple ways to specify movement on the single DriveBase class (instead of using alternative classes). I like the idea of having direct access to the motor duty cycles (bypassing the PIDF) but having the controller track the result. Not as sure about dc for angular velocity, since that is a level of abstraction up from the motors themselves. I also like the idea of being able to specify velocities in mm/s of each wheel and have the PIDF controller active and also tracking the result. It would have great educational value to have the kids code up a number of different movements using a number of different control schemes (e.g. straight(), turn(), drive(), dc(left, right), etc.) and watch the accumulated error in angle() and distance() through multiple trials to "discover" the limits of precision in pure odometry.
Sorry, I should have been clearer. I was referencing "active hold" versus "coast". I think you are suggesting that the presumption with something like DriveBase.tank_drive(left_speed, right_speed) would be "active hold" (e.g. maintain velocity at the specified value). And there is stop() if I want both motors to coast. But what do I do if (in the context of DriveBase) I want to drive one motor but coast the other one? Maybe that is not so important. Maybe that could be done with the hypothetical DriveBase.tank_drive(left, right) which (I think) would presume 0=coast. |
Beta Was this translation helpful? Give feedback.
-
Thanks again for all the input, this is leading to good ideas. As a first step, I’m going to try and see how far we can get by improving the documentation. Also for your related posts. One advantage for saving some API additions or changes for the next release, is that there will be more time to adjust and improve them (and see if users like them). Anything we’d add today would become instantly definitive, which might pose restrictions later on. And power users will be able to try new additions instantly as you have done, well before a full release. (We will make this easier and document where to get the latest development builds). |
Beta Was this translation helpful? Give feedback.
-
You are very welcome and that all sounds great. It sounds like you are planning for 2.0 relatively soon? Also, not sure of your target audience for the doc, but I have a collection of 6th grade FLL veterans that are half-way through their Python course and need stuff to do while on lock-down...I would be happy to turn them loose on what you guys create and consolidate feedback for you. |
Beta Was this translation helpful? Give feedback.
-
If the stars align it should be this week! Fortunately the documentation will probably have more rolling updates so we can continue to improve it. I've added some preliminary sections about using the individual motors (but I should add some examples too) after you make a DriveBase. I've also added a section to experimentally adjust wheel_diameter and axle_track. |
Beta Was this translation helpful? Give feedback.
-
Awesome! We might take some time to respond, but it's certainly being read and processed as fast as we can ☕ . |
Beta Was this translation helpful? Give feedback.
-
This has been fixed. Calling individual motors after running a drivebase now no longer produces an error. |
Beta Was this translation helpful? Give feedback.
-
This code throws the EPERM error:
This statement from the docs continues to perplex me a bit. The use case was using straight() and turn() to have a robot approach a line, then switch over to dc() to effectuate a "tank drive" control to square on the line using two light sensors on the front of the robot. If I call stop() after the call to straight() then all is well, but it seems odd... Here is what the use case code looks like:
Beta Was this translation helpful? Give feedback.
All reactions