ev3dev / ev3dev-lang-python

Pure python bindings for ev3dev
MIT License
429 stars 145 forks source link

Implement BALANC3R #130

Closed dwalton76 closed 8 years ago

dwalton76 commented 8 years ago

Nice article here on these robotos: http://robotsquare.com/2014/07/01/tutorial-ev3-self-balancing-robot/

The author has an older blog post with a RobotC implemenation of the software: http://robotsquare.com/2012/02/13/tutorial-segway-with-robotc/

I started porting that to python here: https://github.com/dwalton76/ev3dev-lang-python/tree/gyro/demo/lego_official_projects

but do not have things working yet.

dwalton76 commented 8 years ago

@rhempel feel free to assign this to me. I created the ticket in case anyone else is interested in working on this with me.

antonvh commented 8 years ago

I'm actually working with Laurens on the python port. We're nearly there... Some trouble with r/w speed. It's patched in the development branch.

dwalton76 commented 8 years ago

Cool. Laurens and I were trading emails in Jan when I was working on this, he was very helpful. I got it somewhat working but it still needed some fine tuning. My branch is https://github.com/dwalton76/ev3dev-lang-python/tree/gyro

feel free to grab anything from there that you like. Laurens said he had a much better algorithm in the works so I stopped working on this until he got that published.

dlech commented 8 years ago

I have a program for the gyroboy in progress. However, I have been having trouble getting the loop time down as well. What kind of loop time are you able to get? I am at 20-25ms. Hoping to get it down below 10ms though. It seems like I was getting this 1+ years ago the last time I was doing a balancing robot in ev3dev. Basically, each sysfs read/write is 3 to 4ms, which is way too long. If we could get it down to 1ms, it would be ok. It depends on if there is something in python that can be changed or if the seek and read/write syscalls just actually take that long.

It's also interesting to me that the first iteration of the loop takes much longer. Here are my function call times in ms for the first iteration.

GT: 0
time: 0
GG: 14
GM: 12
EQ: 1
cntrl: 8
CHK: 1
sleep: 2

Then for a later iteration...

GT: 0
time: 0
GG: 4
GM: 8
EQ: 1
cntrl: 7
CHK: 1
sleep: 0

GG is reading the gyro sensor value. GM reads the motor positions of 2 motors and cntrl writes the duty_cycle_sp of 2 motors.

I've included the patch from @antonvh that removes the extra absolute path lookup, which, by the way, no longer seems to be in the develop branch (did @rhempel force push?)

dlech commented 8 years ago

Interesting. I was using python3, however times are better in python2.

Python 2.7.9 (default, Mar  1 2015, 13:52:09) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> timeit.repeat('f.seek(0); f.read()', 'f = open("/sys/class/tacho-motor/motor0/position", "r+")', number=1000)
[1.2495520114898682, 1.2299458980560303, 1.206686019897461]
Python 3.4.2 (default, Oct  8 2014, 14:47:30) 
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> timeit.repeat('f.seek(0); f.read()', 'f = open("/sys/class/tacho-motor/motor0/position", "r+")', number=1000)
[3.638115724999807, 3.6730630889996974, 3.7583993859989278]
dlech commented 8 years ago

hmm... I implemented the change I suggested for python3 in #155, but I'm not really seeing any changes in performance.

GT: 0
time: 1
GG: 3
GM: 6
EQ: 1
cntrl: 7
CHK: 0
sleep: 2
rhempel commented 8 years ago

No @rhempel did not pull in that patch yet (he thinks)

rhempel commented 8 years ago

First iteration is longer because it sets up the caching subsystem - after that the reads/writes go through already open file handles.

WasabiFan commented 8 years ago

No @rhempel did not pull in that patch yet (he thinks)

I think @dlech was pointing out that the PR was merged but the commits are no longer in the develop branch. That likely means that someone force-pushed while their working HEAD was behind the remote.

dlech commented 8 years ago

I've made a gyroboy program that does not use ev3dev-lang-python. I got the loop time down to about 15ms, so there is some overhead in ev3dev-lang-python that might be able to be shaved off, but the really just might be the best we can do with python and sysfs.

dwalton76 commented 8 years ago

Lauren's has a newer version of his balancing algorithm and he wrote it in Python. I made a class based version of it for ev3dev. I am out of town for work right now but will send a pull request this weekend when I get home. He has his cycle time at 10ms

