CoD4 Nunchuk Leaner


Overview

Jump to the end for instant video gratification of the Nunchuk Leaner in action…

I play Call of Duty 4 online with some buddies. CoD4 gives the player the ability to lean left/right. The advantage of this is that you can take cover behind an object and lean out to shoot. This exposes less of your body and makes it somewhat less guaranteed that you will die the moment you try to look around. To lean, you press and hold q for left and e for right (playing in a standard wasd configuration).

One of the guys I play with (BigBrown) did a project to use a Wii Nunchuk to control leaning in the game. I liked the idea, so I built it as well. Our final implementations are roughly similar, but mine ran into a few problems his didn’t, and resulted in a different design. My system architecture is shown in this figure.

COD4 Nunchuck Leaner Architecture

COD4 Nunchuck Leaner Architecture

The premise is this: the Wii Nunchuk communicates using a two-wire interface. The Arduino development board reads the state of the Nunchuk and passes that information over its serial port. I had to read this using my old computer as my newer gaming box doesn’t have a serial port. The program I wrote on the Linux box reads the serial data, decodes it, determines the tilt, and sends the letter L R, or C over a TCP/IP connection to my gaming machine. A free, open-source Windows scripting program called AutoHotKey receives the command and injects the correct key press or release into Call of Duty 4. The following is a more detailed description of each of those steps presented in the order of the data flow in the system. There should be enough information here to get this project running yourself.

Nunchuk

The Wii Nunchuk has a three-axis accelerometer, analog stick, and two buttons. All this for just $25 (Canadian)! I predict more and more sales of these things for hobbyists, especially given how easy it is to get the data off of them. The Nunchuk communicates using a two-wire interface (a looser variant of I2C). After some initial handshaking, the status of the Nunchuk can be obtained by sending it the byte 0x00, and then asking it for six bytes of data. Chad’s blog (Windmeadow Labs) has an excellent description of the interface and the code for reading the Nunchuk using an Arduino board. The data that comes back from the Nunchuk is as follows:

Byte Description
1 Analog stick x-axis value
2 Analog stick y-axis value
3 X-axis acceleration bits 9:2
4 Y-axis acceleration bits 9:2
5 Z-axis acceleration bits 9:2
6 Bit 0 Z button (0=pressed)
Bit 1 C button (0=pressed)
Bits 3:2 X-axis acceleration bits 1:0
Bits 5:4 Y-axis acceleration bits 1:0
Bits 7:6 Z-axis acceleration bits 1:0

An important point to note here that I failed to spot at first is that the bytes received from the Nunchuk require decoding before they can be properly interpreted. Each byte must be XOR’d with 1716 and then have 1716 added to it (not a copy/paste error, both are 1716). I found that decoded X-axis acceleration values for the Nunchuk level, at rest, centered around 50010 and ranged to 30010 (full left tilt) to 70010 (full right tilt). You can, of course, get values greater or less than these (10 bits gives 0->1023) by flicking the Nunchuk to induce accelerations greater than 1G.

As Chad did, I cut the connector off of my Nunchuk to get access to the wires (although I now regret this a little) and used +5V instead of +3.3V. It hasn’t seemed to hurt so far, but I should really throw a resistor in there. I then spliced in some spare Ethernet cable for extra length. Connecting to the Arduino is as follows:

Wire Description Arduino Pin
White Ground Ground
Red +3.3V +5V
Green Data Analog Pin 4
Yellow Clock Analog Pin 5

Arduino

The Arduino is an open-source development board. All of the discrete components are readily available from suppliers such as Digikey or Jameco, with the exception of the circuit board itself, which is the open-source part of the project. You can buy the circuit board (suppliers available at the Arduino site) and assemble it yourself, or you can buy pre-assembled versions. I bought version 2 of the serial board. Newer versions have a USB interface which would change much of the architecture of this project. Here is my assembled board.

Mike's Assembled Arduino

The LED on the right is loosely resting on the pins and was just used for debugging… it’s not part of the design. The wires on the left are connected to the Nunchuk as in the architecture and table shown above (the colours are different in the photo as I spliced on an Ethernet cable for extra length). You can see a serial cable and power connected at the top. Soldering the board together took me one evening. Once assembled, unless you bought a pre-programmed ATmega8, you will have to burn the bootloader onto the chip. This can be done in a variety of ways, but I did it using a parallel port and a cable that you manufacture yourself as described here. Note that the pin numbering for the parallel port on that page doesn’t match that for a standard parallel port pin definition.

