Poly's Python Projects
A blog describing some projects in Python especially focussed on learning Python and controlling a low cost robotic USB arm.
Tuesday 15 January 2013
Useful extension
You will gather that other events have got in the way of this hobby project. However there is an interesting development here: http://mattdyson.org/projects/robotarm/ which includes both controlling the arm from an XBox360 controller and provides a general class based on the orginal code here.
Monday 30 January 2012
File structure
In response to a comment asking for more detail on the csv file structure... ...here is some detail, my arm is not here right now so this is largely from memory and the notes I have and not tested:
Each line in the file should consists of seven values separated by commas. The first six values should be 0, 1 or 2. The last value is a floating point value (e.g. 0.3).
The first five values on each line indicate the direction that the shoulder, elbow, wrist, grip, rotation: 0 = no movement, 1=move in one direction, 2=move in the opposite direction.
The sixth value should turns the light on (1) or off (0).
The seventh value is the time in seconds that the motors should move at this combination of settings.
The file can be created in any text editor (e.g. notepad, leafpad etc.), or probably in MS Excel etc (by saving as CSV).
A file with the single line:
1, 1, 1, 0, 0, 0 0.5
Would move the shoulder, elbow & wrist motors of half a second with the light turned off.
Whilst this line:
0, 0, 0, 1, 1, 0, 0.75
Would move the grip and turn the light on for 0.75 seconds.
So the file:
1, 1, 1, 0, 0, 0, 0.5
2, 2, 2, 0, 0, 1, 0.5
Should move the shoulder, elbow & wrist motors of half a second with the light turned off, then move them back in the opposite direction for the same time - this time with the light on.
Whilst this moves the arm one motor at a time, flashes the light then moves all motors back at the same time:
1, 0, 0, 0, 0, 0, 0.6
0, 1, 0, 0, 0, 0, 0.6
0, 0, 1, 0, 0, 0, 0.6
0, 0, 0, 1, 0, 0, 0.6
0, 0, 0, 0, 1, 0, 0.6
0, 0, 0, 0, 0, 1, 1.0
0, 0, 0, 0, 0, 0, 0.5
0, 0, 0, 0, 0, 1, 1.5
0, 0, 0, 0, 0, 0, 0.25
0, 0, 0, 0, 0, 1, 0.65
0, 0, 0, 0, 0, 0, 0.33
2, 2, 2, 2, 2, 0, 0.6
I hope that helps any newcomers trying to set up files for the first time. You should be able to copy and paste those examples into your own text editor. You can call the file anything you want, and as you specify the extension you don't need to call it a .csv - but you may need to select the correct file format when saving.
Each line in the file should consists of seven values separated by commas. The first six values should be 0, 1 or 2. The last value is a floating point value (e.g. 0.3).
The first five values on each line indicate the direction that the shoulder, elbow, wrist, grip, rotation: 0 = no movement, 1=move in one direction, 2=move in the opposite direction.
The sixth value should turns the light on (1) or off (0).
The seventh value is the time in seconds that the motors should move at this combination of settings.
The file can be created in any text editor (e.g. notepad, leafpad etc.), or probably in MS Excel etc (by saving as CSV).
A file with the single line:
1, 1, 1, 0, 0, 0 0.5
Would move the shoulder, elbow & wrist motors of half a second with the light turned off.
Whilst this line:
0, 0, 0, 1, 1, 0, 0.75
Would move the grip and turn the light on for 0.75 seconds.
So the file:
1, 1, 1, 0, 0, 0, 0.5
2, 2, 2, 0, 0, 1, 0.5
Should move the shoulder, elbow & wrist motors of half a second with the light turned off, then move them back in the opposite direction for the same time - this time with the light on.
Whilst this moves the arm one motor at a time, flashes the light then moves all motors back at the same time:
1, 0, 0, 0, 0, 0, 0.6
0, 1, 0, 0, 0, 0, 0.6
0, 0, 1, 0, 0, 0, 0.6
0, 0, 0, 1, 0, 0, 0.6
0, 0, 0, 0, 1, 0, 0.6
0, 0, 0, 0, 0, 1, 1.0
0, 0, 0, 0, 0, 0, 0.5
0, 0, 0, 0, 0, 1, 1.5
0, 0, 0, 0, 0, 0, 0.25
0, 0, 0, 0, 0, 1, 0.65
0, 0, 0, 0, 0, 0, 0.33
2, 2, 2, 2, 2, 0, 0.6
I hope that helps any newcomers trying to set up files for the first time. You should be able to copy and paste those examples into your own text editor. You can call the file anything you want, and as you specify the extension you don't need to call it a .csv - but you may need to select the correct file format when saving.
Saturday 10 December 2011
A quick update:
I've obviously not been on for a while. The USB robotic arm project has essentially stalled, mostly because I have nothing "useful" to do with the arm. I think without combining it with some clever machine vision stuff its not going to be much use other than as a pure gizmo/toy. It was however fun trying, but I'm too busy with other projects right now to focus on this. If anyone does have any problems etc with the code I've written I am happy to see if I can help though.
Hopefully there will be some more interesting Python projects in the future which will let me resuscitate the blog!
Hopefully there will be some more interesting Python projects in the future which will let me resuscitate the blog!
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.
Subscribe to:
Posts (Atom)