antonvh commented 8 years ago

I did a python version of the balancer here: https://github.com/antonvh/segway It's forked from the original by @laurensvalk here: https://github.com/laurensvalk/segway

I used ev3dev and with my patch (https://github.com/antonvh/ev3dev-lang-python/commit/abdcae4b3fbf0902a7c515041885fb21ec13cf10) I get loop times of around 16-17ms which is enough to balance.

Laurens' version is without ev3dev and directly reads files. He's getting loop times of around 11ms. I think the ev3dev overhead is still too large. A few lines of python shouldn't make milliseconds of difference. I wonder where that comes from.

I also did a BrickPi version: https://github.com/antonvh/segway/tree/brickpi

On the Pi I get loop times of 10ms with ev3dev. But the BrickPi is acting up. It's all shaky and it feels like 'updateMotorsAndSensors()' is called too often by the driver. I have to investigate that some more, but I ran out of time.

laurensvalk commented 8 years ago

Actually, my loop takes about 6 ms. (I spend the remaining 4ms busy waiting.)

@dlech Feel free to try out this code. It is tuned for BALANC3R, but not much tuning of the gain values should be required for GyroBoy.

ddemidov commented 8 years ago

I think that ev3dev-lang-python overhead involves a couple of function calls (which are expensive in python), and a dictionary lookup for the cached file handle (even more expensive). This requires some testing, but I don't see how we could get rid of any of those.

antonvh commented 8 years ago

@ddemidov Would storing the file handle as a Device() property speed things up? This would maybe avoid function calls as Motors etc are subclassed?

On Thu, Mar 31, 2016 at 9:23 AM Denis Demidov notifications@github.com wrote:

I think that ev3dev-lang-python overhead involves a couple of function calls (which are expensive in python), and a dictionary lookup for the cached file handle (even more expensive). This requires some testing, but I don't see how we could get rid of any of those.

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/rhempel/ev3dev-lang-python/issues/130#issuecomment-203794226

ddemidov commented 8 years ago

Best way to find out is to run some tests (which I am not able to do now). The implementation should be careful not to leak file handles though.

dlech commented 8 years ago

On the Pi I get loop times of 10ms with ev3dev. But the BrickPi is acting up. It's all shaky and it feels like 'updateMotorsAndSensors()' is called too often by the driver. I have to investigate that some more, but I ran out of time.

updateMotorsAndSensors is actually only being called every 10ms in the current BrickPi drivers. I am increasing it to 4ms in the next kernel release for better motor control. I don't understand how calling updateMotorsAndSensors "too often" could be a bad thing.

rhempel commented 8 years ago

To void the cache lookup, maybe we should consider setting instance variables for the file handles (one per attribute) - we can easily create these programmatically based on the language spec. If the instance variable is None then we need to open the file.

I should probably build one of these balancing robots myself - for science :-)

dlech commented 8 years ago

I went as bare metal as I could (avoiding function calls, using low-level file io, etc.) and got the loop time down below 10ms and it seems to be working fairly well. It's not nearly as elegant of a program though.

https://gist.github.com/dlech/340ca6ae8bc545d14bc2c3b90b7e60fe/a6ef9d04cc4df5cbeaeda4b56e588531b2f740bd

dlech commented 8 years ago

I did try @laurensvalk's program with the GyroBoy. It works ok, but I found it to be "all shaky" like @antonvh's BrickPi version. I didn't try tuning it yet though.

laurensvalk commented 8 years ago

Gyroboy has much bigger wheels. Instead of tuning, you could put on the small wheels. Tuning essentially scales for this.

The center of mass is a bit different as well, but this should have a smaller effect.

I might provide values for Gyroboy too at one point; I just need to rebuild it.

laurensvalk commented 8 years ago

This is about as stable as it gets.

laurensvalk commented 8 years ago

This one actually runs ev3dev. Kudos to @antonvh for printing the line-following tiles.

dwalton76 commented 8 years ago

I submitted a pull request for what I have. https://github.com/rhempel/ev3dev-lang-python/pull/157

The bulk of the code is @laurensvalk 's, I just ported it to a class and added support to use a remote control.

rhempel commented 8 years ago

@dwalton, I will be rewriting the attribute access in ev3dev-lang-python to take advantage of some significant speed improvements. See ev3dev-lang-python https://github.com/rhempel/ev3dev-lang-python/issues/155

laurensvalk commented 8 years ago

