Networking and communications

Assignment

Design and build a wired &/or wireless network connecting at least two nodes. 

Ideas and Approach

For this project, the AS220 team decided to design a game between people using IR technology. 

Design Files

Steps Taken

Design Board

As a team we sat around and discussed how we'd design the board.  Everyone wanted to design different sending and receiving devices (e.g. from fun laser looking guns to hats).  That being the case we decided to make a board that would allow people to connect the IR components and LED in different ways.  So we sat around and came up with the appropriate components we thought we'd need.  The requirements were:
- An RGB LED to show status.
- An IR Receiver
- An IR Sender
- A 2x3 eader for programming
- A 2x2 header for power

Here's the board design that we ended up with...
Schematic

Board

Get IR Receive working on Arduino using Sainsmart


So I started by doing research on how to get the Arduino working with the Sainsmart IR Receiver.  It turns out I had the Sainsmart Sensor Shield and the sensor kit.  I had purchased these and thought it would make sense to start learning about IR using these.   So I downloaded their libraries and examples

Learning #1: The Arduino requires changes to the IRRemote Libraries
When I tried to get their basic examples working, I ran into following compile errors...
In file included from /Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremote.cpp:13:
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremoteInt.h:87: error: 'uint8_t' does not name a type
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremoteInt.h:88: error: 'uint8_t' does not name a type
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremoteInt.h:89: error: 'uint8_t' does not name a type
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremoteInt.h:92: error: 'uint8_t' does not name a type
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremote.cpp: In member function 'void IRsend::mark(int)':

...there were many more lines...
Upon researching it, I came across the following Arduino forum post.  It suggested that the IR library had the wrong include file.  Getting the newer library got things past the compile errors. 


Get IRRemote.h Library working on attiny

As I started to dig into how to get the attiny working, I came across the following post describing how to get simple text strings going between microprocessorsShawn also suggested we checkout the following Sparkfun tutorial.  Not only did this person start with a basic Arduino, but it showed how to do basic connections of attiny85s.  Unfortunately, I went down the path of trying to get the IRRemote.h library working with the attiny84. 

Learning #1: Don't always assume things programmed for attiny
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremote.cpp: In member function 'void IRsend::mark(int)':
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremote.cpp:172: error: 'TCCR2A' was not declared in this scope
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremote.cpp:172: error: 'COM2B1' was not declared in this scope
/Users/pfearey/Documents/Arduino/libraries/InfraredReceive/IRremote.cpp: In member function 'void IRsend::space(int)':
I found this forum post, which basically says that the attiny85 isn't compatible with the original IR library.  Fortunately, Nick Gammon is a great guy and he rewrote much of the library so that it will work with the attiny.  Here's the library he wrote.  Having said that...I downloaded the library and I still can't get it working.  The problem is with the timers and how the library references the timers. 

http://www.righto.com/2010/11/testing-arduino-ir-remote-library.html

CHANGED THE HEADER FILES TO MAKE THEM WORK WITH ATTINY84
Added the following to three files...IRRemote.h, IRRemoteInt.h and IRRemote.cpp

COMMENTED OUT LINES IN HEARDER FILES   
279/309:   // RESET_TIMER2;
 209:         // TIMSK &= ~_BV(TOIE0); //Timer2 Overflow Interrupt
260           // sbi(TIMSK,TOIE0);

*****   IT COMPILED..YEAH

CHANGED IRsend::enableIROut TO PIN 8 (from 3) in IRRemote.cpp
  pinMode(8, OUTPUT);
  digitalWrite(8, LOW); // When not sending PWM, we want it low

CHANGED from pin 6 to 8 on timer in IRRemoteInt.h
#elif defined( __AVR_ATtinyX4__ )
  #define IR_USE_TIMER1   // tx = pin 8  

After all this, I decided that I was not a good idea to use the IRRemote.h header files.  There were two reasons I decided to walk away from this...
1) After digging into the code, I realized it was largely meant for interfacing with the TV remote codes.  It wasn't really meant for sending specific letters or a coded system for programming a game.
2) I thought it would be easier to send simple codes using simple lighting (and turning off) the IR light. 
 

