ZenPin PoC

From Hive13 Wiki
Revision as of 16:45, 12 November 2009 by Craig (talk | contribs) (src code posted)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search


<ul><li>Property "ProjectImage" (as page type) with input value "Image:ZenPin1.jpg|300px" contains invalid characters or is incomplete and therefore can cause unexpected results during a query or annotation process.</li> <!--br--><li>"{{{StartDate}}}" contains an extrinsic dash or other characters that are invalid for a date interpretation.</li></ul>

Hive13 Project
ZenPin PoC
[[ZenPin1.jpg|200px]]
Status: Completed

Overview

Contest submission by: Craig

The concept of the ZenPin is to make it so when a person enters a PIN code that if somebody is watching they can not simply re-enter the pin. This is similar to a one-time password system but doesn't not require the user to memorize or carry any pincodes. Instead of memorizing a pin the user memorizes an algorithm.

For this Proof-of-Concept (PoC) the algorithm is very simple. The layout is:

    (R)     (Y)     (G)

    7   |    8    |  9
   ---------------------
    4   |    5    |  6
   ---------------------
    1   |    2    |  3
   ---------------------
    C   |    0    |  E

Where (R) = Red LED, (Y) = Yellow/Orange LED, (G) = Green LED

PIN codes are 5 digits long.

Goal

Get the green light to stay solid and the "good" tone to play.

Failure if the red light goes solid and the "bad" tone plays :)

How it works

The user enters any number they want to start. The system then randomly generates a 4 digit pin. Then the system uses the lights to "hint" as to what the next pin is. It will find the shortest distance to the next number via adding or subtracting and then blink either the Red LED or the Yellow LED for the amount of times needed. The Red LED represents subtraction while the Yellow represents addition. If the red blinks 3 times then you subtract 3 from the last number you just pressed. The number will roll over. For instance, if you press a 9 and the Yellow light blinks twice, then the next number is a 1.

If both the Red and the Yellow light blink at the same time then the next number is the same.

You can press 'C' to start over. This is verified by a quick blink of all lights.

You can press 'E' to repeat the last hint.

If you get any number wrong in the sequence then there will not be any indication. Instead the system will give you "hints" to the wrong numbers. Only after 5 digits will you hear the "bad" tone and the red light will stay on.

Inside of ZenPin

Source Code

Here is the Arduino Sourecode.

/* 
 Keypad concept for Passcode Challenge #1.  The idea is that
 instead of memorizing a passcode you can memorize a simple algorithm.
 this prevents ppl from shoulder surfing to see your PIN.  In this
 example there are two LEDs, addLed and subLed.
 
 To enter a passcode you can press any button.  After you press the
 first button the chip picks a random 4 digit PIN.  It then
 determines if it is quicker to add or subtract to reach the next
 number in the pin.  It will then send short blinks to the
 appropriate LED to tell you what the next pin is.
 
 Example:
 
 User Press #5.
 Board picks a random 4 digit pin, 3912
 Board blinks subLed 2 time
 User Presses 3
 Board blinks subLed 4 times
 User Presses 9
 Board blinks addLed 2 times
 User Presses 1
 Board blinks addLed 1 time
 User Presses 2
 Board "unlocks"
 
 If at any time the user enters the wrong number then the board
 will pick random Leds and blink patterns for each press after.
 
 Once 5 keys have been pressed a "bad" state will be displayed.

 If the next key is the same then both Leds blink once at the same time.
 
 Pressing 'C' will reset the counter.
 Pressing 'E' will redisplay the hint.
 
 -- Craig
 */

// Set PIN constants
const int addLed = 9;
const int subLed = 10;
const int winLed = 11;
const int speakerPin = 12;
const int grid1 = 8;
const int grid2 = 7;
const int grid3 = 6;
const int grid4 = 5;
const int grid5 = 4;
const int grid6 = 3;
const int grid7 = 2;

// Set other constants
const int DEBUG = 1;
const int MAXPINS = 4;
const int BLINKMS = 150;

// Set globals
int pinCount = 0;
int badPins = 0;
int pin[MAXPINS];
int buttonState = 0;
int higher = false;
int blinkCnt = 0;

