Saturday, 28 April 2007

Ever decreasing circles

I wrote some test code in Python to check that my firmware was working properly. This turned out to be incredibly easy using the socket and struct modules. I have not written a lot of Python before but it never ceases to amaze me how much more productive it is compared to C++. Handling the Ethernet UDP comms required only a few lines of code. Here are extracts from the constructor and destructor :-
from socket import *
from struct import *

class Hydra:
def __init__(self, ip_addr):
# Create sockets
self.sendSock = socket(AF_INET,SOCK_DGRAM)
self.sendAddr = (ip_addr,21000)

self.recvSock = socket(AF_INET,SOCK_DGRAM)

def __del__(self):
# Close sockets
Below is the code that sends a command and receives the reply. UDP packets are not guaranteed to arrive at their destination and if they do they are not guaranteed to arrive in the same order they were sent in, although on a small single segment LAN they generally do. Packets are protected by CRCs and checksums so if you do receive a packet you can be pretty sure it is not corrupt.

To make a reliable protocol timeouts, retries and packet sequence numbers are required. I also added a 4 byte magic number at the start of each packet just to make sure a stray packet from a rogue application or the Web could not cause HydraRaptor to lose the plot.
    def do_cmd(self,cmd):
tries = 3
while tries:
self.sendSock.sendto('\x12\x34\x56\x78' + cmd,self.sendAddr)
data,addr = self.recvSock.recvfrom(1024)
return data
except timeout:
print "read failed"
tries = tries - 1
raise 'comms failed'
Here is the code to wait for the machine to be ready and then instruct it to go to to a 3D position.
   def get_status(self):
return unpack('>hhhhBB', self.do_cmd('\x00\x00'))

def goto_xyz(self,pos,delay):
while 1:
a, b, c, steps, seq, ready = self.get_status()
if steps == 0 and ready:
seq = (seq + 1) & 255
self.do_cmd(pack('>BBhhhh', self.goto_xyz_cmd, seq, pos[0], pos[1], pos[2], delay))
Notice how easy it is to marshal and unmarshal packets using the struct module. The big endian to little endian conversion is handled simply by putting a > sign at the start of the format string.

Below are some test patterns. The 25 circles in the centre are 1 - 25mm radius and are drawn with the minimum number of line segments to keep within 0.05mm accuracy. This is calculated with the formula
π / acos((r - 0.05) / (r + 0.05))
The outer circle is made with 360 line segments.

Not only can I now drive HydraRaptor from a script but I can also interact directly with it via the command line:
>>> hydra = Hydra("")
>>> print hydra
At (9944,11943,400) steps = 0
>>> hydra.goto_xyz((0,0,0),1000)
>>> print hydra
At (3310,3976,133) steps = 3976
>>> print hydra
At (0,0,0) steps = 0
I noticed that although the Z axis is repeatable in the short term the distance to the paper varied by about 0.1mm over a two day period. I expect this is due to the MDF expanding and contracting slightly. I plan to make a tool height sensor mounted on the XY table to make it easy to switch tools. This should also compensate for the wood changes.

The next thing to do is use my existing milling setup to make some motor mounts to make an improved milling machine. To do this I will extend my Python script to allow me to define an outline as a list of lines and arcs and then mill around it.

I will then try to make the RepRap FDM extruder with the improved milling machine.


  1. True, python is productive compared to C++.

    I don't like the fact that it is a bit prescriptive about how you use whitespace though.

    Ruby is nice - you should try it if you haven't already!


  2. Yes must give Ruby a try sometime, EW also speaks highly of it. I like the Python whitespace thing because it forces people to indent their code properly and removes the need for braces. It's a pain if you want to comment bits of code out though. It's particularly a pain with this blog as it seems to have a bug which eats whitespace each time you edit a page.

  3. Well, Ruby doesn't (often) use braces, or at least not in the same way.
    Longer code blocks use a "do/end" type thing, but the "do" somehow looks right on the end of the conditional (or whatever) whereas in C[++] opening braces look *wrong* there!
    People shouldn't need to be forced to indent properly. Competent engineers indent properly anyway. If anyone doesn't, their code isn't worth looking at anyway!

    Anyway, I'm enjoying your blog. Once you've got one up and running, I'll have to get you to replicate me one! :)


  4. Don't I say "anyway" a lot!

    Usually a sign of someone with little useful or interesting to say. Not in this case obviously! :-P


  5. Yes if it works you are welcome to a replicated set of parts. The RepRap ethos is that you are duty bound to pass on at least two to keep the growth exponential. I am all for world domination !