I programmed the Arduino bootloader in Linux. I couldn’t use my gaming rig because it doesn’t have a parallel or serial port. However, even if it did, the Arduino tools are all Linux-based. Although you can run them under Windows using Cygwin, I am running Vista 64, and Cygwin doesn’t yet support 64-bit Windows. At first, I couldn’t get the Arduino environment to load the bootloader. It seems that newer versions don’t support the serial version of the board very well. I stepped back to using version 7 and the bootloader went onto the Arduino just fine.

The Arduino project has an IDE with compiler for programming. The language is obviously based on C. The really nice feature for this project is that the ATmega8 has hardware support for a two-wire interface (twi) and the Arduino has a two-wire library available for interfacing with it. To get the Arduino to properly interface with the Nunchuk, I had to change one parameter in the twi library. My changes are not the same as those Chad used. I only had to specify the ATmega8 as being in use, whereas Chad also changed the frequency of the two-wire bus. Open lib/targets/libraries/Wire/utility/twi.h and change the following:

From To
//#define ATMEGA8 #define ATMEGA8

Remember to delete twi.o if it already exists, then recompile and verify that it was recreated.

The program I wrote to run on the Arduino is based off the one that Chad wrote. I added some error checking, and while I decode the bytes, I don’t perform any other examination of the data on the Arduino. Basically, I read the Nunchuk roughly every 20ms, decode it, add some characters to separate the numbers (the data is in ASCII over the serial connection), pump the data out the serial port, and let my Linux box do the work of interpreting the Nunchuk’s state. I pulse pin 13 on/off with each send so I can verify it is operating by connecting an LED to pin 13. This pin is useful because it has an integrated resistor on the Arduino board and happens to be adjacent to a ground. Here is the code running on the Arduino:

Arduino Code

////////////////////////////////////////////////////////////////////////////////
//
// File: read_nunchuk_improved.pde
//
// Author: Mike LeSauvage (code base from Chad at Windmeadow labs).
//
// Description: This file has the code to allow the Arduino to read from a
//              Wii Nunchuk using a two-wire interface and pass that info
//              over the serial cable.
//
////////////////////////////////////////////////////////////////////////////////
#include <string.h>
#include <Wire.h>

////////////////////////////////////////////////////////////////////////////////
//
// Function: setup
//
// Description: This function is executed on Arduino boot.  Pin 13 is set for
//              output (blinking light), serial comms speed is set, and
//              the two-wire interface is configured to communicate to the
//              Nunchuk.
//
// Parameters: None.
//
// Returns: Nothing.
//
////////////////////////////////////////////////////////////////////////////////
void setup()
{
  pinMode(13, OUTPUT);
  Serial.begin(19200);

  Wire.begin();                  //Join I2C bus as master (since no address specified).
  Wire.beginTransmission(0x52);  //0x52 is Nunchuk's address.
  Wire.send(0x40);               //Handshake part I
  Wire.send(0x00);               //Handshake part II
  Wire.endTransmission();

}

////////////////////////////////////////////////////////////////////////////////
//
// Function: loop
//
// Description: This is the function called continually as the Arduino executes.
//              For this program, it reads six bytes from the Nunchuk, decodes
//              each one as it arrives, adds packet info, and sends them over
//              the serial port.
//
// Parameters: None.
//
// Returns: Nothing.
//
////////////////////////////////////////////////////////////////////////////////
void loop()
{
  int         count=0;
  static int  ledVal=LOW;
  uint8_t     buffer[6];               //Buffer of unigned bytes for response.

  Wire.requestFrom(0x52, 6);           //Request 6 bytes from Nunchuk;
  while(Wire.available() && count<6)
  {
    buffer[count]=(Wire.receive()^0x17) + 0x17;  //Decode byte.
    count++;
  }

  if(count == 6)
  {
    Serial.print("*");                     //Indicate start of data with a *
    for(count=0; count<6; count++)
    {
      Serial.print(buffer[count], DEC);    //Send in base-10.
      Serial.print("-");                   //Indicate end of number.
    }
  }
  else
  {
    Serial.print("X");                   //Report error.
    while(Wire.available())              //Flush the buffer.
      Wire.receive();
  }
  Wire.beginTransmission(0x52);          //Reset for new data by sending 0x00.
  Wire.send(0x00);
  Wire.endTransmission();
  delay(20);                             //Approximate read rate of 50hz.
  ledVal=ledVal^0x01;                    //Blink after each send.
  digitalWrite(13, ledVal);
}

Linux Box

If my gaming PC had a serial port I wouldn’t have needed this part of the project. The sole purpose of the Linux box is to receive the serial data and send it to my Vista box in the form of single-letter commands. There are alternatives such as a serial-to-Ethernet converter, but they all seem to cost around $100USD and I had a spare PC sitting around anyway.

