-
-
Notifications
You must be signed in to change notification settings - Fork 16
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
motorutil: add steering block equivalent #50
Conversation
@@ -0,0 +1,276 @@ | |||
// Copyright ©2016 The ev3go Authors. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's 2017 now 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but the code it ripped from the znap example, which was written last year.
motorutil/steer.go
Outdated
// specified speed. The valid range of direction is -1 (hard left) to +1 (hard right). | ||
// For directions between -1 and 1 neither motor will run backwards, but for -1 and 1 | ||
// the motors will run in opposite directions to allow a turn on the spot in the given | ||
// direction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the EV3-G software, the range is -100 to 100 for steering. For these values, the robot will basically turn in place (left runs forward and right runs backwards at same speed).
I think this is what you are getting at, but saying "neither motor will run backwards" doesn't sound right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thing, the EV3 does not have a floating point unit, so anything that uses floats will be less efficient. I don't know that there would be a significant (noticeable) difference here. It is just something to consider. I generally try to avoid float just on principal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another more useful thing to think about is one feature of the official LEGO firmware that is frequently requested in ev3dev is that motors are "synchronized", meaning that if one has a higher load than the other, the faster one will wait for the other to catch up, so to speak. I think the leJOS guys have a blog about this for an arbitrary number of motors. LEGO does this in the kernel drivers for 2 motors, but ev3dev doesn't to it at all, so it would have to be implemented in Go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is what you are getting at, but saying "neither motor will run backwards" doesn't sound right.
When I've experimented, there is forward progression except with very tight turns, where you see a complete spin. The comment about no reverse motor for the other cases is true (at least for this code).
Another thing, the EV3 does not have a floating point unit, so anything that uses floats will be less efficient. I don't know that there would be a significant (noticeable) difference here. It is just something to consider. I generally try to avoid float just on principal.
I don't think there is a huge issue here since it's only a very minor part of the code path.
I'll look into synchronising the motors, but it's not a juge priority.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From looking at the leJOS article, it's not clear that I'd be able to do very much better than what is there now without significant added complexity and running the motors with run-direct
. Fortunately, because the Go code is natively compiled there is no JIT warmup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed direction
to take an int
argument; it simplifies the API and removes the need for a new error interface type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right about the turns; I can't read my own code which is upsetting. I have clarified the docs and added tests to demonstrate the expected behaviour.
This is useful for future reference.
31ab05e
to
88a02bc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to take this for a test drive to really get a feel for the behaviors here. But I would like to do it when I am a bit more awake (in the next day or two), but here are a couple other suggestions for now.
motorutil/steer.go
Outdated
"github.com/ev3go/ev3dev" | ||
) | ||
|
||
// Steering implements a paired-motor steering unit similar to an EV-G steering block. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EV-G > EV3-G
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
motorutil/steer.go
Outdated
Timeout time.Duration | ||
} | ||
|
||
// SteerCounts steers in the given turn for the given tacho counts and at the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather see degrees or rotations here. Based on feedback from users, I have come to the conclusion that most people have a hard time grasping the concept of "tacho counts". It also makes the code more portable (On BrickPi, the hardware actually uses 720 counts per rotation, but we tweaked the driver to match the 360 counts on EV3 because people just don't understand.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can't be rotations unless I use a float64
since otherwise it precludes part rotation. I can add a comment in the documentation that the counts/rot can be obtained with a call to t.CountsPerRot
for each motor, but I'd rather not put those calls into the SteerCounts
method since the value can be cached by the user after an initial call (and as you say counts ≅ degrees).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nicer if this library cached the CountsPerRot value and did the conversion so that the user doesn't have to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was a conscious design decision; none of the devices in ev3go save state that is held by the underlying driver since it may become invalid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack. But, address
, commands
, count_per_rot
, count_per_m
, full_travel_count
, driver_name
, max_speed
and stop_actions
are fixed values and will never change, so they should be safe to read once and cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think you might be right. The only case where those would be invalidated is if the motor is unplugged and replugged, but then the ID is also invalid, so all bets are off.
This is #53.
motorutil/steer.go
Outdated
|
||
// SteerCounts steers in the given turn for the given tacho counts and at the | ||
// specified speed. The valid range of turn is -100 (hard left) to +100 (hard right). | ||
// If counts is negative, the turn will be made in reverse. The sign of speed is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignoring the sign of speed is another thing that, based on user feedback, is non-intuitive.
Here is an example of something I've been playing around with lately that shows how you can implement this to hide the driver implementation details from the user and make things more intuitive. It is just for a single motor instead of steering, but is similar. The function converts rotations to tacho counts and handles negative speed values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignoring the sign of speed is another thing that, based on user feedback, is non-intuitive.
This is merely following the underlying ev3dev speed_sp
behaviour. If that changes, I'll update this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of taking that approach, but the documentation becomes more complex in that then it would need to say the direction of travel is the sign of the product of counts and speed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs may be more complex, but users expect negative speeds to "just work" before ever consulting the documentation (at least in my experience).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the expected behaviour of -speed and -position in conjunction? This is the problem for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, it's a deal. I'll make that change tonight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
motorutil/steer.go
Outdated
} | ||
|
||
// Wait waits for the last steering operation to complete. A non-nil error will either | ||
// implements the Cause method, which may be used to determine the underlying cause, or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implements > implement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Very happy to wait. I'll have those comments addressed by then hopefully. |
Never returns from By making |
The arithmetic works as I expect for those:
I can't set up a robot to look at this for the next several days, so does the Note that errors are not sticky here, which is a deviation from the ev3go/ev3dev API to normal idiomatic Go, but one that is necessary for allowing exposure of the motors in the struct, which allows greater control. I need to think about this further. |
I am checking errors. I just left them out for brevity. Here is the error after setting a 5 second timeout
I suppose this could be a driver bug. If we are running the motor a 0 speed (or something close to it), it probably says running and not stalled, but if the position setpoint is not also 0, then we never reach the position. At steering = 50, right motor does not turn. |
Thanks. I guess I can not write if speed or counts is zero, but it seems to me that a zero write should not show up as a stall. I'm adding sticky errors now - at least a first draft, since it changes the |
I'm confused by this. When the turn results in a non-turning motor, the relative position setpoint is also zero. This does seem like a driver bug. If you do
what does |
I found a bug that hides this problem for the case of positive 50 steering. Will comment on the relevant line. |
motorutil/steer.go
Outdated
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
stat, ok, err := ev3dev.Wait(s.Left, ev3dev.Running, 0, 0, false, s.Timeout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s.Left
should be device
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. That clears up that bit of confusion.
Fixed.
Same as the error message:
|
Cool, that explains it, but it looks to me like surprising behaviour from the driver. I can paper over it here by doing a |
Although this could be considered a driver bug, I don't think it would be good to rely on driver behavior here. We basically have a divide by zero error here. Not literally, but if you think about it, we are trying to move 0 distance at 0 speed, so we will get there in 0/0 seconds. We are requesting something that is physically undefined. So there isn't really a "right" thing for the driver to do. It would be simple enough to add a check here to say that if we have these conditions, then just send the |
Likewise, if you request a non-zero position setpoint, but a zero speed setpoint and expect the motor to move, that is a programming error. The |
Yes, that's true, but if a |
Ack. I'll see what I can do in the driver. It's really probably pretty simple to fix, now that I have thought about it some more. |
I've added the zero relative position conditional |
Now that that is sorted, back to general comments... After having played around with this a bit, I actually don't mind the error handling the way it is now. I just wrote a And the steering value works just like the EV3-G software, so it is easy to get the hang of. |
And a more general comment on ev3dev libraries in general... I would really like for ev3dev libraries to hide all of the driver implementation details from users. Why?
This is why I am hoping that you will take my suggestions here of using rotations and/or degrees here instead of "counts" and also allow for a negative speed parameter even though it works differently in the hardware drivers. Sure, people can figure these things by reading the documentation, but programming is way more fun when things just work as expected without having to read the docs at all. And I wouldn't be making a big deal out of it, but these two things in particular have come up repeatedly over the last 4 years. If my recollections can be trusted, I can safely say that users, in general, expect negative speed values to work and they expect motor rotations in degrees. |
One other thing that would be convenient and help future-proof would be to add a parameter to SteerCounts and SteerDuration for the stop action. |
While the original error handling was my natural first approach (the Go-idiom) it differed from the rest of the device methods, and so would have been surprising to users of those types. Are you OK with the new error-stateful error handling? |
I'll handle to comments in #50 (comment) and #50 (comment) later today if I get some free time. |
Yes. If I'm understanding correctly, I think it means that I will be able to write (sans error handling)
which is nice. And I can still use the |
|
Is there anything further that you would like done here? |
did you change the speed to allow a negative value? |
Sorry, I have now, thank you for reminding me. PTAL BTW Is there any plan to change |
I don't like to make breaking changes to the drivers at this point, but for this case, yes it is tempting to make the change in the driver... |
You are too fast. 😄 Here is my new test program. It works nicely. package main
import (
"log"
"time"
"github.com/ev3go/ev3dev"
"github.com/ev3go/ev3dev/motorutil"
)
func main() {
lMotor, err := ev3dev.TachoMotorFor("ev3-ports:outB", "lego-ev3-l-motor")
if err != nil {
log.Fatalln("Missing large motor on output port B")
}
rMotor, err := ev3dev.TachoMotorFor("ev3-ports:outC", "lego-ev3-l-motor")
if err != nil {
log.Fatalln("Missing large motor on output port C")
}
steer := motorutil.Steering{Left: lMotor, Right: rMotor, Timeout: 5 * time.Second}
if steer.Err() != nil {
log.Fatalln(steer.Err())
}
must(lMotor.SetStopAction("hold").Err())
must(rMotor.SetStopAction("hold").Err())
must(steer.SteerCounts(400, 0, 360).Wait())
must(steer.SteerCounts(-400, 0, 360).Wait())
must(steer.SteerCounts(400, 100, 360).Wait())
must(steer.SteerCounts(-400, 100, 360).Wait())
must(steer.SteerCounts(400, -50, 360).Wait())
must(steer.SteerCounts(-400, -50, 360).Wait())
}
func must(err error) {
if err != nil {
log.Fatalln(err)
}
} One thing I was going to suggest was to have a parameter to allow setting the stop action for both motors. Either that, or just make "hold" the default when you initialize a TachoMotor since that is what I tend to use 90% of the time anyway. |
Sorry.
This is part of the design which flows from Go philosophy. The motors are exposed so that they are obviously reconfigurable while held by the |
Though, I do see merit in doing that. I'll add a method |
@dlech Please take a look.
It has never been entirely clear to me the behaviour of the steering block at the ends of the steering range for EV-G, so this is a fudge. If you have a better suggestion I'll be happy to include it.