Reading Wii Nunchuck data in Python

From XPUB & Lens-Based wiki
Revision as of 11:31, 24 November 2011 by Jasper van Loenen (talk | contribs)

Brief description: Use the Wii Nunchuck to draw in the terminal and turn the drawing into a SVG


Since I installed Linux I use the terminal a lot. I really like how you can use something text based and really basic to perform all kinds of complex tasks.

After messing with SVG for a bit I thought it might be interesting to use the text based terminal as the interface to a SVG generating script. Also, I wanted to see how I could use the terminal together with an Arduino, so I used one to connect my Python script too a Wii controller (Nunchuck), which is used as input for the script.

The Nunchuck's joystick controls the cursor, the Z (large) button lets jou draw lines (press to select begin point, press again to select end point) and the c button creates an SVG and shows it. Using the WiiChuck class the arduino reads the data which is then send to Python though a serial connection. To connect the Nunchuck to the Arduino without cutting the cable I used the Wiichuck adapter. Both the class and the adapter are available here.

[Disclaimer] Since I wrote the code from scratch and added 'features' as I went along I bet it's far from optimized. But it seems to do the job.


PythonArduinoNunchuck.png

Python code

#=========================================================
# Import libs and set basic vars
#=========================================================
import serial, os, math

width, height = 50, 20
xpos, ypos = 0, 0
sx, sy = -1, -1
zbut, prevZbut, cbut = 0, 0, 0
cursorChar, prevCursorChar = "o", ""
svgScale = 10.0
svgHeightCorrection = 2

theScreen = [] #will hold all of the 'pixels'
fullRow = "" # the line of #s for use at the top and bottom
for x in range(0, width+2):
    fullRow += "#"

#initiate the screen arrays
for y in range(0, height):
    xlist = []
    for x in range(0, width):
        xlist.append(" ")
    theScreen.append(xlist)

svgOut = ""
#=========================================================
# function to draw the arrays to the screen
#=========================================================
def display():
    global theScreen
    global cursorChar
    global xpos
    global ypos

    prevCursorChar = theScreen[ypos][xpos] 
    theScreen[ypos][xpos] = cursorChar
    os.system("clear")
    print fullRow
    for y in theScreen:
        printLine = "#"
        for x in y:
            printLine += str(x)
        print printLine+"#"
    print fullRow
    theScreen[ypos][xpos] = prevCursorChar

#=========================================================
# function to draw a line
#=========================================================
def drawLine(x1, y1, x2, y2, useInSvg):
    global theScreen
    global svgOut
    global svgScale
    global svgHeightCorrection

    bx1, by1, bx2, by2 = int(x1), int(y1), int(x2), int(y2)

    x1 = max(0, min(x1, width))
    x2 = max(0, min(x2, width))
    y1 = max(0, min(y1, height))
    y2 = max(0, min(y2, height))
        
    d = math.atan2(y2-y1, x2 - x1)*180/math.pi #d = direction
    d2 = d
    if d2 < 0:
        d2 = (360-abs(d2))        
    dist = math.floor(math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)))
    lineChar = "*"
    if (d2 >= 360-22.53 or d2 < 22.5) or (d2>=157.5 and d2<202.5):
       lineChar = "~"
    elif (d2 >= 22.5 and d2 < 67.5) or (d2>=202.5 and d2<247.5):
       lineChar = "\\"
    elif (d2>=67.5 and d2<112.5) or (d2>=247.5 and d2<292.5):
       lineChar = "|"
    elif (d2>=112.5 and d2<157.5) or (d2>=292.5 and d2<337.5):
       lineChar = "/"
    
    step = 0
    while(step < dist):
        x1 += math.cos(d*math.pi/180)
        y1 += math.sin(d*math.pi/180)
        if x1>width:
            x1 = width-1
            x2 = width-1
        elif x1<0:
            x1 = 0
            x2 = 0
        if y1>height:
            y1 = height-1
            y2 = height-1
        elif y2<0:
            y1 = 0
            y2 = 0
        drawY = int(math.floor(y1))
        drawX = int(math.floor(x1))
        if drawY < len(theScreen):
            if drawX < len(theScreen[drawY]):
                theScreen[drawY][drawX] = lineChar
        step = step + 1
    theScreen[by1][bx1], theScreen[by2][bx2] = "*", "*"

    if useInSvg==1:
        svgOut = svgOut + """<line x1="{0}" y1="{1}" x2="{2}" y2="{3}"
    style="stroke:rgb(0,0,0);stroke-width:{4}"/>""".format(bx1*svgScale, by1*(svgScale*svgHeightCorrection), bx2*svgScale, by2*(svgScale*svgHeightCorrection), svgScale)