Getting Simple Morse Code working with IR


So now I turned my attention to getting the send and receive working.  First, I needed to get the serial monitor working so I could debug.  What I quickly realized was that the board we'd produced didn't have the FTDI TX/RX wired up.  That meant I had to just use that for the sending side while I worked on programming the receive (and debugging through the serial port on the receive.

ATTINY84:
For this I just had the loop run the following line.  This meant that it just sent a 1 and then waited a second.  That's all it did and it just kept send a 1 over and over.  My theory was that I would work on the receive code.
   charBlink(49);
    delay(1000);

ARDUINO:
This is what I used to read and debug the code.  My hole goal was to get the Arduino to read when the light when on and off with special attention being paid to the length of time the light was lit.  I did find some good code online that was setup for the matching of the DIT/DOT sequences.  So using those I tried to get them talking....

...this didn't go well.

For some reason, the "pulse lengths" (aka the time the light was turned on), never seemed to match the times that the IR light was on/off.  The IR reading never seemed to track with when the light was on.  If you look at the "EXAMPLE OUTPUT" on the right, you'll see that the pulse lengths are not even close. 

 /*
// Following is for Arduino
    #define RledPin 13   // This is the pin the LED is connected to
    #define buttonPin 10 // This is the pin that will read the button
    #define IRtxPin 8    // This is the pin that will send IR messages
    #define IRrxPin 11   // This is the pin that will READ the IR messages
    #define DEBUG_PRINT(x)     Serial.print (x)
    #define DEBUG_PRINTDEC(x)  Serial.print (x, DEC)
    #define DEBUG_PRINTLN(x)   Serial.println (x)
*/
// following is for attiny
    #include <SoftwareSerial.h>
    #define RledPin 5    // This is the pin the LED is connected to
    #define buttonPin 10 // This is the pin that will read the button
    #define IRtxPin 8    // This is the pin that will send IR messages
    #define IRrxPin 7    // This is the pin that will READ the IR messages
    #define SWrxPin 255   // this maps to the TX on the FDDI board
    #define SWtxPin 9   // this maps to the RX on the FDDI board
    #define DEBUG_PRINT(x)     SWSerial.print (x)
    #define DEBUG_PRINTDEC(x)  SWSerial.print (x, DEC)
    #define DEBUG_PRINTLN(x)   SWSerial.println (x) 

// The following variable will set how verbose we want the Serial debugging to be
const int debug_level = 2;  // 1 = high level debugging only; 2 = verbose logging

// These are the values for the short and long blinks.  Rules suggest dash should be 3 times longer than dot
// We should try to get this down to the shortest possible values. 
const int dot = 50; // One unit
const int dash = dot * 3; // Three units
const boolean DOT = false;
const boolean DASH = true;
const struct MorseTree *tree = NULL;
int button = -1;
int pulseThreshold = dot; // the threshold between a dot and a dash, in milliseconds.
int letterSeparation = dash; // the length of time, in milliseconds, that must pass with no input between letters.
int wordSeparation = dot * 7; //the length of time, in milliseconds, that must pass with no input between words.

#define MAX_CHAR_LENGTH 6

// Here are some constants that will make the code more readable
const int wantToPlay = 1; // the ASCII value for "1"...meaning..."I am player one and am ready to go"
const int yesWantToPlay = 2; // the ASCII value for "2", meaning..."I will be player two"
const int rock = 3; // the ASCII value for R...meaning...I am playing "Rock"
const int paper = 4; // the ASCII value for P...meaning...I am playing "Paper"
const int scissor = 5; // the ASCII value for S...meaning...I am playing "Scissor"

SoftwareSerial SWSerial(SWrxPin, SWtxPin); // RX, TX
 
void setup(){

  pinMode(RledPin, OUTPUT);
  pinMode(IRtxPin, OUTPUT);
  pinMode(IRrxPin, INPUT);
  pinMode(buttonPin, INPUT);
 
  // This line is only needed if planning on doing morse code
  // NOTE: It takes about 10 seconds to start up
  //initMorse(IRrxPin, pulseThreshold, letterSeparation, wordSeparation);
 
  SWSerial.begin(9600);
}

void loop()                    
{  
   // Uncomment this if you want to program the sending...
   // charBlink(49);
  
   findPartner();
   delay(1000);
   // When get here, another IR receiver has been found and communicated with
 
   //playGame();
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++ findPartner
// This is the routine that runs at the start of the game.  It is how each of the two devices
// will initially discover each other.  This routine will run until it has found a partner to
// play with.  While this routine runs the LED will blink yellow.
void findPartner(){
   debugLine(3, "Running findPartner");

   boolean haveAPartner = false;  
  
  do {
       // This will send out a request for someone to play.  This
       // will be done by sending a "1" (as in..."I am player 1").
       // After the "1" is sent, this will loop for two seconds waiting to see if a "2"
       // is sent back as a "2" will indicate "I am in and ready to play as player 2"
       // The only problem I can see here is...what if someone also tries to communicate in the
       // same time that the 1 is transmitted.  This will only be an issue if the 1 takes too long.
       // If that is the case, we will need to wait a random amount of time. 
      debugLine(1, "Looking for a partner to play");

      //charBlink(49);
      char c = getNextChar();
   
      debugLine(2, String(c));
     
      if (int(c) == 1) {
        digitalWrite(RledPin, HIGH);
        delay(1000);
        digitalWrite(RledPin, LOW);
        debugLine(2, "Game On");
        haveAPartner == true;
      }
     
   } while (haveAPartner == false);

}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++ playGame
// This is the routine is going to be the bulk of the game.  It will negotiate who's playing
// and what they have choosen for a move.  The hard part will be doing this so that the players
// don't step on each other's move.  You want players to be able to communicate their moves at the
// same time rather than having to sit and wait. 
void playGame(){ 
  boolean myMoveMade = false; // This will keep track of whether P1 has made their move
  boolean otherMoveMade = false; // This will keep track of whether P2 has made their move
 
  int myMove = 0;
  int otherMove = 0;
 
  do {  // Loop until both players have made their move
 
     // Check to see if current user made a move
    
     // Check to see if other user transmitting a move
    
  } while ((myMoveMade == false) && (otherMoveMade == false));

  declareWinner(myMove, otherMove);


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++ declareWinner
// This routine will declare the winner...
int declareWinner(int myMove, int otherMove){
    int winner = 0;
   
    switch (myMove) {
      case rock:
          if (otherMove == rock)
              winner = 0;
          else if (otherMove == paper)
              winner = 2;
          else
              winner = 1;
          break;
      case paper:
          if (otherMove == rock)
              winner = 1;
          else if (otherMove == paper)
              winner = 0;
          else
              winner = 2;
          break;
      case scissor:
          if (otherMove == rock)
              winner = 2;
          else if (otherMove == paper)
              winner = 1;
          else
              winner = 0;
          break;
      default:
        // statements
          break;
    }
   
    if (winner == 1) {  // This player one
      // Turn LED to Green and turn it on for 2 seconds
     
    }
    if (winner == 2) {  // The other player won
      // Turn LED to Red and turn it on for 2 seconds
     
    }
    if (winner == 0) {  // It's a tie
      // Turn LED to Yellow for one second and reset
    }
}




// ++++++++++++++++++++++++++++++++++++++++++++++++++++++ debugLine
// This is a general routine that will be used for debugging.
// It will use the SoftwareSerial library to send back debugging
// through the FDDI cable.  It will be passed two variables...
//   level    This will determine how verbose a debugging line the line is
//            and the line will only be printed to the screen if that level of debugging is included
//   message  This is the message that will be printed out.
void debugLine(int level, String message) {
   if (debug_level >= level) {
          SWSerial.println(message);
   }
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++ blink
// This is at the core of the entire send routine.  It is what will light the IR light.
//  It is passed one value...an integer that determines the length of time it should be lit.
void blink(int L){
  digitalWrite(IRtxPin, HIGH);   // set the LED on
  delay(L);                  // wait for l/100 of a second
  digitalWrite(IRtxPin, LOW);    // set the LED off
  delay(dot);
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++ charBlink
void charBlink(int c){
// This routine lights the LED using the morse code pattern.  It takes in integeger that represents
// the ASCII code for the specific character. 
  switch (c) {
    case 1:  // Player 1
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      debugLine(2, "Sent Player 1");
      return;

    case 2:  // Player 2
      blink(dot);
      blink(dot);
      blink(dash);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      debugLine(2, "Sent Player 2");
      return;

    case 3:  // Rock
      blink(dot);
      blink(dot);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      debugLine(2, "Sent Rock");
      return;

    case 4:  // Paper
      blink(dot);
      blink(dash);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      debugLine(2, "Sent Paper");
      return;
 
    case 5:  // Scissor
      blink(dot);
      blink(dash);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      debugLine(2, "Sent Paper");
      return;

    case 48:  // 0
      blink(dash);
      blink(dash);
      blink(dash);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      debugLine(2, "Sent 0");
      return;

    case 49:  // 1
      blink(dot);
      blink(dash);
      blink(dash);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      debugLine(2, "Sent 1");
      return;

    case 50:  // 2
      blink(dot);
      blink(dot);
      blink(dash);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;

    case 51:  // 3
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;

    case 52:  // 4
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;

    case 53:  // 5
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;

    case 54:  // 6
      blink(dash);
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;

    case 55:  // 7
      blink(dash);
      blink(dash);
      blink(dot);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;

    case 56:  // 8
      blink(dash);
      blink(dash);
      blink(dash);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;

    case 57:  // 9
      blink(dash);
      blink(dash);
      blink(dash);
      blink(dash);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;

    case 65:  // A
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 66:  // B
      blink(dash);
      blink(dot);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 67:  // C
      blink(dash);
      blink(dot);
      blink(dash);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 68: // D
      blink(dash);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 69:  // E
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
     
    case 70:  // F
      blink(dot);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 71:  // G
      blink(dash);
      blink(dash);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 72:  // H
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 73:  // I
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 74:  // J
      blink(dot);
      blink(dash);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 75:  // K
      blink(dash);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 76:   // L
      blink(dot);
      blink(dash);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 77:   // M
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 78:  // N
      blink(dash);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;   
 
    case 79:  // O
      blink(dash);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 80:  // P
      blink(dot);
      blink(dash);
      blink(dash);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 81:  // Q
      blink(dash);
      blink(dash);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 82:  // R
      blink(dot);
      blink(dash);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return; 
 
    case 83:  // S
      blink(dot);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 84:  // T
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 85:  // U
      blink(dot);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 86:  // V
      blink(dot);
      blink(dot);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 87:  // W
      blink(dot);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 88:  // X
      blink(dash);
      blink(dot);
      blink(dot);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 89:  // Y
      blink(dash);
      blink(dot);
      blink(dash);
      blink(dash);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    case 90:  // Z
      blink(dash);
      blink(dash);
      blink(dot);
      blink(dot);
      delay(letterSeparation); // After each letter, three dots of silence is observed.
      return;
 
    default:
      debugLine(2, "Sorry, i didn't recognize that character ");
    }

}

// ***************************************************************************
// THE FOLLOWING CAME FROM SOMEONE ELSE
// http://bitbucket.natmote.net/arduino/src/c8866226c649/morse_decoder/?at=default

void initMorse(int lbutton, int lpulseThreshold, int lletterSeparation, int lwordSeparation)
{
  tree = generateMorseTree();
  button = lbutton;
  pulseThreshold = lpulseThreshold;
  letterSeparation = lletterSeparation;
  wordSeparation = lwordSeparation;
}


struct MorseTree
{
  // '-' unless this is a leaf node
  char character;
  struct MorseTree *dotChild;
  struct MorseTree *dashChild;
};

struct MorseData
{
  char character;
  bool code[MAX_CHAR_LENGTH];
  byte codeLength;
};

void initMorseTree(struct MorseTree *tree)
{
  tree -> character = '-';
  tree -> dotChild = NULL;
  tree -> dashChild = NULL;
}

struct MorseTree *newMorseTree()
{
  struct MorseTree *tree = (struct MorseTree *) malloc(sizeof(struct MorseTree));
  initMorseTree(tree);
  return tree;
}

void addTreeMember(struct MorseTree *tree, struct MorseData &data)
{
  struct MorseTree *current = tree;
  for (byte i = 0; i < data.codeLength; i++)
  {
    boolean currentSymbol = data.code[i];
    if (currentSymbol == DOT)
    {
      if (current -> dotChild == NULL)
        current -> dotChild = newMorseTree();
      current = current -> dotChild;
    }
    else
    {
      if (current -> dashChild == NULL)
        current -> dashChild = newMorseTree();
      current = current -> dashChild;
    }
  }

  // now current must be a leaf node
  current -> character = data.character;
}

void addTreeMembers(struct MorseTree *tree, struct MorseData data[], byte dataLength)
{
  for (byte i = 0; i < dataLength; i++)
    addTreeMember(tree, data[i]);
}

void addAlphabet(struct MorseTree *tree)
{
  struct MorseData data[] = {
    {'A', {DOT, DASH}, 2},
    {'B', {DASH, DOT, DOT, DOT}, 4},
    {'C', {DASH, DOT, DASH, DOT}, 4},
    {'D', {DASH, DOT, DOT}, 3},
    {'E', {DOT}, 1},
    {'F', {DOT, DOT, DASH, DOT}, 4},
    {'G', {DASH, DASH, DOT}, 3},
    {'H', {DOT, DOT, DOT, DOT}, 4},
    {'I', {DOT, DOT}, 2},
    {'J', {DOT, DASH, DASH, DASH}, 4},
    {'K', {DASH, DOT, DASH}, 3},
    {'L', {DOT, DASH, DOT, DOT}, 4},
    {'M', {DASH, DASH}, 2},
    {'N', {DASH, DOT}, 2},
    {'O', {DASH, DASH, DASH}, 3},
    {'P', {DOT, DASH, DASH, DOT}, 4},
    {'Q', {DASH, DASH, DOT, DASH}, 4},
    {'R', {DOT, DASH, DOT}, 3},
    {'S', {DOT, DOT, DOT}, 3},
    {'T', {DASH}, 1},
    {'U', {DOT, DOT, DASH}, 3},
    {'V', {DOT, DOT, DOT, DASH}, 4},
    {'W', {DOT, DASH, DASH}, 3},
    {'X', {DASH, DOT, DOT, DASH}, 4},
    {'Y', {DASH, DOT, DASH, DASH}, 4},
    {'Z', {DASH, DASH, DOT, DOT}, 4},
  };

  addTreeMembers(tree, data, 26);
}

void addNumbers(struct MorseTree *tree)
{
  struct MorseData data[] = {
    {'1', {DOT, DASH, DASH, DASH, DASH}, 5},
    {'2', {DOT, DOT, DASH, DASH, DASH}, 5},
    {'3', {DOT, DOT, DOT, DASH, DASH}, 5},
    {'4', {DOT, DOT, DOT, DOT, DASH}, 5},
    {'5', {DOT, DOT, DOT, DOT, DOT}, 5},
    {'6', {DASH, DOT, DOT, DOT, DOT}, 5},
    {'7', {DASH, DASH, DOT, DOT, DOT}, 5},
    {'8', {DASH, DASH, DASH, DOT, DOT}, 5},
    {'9', {DASH, DASH, DASH, DASH, DOT}, 5},
    {'0', {DASH, DASH, DASH, DASH, DASH}, 5},
  };

  addTreeMembers(tree, data, 10);
}

void addPunctuation(struct MorseTree *tree)
{
  struct MorseData data[] = {
    {'.', {DOT, DASH, DOT, DASH, DOT, DASH}, 6},
    {',', {DASH, DASH, DOT, DOT, DASH, DASH}, 6},
    {'?', {DOT, DOT, DASH, DASH, DOT, DOT}, 6},
  };

  addTreeMembers(tree, data, 3);
}

struct MorseTree *generateMorseTree()
{
  // since this will live for the life of the program anyway, we won't even bother to have a strategy for freeing it.
  struct MorseTree *tree = newMorseTree();
  initMorseTree(tree);

  addAlphabet(tree);
  addNumbers(tree);
  addPunctuation(tree);

  return tree;
}

// ---------------------------------------
// Arduino related stuff:
// ---------------------------------------

// waits for the given pin to go to the given state. To reduce noise,
// the pin must read the required value twice, with a short delay
// between the readings, to count as a reading.
void waitFor(int pin, int state)
{
  debugLine(3, "Running waitFor on pin");
  debugLine(3, String(pin));
  debugLine(3, "   ...and am waiting for");
  debugLine(3, String(state));

  do
  {
    // spin until the pin reads the given state
    while (digitalRead(pin) != state) { }
    // delay, to verify the reading
    delay(30);
    // continue if the reading has changed
  } while (digitalRead(pin) != state);
}

boolean getNextSymbol()
{
  debugLine(3, "Running getNextSymbol");

  waitFor(button, HIGH);
  debugLine(3, "Got a high");
  unsigned long start = millis();

  waitFor(button, LOW);
  debugLine(3, "Went low");
  unsigned long ending = millis();

  unsigned long pulseLength = ending - start;

  //Serial.print("Pulse length = ");
  //Serial.print(pulseLength);
  //Serial.print(" which is a ");
  if (pulseLength > pulseThreshold) {
    //Serial.println("DASH");
    return DASH;
  }
  else {
    //Serial.println("DOT");
    return DOT;
  }
}

boolean shouldTimeOut()
{
  unsigned long start = millis();
  while (digitalRead(button) == LOW)
  {
    if (millis() - start > letterSeparation)
      return true;
  }

  return false;
}

boolean shouldBeSpace()
{
  unsigned long start = millis();
  while (digitalRead(button) == LOW)
  {
    if (millis() - start > wordSeparation)
      return true;
  }

  return false;
}

char getNextChar()
{
  debugLine(3, "Running getNextChar");
  static boolean lastCharWasSpace = false;
  const struct MorseTree *current = tree;
  byte symbolCount = 0;

  if (!lastCharWasSpace && shouldBeSpace())
  {
    lastCharWasSpace = true;
    return ' ';
  }

  lastCharWasSpace = false;

  while (true)
  {
    symbolCount++;
    boolean currentSymbol = getNextSymbol();
    debugLine(3, (currentSymbol == DOT ? "DOT" : "DASH"));
    if (currentSymbol == DOT)
      current = current -> dotChild;
    else
      current = current -> dashChild;

    if (current == NULL)
      return '-';

    if (shouldTimeOut() || symbolCount >= MAX_CHAR_LENGTH) {
      debugLine(2, "Got a character!");
      return current -> character;
    }
  }

  // should never get here
  return '!';
}




IR TESTING SETUP


+++++++++++++++++++++++++++++++++++++++++
++++++++++++ EXAMPLE OUTPUT ++++++++++++
+++++++++++++++++++++++++++++++++++++++++
+++ Output showing variant pulse lengths
Pulse length = 10736 which is a DASH
Pulse length = 568 which is a DASH
Pulse length = 1933 which is a DASH
Pulse length = 31 which is a DOT
Pulse length = 65 which is a DASH
-
Pulse length = 31 which is a DOT
Pulse length = 8360 which is a DASH
Pulse length = 30 which is a DOT
Pulse length = 31 which is a DOT
Pulse length = 63 which is a DASH
-
Pulse length = 3789 which is a DASH
Pulse length = 31 which is a DOT
Pulse length = 122 which is a DASH
Pulse length = 61 which is a DASH
Pulse length = 31 which is a DOT
-
Pulse length = 9574 which is a DASH
Pulse length = 3347 which is a DASH
Pulse length = 158 which is a DASH
Pulse length = 6831 which is a DASH
Pulse length = 122 which is a DASH
Pulse length = 3010 which is a DASH