Given that it’s part of the loop now, I use the Linux box to decode the Nunchuk data, determine the tilt, and send left/right/center commands to AutoHotKey on the Vista box. I wrote the program in C. The only hard part about this was deciphering the vast number of options for configuring a serial port. One interesting discovery I made during this portion is that changes to the serial port are persistent across programs. I came across this because I had been using the Arduino IDE’s built-in serial monitor for debugging after each boot. I then ran my own program, and it always worked. The first time I ran my own program without first running the Arduino IDE serial monitor my program failed. It turned out I was missing some key configuration options. They have since been added.

The program is responsible for receiving data over the serial port. The format I send from the Arduino uses a * to indicate the start of a data set from the Nunchuk. A separates each number. Here is some sample data:

*133-123-138-160-176-79-*133-122-138-160-79-*  ...etc...

The program first has to change the text (each number is 1 to 3 ASCII characters) back into integers. It then determines the X-axis acceleration by combining the first byte with some bits from the 6th byte (as shown in the table in the Nunchuk section). It also checks to see if the C button on the Nunchuk is pressed. If it is, it uses the current value of the X-axis tilt as the new center. This is useful if the Nunchuk has shifted on your head. After experimenting, I found a delta of 40 from center a good amount to decide if the tilt is left or right.

The previous tilt state is maintained by the program. Once the current tilt is determined, if it is a change from the previous tilt, the program sends out the new value as a single byte: the character L, R, or C indicating left, right, or center. This character is sent over a TCP/IP connection to my gaming machine.

The program is run from the command line. The format is one of the following:

./readarduino.bin <IP address> <port>
./readarduino.bin debug

In the first form, you simply specify which computer/port you want to connect to. The script I wrote for AutoHotKey assumes port 8765, so to connect on my network I enter:

./readarduino.bin 192.168.0.100 8765

The second form is for debugging and examining data coming over the serial port. In this case, no TCP/IP connection is made, but all data coming in over the serial port is output to the screen. This is useful for determining the axis center and tilt limits for however you have connected the Nunchuk to your head.

I’m not going to make any particular comments on the code. I figured out the serial and TCP/IP connection code through a number of web pages I found through Google. I compile from the command line using

gcc -Wall ./readarduino.c -o readarduino.bin

readarduino.c

////////////////////////////////////////////////////////////////////////////////
//
// File: readarduino.c
//
// Author: Mike LeSauvage
//
// Description: This program reads data sent to it from a Wii Nunchuk over
//              a serial port. It transforms the text back into integers,
//              determines the X-tilt of the Nunchuk, and sends the letter
//              L R or C over a TCP/IP connection to indicate that the
//              Nunchuk is tilted left, right, or center.
//              If the user presses the C button on the Nunchuk, the program
//              uses the current X-axis tilt as the new center.
//
// Created: 20 Feb 2008
//
////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>      //Standard input/output definitions
#include <string.h>     //String function definitions
#include <unistd.h>     //UNIX standard function definitions
#include <fcntl.h>      //File control definitions
#include <errno.h>      // Error number definitions
#include <termios.h>    //POSIX terminal control definitions
#include <stdlib.h>     //Standard library.
#include <netinet/in.h> //Has internet structures.
#include <netdb.h>
#include <arpa/inet.h>  //Has inet_aton()

//Symbols received from Arduino.
#define SYM_START '*'  //Start of serial data set.
#define SYM_ERROR 'X'  //Error in set.
#define SYM_BREAK '-'  //Break between numbers in set.

//Defines
#define TILT_AMT    40    //Delta for left/right tilt.
#define MAX_DIGITS   4    //Max digit per Nunchuk parameter. Includes null char.

////////////////////////////////////////////////////////////////////////////////
//
// Function: OpenPort
//
// Description: This function opens a serial port at 19200bps, 8 data bits,
//              1 stop bit, and no parity (Arduino default).
//              It sets the VMIN parameter to 1, indicating that calls to read()
//              on the port should block until at least one byte is available.
//
// Parameters: None.
//
// Returns: int - The file descriptor for the connection.
//
////////////////////////////////////////////////////////////////////////////////
int OpenPort(void)
{
  int fd;                  //File descriptor
  struct termios options;

  fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY  | O_NDELAY);  //O_NDELAY means
  if (fd == -1)                                            //don't wait for DCD
  {                                                        //line state.
    perror("open_port: Unable to open /dev/ttyS0 - ");
    exit(1);
  }
  else
  {
    fcntl(fd, F_SETFL, 0);                  //Turn on blocking.
    tcgetattr(fd, &options);
    cfsetispeed(&options, B19200);          //Input and output speed
    cfsetospeed(&options, B19200);          // to 19200bps.
    options.c_cflag |= (CLOCAL | CREAD);    //Don't become port owner.
    options.c_cflag &= ~PARENB;             //No parity.
    options.c_cflag &= ~CSTOPB;             //1 stop bit.
    options.c_cflag &= ~CSIZE;              //Clear data bits and then...
    options.c_cflag |= CS8;                 // set to 8.
    options.c_cc[VMIN]=1;                                  //Block for 1 byte.
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);    //Raw mode.
    tcsetattr(fd, TCSANOW, &options);
  }

  return (fd);
}