def drawEllipse(x, y):
    global svgOut
    print "at: "+str(x)+", "+str(y)
    svgOut = svgOut + """<ellipse cx="{0}" cy="{1}" rx="{2}" ry="{2}"
  style="fill:black;"/>""".format(float(x)*2*svgScale, float(y)*svgScale, svgScale/2)

#=========================================================
# Save image, open and quit
#=========================================================
def saveOpenAndQuit():
    global svgOut
    global width
    global height
    global svgScale
    svgOut = """<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{0}" height="{1}">""".format(svgScale*width, svgScale*(height*svgHeightCorrection)) + svgOut
    svgOut = svgOut + "</svg>"

    fo = open("image.svg", "wb")
    fo.write( svgOut);
    fo.close()
    os.system("google-chrome image.svg &")

#=========================================================
# Main loop
#=========================================================
loop = 0
ser = serial.Serial('/dev/ttyACM0', 19200)
while 1:
    loop = loop + 1
    serialData = ser.readline()
    serialData = serialData.replace("\r\n", "")
    dataList = serialData.split(',')

    if len(dataList)==6:
        prevZbut = zbut
        accx = float(dataList[0])
        accy = float(dataList[1])
        zbut = float(dataList[2])
        cbut = float(dataList[3])
        joyx = float(dataList[4])
        joyy = float(dataList[5])
        
        #move cursor around
        if joyx>150:
            xpos = xpos + 1
        elif joyx<100:
            xpos = xpos - 1

        if joyy>150:
            ypos = ypos - 1
        elif joyy<100:
            ypos = ypos + 1

        xpos = max(0, min(xpos, width-1))
        ypos = max(0, min(ypos, height-1))

        #remember previous char on cursor location
        prevCursorChar = theScreen[ypos][xpos]
        
        if cbut==1:
            saveOpenAndQuit()
        cursorChar = "o"

        if zbut==1 and prevZbut==0:#click, starting to draw
            sx, sy = xpos, ypos
            theScreen[ypos][xpos] = "*"
            cursorChar = "x"
            display()
        elif zbut==1 and prevZbut==1:#dragging around
            cursorChar = "*"
            #somehow I need to iterate over the tempScreen and theScreen arrays
            #instead of just copying them with .extend
            tempScreen = []
            for y in range(0, height):
                xlist = []
                for x in range(0, width):
                    xlist.append(theScreen[y][x])
                tempScreen.append(xlist)

            drawLine(sx, sy, xpos, ypos, 0)
            display()
            theScreen = []
            for y in range(0, height):
                xlist = []
                for x in range(0, width):
                    xlist.append(tempScreen[y][x])
                theScreen.append(xlist)

        elif zbut==0 and prevZbut==1:#released
            theScreen[ypos][xpos] = "*"
            cursorChar = "x"
            if sx==xpos and sy==ypos:
                drawEllipse(xpos, ypos)
            else:
                drawLine(sx, sy, xpos, ypos, 1)
            sx, sy = -1, -1
            display()

        elif zbut==0 and prevZbut==0:#doing nuthin'
            cursorChar = "o"
            display()

Arduino code

#include <Wire.h>
#include "nunchuck_funcs.h"

int loop_cnt=0;

byte accx,accy,zbut,cbut,joyx,joyy;
int ledPin = 13;


void setup()
{
  Serial.begin(19200);
  nunchuck_setpowerpins();
  nunchuck_init(); // send the initilization handshake
}

void loop()
{
  if( loop_cnt > 100 ) { // every 100 msecs get new data
    loop_cnt = 0;

    nunchuck_get_data();
    //nunchuck_print_data();

    accx  = nunchuck_accelx(); // ranges from approx 70 - 182
    accy  = nunchuck_accely(); // ranges from approx 65 - 173
    zbut = nunchuck_zbutton();
    cbut = nunchuck_cbutton(); 
    joyx = nunchuck_joyx();
    joyy = nunchuck_joyy(); 
    
    Serial.print((byte)accx,DEC);
    Serial.print(",");
    Serial.print((byte)accy,DEC);
    Serial.print(",");
    Serial.print((byte)zbut,DEC);
    Serial.print(",");
    Serial.print((byte)cbut,DEC);
    Serial.print(",");
    Serial.print((byte)joyx,DEC);
    Serial.print(",");
    Serial.print((byte)joyy,DEC);
    Serial.println();
  }
  loop_cnt++;
  delay(1);
}