Pages

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?

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:

# 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.

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.

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.
 

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:
      -f FILENAME, --file=FILENAME
                        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
Firstly 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).

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:

  1. The ability to process a file (csv?) which will contain a list of commands
  2. The ability to reset the arm to an "origin" position / reference point with a single instruction
If the coding goes smoothly these features may go live late tonight.

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.
These may not happen in that order, and I don't know how long each will take.

What other features would be useful/helpful?

Why the adverts?

The adverts in the blog are as much an experiment as the software itself! 

I'm intrigued to see how much 'revenue' a Google AdSense can bring to a small niche blog.  Likewise I've registered for Amazon Associates.  The site isn't costing me anything (except time) so doesn't need to make money.  But I've been advertising through Google Ads in my day job for sometime, and wondered how the experience is on the other side.  Amazon Associates - seems logical since I will from time-to-time mention books here anyway.

Sunday 16 January 2011

Parsing commands from the command prompt

Beginning Python: From Novice to Professional whilst an excellent and lengthy book doesn't cover in any detail parsing commands from the command prompt (whilst it tells us optparse exists - it doesn't have the space to explain how to use it).  The Python Documentation whilst detailed is "hard work" - and I've now discovered a much better tutorial on optparse - which will enable some refinement of the next iteration of software.

New version!

Well the code I posted earlier was just about the simplest control you could have for the robotic arm.  I've been playing around tidying it up a lot into some functions and have just tested a new version.  Its far from perfect - but I think it represents the next milestone in development: the arm movement is defined through a series of command line arguments / switches.  You can download the file here.

The simplest way to understand how to use it (as a user rather than a programmer) will be to run the following command:
python arm_1.py --help
 which will bring up something like:
 Python program for connecting to USB Robotic Arm
Usage: arm_1.py [options]

Options:
  -h, --help            show this help message and exit
  -s SHOULDER, --shoulder=SHOULDER
                        Value 0,1,2 for shoulder motor (off/up/down)
  -e ELBOW, --elbow=ELBOW
                        Value 0,1,2 for elbow motor (off/up/down)
  -w WRIST, --wrist=WRIST
                        Value 0,1,2 for wrist motor (off/up/down)
  -g GRIP, --grip=GRIP  Value 0,1,2 for grip (off/in/out)
  -r ROTATE, --rotate=ROTATE
                        Value 0,1,2 for rotation (off/cw/ccw)
  -l LIGHT, --light=LIGHT
                        Value 0,1 for light (on/off)
  -t TIMEDELAY, --time=TIMEDELAY
                        Time (s) to wait before off default0.1
So e.g. python arm_1.py -g 1 -w 2 will close the gripper and move the wrist down simultaneously for 0.1 seconds.  Whilst python arm_1.py -r 2 -l 1 -t 3 will rotate the arm in the base counter clockwise and turn the light on for 3 seconds.

NOTE Depending on how you have your user privilidges set up you may not have access to the USB port - I need to run all these commands as root, e.g. sudo python arm_1...



Syntax highlighting

I've added Syntax Highlighting to the blog using the method described here: http://heisencoder.net/2009/01/adding-syntax-highlighting-to-blogger.html which should help with readability.

Some basic troubleshooting

I'm no expert in USB or Linux, but here are a few debugging things which might be useful for others trying to get the arm working.

1.  Is it switched on!  Without the switch in the on position the device won't show up - should be obvious but is of course worth checking!  I also think if you leave it on doing nothing it seems to switch itself off (although thats an assumption because it seemed to need "reset" this morning).  This also means that dead batteries will presumably mean no response from the device.

2.   Run lsusb at the command prompt.  You should see something like this amongst the response:
Bus 002 Device 022: ID 1267:0000 Logic3 / SpectraVideo plc
3. If you are having problems then the command dmesg may help diagnose the problem, although where to start may not be easy - but here's what a working system reports with dmesg | grep USB reports:

[ 1845.033134] usb 2-1: new low speed USB device using uhci_hcd and address 4
If you disconnect the cable and run again you will then see
[ 1952.176133] usb 2-1: USB disconnect, address 4
4. If the light works but the motors don't (this happened to me) it is probably a wiring problem.  In my case the single orange wire had come disconnected from the "pins" on the board and just needed slipped back on.

The simplest code!

Here is a very simple python code which will let you control the arm:

# Minimalist version of USB arm control to show how simple it could be! (c) N Polwart,  2011

import usb.core, time

dev = usb.core.find(idVendor=0x1267, idProduct=0x0000)
if dev is None:
    raise ValueError('Device not found')           # if device not found report an error

dev.set_configuration()

datapack=0x40,0,0      # change this to vary the movement
bytesout=dev.ctrl_transfer(0x40, 6, 0x100, 0, datapack, 1000)

time.sleep(1)    # waits for 1 second whilst motors move.

datapack=0,0,0
bytesout=dev.ctrl_transfer(0x40, 6, 0x100, 0, datapack, 1000)

A version with some more detailed comments / documentation and user feedback when it runs is available to download arm_0.py

There programs all require the pyUSB library - which can be downloaded from sourceforge.  This is the version I am using.  Download it, extract the zip file and run "python setup.py install" and it should be done.

Why Python? Why Linux?