////////////////////////////////////////////////////////////////////////////////
//
// Function: GetSocket
//
// Description: Opens a socket at the given IP Address and port.
//
// Parameters: char *ipAddress - IP address as a dotted quad string.
//             int  ipPort     - Port to connect to at that address.
//
// Returns: int - The socket.
//
////////////////////////////////////////////////////////////////////////////////
int GetSocket(char *ipAddress, int ipPort)
{
  int    theSocket, status;
  struct sockaddr_in serverName={0};

  theSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);  //Create a TCP socket.
  if(theSocket == -1)
  {
    printf("\nFailure to open socket.\n");
    exit(1);
  }

  serverName.sin_family=AF_INET;
  serverName.sin_port=htons(ipPort);           //Convert port to correct format.
  inet_aton(ipAddress, &serverName.sin_addr);  //Get network address from dotted
                                               //quad IP address as string.

  status=connect(theSocket, (struct sockaddr*)&serverName, sizeof(serverName));
  if(status == -1)
  {
    printf("\nError in connect()\n");
    exit(1);
  }

  return theSocket;
}

////////////////////////////////////////////////////////////////////////////////
//
// Function: main
//
// Description: Main program.
//
// Parameters: argc - The number of command line parameters.
//             argv - Array of command line strings (first is path/name)
//
// Returns: int - Program status.
//
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char **argv)
{
  char stateVals[3]={'L', 'C', 'R'};  //Characters to send.
  int fd;                             //File descriptor for serial port.
  char buffer[MAX_DIGITS];            //Input buffer.
                                      // plus a null character.
  int  nunchukState[6];               //The six nunchuk bytes.
  int  nbytes;                        //Number of bytes read via read().
  int  byteCnt;                       //Current byte in a number being recv'd.
  int  numCnt;                        //Count of numbers recv'd (0 to 6)
  int  xTilt, btnC;
  int  centerVal=500;                 //Nunchuk level val for x-axis at rest.
  int  oldState=1, newState=1;        //States: 0=left, 1=center, 2=right.
  int  debug=0;                       //1 if in debug state.

  int    clientSocket;                      //TCP/IP socket.
  char   *remoteHost = NULL;                //TCP/IP address.
  int    remotePort;                        //TCP/IP port.

  if(argc == 2)                                 //This section just examines
  {                                             //the command line parameters
    if(!strcmp(argv[1], "debug"))               //and determines if the user
      debug=1;                                  //wants debug mode, or
  }                                             //entered a valid TCP/Ip
  else if (argc == 3)                           //address.
  {
    remoteHost=argv[1];
    remotePort=atoi(argv[2]);
    if(remotePort == 0)
    {
      printf("\nInvalid port: %s\n", argv[2]);
      exit(1);
    }
  }
  else
  {
    printf("\nUsage: %s <address> <port>\n", argv[0]);  //Incorrect usage.
    exit(1);
  }

  //Open TCP/IP connection.
  if(!debug)
    clientSocket = GetSocket(remoteHost, remotePort);

  //Open serial port.
  fd=OpenPort();

  while(1)
  {
    while(1)                        //Wait for start of data sequence.
    {
      nbytes=read(fd, buffer, 1);   //Read one byte at a time.
      if(nbytes > 0)
        if(buffer[0] == SYM_START)
        {
          byteCnt=0;                //Reset bytes received for current number.
          numCnt=0;                 //Reset number count.
          break;
        }
    }

    while(1)                                //Read in a single number,
    {                                       //composed of multiple ASCII chars.
      nbytes=read(fd, &buffer[byteCnt], 1);
      if(nbytes<0 || buffer[byteCnt]==SYM_ERROR || buffer[byteCnt]==SYM_START || byteCnt >= MAX_DIGITS)
      {
        printf("Bad data from nunchuk.\n");  //Bad if no bytes read, error symbol sent, start symbol
        break;                               //sent mid-number, or too many digits.
      }
      if(buffer[byteCnt]==SYM_BREAK)         //End of number encountered.
      {
        buffer[byteCnt]='\0';                //Replace break char with NULL for string end.
        nunchukState[numCnt]=atoi(buffer);   //Convert to integer.
        byteCnt=0;                           //Reset byte count
        numCnt++;                            //Increment number of Nunchuk numbers received.
        if(numCnt==6)
          break;
      }
      else
        byteCnt++;
    }

    if(numCnt==6)                                //All Nunchuk data received.
    {
      xTilt=nunchukState[2];                     //Get X-axis acceleration by
      xTilt=xTilt<<2;                            //shifting left 2 bits and
      xTilt=xTilt|((nunchukState[5]>>2)&3);      //adding bits 3:2 from the
                                                 //last Nunchuk number.
      btnC = !((nunchukState[5]>>1)&1);          //Get C button status, and
                                                 //invert so 1=pressed.

      if(btnC)                                   //C pressed, get new ctr val.
      {
        centerVal=xTilt;
        printf("New center: %d\n", centerVal);
      }

      if(xTilt<centerVal-TILT_AMT)           //Determine tilt state.
        newState=0;
      else if(xTilt>centerVal+TILT_AMT)
        newState=2;
      else
        newState=1;

      if(debug)
        printf("xTilt: %d\n", xTilt);    //Print tilt state if debug on.

      if(oldState != newState)           //Send tilt character over TCP/IP.
      {
        if(!debug)
          send(clientSocket, &stateVals[newState], 1, 0);
        oldState=newState;
      }
    }

  }

  close(fd);           //Program terminated, clean up.
  close(clientSocket);

  return 0;
}

