summaryrefslogtreecommitdiff
path: root/Puzzle_Box/Puzzle_Box.pde
blob: 53f4f96a7007cc6c1b23ed0ec14b369614133946 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
/*
puzzle_box_sample.pde - Sample Arduino Puzzle Box sketch for MAKE.
COPYRIGHT (c) 2008-2011 MIKAL HART.  All Rights Reserved.
 
This software is licensed under the terms of the Creative
Commons "Attribution Non-Commercial Share Alike" license, version
3.0, which grants the limited right to use or modify it NON-
COMMERCIALLY, so long as appropriate credit is given and
derivative works are licensed under the IDENTICAL TERMS.  For
license details see

  http://creativecommons.org/licenses/by-nc-sa/3.0/
 
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
This code is written to accompany the January, 2011 MAKE article 
entitled "Reverse Geocache Puzzle Box".
 
This sketch illustrates how one might implement a basic puzzle box 
that incorporates rudimentary aspects of the technology in Mikal 
Hart's Reverse Geocache(tm) puzzle.
 
"Reverse Geocache" is a trademark of Mikal Hart.

For supporting libraries and more information see 

  http://arduiniana.org.
*/

#include <PWMServo.h>
#include <NewSoftSerial.h>
#include <TinyGPS.h>
#include <EEPROM.h>
#include <LiquidCrystal.h>

#include "Puzzle_Box.h"
#include "the_eye.h"

/* Hardware Objects */
NewSoftSerial nss(GPSrx, GPStx);
LiquidCrystal lcd(LCD_RS, LCD_RW, LCD_Enable, LCD_DB4, LCD_DB5, LCD_DB6, LCD_DB7);
TinyGPS tinygps; 
PWMServo servo;

/* Running Information */
int currentStage = MAIN_STAGE;
int attempt_counter;
int currentEyeAnimationStep = 0;
long lastLoopTime = 0;

float currentDistance = 0;


/* The Arduino setup() function */
void setup()
{
  /*Turn on switch LED*/
  pinMode(LED_pin, OUTPUT);
  digitalWrite(LED_pin,HIGH);
  
  /* attach servo motor */
  servo.attach(servo_control);

  /* establish a debug session with a host computer */
  Serial.begin(115200);

  /* establish communications with the GPS module */
  nss.begin(4800);

  /* establish communication with 8x2 LCD */
  lcd.createChar(0, eye1);
  lcd.createChar(1, eye2);
  lcd.createChar(2, eye3);
  lcd.createChar(3, eye4);
  lcd.createChar(4, eye5);
  lcd.createChar(5, eye6);
  lcd.createChar(6, eye7);
  lcd.createChar(7, eye8);
  lcd.begin(8, 2); // this for an 8x2 LCD -- adjust as needed 
  
  /* Make sure Pololu switch pin is OUTPUT and LOW */
  pinMode(pololu_switch_off, OUTPUT);
  digitalWrite(pololu_switch_off, LOW);
  
  /* make sure motorized latch is closed */
  servo.write(CLOSED_ANGLE); 
  
  /* read the attempt counter from the EEPROM */
  attempt_counter = EEPROM.read(EEPROM_OFFSET);
  if (attempt_counter == 0xFF) // brand new EEPROM?
    attempt_counter = 0;

}

/* The Arduino loop() function */
void loop()
{

    // Check for a stage transition
    int buttonState = digitalRead(BUTTON_PIN);

    if (buttonState = HIGH) {
        currentStage = BUTTON_STAGE;
    }
  
    // Find our stage
    switch (currentStage) {
        case MAIN_STAGE:
            doMainStage();
            break;

        case BUTTON_STAGE:
            doButtonStage();
            break;

    }

    // Find the current distance just to be ready
    doUpdateDistance();

    // Check for override login attempts
    doCheckOverrideSerial();

    /* Turn off after 5 minutes */
    if (millis() >= 300000)
        PowerOff();
}

/**
 * This is what we do while idle...
 */
void doMainStage() {
    /* Timeline
     *    0  E On (500 ms)
     *  500  E Off (200 ms)
     *  700  E On (500 ms)
     * 1200  E off (200 ms)
     * 1400  E on (1600 ms)
     * 3000  Shift Anim. (200 ms/frame * 13 frames = 2600 ms)
     * 5600  On (3000 ms)
     */

    int delta = millis() - lastLoopTime;

    if (delta < 500) {
        // On
        toggleEye(true);

    } else if (delta < 700) {
        // Off
        toggleEye(false);

    } else if (delta < 1200) {
        // On
        toggleEye(true);

    } else if (delta < 1400) {
        // Off
        toggleEye(false);

    } else if (delta < 3000) {
        // On
        toggleEye(true);

    } else if (delta < 5600) {
        // Shift Animation
        stepEyeAnimation();

    } else {
        // On
        toggleEye(true);
        
        // Reset timer
        lastLoopTime = millis();
    }
}