Since the whole raison d'etre for this blog is that I'm teaching myself Python it might make sense to explain why!  I've programmed in one form or another right back to my first home computer - a 48k Spectrum back in the early 1980s - generally speaking all simple programs to address a specific need I had at the time.  As a result I've ended up with a basic understanding of BASIC (several variants including Visual Basic and VBA), COMAL (a language used mainly in schools for teaching (and possibly not at all any more!), C++, Delphi (Pascal), Javascript and Fortran which combined gives you the ability to read most languages.  However I am far from an expert in any of them - so when I needed to write a program to control a serial device from either a Linux or Windows computer I started exploring the options.  After some searching it became clear that this could involve a bit of effort porting across platforms - and even C++ which is routinely "offered" on both platforms was likely to throw up problems with needing different serial libraries.  I was then introduced to Python and clicked with it immediately - perhaps those early days with BASIC and COMAL interpretters on a Spectrum and BBCB convinced my of the advantages of Interpretted languages!  When my 7 year old son declared he'd like to write a program I chose Python and he was learning the elementary steps quickly - with none of the pain involved in compiling etc, but I had also seen some of the really advanced features and impressive programs which had been written in Python and I was convinced. 

So why do I want to program in both Linux and Windows.  Firstly let me say whilst I am pretty computer literate and reasonably confident with most things I am not a serious Linux geek!  I'm also not a Windows/Microsoft hater, but I do get frustrated when computers which ran fine when they were new slow down amongst clutter either legitimately added by users or malware acquired through use.  I started off my experiments with Linux a couple of years ago using PCLinuxOS to overcome this and enable me to use a really cheap PC and whilst I found some things worked really well - I also found a few things incredibly frustrating - in particular doing anything "non standard" as the PCLinuxOS philosophy is not to tweek the OS in case you break it.  One day I did break it and rather than reinstall PCLinuxOS decided to try Mint - a different distribution of Linux which is also designed to be really simple to use, especially for people migrating from Windows.  Mint was amazing and essentially everything (except an old webcam I have) works out the box and just how you would expect it to!  I've also got a netbook (Acer Aspire A110) which until recently was running the Linpus Linux OS that came with it - but has just been moved onto the Mint LXDE distribution.  Mint is based on Ubuntu (the most popular distribution of Linux) - so if something I suggest works for me in Mint it will almost certainly work on native Ubuntu too.  In fact it will probably work on any Linux as it will be unusualy for me to be doing anything too clever!

Whilst I like Mint, and almost never have to go back to Windows for normal home or office tasks, it would be niave to ignore the huge number of machines running Windows, and I have some scientific apparatus which only runs in Windows - so any code I need to run there also needs to run in Windows too.

I'll start off this project with the following basic assumptions about anyone who wants to borrow, use or modify my code:
  1. You are running Linux.  The code should port to Windows (or other systems) with minimum effort - but at this stage I'm only writing it with Linux in mind.  A "next step" will be to make it cross platform!
  2. You already have Python installed.  This is probably the case if you are using Linux but if not then it should be readily available via apt / yum / synaptic or whichever package manager you use.  e.g. something like: sudo apt-get install python
  3. I'm using version 2.6 of Python - I can't guarantee backwards compatibility but will highlight anything that becomes obvious to me as an issue.  The change to Python v3 involves some major differences.  I see it as a future project to check and fix any forward compatibility issues (probably once v3 is finalized and a bit more established).
  4. I assume you already know (or will work out) how to use Python, and edit the source code files etc.  I intend as things develop to produce a "distributable" version which will allow any old person to install and run my code - but the early stuff will all by simply *.py files and you'll need to install libraries etc if they are missing (anything I need to download I'll briefly describe how/where).
  5. You'll recognise this is a work in progress, from someone developing their knowledge so it comes without any guarantees or promises it can be made to work on your system.  If you want to be confident you can get your robot arm to work use the software supplied by the manufacturer and run it in Windows!  Documentation will not be at a "professional" level!
  6. This is a bit of fun and learning for me.  If you want to use the code feel free.  If you want to modify the code feel free.  If it is being published or used in a public arena then I'd ask for an acknowledgement in the source code and on any "credits". If you make improvements then why not send me the code (or post it in a comment) and then it can be shared here for everyone to see.  In the unlikely event you think there is money to be made from this (or a derivative of it) then please get in touch and we can formalise license terms.

Saturday 15 January 2011

In the beginning...

So a quick explanation of why this page exists...


I've been playing with the language Python for a few months now for ad hoc small projects related to my day job.  I've been meaning to learn it in a slightly more structured manner, and got this book for Christmas.

I also got given this robotic arm from Maplin (http://www.maplin.co.uk/robotic-arm-kit-with-usb-pc-interface-266257) and since it has no Linux software I thought this would be a good opportunity to combine learning and practicing some of the more sophisticated Python features, whilst at the same time producing an easy to use Python script that others could make use of.

In my googling for Linux software for this arm I found this: http://www.linuxquestions.org/questions/programming-9/c-code-for-maplin-usb-robot-arm-851033/ - but since there were questions about it actually working and it would mean part learning another language variant (I'm vaguely literate in at least half a dozen computer languages but fluent in none!).  That didn't suit - but did provide some useful info on the possibility of doing it in Python.  A few days after I started some C code was then published too (http://notbrainsurgery.livejournal.com/38622.html) - which I've not tried - but aware that non programmers are not likely to embrace C as much as they might with Python I decided to finish what I started.  Last night I debugged my first prototype and got it moving the arm.  My plan is to post a cleaned up version of that code as the beginning of this page sometime this weekend, and then add more advanced functions and packaged code which might be useful for total new comers etc.

Of course I'd be delighted to hear about features you think would be useful and I'll consider them; or indeed I'm quite happy for you to modify the code to include those features yourself - feel free to send me such improvements if you'd like me to share them here.