AutoHotKey

The last step in the project is to get the commands from the Linux box to AutoHotKey (AHK), and from there into Call of Duty 4. AutoHotKey is a Windows scripting program that is usually used to respond to keys or other triggers from a user at the computer and inject commands into Windows. It can bring programs to the foreground, type, simulate mouse clicks, etc. For this project, I required it to respond to commands coming over TCP/IP.

There is no built-in support for sockets in AHK. Fortunately, the author added the ability to directly access dynamic linked libraries (dlls) on the system for cases just such as this. I found sample code for a client and a server script. Starting with the server code, I changed it so it would be more responsive (the buffer is only 2 bytes big, and only one byte is read at a time) and as well as altering the overall structure so it is loop-based instead of responding to events. Both changes were required to improve performance. When I started with the original code, there was a 300ms delay from the time I tilted my head until the game received the key. After my changes to the socket code, the delay is around 30ms or less.

The script only injects keys on change, so it remembers state. Theoretically, both this code and the Linux box do the same, but by having this script remember state, I could go sloppy with the Linux code if I desired. State must be maintained somewhere though as the script doesn’t simply inject the letter q when I tilt my head left…it sends a “q down” input. This is to simulate how the key is used in the game: the lean is maintained as long as the key is held down. When I return my head to center, a “q up” input is sent.

Three other notes on this script are:

  • The IP address at the top must be the same as the computer on which you’re running AHK.
  • Because I use the SHIFT key to crouch, a “q down” input makes it look like I’ve let go of SHIFT (because q isn’t capitalized). To counter this, I read the state of the SHIFT key before sending my lean key. I then re-send that input so I will stay crouched if I was. When I didn’t do this what I would see while crouched if I tilted my head to the right was my player standing up, leaning right, and then crouching again. This approach fixed the problem. However, there are very rare cases where this results in my character being stuck crouched if I released the SHIFT key in between reading the key and re-sending it. I’m looking for a better approach.
  • I ensure that Call of Duty 4 has the focus before I send the keys so I don’t inject random key presses of q and e in other programs. You still have to be careful in the menus when doing things like typing an IP address for a server to connect to. As noted in the code, you can create a file called debug.txt and open it in Notepad to debug. Just change the code so the program it looks for as having the focus is: debug.txt - Notepad

AutoHotKey Script

;Script: Leaning Control in Call of Duty 4 via TCP/IP
;
;Author: Mike LeSauvage
;
;Description: This script is run upon start and exits only once the socket is
;             closed. It starts by waiting for an incoming connection. Once
;             established, it enters a forever loop where it receives single
;             bytes over the socket. The byte can be any of: L C R
;             This tells the script if the player wants to lean left, right,
;             or remain in the center. The script remembers what the player
;             is currently doing, and injects the proper key (Q=lean left,
;             E=lean right) on change. Keys are sent as presses and
;             releases to simulate actual keyboard use.
;             One final problem to solve was that state of the SHIFT key
;             had to be recorded and resent as I use it for crouching.
;             When this isn't done, when q down is sent, this looks like
;             SHIFT isn't held down, and my player would stand up.
;
;Note: The script makes heavy use of DLL calls to the winsock library, which
;      I didn't investigate in detail. Original TCP/IP server script from
;      http://www.autohotkey.com/forum/topic13829.html&highlight=socket