This is great! When the speed improvement becomes part of the latest image release, I'll update my Segway code to work with ev3dev-lang-python instead of directly reading files.

edmundronald commented 8 years ago

My feeling is timing jitter is a worse problem for this type of control than the loop rate. If someone could manage to get precise 50 Hz or even 20Hz or so that should be plenty. Just a feeling. BTW

I made Gyroboy and it worked with some ev3dev Python code I got sent by dlech, but I had to tune the coefs a bit to "stiffen" the control; I also had to mod it for the right wheel size. Both took less than 1 hour. I made Balancer with the wheels I ripped off Gyroboy, and Lauren's Lego code and it worked straight off.

My request would be to implement some mechanism in ev3dev that allows python threads to be triggered at fairly precise intervals, that are reproducible whatever the OS version of the day. My intuition says that any simple PID code coeficients will be very sensitive to the time slice and the timing precision.

Edmund

rhempel commented 8 years ago

I got @dwalton76's code working with the latest version of core.py in the feature-remove-attribute-cache branch - the BALAC3R is a bit jittery, but that may be because we may need to tighten up the loop speed, or because the jitter on loop time is too high. The main thing is that it "just works" which is awesome. I have just got it balancing today - tomorrow is for getting the remote control and then maybe the line follower working. I am super excited about the new Python binding, and have learned a bit more about Python than I knew before. Special thanks to @laurensvalk for the BALANC3R instructions and to @dwalton76 for providing updated code. By working together and bouncing ideas off each other, we have managed to create a much better binding that what we started with.

dwalton76 commented 8 years ago

cool :) FYI the pull request code has remote control support already...hadn't done anything with line following though.

laurensvalk commented 8 years ago

@edmundronald :

My feeling is timing jitter is a worse problem for this type of control than the loop rate. If someone could manage to get precise 50 Hz or even 20Hz or so that should be plenty

There is some interesting discussions and some attempts at this in #324.

I will retune the code for 20 ms (50 Hz) so we have some more space for other things. But even at 10 ms (100 Hz) jitter isn't really an issue. Tuning seems to be more critical. I'll see if it helps to account for battery level.

@rhempel :

The main thing is that it "just works" which is awesome. I have just got it balancing today - tomorrow is for getting the remote control and then maybe the line follower working.

I've not yet checked @dwalton76 's modifications, but there should be a "steering" and "speed" variable that you can modify without worrying about the balancing algorithm.

Then line following becomes just two lines:

speed = 10 #Or more, depending on how tight the corners are

steering = (RelectedLightValue - Threshold)*ScaleFactor

This is essentially a proportional controller where ScaleFactor controls the tightness of the turn. Depending on the light sensor value mode you choose (raw or percentage), you'll want to start with a ScaleFactor such that steering always ends up approximately in the range [-20, 20] and then tune it for slightly more or less tight turns.

laurensvalk commented 8 years ago

@dwalton76 I see you currently have a function for remote control:

 def make_move(self, button)

Perhaps it would be nice to have a slightly more general purpose function like this:

def make_move(self, speed, steering)

Then line following and remote control in the main loop can use this to interface with balancing.

rhempel commented 8 years ago

Right now @dwalton's code does not have the light sensor built in, just the remote control. I may pull in your line following code while I'm at FIRST in St Louis this week

dwalton76 commented 8 years ago

@rhempel any objections to me approving my own pull request (or if you want to do it, either way is fine)? Then someone can add line follower support on top of what I've already done.

dwalton76 commented 8 years ago

@laurensvalk I need to look at it some more but I think make_move() is expected to have only that one button arg so that it can play nicely with the remote-control hook.

rhempel commented 8 years ago

@dwalton, go ahead and approve your PR for the demo code changes

edmundronald commented 8 years ago

Can someone volunteer to help me get this,code on my EV3? I had a version running Gyroboy- am ok with Linux but don't know about Git.

btw I think timing jitter is equivalent roughly to multiplicative noise (rather than additive). I have a background (PhD) in neural net control.

dwalton76 commented 8 years ago

@edmundronald roughly you want to do

dwalton76 commented 8 years ago

PR merged

edmundronald commented 8 years ago

@dwalton thank you very much. I will do this and come back.

Edmund

edmundronald commented 8 years ago

@dwalton76 Done.

