A blog describing some projects in Python especially focussed on learning Python and controlling a low cost robotic USB arm.
Friday, 28 January 2011
Backlash!
Well, its been over a week since I last updated the blog, but in the meantime I've not been completely ignoring the code. I've added some improvements and will "release" an improved version with better error trapping in the next few days. But I have encountered a problem! Basically the backlash in the motors/gearboxes is such that moving the arm for 2 seconds in one direction is not "undone" by moving it for the same time in the opposite direction. I'm researching at the moment to see if the error is "per command", start/stop related or directional (e.g. gravity) etc. Once I understand the error I'll try to mitigate it (it probably won't be perfect but it has to be better than I get right now). Anyone found interesting ways to do this?
Wednesday, 19 January 2011
So the "main bit"...
So what's going on in main() then?
These statements, do the following:
This group of commands are only run if the command line options passed in a filename. If there is a filename, the file is opened (using the csv function). Then for each row in the file, we read it in, convert the values to integers (they should be 0,1 or 2 for off, up, down etc on the motor). The command in the csv file should be in the correct order (shoulder, elbow, wrist, grip, rotate, light and time (time being a floating point value)) so there is no need for any "clever" dictionary type stuff. These first 5 parameters are passed to "build command" which turns each 0,1,2 value into the 3 bytes which are sent to the arm to control it. This command is then sent to our device. If the command is successfully sent it returns "3" for the number of bytes written; there is a basic error trap here in case of failure. The software then waits for the time defined in the final parameter on that line of the csv file. Then sends a "0" command to the device to stop all the motors. The "zip" function is then used to zip the "keys" and the "values" together, these are then added to the "resetdata" so that it can be stored for later recall.
These commands are only processed if the -o option is used on the command line. (As its an elif statement it is not run if the file option has been run). They read in the data stored in resetarm.dat file, and then move each of the motors in the right direction to bring it back to the origin. Then it sets the resetarm.dat values to zero (by calling our zeroreset() function).
If the -z command line option was used (and -f or -o were not) then it resets the values in resetarm.dat to zero.
If none of the z/o/f command options were used then this code is run instead:
That lot has taken a bit longer to write than I expected so I don't think I'll detail each function at this stage - as they are all subject to change.
def main() : options=parse_commandline() resetdata=get_resetdata() dev=connecttoarm()
These statements, do the following:
- Gather the command line options work out what they are meant to do and store the details in options. Its does this by calling the parse_commandline() function.
- Get the contents of the resetarm.dat file (by calling the get_resetdata() function). Resetarm.dat file is explained a bit here, but is basically a little cheat that allows us to "remember" where the arm is. The file is not particularly human readable because the pickle function isn't intended to be easy to read. However the pickle function is effectively storing a python dictionary who's keys are each of the motor names and who's values are the time in seconds that the motor has been running (with positive values in one direction and negative in the other). The data is stored within the program in the "dictionary" resetdata
- Calls the connect to arm function, finds our device and allocated the details to the "handle" dev - so we can refer to it elsewhere.
if options.filename!=None : filehandle=csv.reader(open(options.filename,'rb'), delimiter=',') # opens the resetarm file to read for row in filehandle: rownumeric=[] for each in row[:6]: rownumeric.append(int(each)) command=buildcommand(*rownumeric[:6]) if sendcommand(dev,command)<>3 : print "Possible error sending data" rownumeric.append(float(row[6])) time.sleep(rownumeric[6]) ## need to increment reset file positions! sendcommand(dev) # create dictionary from values datadict=dict(zip(['shoulder','elbow','wrist','grip','rotate'],rownumeric[:6])) resetdata=store_reset_values(datadict, resetdata, rownumeric[6])
This group of commands are only run if the command line options passed in a filename. If there is a filename, the file is opened (using the csv function). Then for each row in the file, we read it in, convert the values to integers (they should be 0,1 or 2 for off, up, down etc on the motor). The command in the csv file should be in the correct order (shoulder, elbow, wrist, grip, rotate, light and time (time being a floating point value)) so there is no need for any "clever" dictionary type stuff. These first 5 parameters are passed to "build command" which turns each 0,1,2 value into the 3 bytes which are sent to the arm to control it. This command is then sent to our device. If the command is successfully sent it returns "3" for the number of bytes written; there is a basic error trap here in case of failure. The software then waits for the time defined in the final parameter on that line of the csv file. Then sends a "0" command to the device to stop all the motors. The "zip" function is then used to zip the "keys" and the "values" together, these are then added to the "resetdata" so that it can be stored for later recall.
elif options.reset_to_origin==True : for key in resetdata : if resetdata[key]<0 : move_to_reset(dev, key, 1, abs(resetdata[key])) elif resetdata[key]>0 : move_to_reset(dev, key, 2, abs(resetdata[key])) zeroreset()
These commands are only processed if the -o option is used on the command line. (As its an elif statement it is not run if the file option has been run). They read in the data stored in resetarm.dat file, and then move each of the motors in the right direction to bring it back to the origin. Then it sets the resetarm.dat values to zero (by calling our zeroreset() function).
elif options.zero==True : zeroreset() # zero the values in the reset file.
If the -z command line option was used (and -f or -o were not) then it resets the values in resetarm.dat to zero.
If none of the z/o/f command options were used then this code is run instead:
else : command=buildcommand( shoulder=options.shoulder, elbow=options.elbow, wrist=options.wrist, grip=options.grip, rotate=options.rotate, light=options.light) if sendcommand(dev,command)<>3 : print "Possible error sending data" resetdata=store_reset_values(options.__dict__, resetdata, options.timedelay) time.sleep(options.timedelay) sendcommand(dev) #turn off! dev.reset() #reset USB port (no need to release?)These commands essentially do the same as the data read in from the file but using the data provided at the command prompt - this makes them slightly more complicated as they aren't necessarily supplied in the "correct" order to feed directly to the "build command" function based on their position and so have to be passed with their names.
That lot has taken a bit longer to write than I expected so I don't think I'll detail each function at this stage - as they are all subject to change.
What's going on "under the hood"
I've been asked to explain a little about what is going on behind the scenes in the program. As everything is still in development I won't go into too much detail because there are various bits and pieces that I want to add which might result in changes to this code, but at the same time it might help if you knew roughly what the thinking was (if there was any!) behind what I wrote.
I thought the best thing to do was "walk through" the main program (i'm talking about v1.1, released here) and come back to discuss the various other functions in later posts.
Firstly some basics for anyone really new to python:
The real basics: the # symbol is used in python for comments - the program ignores anything that follows it on this line. These comments are either to remind myself of what something does or to help you follow it (or hopefully both).
The import command calls in other modules that this program uses, effectively saving writing complex code from scratch each time. The modules that the program imports are:
usb.core - the module which lets us communicate with the USB port. This is part of the pyUSB package which you need to install.
sys - imports some basic commands that allow communication with the system, e.g. command line, inputs-outputs, etc... however I don't think its actually being used (it was in a prototype when I was playing around) - so it might vanish in an update!
time - imports "time" functions - which,l not surprisingly measure time, introduce delays etc.
optparse - is a clever module which parses (gathers, chops up and allocates) the command line options and values
pickle - is another smart module from python's inbuilt libraries which lets you move chunks of data around rather easily - especially for loading and dumping variables to a file
csv - is a module specifically for importing and exporting comma separated value files
Each of the links above points to some detailed documentation about the relevant module.
As per convention the program calls the main() function via the line lurking right at the end of the file:
This will only directly run the main function if the program is run from the command line (or interpreter) but not if "imported" as part of another module.
I thought the best thing to do was "walk through" the main program (i'm talking about v1.1, released here) and come back to discuss the various other functions in later posts.
Firstly some basics for anyone really new to python:
# Python control for Maplin Robotic Arm # v 1.1 (c) Neil Polwart 2011 import usb.core, sys, time, optparse, pickle, csv
The real basics: the # symbol is used in python for comments - the program ignores anything that follows it on this line. These comments are either to remind myself of what something does or to help you follow it (or hopefully both).
The import command calls in other modules that this program uses, effectively saving writing complex code from scratch each time. The modules that the program imports are:
usb.core - the module which lets us communicate with the USB port. This is part of the pyUSB package which you need to install.
sys - imports some basic commands that allow communication with the system, e.g. command line, inputs-outputs, etc... however I don't think its actually being used (it was in a prototype when I was playing around) - so it might vanish in an update!
time - imports "time" functions - which,l not surprisingly measure time, introduce delays etc.
optparse - is a clever module which parses (gathers, chops up and allocates) the command line options and values
pickle - is another smart module from python's inbuilt libraries which lets you move chunks of data around rather easily - especially for loading and dumping variables to a file
csv - is a module specifically for importing and exporting comma separated value files
Each of the links above points to some detailed documentation about the relevant module.
As per convention the program calls the main() function via the line lurking right at the end of the file:
if __name__== '__main__':main()
This will only directly run the main function if the program is run from the command line (or interpreter) but not if "imported" as part of another module.
Labels:
imported modules,
python
Tuesday, 18 January 2011
Missing resetarm.dat file
If you are trying to use v1.1 ('released' here) then you will have discovered the "missing resetarm.dat file" bug that Skimitar talks about here.
The quick fix!
The simplest fix is to download the file from here and put it in the same directory as the arm_1_1.py file.
Why does it need this file?
One of the irritations about using the arm is that after many commands you often want it to go "back to the start" - either because you've gone off in the wrong direction or because you've completed the task you were trying to do. There is no "reset" command built into the arm, indeed the arm is totally unaware of its current position, so to "undo" the movement we need to keep track of the current position. The program does this by holding each motor position in a python dictionary which it then "dumps" to a file (so that it remains 'persistent' beyond this session of the program). Next time the program is run it loads the position as its starting position. When you then want to "reset" the arm you simply run it with the command line switch -o and it reverses total movement accumulated since its last reset. This seems to work quite well - although there is backlash in the motors and if you "overrun" any of the motors it will get confused - as a result sometimes you will need to manually adjust it and then "zero" the reset position using the -z command line option. I'm going to explain how each bit of the software was intended to work in a series of blog posts tonight and tomorrow - so watch this space for more info.
The long term fix
The next release will include exception trapping for this missing file which will enable the program to skip the read instruction if the file is missing.
The quick fix!
The simplest fix is to download the file from here and put it in the same directory as the arm_1_1.py file.
Why does it need this file?
One of the irritations about using the arm is that after many commands you often want it to go "back to the start" - either because you've gone off in the wrong direction or because you've completed the task you were trying to do. There is no "reset" command built into the arm, indeed the arm is totally unaware of its current position, so to "undo" the movement we need to keep track of the current position. The program does this by holding each motor position in a python dictionary which it then "dumps" to a file (so that it remains 'persistent' beyond this session of the program). Next time the program is run it loads the position as its starting position. When you then want to "reset" the arm you simply run it with the command line switch -o and it reverses total movement accumulated since its last reset. This seems to work quite well - although there is backlash in the motors and if you "overrun" any of the motors it will get confused - as a result sometimes you will need to manually adjust it and then "zero" the reset position using the -z command line option. I'm going to explain how each bit of the software was intended to work in a series of blog posts tonight and tomorrow - so watch this space for more info.
The long term fix
The next release will include exception trapping for this missing file which will enable the program to skip the read instruction if the file is missing.
Labels:
missing resetarm.dat
Amazing uptake level!
I wasn't sure anyone would be interested in this blog or my python progs - but it seems I am wrong and even my early stage experiments are attracting some interest! Someone has even gone as far as using my code as the basis for making a cup of tea:
Which perhaps isn't the most exciting video in the world! But I'm feeling good that my code has helped someone else have some fun! So if the code is helping you send my your videos, suggestions for improvement, feature requests or just let me know what you are up to! Oh - and I also want to hear your bugs as I've only got time for very limited testing.
Which perhaps isn't the most exciting video in the world! But I'm feeling good that my code has helped someone else have some fun! So if the code is helping you send my your videos, suggestions for improvement, feature requests or just let me know what you are up to! Oh - and I also want to hear your bugs as I've only got time for very limited testing.
Labels:
making tea robotic arm
Another version...
Well, we now have v1.1 (download here)
As predicted this introduces a number of new features which I will discuss in more detail in separate posts over the next day or two. The help screen (python arm_1_1.py --help) will provide general guidance for anyone itching to get going!
I've tidied up a few bits of the code into separate functions which I'll write about in coming days in case anyone wished to call them from their own code.
There is still very little error/exception handling built in so be aware that this may crash the program unexpectedly! You should also beware that there is nothing to stop you running the motor continuously at the limit of its range and screwing up the gearbox. I'm toying with the idea of adding some "safety checks" to prevent this - would that be useful or too restrictive?
Tip: if you do end up with the motor running continuously and need it to stop then simply run the program with no command line switches.
The key new features are:
I suggest you move your arm to some sensible origin point (midway on all motors?) and then run python arm_1_1.py -z to define this position as "zero". Now when you run commands (with the s,e,w,g,r, and f options) the program will store the movement details, and this can then be reversed by running python arm_1_1.py -o to bring the arm back to this start point. Beware if you get the dreaded "click click click of the motor going 'too far' the software is unaware of this and will "overshoot" on returning to origin.
The -f option calls a file with comma separated values listed as values of 0,1,2 for shoulder, elbow, wrist, grip, rotate, light and a floating point value for time. Multiple lines may be included. All parameters must be present and must be in the correct order.
EDIT: NOTE: Please read this post too - you will also need this file.
As predicted this introduces a number of new features which I will discuss in more detail in separate posts over the next day or two. The help screen (python arm_1_1.py --help) will provide general guidance for anyone itching to get going!
I've tidied up a few bits of the code into separate functions which I'll write about in coming days in case anyone wished to call them from their own code.
There is still very little error/exception handling built in so be aware that this may crash the program unexpectedly! You should also beware that there is nothing to stop you running the motor continuously at the limit of its range and screwing up the gearbox. I'm toying with the idea of adding some "safety checks" to prevent this - would that be useful or too restrictive?
Tip: if you do end up with the motor running continuously and need it to stop then simply run the program with no command line switches.
The key new features are:
-f FILENAME, --file=FILENAMEFirstly these options are all mutually exclusive, and applied in the priority order given above, using any of these options also ignores any of the individual motor controls (i.e. if -f somefile.txt is used, it will ignore any other options).
path/name of a file describing a sequence arm
movements
-o, --origin Restore arm to the 'origin' position
-z, --zero Zero all values in resetarm file' position
I suggest you move your arm to some sensible origin point (midway on all motors?) and then run python arm_1_1.py -z to define this position as "zero". Now when you run commands (with the s,e,w,g,r, and f options) the program will store the movement details, and this can then be reversed by running python arm_1_1.py -o to bring the arm back to this start point. Beware if you get the dreaded "click click click of the motor going 'too far' the software is unaware of this and will "overshoot" on returning to origin.
The -f option calls a file with comma separated values listed as values of 0,1,2 for shoulder, elbow, wrist, grip, rotate, light and a floating point value for time. Multiple lines may be included. All parameters must be present and must be in the correct order.
EDIT: NOTE: Please read this post too - you will also need this file.
Monday, 17 January 2011
Coming soon...
Since we seem to have gathered a dozen or so readers in only a few days I thought it might be useful to know what I have in the pipeline.
The next version (1.1) will include:
Subsequent versions will include:
What other features would be useful/helpful?
The next version (1.1) will include:
- The ability to process a file (csv?) which will contain a list of commands
- The ability to reset the arm to an "origin" position / reference point with a single instruction
Subsequent versions will include:
- a graphical user interface (windows type interface)
- better error trapping
- the ability to teach the arm a series of commands and then have then repeated back
- A library / module intended for others to call / import and use in their own program.
What other features would be useful/helpful?
Subscribe to:
Posts (Atom)