/**
 * This is what we do when the button has been pressed.
 */
void doButtonStage() {
  /* increment it with each run */
  ++attempt_counter;

  /* Greeting */
  Msg(lcd, "Hello", "Jesse!", 1500);
  Msg(lcd, "Welcome", "to your", 1500);
  Msg(lcd, "puzzle", "box!", 1500);

  /* Game over? */
  if (attempt_counter >= DEF_ATTEMPT_MAX)
  {
    Msg(lcd, "Sorry!", "No more", 2000);
    Msg(lcd, "attempts", "allowed!", 2000);
    PowerOff();
  }

  /* Print out the attempt counter */
  Msg(lcd, "This is", "attempt", 2000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(attempt_counter);
  lcd.print(" of "); 
  lcd.print(DEF_ATTEMPT_MAX);
  delay(2000);

  /* Save the new attempt counter */
  EEPROM.write(EEPROM_OFFSET, attempt_counter);

  Msg(lcd, "Seeking", "Signal..", 0);

  doCheckAccess();
}


/**
 * This function updates the distance, if possible.
 */
void doUpdateDistance() {
  /* Has a valid NMEA sentence been parsed? */
  if (nss.available() && tinygps.encode(nss.read()))
  {
    float lat, lon;
    unsigned long fix_age;

    /* Have we established our location? */
    tinygps.f_get_position(&lat, &lon, &fix_age);
    if (fix_age != TinyGPS::GPS_INVALID_AGE)
    {
      /* Calculate the distance to the destination */
      currentDistance = TinyGPS::distance_between(lat, lon, DEST_LATITUDE, DEST_LONGITUDE);
    }
  }

}

/**
 * This function checks for communication traffic from the usb serial port.
 */
void doCheckOverrideSerial() {

}

void doCheckAccess() {
  /* Are we close?? */
  if (currentDistance <= RADIUS)
  {
    Msg(lcd, "Access", "granted!", 2000);
    servo.write(OPEN_ANGLE);
  }

  /* Nope.  Print the distance. */
  else
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Distance");
    lcd.setCursor(0, 1);
    if (currentDistance < 1000)
    {
      lcd.print((int)currentDistance);
      lcd.print(" m.");
    }

    else
    {
      lcd.print((int)(currentDistance / 1000));
      lcd.print(" km.");
    }
    delay(4000);
    Msg(lcd, "Access", "Denied!", 2000);
  }

  PowerOff();
}




/* Called to shut off the system using the Pololu switch */
void PowerOff()
{
  Msg(lcd, "Powering", "Off!", 2000);
  lcd.clear(); 
  
  /*Turn off switch LED*/
  pinMode(LED_pin, OUTPUT);
  digitalWrite(LED_pin,LOW);
  
  /* Bring Pololu switch control pin HIGH to turn off */
  digitalWrite(pololu_switch_off, HIGH);

  /* This is the back door.  If we get here, then the battery power */
  /* is being bypassed by the USB port.  We'll wait a couple of */
  /* minutes and then grant access. */
  delay(120000);
  servo.write(OPEN_ANGLE); // and open the box 

  /* Reset the attempt counter */
  EEPROM.write(EEPROM_OFFSET, 0); 
  
  /* Leave the latch open for 10 seconds */
  delay(10000); 

  /* And then seal it back up */
  servo.write(CLOSED_ANGLE); 

  /* Exit the program for real */
  exit(1);
} 

/* A helper function to display messages of a specified duration */
void Msg(LiquidCrystal &lcd, const char *top, const char *bottom, unsigned long del)
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(top);
  lcd.setCursor(0, 1);
  lcd.print(bottom);
  delay(del);
}

// __|XX|__
// __|XX|__
// Location = 0 centers the eye.
void drawEye(int location)
{
  lcd.clear();

  location = location + 2;
  
  for (int i = 0; i < 4; i++) {
    lcd.setCursor(location + i, 0);
    lcd.write(i);
  }
  
  for (int i = 0; i < 4; i++) {
    lcd.setCursor(location + i, 1);
    lcd.write(i + 4);
  }
}

void stepEyeAnimation() {
    drawEye(eyeAnimationSteps[currentEyeAnimationStep]);
    currentEyeAnimationStep++;
    currentEyeAnimationStep = currentEyeAnimationStep % 12;
}

void toggleEye(bool on) {
    // Eyes on
    if (on) {
        lcd.display();
        digitalWrite(LED_pin, HIGH);
    
    } else {
      lcd.noDisplay();
      digitalWrite(LED_pin, LOW);
    } 
}

/**
 * Convert the distance to a particular unit.
 *
 */
float toRandomUnit(int choice, float dist) {
    switch (choice) {
        // feet
        case 0:

        // meters
        case 1:

        // cubits
        case 2:

        // hands
        case 3:


    }
}