The AxiDraw server is useful if:
The AxiDraw server is not useful if:
Our Python server uses the super-cool "Unofficial Python library" called axi from fogleman at https://github.com/fogleman/axi.
Fogleman's axi library handles the low-level USB location of and communication with the AxiDraw plotter. We use this support to establish a pipe of communication with the plotter.
Fogleman's axi library also has a collection of handy high-level utilities to do path planning and other tasks which you can access as an option (more below).
So, first follow the installation instructions on Fogleman's github page to install the axi package in your copy of Python.
Python axi module Installation tip: the particular MacBook on which I was installing Fogleman's axi package happened to have both python and python3 installed, and this caused huge problems for me because various parts of the installation kept trying to use python instead of python3. While Fogleman's module is downloadable from his github page, his module depends on 4-5 other modules that the installer must download from a central repository, and this failed for me repeatedly. You can tell that some part of the installer is using python (not 3) because you will get inscrutable TLS/SSL errors that make no sense. I had to change various parts of the instuctions by adding 3's everywhere, including using pip3 and I think even modifying one script involved in the installation, and also I had to manually install each of the 4-5 modules you will see mentioned when you try to install, before axi would finally install successfully. It's better if you just don't have pre-3 python at all and if your python3 is called just "python."
You will not need Fogleman's axi command-line program to use our server, but it's a good test to make sure this layer is working ok.
Here are a few examples of EBB commands (see their documentaition for all the details):
V # get version of AxiDraw firmware SC p v # configure params EM m m # enable XY motors m=microstepping mode EM 0 0 # disable XY motors QM # motor status (1 somewhere means still moving) SP 0 delay # pen down SP 1 delay # pen up XM duration a b # stepper moveThis lets you control the plotter X, Y, and Z position at the most basic level.
Advantage: You have full control of the plotter.
Disadvantage: You have to do all your own path planning for efficient drawing of complex scenes (e.g. slow down around curves, draw lines close together, etc.).
Possible Dangers: You can easily permanently damage your plotter if you tell it to go past its XY or Z (pen height) range, and probably in other ways too. Use at your own risk!
The PATHCMD command has three sub-commands:
PATHCMD drawing_start PATHCMD stroke <number_of_points> x y x y x y ... PATHCMD drawing_endEach stroke sub-command describes the path of one pen-down-move-move-move-pen-up sequence, with an unlimited number of moves per stroke and an unlimited number of strokes.
Here's an example:
PATHCMD drawing_start PATHCMD stroke 3 0.1 0.4 0.6 2.3 3.4 2.0 PATHCMD stroke 2 0.1 0.6 2.3 3.4 PATHCMD drawing_endThe x and y coordinates are scaled so that 0.0 maps to coordinate 0 on the printer (closest to the USB connector in both axes) and 1.0 maps to the V3_SIZEX/V3_SIZEY scaling value (in inches) from axiserver.py. You can use coordinates greater than 1.0 and they will simply extrapolate beyond V3_SIZEX/V3_SIZEY. So for example if you have an 8.5 x 11 inch piece of paper and you set V3_SIZEX == V3_SIZEY == 8.5 then you can access the extra 2.5 inches in one dimension by using x values greater than 1.0. You will generally want to set V3_SIZEX == V3_SIZEY unless you use the special NX/NY feature described below.
Advantage: You get the high drawing speed and accuracy benefits of path planning.
Advantage 2: If you issue several drawing_start/drawing_end commands in a row, and you have set the NX and NY parameters in axiserver.py to something other than 1, then axiserver.py will subdivide your paper into sub-rectangles and send each plot to a new sub-rectangle. The 0.0-1.0 values described above get further scaled down to fit the sub-rectangle. This is great when you are debugging some plotting code and you don't want to keep replacing the paper or moving the plotter head. If NX != NY then you will want to set V3_SIZEX/V3_SIZEY accordingly to prevent your drawing from being stretched or squished in one axis. For example, if NX==3 and NY==2 then a reasonable scaling value to keep things in proportion is V3_SIZEX==12 and V3_SIZEY==8.5.
Disadvantage: You don't have as much control as the low-level API (which is useful for things like our 3-D AxiDraw Plotting Hack). You only have access to one small part of what Fogleman's library offers.
Possible Dangers: You can still easily permanently damage your plotter if you tell it to go past its XY or Z (pen height) range, and probably in other ways too. Use at your own risk!
" print("hi") "and the server will happily "exec" your code, no matter what it is.
Advantage: You have access to the Fogleman axi object and so you can use any feature of his axi Python library, or for that matter any other library or any facility available to Python!
Disadvantage: You have to figure out the axi library, which is a bit more complex than PATHCMD.
Definite Danger: SUPER SUPER SECURITY HOLE! This feature can only be used on a secure local network where you trust every device on the network. It essentially gives the whole world complete access to your computer without a password! They can do anything a Python script can do, including delete all your files and run programs. For that reason, Method 3 is disabled by default. You can enable the feature by uncommenting the "exec" line in the server. Use at your own risk!
Possible Dangers: You can still easily permanently damage your plotter if you tell it to go past its XY or Z (pen height) range, and probably in other ways too. Use at your own risk!
% python3 axiserver.pyBy default, the server listens on the highly creative port 12345, but you can change this in the code where it says
CHANGE PORT NUMBER HERE
.% telnet myserver 12345You can type commands and see responses immediately.
# # axiserver.py - tiny AxiDraw plot server # # Chris Pirazzi and Paul Haeberli # # For details and instructions, see https://lurkertech.com/axiserver # import axi import sys device = axi.Device() # V3_SIZEX and V3_SIZEY # - this value is NOT the size of your paper # - this value DOES control how PATHCMD scales x and y to your paper size # - x,y coordinate values passed to PATHCMD get mapped into to this range # - with 0.0 mapped to 0 (closest to the USB connector in both axes) # - and 1.0 mapped to V3_SIZEX or V3_SIZEY # - you can use coordinates >1.0 no problem # - they simply extrapolate beyond V3_SIZEX or V3_SIZEY # - so with an 8.5 x 11 inch paper you might choose V3_SIZEX==V3_SIZEY==8.5 # - with x coordinates > 1.0 to reach the extra 2.5 inches # - it's important that V3_SIZEX==V3_SIZEY if NX==NY==1 # - otherwise your PATHCMD drawing will be stretched or squished in one axis # - if you set NX and NY to values other than 1 # - V3_SIZEX and V3_SIZEY must have the opposite proportion to avoid squishing # - for more details, see https://lurkertech.com/axiserver # V3_SIZEX = 8.5 V3_SIZEY = 8.5 # - sets the division of the plotter surface # - each new plot goes into the next place on the paper # - helpful when you're debugging some plotting code # - lets you avoid constantly putting new paper or manually moving the pen # - to plot on the whole surface set these to 1 # - if these are 1, V3_SIZEX should equal V3_SIZEY # - if these are not 1, V3_SIZEX/V3_SIZEY must stay in proportion # - for more details, see https://lurkertech.com/axiserver # NX = 1 NY = 1 PADDING = 0.1 # how the plot area is divided CELLSIZEX = V3_SIZEX/NX CELLSIZEY = V3_SIZEY/NY POSX = 0 POSY = 0 def nextpos(): global POSX global POSY POSX = POSX+1; if(POSX == NX): POSX = 0 POSY = POSY+1 if(POSY == NY): POSY = 0 def goodbuf(buf): if not buf: return False if buf[0] == 0xff: return False if buf == '': return False return True sofar = "" def recv(connection): global sofar while True: off = sofar.find("\n"); if -1 != off: break #print("reading more from connection") buf = connection.recv(1024) if not buf: return None if buf[0] == 0xff: return None if buf == '': return None buf = buf.decode("utf-8") #print("read [" + buf + "] from connection") sofar += buf ret = sofar[0:off]; ret = ret.rstrip() sofar = sofar[off+1:] #print("remaining sofar is [" + sofar + "]") #print("returning [" + ret + "] to caller") return ret; curpath = [] paths = [] def stroke_start(): global curpath curpath = [] def stroke_addpoint(x, y): global curpath curpath.append((x, y)) def stroke_end(): global paths global curpath paths.append(curpath) def drawing_start(): print("DRAWING START") global paths paths = [] def drawing_end(): global paths d = axi.Drawing(paths) d = d.scale(1.0, -1.0) d = d.scale_to_fit(CELLSIZEX, CELLSIZEY, PADDING) d = d.translate(POSX*CELLSIZEX, POSY*CELLSIZEY) axi.draw(d) nextpos() print("DRAWING END") print("") def pathcmd(*ary): print(ary) if ary[1] == "drawing_start": print("PCMD: start") drawing_start() elif ary[1] == "drawing_end": print("PCMD: end") drawing_end() elif ary[1] == "stroke": print("PCMD: stroke") npoints = ary[2] print("npoints" + npoints) stroke_start() for i in range(int(npoints)): x = float(ary[2*i+3]) y = float(ary[2*i+4]) print("pointx: " + str(x) + "pointy: " + str(y)) stroke_addpoint(x, y) stroke_end() else: print("strange PATHCMD: " + ary[1]) import socket,os sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('', 12345)) # CHANGE PORT NUMBER HERE! sock.listen(5) while True: connection,address = sock.accept() sendit = lambda string: connection.send(string.encode("utf-8")) #print("got connection") sofar = ""; while True: buf = recv(connection) if buf is None: break source = "" if(buf == "\""): #print("starting some python code") while True: buf = recv(connection) if buf is None: break if(buf == "\""): #print("doing exec of:\n" + source) # DANGEROUS on public networks!!!! # uncomment for python interface #exec(source) break source += buf + "\n" if source != "": continue if buf is None: break ary = buf.split(" ") #print("cmd ary:") print(ary) print(ary[0]) if ary[0] == "PATHCMD": pathcmd(*ary) else: response = device.command(*ary) print("device response: " + response) response += "\n" response = response.encode('utf-8') connection.send(response) #print("connection closed") connection.close()
This is basically just boilerplate C socket client code with some examples of sending commands using the two higher-level APIs above:
Code is also available for download here.
// // axiclient - // // Chris Pirazzi and Paul Haeberli // // Demo C code to send path to axidraw plotter over the net. // // For details and instructions, see https://lurkertech.com/axiserver // #include #include #include #include #include #include static char axidraw_buffer[BUFSIZ]; static int axidraw_sockfd; static void axidraw_send_command(const char *str) { int str_len = strlen(str); if (write(axidraw_sockfd, str, str_len) == -1) { perror("write"); return; } } static void axidraw_read_response(int echo) { ssize_t nbytes_read; if ((nbytes_read = read(axidraw_sockfd, axidraw_buffer, BUFSIZ)) > 0) { if (echo) { write(STDOUT_FILENO, axidraw_buffer, nbytes_read); if (axidraw_buffer[nbytes_read - 1] == '\n') fflush(stdout); } } } static int axidraw_init() { char protoname[] = "tcp"; struct protoent *protoent; in_addr_t in_addr; struct hostent *hostent; struct sockaddr_in sockaddr_in; const char *axidraw_hostname = "studio-muji.local"; unsigned short axidraw_port = 12345; protoent = getprotobyname(protoname); if (protoent == NULL) { perror("getprotobyname"); return 0; } axidraw_sockfd = socket(AF_INET, SOCK_STREAM, protoent->p_proto); if (axidraw_sockfd == -1) { perror("socket"); return 0; } /* Prepare sockaddr_in. */ hostent = gethostbyname(axidraw_hostname); if (hostent == NULL) { fprintf(stderr, "error: gethostbyname(\"%s\")\n", axidraw_hostname); return 0; } in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list))); if (in_addr == (in_addr_t)-1) { fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list)); return 0; } sockaddr_in.sin_addr.s_addr = in_addr; sockaddr_in.sin_family = AF_INET; sockaddr_in.sin_port = htons(axidraw_port); /* Do the actual connection. */ if (connect(axidraw_sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) { perror("connect"); return 0; } return 1; } static void axidraw_close() { close(axidraw_sockfd); } #ifdef USE_PYTHON_INTERFACE // insecure Method 3 // // PYTHON axidraw commands to define a drawing // - these use insecure Method 2 // - so you would need to uncomment the 'exec' in axiserver.py to use these // - it's probably better to use PATHCMD as shown below // static void py_stroke_start() { axidraw_send_command("\"\n"); axidraw_send_command("stroke_start()\n"); } static void py_stroke_addpoint(float x, float y) { char cmdstr[1024]; sprintf(cmdstr, "stroke_addpoint(%f,%f)\n", x, y); axidraw_send_command(cmdstr); } static void py_stroke_end() { axidraw_send_command("stroke_end()\n"); axidraw_send_command("\"\n"); } static void py_drawing_start() { axidraw_send_command("\"\n"); axidraw_send_command("drawing_start()\n"); axidraw_send_command("\"\n"); } static void py_drawing_end() { axidraw_send_command("\"\n"); axidraw_send_command("drawing_end()\n"); axidraw_send_command("\"\n"); } #endif // USE_PYTHON_INTERFACE // insecure Method 3 int main() { if (!axidraw_init()) { exit(1); return 1; } #ifdef USE_PYTHON_INTERFACE // insecure Method 3 // // here we send commands in python to the server. // WARNING: this may make your host vulnerable! // you need to uncomment the "exec" in server.py // for this to work. // py_drawing_start(); py_stroke_start(); py_stroke_addpoint(0.0, 0.0); py_stroke_addpoint(1.0, 0.0); py_stroke_addpoint(1.0, 1.0); py_stroke_addpoint(0.0, 1.0); py_stroke_addpoint(0.0, 0.0); py_stroke_end(); py_drawing_end(); #endif // USE_PYTHON_INTERFACE // insecure Method 3 #define USE_PATHCMD_INTERFACE #ifdef USE_PATHCMD_INTERFACE // Method 2 // // here we send PATHCMD commands to the server // axidraw_send_command("PATHCMD drawing_start\n"); axidraw_send_command("PATHCMD stroke 5 0.0 0.0 1.0 0.0 1.0 1.0 0.0 1.0 0.0 0.0\n"); axidraw_send_command("PATHCMD stroke 5 0.2 0.2 0.8 0.2 0.8 0.8 0.2 0.8 0.2 0.2\n"); axidraw_send_command("PATHCMD drawing_end\n"); #endif // USE_PATHCMD_INTERFACE // Method 2 axidraw_close(); exit(0); return 0; }
ssh Advantages: Secure connection method that works over the general internet and on untrusted networks. Lets client do pretty much anything on the server, but only if they have proper credentials. No need for a custom server program.
ssh Disadvantages: Making an ssh connection from client C/Python/Perl/... code is much much more code (not just the 30-line samples you see above), unless the client uses their programming language's "system()/popen()" call to run a command-line ssh client, which may be inefficient depending on the application. The client gets complete access to the server (can run any program, delete files accessible by the ssh user), so the owner of the server needs to trust the client user more than with Method 1 and Method 2 above (but less than with Method 3!). It is possible to limit impact somewhat by creating a separate user just for this purpose. The client cannot use the high-level functionality of PATHCMD (including its ability to put multiple drawings on one page in sub-rectangles) but on the other hand it has access to a world of ready-made scripts to do things with AxiDraw.
Submit This Site | Like what you see? Help spread the word on social media: | |||
| ||||
Support This Site | This free site is supported by reader contributions. You can contribute in the following ways: | |||
Use your credit card or PayPal to donate in support of the site. | ||||
Use this link to Amazon—you pay the same, I get 4%. | ||||
Learn Thai with my Talking Thai-English-Thai Dictionary app: iOS, Android, Windows. | ||||
Experience Thailand richly with my Talking Thai-English-Thai Phrasebook app. | ||||
Visit China easily with my Talking Chinese-English-Chinese Phrasebook app. | ||||
I co-authored this bilingual cultural guide to Thai-Western romantic relationships. | ||||
Copyright | All text and images copyright 1999-2023 Chris Pirazzi unless otherwise indicated. | |||