(Ran the update process specced here http://www.ev3dev.org/support/ Prefixed sudo to your instructions, and ran the rest. )

What now? Where is the latest compatible code for Balancer, how do I run it?

Edmund

edmundronald commented 8 years ago

@dwalton76 I tried running a file in the demo dir, not much luck.


robot@ev3dev:~/ev3dev-lang-python/demo$ ls -l total 24 -rwxr-xr-x 1 robot robot 4582 Apr 27 23:12 auto-drive.py -rwxr-xr-x 1 robot robot 844 Apr 27 23:12 BALANC3R drwxr-xr-x 2 robot robot 4096 Apr 27 23:12 lego_official_projects -rw-r--r-- 1 robot robot 226 Apr 27 23:12 README.rst -rwxr-xr-x 1 robot robot 3787 Apr 27 23:12 remote-control.py robot@ev3dev:~/ev3dev-lang-python/demo$ python BALANC3R 2016-04-27 23:48:41,169 INFO: Starting BALANC3R 2016-04-27 23:48:41,443 ERROR: unsupported operand type(s) for +: 'NoneType' and 'str' Traceback (most recent call last): File "build/bdist.linux-armv5tejl/egg/ev3dev/GyroBalancer.py", line 281, in main touchSensorValueRaw = open(self.touch._path + "/value0", "rb") TypeError: unsupported operand type(s) for +: 'NoneType' and 'str' Traceback (most recent call last): File "BALANC3R", line 28, in robot.main() File "build/bdist.linux-armv5tejl/egg/ev3dev/GyroBalancer.py", line 400, in main File "build/bdist.linux-armv5tejl/egg/ev3dev/GyroBalancer.py", line 182, in shutdown NameError: free variable 'touchSensorValueRaw' referenced before assignment in enclosing scope

robot@ev3dev:~/ev3dev-lang-python/demo$

rhempel commented 8 years ago

@laurens or @antonvanh - do you have the balancer code with the line follower as well?

laurensvalk commented 8 years ago

@rhempel : Yes, I just uploaded the line following example to my github just for you. I'm guessing it is for the FIRST festival so I'll keep it short. You need to:

Also, you just tagged another Github user called Laurens.

laurensvalk commented 8 years ago

Or if you use Git, this is all you need to do, as I believe it maintains the executable settings:

@edmundronald in your case, if you don't need line following just yet, run the other program, so just do:

edmundronald commented 8 years ago

@laurensvalk I followed your instructions. The program segway.py seems to run, except it doesn't do anything apart from exiting when I press the touch sensor. Here is the console output.

robot@ev3dev:~/segway/ev3/ev3dev/python$ ./segway.py
-----------------------------------
Calibrating...
GyroOffset:  0.07
-----------------------------------
GO!
-----------------------------------
Loop time: 10.436174568965518 ms
-----------------------------------
STOP
-----------------------------------
robot@ev3dev:~/segway/ev3/ev3dev/python$ 
edmundronald commented 8 years ago

@laurensvalk I've checked that the motors are on A and D, and are seen by Brickman, the values change when I twist the wheels. And I extracted the SD card and ran your Balanc3r-Remote-control project with great success, it balances. It's just the Python version which doesn't move the wheels. Any ideas?

btw, I also ran the demo in https://github.com/rhempel/ev3dev-lang-python.git and it runs errorfree, now that I have added the touch sensor to the hardware, but it also doesn't move the motors.

Edmund

dlech commented 8 years ago

my guess is a kernel version/ev3dev-lang-python version mismatch. there have been breaking changes to the motor drivers, so if they don't match, motors don't work.

Also, I would suggest https://help.github.com/categories/writing-on-github/

if you put a "code fence" around your terminal output, it makes it much easier to read.

^ this is the start of a code fence

code goes here

v this is the end of the code fence

edmundronald commented 8 years ago

@dlech What should I do at this point? Until "hello world" runs, I am slavishly following instructions. I have now scanned through the netiquette link. Here anyway is a codefence test:

Ceci n'est pas du code
I wish there were backticks on my french keypboard
Buvez de ce whisky que le patron juge fameux
dlech commented 8 years ago

You can edit your previous post so we can read it. :wink:

Also, what kernel are you running on your EV3 (output of uname -r)?

edmundronald commented 8 years ago

@dlech Edited. Now, why didn't I think of that by myself? :)

robot@ev3dev:~$ uname -r
3.16.7-ckt26-10-ev3dev-ev3
robot@ev3dev:~$