/* Given row-col return button in ASCII */
int whichKey(int row, int col) {
 /* Guess: Each keypress will turn 2 pins HIGH */ 
 /* C  |  0  |  C-E  = 4-5  |  4-6  |  4-7
    1  |  2  |  3    = 3-5  |  3-6  |  3-7
    4  |  5  |  6    = 2-5  |  2-6  |  2-7
    7  |  8  |  9    = 1-5  |  1-5  |  1-7
 */

  switch(col) {
   case 5:
    switch(row) {
     case 4:
      return 'C';
      break;
     case 3:
      return '1';
      break;
     case 2:
      return '4';
      break;
     case 1:
      return '7';
      break;
      default:
        return 0;
    }
   case 6:
    switch(row) {
     case 4:
      return '0';
     case 3:
      return '2';
     case 2:
      return '5';
     case 1:
      return '8';
      default:
        return 0;
    }
   case 7:
    switch(row) {
     case 4:
      return 'E';
     case 3:
      return '3';
     case 2:
      return '6';
     case 1:
      return '9';
      default:
        return 0;
    }
    default:
      Serial.println("ERROR: Invalid Column");
      return 0;
  }

 return 0;
}

/* Check to see if a keywas pressed */
int getKeyPress() {
  int pinData = 0;
  int column = 0;

 /* We'll check for the Y cords first (smaller combos) */
 pinData = digitalRead(grid5);
 if(pinData) {
   pinData = digitalRead(grid6);
   if(pinData) {
     pinData = digitalRead(grid7);
     if(pinData) {
       return 0;  /* Exit early, no reason to waste more cycles */
     } else {
       column = 7;
     }
   } else {
     column = 6;
   }
 } else {  /* We know it's column 5 */
   column = 5;
 }
 /* If we make it here we know some button has been pressed */
 pinData = digitalRead(grid1);
 if(pinData) {
   pinData = digitalRead(grid2);
   if(pinData) {
     pinData = digitalRead(grid3);
     if(pinData) {
       pinData = digitalRead(grid4);
       if(pinData) {
         return 0;
       } else {
         return whichKey(4, column);
       }
     } else {
       return whichKey(3, column);
     }
   } else {
     return whichKey(2, column);
   }
 } else {
   return whichKey(1, column);
 }
 return 0;
}

// Taken from the arduino example 'melody'
void playTone(int tone, int duration) {
  for (long i = 0; i < duration * 1000L; i += tone * 2) {
    digitalWrite(speakerPin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speakerPin, LOW);
    delayMicroseconds(tone);
  }
}

// Play good tone
void playGood() {
  playTone(1519, 150);
  delay(150);
  playTone(1519, 150);
  delay(150);
  playTone(1275, 400);
  delay(150);
}

// Play bad tone
void playBad() {
  playTone(1275, 300);
  delay(150);
  playTone(1700, 600);
  delay(150);
}

// Blinks all lights once, setup / clearing
void blinkAll() {
  digitalWrite(addLed, HIGH);
  digitalWrite(subLed, HIGH);
  digitalWrite(winLed, HIGH);
  delay(BLINKMS);
  digitalWrite(addLed, LOW);
  digitalWrite(subLed, LOW);
  digitalWrite(winLed, LOW);
}
 
// Blinks the hint based on the globals: higher, blinkCnt  
void blinkHint() {
 int cnt;

 if(!blinkCnt && pinCount) {
    digitalWrite(addLed, HIGH);
   digitalWrite(subLed, HIGH);
   delay(BLINKMS);
   digitalWrite(addLed, LOW);
   digitalWrite(subLed, LOW);
   delay(BLINKMS);
   return;
 }
 // No game in progress
 if(!(blinkCnt && pinCount)) {
   blinkAll();
   return; 
 }

 if(DEBUG) {
   if(higher) {
     Serial.print("Add ");
   } else {
     Serial.print("Sub ");
   }
   Serial.print(blinkCnt, DEC);
   Serial.println();
 }
 
 for(cnt = 0; cnt < blinkCnt; cnt++) {
   if(higher) {
    digitalWrite(addLed, HIGH);
    delay(BLINKMS);
    digitalWrite(addLed, LOW);
    delay(BLINKMS); 
   } else {
    digitalWrite(subLed, HIGH);
    delay(BLINKMS);
    digitalWrite(subLed, LOW);
    delay(BLINKMS);     
   }
 }   
}