;Change the following two lines depending on your network setup
;--------------------------------------------------------------
NetAdd  = 192.168.0.100
NetPort = 8765
;--------------------------------------------------------------

keyState = 1       ;Left=0, Centre=1, Right=2
receiveBufSz = 2   ;TCP/IP Socket receive buffer size.

SetBatchLines, -1  ;Ensures AutoHotKey won't sleep between lines.
OnExit, ExitSub    ;When AutoHotKey exits, it will call ExitSub (to clean up).

ourSocket := Connection_Init(NetAdd, NetPort)  ;Get connection to controlling computer.
if(ourSocket = -1)
  Exit

Loop
{
  VarSetCapacity(msg, receiveBufSz, 0)  ; Create msg string and clear to 0 to null terminate string from recv()

  ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", ourSocket, "Str", msg, "Int", 1, "Int", 0)

  if ReceivedDataLength = 0      ; The connection was gracefully closed,
  {
    SoundPlay, *48
    ExitApp
  }
  if ReceivedDataLength = -1
  {
    WinsockError := DllCall("Ws2_32\WSAGetLastError")
    MsgBox % "recv() indicated Winsock error " . WinsockError
    SoundPlay, *16
    ExitApp
  }

  WinGetActiveTitle, title       ;Get the title of the active window.
  if title = Call of Duty 4      ;Only send keys to CoD4. When debugging, use: debug.txt - Notepad
  {
    oldState = %keyState%        ;This section determines how we should now lean.
    if msg = L
      keyState = 0
    else if msg = C
      keyState = 1
    else if msg = R
      keyState = 2

    if oldState != %keyState%
    {
      GetKeyState, shiftState, Shift       ;Record the current shift key state in case I'm crouching.

      if oldState = 0
        SendInput {q up}
      else if oldState = 2
        SendInput {e up}
      if keyState = 0
        SendInput {q down}
      else if keyState = 2
        SendInput {e down}

      if shiftState = D                    ;Restore the shift if required.
        Send {Shift Down}

    }
  }
}

;-----------------------------------------------------------------------------
;
;Function: Connection_Init
;
;Description: This function handles socket creation/binding, and then listens
;             for incoming connections.  When a connection has been accepted,
;             it returns the socket number.  If an error condition is
;             encountered, it returns -1 to indicate an error.
;
;Parameters: IPAddress - Address on which to create the socket (this machine).
;            Port - The port to listen on.
;
;Returns: The socket number, or -1 on error.
;
;-----------------------------------------------------------------------------
Connection_Init(IPAddress, Port)
{
  ;Create socket structure.  Only about 14 bytes in size, so 32 should be safe.
  ;Then fill out the structure, requesting Winsock 2.0 (0x0002)
  VarSetCapacity(wsaData, 32)
  result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData)
  if ErrorLevel
  {
    MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
    return -1
  }
  if result  ; Non-zero, which means it failed.
  {
    MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
    return -1
  }

  ;These are the values for a TCP/IP connection.
  AF_INET = 2
  SOCK_STREAM = 1
  IPPROTO_TCP = 6
  socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
  if socket = -1
  {
    MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
    return -1
  }

  ; Prepare for connection:
  SizeOfSocketAddress = 16
  VarSetCapacity(SocketAddress, SizeOfSocketAddress)
  InsertInteger(2, SocketAddress, 0, AF_INET)   ; sin_family
  InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2)   ; sin_port
  InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4)   ; sin_addr.s_addr

  ; Bind to socket:
  if DllCall("Ws2_32\bind", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
  {
    MsgBox % "bind() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
    return -1
  }
  if DllCall("Ws2_32\listen", "UInt", socket, "UInt", "SOMAXCONN")
  {
    MsgBox % "LISTEN() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
    return -1
  }

  ;Wait for incoming connections.
  Loop
  {
    connectionSocket := DllCall("Ws2_32\accept", "UInt", socket, "UInt", 0, "Int", 0)
    if connectionSocket > 1
    {
      MsgBox Incoming connection accepted
      break
    }
  }
  return connectionSocket
}

;-----------------------------------------------------------------------------
;
;Function: InsertInteger
;
;Description: This function is used for filling out structures for use with
;             DLL calls. It inserts an integer in a given memory location
;             at an offset.  The caller must ensure that pDest has sufficient
;             capacity.  Only pSize bytes are altered to maintain the
;             original data in the structure.
;
;Parameters: pInteger - The integer to insert.
;            pDest    - The destination structure memory.
;            pOffset  - The offset location into the structure.
;            pSize    - The size of the integer to insert.
;
;Returns: Nothing.
;
;-----------------------------------------------------------------------------
InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
{
    Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
        DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}