// Blinks an LED based on the last pin pressed
void blinkLed(int pin, int guess) {
 int diff = pin - guess;
 
 higher = false;
 blinkCnt = 0;
 
 // If the next number is the same the blink both LEDs once
 if(!diff) {
   blinkHint();
   return;
 }
 if(diff > 5) {
  blinkCnt = 10 - diff;
 } else if(diff < -5) {
  blinkCnt = diff + 10;
  higher = 1; 
 } else {
   blinkCnt = diff;
   if(diff > 0) {
     higher = true;
   }
 }
 blinkCnt = abs(blinkCnt);

 blinkHint();
}
 
void setup() {
 Serial.begin(9600); 
 // Analog Pin0 must be unconnected so we can read random noise
 randomSeed(analogRead(0));
 // Ensure lights are off
 pinMode(addLed, OUTPUT);
 pinMode(subLed, OUTPUT);
 pinMode(winLed, OUTPUT);
 // Speaker
 pinMode(speakerPin, OUTPUT);
 
 // This is the default state for digital pins, but meh...
 pinMode(grid1, INPUT);
 pinMode(grid2, INPUT);
 pinMode(grid3, INPUT);
 pinMode(grid4, INPUT);
 pinMode(grid5, INPUT);
 pinMode(grid6, INPUT);
 pinMode(grid7, INPUT);
 // Enable pull up resistors
 digitalWrite(grid1, HIGH);
 digitalWrite(grid2, HIGH);
 digitalWrite(grid3, HIGH);
 digitalWrite(grid4, HIGH);
 digitalWrite(grid5, HIGH);
 digitalWrite(grid6, HIGH);
 digitalWrite(grid7, HIGH);
 
 //Blink all lights to show we are done with setup
 blinkAll();
}

void loop() {
 int cnt;
 int key =  getKeyPress();
 delay(40);
 int key2 = getKeyPress(); // Attempt to debounce switch
 if(key == key2) {
   if(key && !buttonState) {
     if(DEBUG) {
       Serial.print("Key pressed: ");
       Serial.print(key, BYTE);
       Serial.println(); 
     }
     buttonState = 1;
     // Reset PIN attempt
     if(key == 'C') {
      pinCount = 0; 
      badPins = 0;
      blinkAll();
      return;
     }
     // Repeat last hint
     if(key == 'E') {
       blinkHint();
       return;
     }
     // Check to see if we are already processing a PIN
     if(pinCount) {
       pinCount++;
       // User PIN == Random Pin and no bad Pins yet
       if(key == pin[pinCount-2] && !badPins) {
         if(pinCount < MAXPINS+1) {
           blinkLed(pin[pinCount-1], key);
         }
       } else {
         // Bad Pin
         badPins++;
         // Blink code to a random pin
         if(pinCount < MAXPINS+1) {
           if(DEBUG) {
             Serial.println("BadPins, Faking Guess");
           }
           blinkLed('0'+random(10), key);
         }
       }
     } else {
       // This is the first key pressed, initialize new random pin
       pinCount++;
       for(cnt = 0; cnt < MAXPINS; cnt++) {
         pin[cnt] = '0' + random(10); 
       }
       if(DEBUG) {
         Serial.print("Random PIN: ");
         for(cnt = 0; cnt < MAXPINS; cnt++) {
           Serial.print(pin[cnt], BYTE);
         }
         Serial.println();
     }
     blinkLed(pin[0], key);
    }
   }
 }  // End if key == key2
 if(!key && buttonState) {
   buttonState = false;
 }
 // 5-Digit PIN w/ the first one not counting
 if(pinCount > MAXPINS && !buttonState) {
  if(!badPins) {
   Serial.println("ACCESS GRANTED");
   digitalWrite(winLed, HIGH);
   playGood();
   //delay(2000);
   digitalWrite(winLed, LOW);
  } else {
   Serial.println("FAIL");
   digitalWrite(subLed, HIGH);
   playBad();
   //delay(2000);
   digitalWrite(subLed, LOW);
  } 
  pinCount = 0;
  badPins = 0;
 }
}

TODO List

  • Nada, It's done!

Parts involved with this entry

  • Arduino
  • minikey 12 button keypad ($1.50 at mendlesons)
  • 3 LEDs
  • 9v Battery
  • Box from the clearance rack of TJ Max ;)