;-----------------------------------------------------------------------------
;
;Subroutine: ExitSub
;
;Description: This sub is called when the script is exiting. It cleans up
;             the socket. According to the MSDN, WSACleanup closes any open
;             sockets.
;
;-----------------------------------------------------------------------------
ExitSub:
  DllCall("Ws2_32\WSACleanup")
  ExitApp

Conclusion


The video is a little washed out, but you should still be able to make out the leaning action. As you can see, the Nunchuk Leaner works great with essentially no delay from tilt-of-head to lean-in-game. It adds immersion to the game and makes it more fun to play.

The project itself was a lot of fun to work on. On the down side, I really couldn’t look more like a geek with that Nunchuk on my head. My wife laughs pretty much every time she sees it. When BigBrown did the project, he removed the circuit board from inside the Nunchuk and placed it inside his headset. I might want to use the Nunchuk for other projects in the nice form factor, so I can’t bring myself to open it up yet.

The main improvement I would make would be to buy a serial-to-Ethernet converter. Getting the system up and running now involves running a second computer and starting the software from the command line. I’m waiting until I can find a converter for less than $50.

Comments are welcome.

29 Comments

  1. Comment by El Foosballo on March 8, 2008 at 3:27 pm

    This is incredible. You are the coolest nerd EVER.

    Seriously.

    Reply

  2. Comment by Granit on March 9, 2008 at 8:06 pm

    Hacker!

    Reply

  3. Comment by Matty Lorrain on March 10, 2008 at 7:37 pm

    That is unreal, my nerd senses are tingling.

    Reply

  4. Comment by Rick Parent on March 10, 2008 at 8:43 pm

    HAHAHAHA awesome!

    (Needs more I pwn n00bs t-shirt though)

    Reply

  5. Comment by Matt Gillmore on March 11, 2008 at 7:29 am

    Now we know why you’re so good at CoD4. I’ll take one…how much? That would be something useful to build directly into gaming headsets. Hopefully the industry jumps on board soon.

    Reply

  6. Comment by Mike Bathurst on March 11, 2008 at 9:51 am

    Now only if you could have gotten the nunchuck to control virtual lego robots…

    Reply

  7. Comment by David Vessey on March 11, 2008 at 10:44 pm

    … Is this how you’re going to kill me?

    *runs and hides*

    You’ll also be happy to know I continue to make things harder on myself – we’re using doxygen and latex to create our whole DDD :)

    Reply

  8. Comment by Blaine Losier on March 14, 2008 at 3:47 pm

    That’s awesome!!!

    Reply

  9. Comment by Joe on March 14, 2008 at 8:33 pm

    Brilliant.

    Reply

  10. Comment by Jim on April 29, 2008 at 12:36 am

    What about a serial to USB cable? They’re between $15 and $25 and come in both RS-232 and TTL flavors. Plug this directly into the Vista box and just deal with the virtual serial port that will appear. Usually these things are driverless (i.e. they use the built in USB profiles) so it might be one case where Vista isn’t a problem. Don’t know, though, I have zero experience with Vista even as a user. I’m sure scripting on the Vista box alone will be easier than interfacing with the linux box then slamming the data across the newtork to the gaming machine.

    Reply

    Reply by Mike LeSauvage on July 11th, 2008 at 1:58 pm

    USB would work just fine I think. As long as it really shows up as a serial port then I could write the script to just run on the Vista box. The reason I went with this architecture was that I had all these pieces at hand, so it didn’t cost me any money to build the system. The USB cable, while only $30, would still have been a bit of a gamble.

    Reply

  11. Comment by kelly on May 28, 2008 at 5:32 am

    im using the same idea which ive had for a long time i might add for driving a wheelchair. ive designed hardware to allow a small capacitive based touch pad or the wii mote to drive chair. Unfortunately im having issues with getting data from wii through I2C. Works fine on blackfin evaluation system but not on 8051 based processor and i cant see why. I just keep getting FF back in the data regs.

    Reply

  12. Comment by Eric on July 22, 2008 at 8:56 am

    where can you buy the arduino pre-made?

    Reply

    Reply by Mike LeSauvage on July 22nd, 2008 at 6:41 pm

    There’s a list on the Arduino Home Page. The link is at the top left of the page called “buy“. Happy hunting!

    Reply

  13. Comment by Tobias E on January 12, 2009 at 11:09 am

    A neater option would be having the Arduino control the Q and E key directly, maybe by using a 4066.
    It would be faster and toss the need for special programs and the Linux server.

    An addition would be using the other axis to control stand/walk/run and crawl.

    Hmmm…I think I just got me a project……

    Reply

    Reply by Mike LeSauvage on January 12th, 2009 at 10:28 pm

    Neat idea! I’m assuming you’re meaning to tap into the keyboard directly. That’s something I’ve wanted to do before but it seems like with the thin plastic film, I can’t figure out where to wire anything in! Another option, I suppose, would be a generic USB HID interface of some sort. I looked at USB before and it seemed like a royal pain…

    Good luck with that project! Give a link if you get something running.

    Mike

    Reply

    Reply by Tobias E on January 13th, 2009 at 5:10 am

    It’s been a while since I opened a keyboard, though I suppose they look the same today.
    A processor (8048 or something) is connected to the keys in a matrix/array, so it would be simple to track down which pins to short.
    I’m home at the moment because of my back’s hurt, maybe I can dissect a keyboard later.
    Just some more painkillers….

    Later,
    Tobias

    Reply

  14. Comment by Tobias E on January 15, 2009 at 9:27 pm

    Controlling the keyboard keys are really possible.
    I connected three wires to the little keyboard control board inside a HP keyboard.
    These wires were connected to a 4066 analog switch. By controlling the 4066 switches I can inject a ‘q’ or an ‘a’, thus it would be a simple task to let an Arduino read the Nunchuk and control ‘q’ and ‘e’ accordingly.
    Gotta try this!
    I couldn’t find my camera so no pictures were taken. Maybe I do antother keyboard later and takes some pics to let you see how it’s done.

    Tobias

    Reply

    Reply by Tobias E on January 15th, 2009 at 9:33 pm

    OK, it’s late in Sweden, so some typos….
    I mean the keys ‘q’ and ‘e’, no else. Tilting your head would make your FPS figure lean left or right.
    Tilt forward and backwards could control lay down and reload, or whatever.

    And sensors on the feet could mean…..(can go on forever…)

    Reply

    Reply by Mike LeSauvage on January 19th, 2009 at 9:24 pm

    That’s really cool. I have a broken keyboard in the basement…maybe I can get it to do this. Thanks!

    Mike

    Reply

  15. Comment by Tobias E on January 21, 2009 at 5:57 pm

    An even neater solution is to connect an Arduino between the keyboard and the PC.
    The Arduino pass all commands to and from the keyboard, as well as it can send the apropiate keyboard codes depending on the tilt of the Nunchuk.

    Cheaper and faster to implement though is the modifed keyboard solution.
    The Arduino solution requires some programming, but the software is pretty simple.

    Reply

  16. Comment by Ponch on July 24, 2009 at 2:19 pm

    Great idea. But was there a particular reason that you opted not to simply connect a WiiMote via bluetooth to the PC as so many others have done? Once connected, it would be relatively simple to convert the accelerometer readings to your desired keystroke.

    Reply

    Reply by Mike LeSauvage on August 10th, 2009 at 9:13 am

    Only that I didn’t have a Bluetooth dongle at the time and it wasn’t native on my motherboard. Well, I also didn’t require the Wiimote itself for this project – although using the two together could give me more tilt options. I think if I get back to this I would definitely use the two together.

    Mike

    Reply

  17. Pingback by Bus Pirate: Wii Nunchuck quick guide « Dangerous Prototypes on August 19, 2009 at 8:20 am

    […] Reading data from the nunchuck takes two steps. First, set the read pointer. Next, read six values to get the measurements, decoding guide here. […]

  18. Comment by SheeEttin on October 16, 2009 at 6:47 pm

    You might consider binding actions to “dead” keys (that may or may not exist on your actual keyboard), and simulate THOSE keypresses, so any accidental input doesn’t do anything.

    Or maybe it’s possible to pretend it’s a joystick…

    Reply

  19. Comment by Free NX on January 26, 2010 at 11:09 pm

    This game is so far ahead of its time

    Reply

  20. Comment by meet patel on September 21, 2011 at 9:13 am

    my nunchunk has White ,Red ,Green ,wires but no yellow wire but a black and blue wires what to do plz help
    WERE IS CLOCK BLUE OR BLACK

    Reply

  21. Comment by Sickbag on September 30, 2014 at 7:32 pm

    Just for the information, apparently the free program called glovepie for PC is already made for this kind of stuff (i saw that you can program a kinect or a wiimote on it) and it make it a lot easier for people like me who dont really know programmation. It let you do anything with pretty much any devices on your computer. You have to write the whole thing in a language really close to the basic and theres a lot of help, tuto and shared codes for different games on their wiki website.

    Reply

  22. Comment by Emilie on November 8, 2016 at 9:46 am

    I see interesting posts here. Your website can go viral
    easily, you need some initial traffic only.
    How to get it? Search for: ricusso’s methods massive traffic

    Reply

Comments RSS TrackBack Identifier URI

Leave a comment

Neglect-O-Blog is proudly powered by WordPress and themed by Mukkamu