Zeitgesteuerte Stationen
z.B. für einen Minimulti. Man kann es evtl. auch als “Echtzeitcache” bezeichnen. Eine Zeitsteuerung kann gut die Aufgabe erledigen, das die Cacher nur eine bestimmte Zeit zwischen zwei Station haben, um weiterzukommen. (Der andere Fall ist, die Station nur zu einer bestimmten Zeitraum oder Uhrzeit “bedienen” zu können.)
Schwierigkeit des Nachbaus: D1.5 Prorammcode, D3 Elektronik, D3 für die Box
Die Idee:
Folgende Situation: Die Cacher haben die Aufgabe, innerhalb von einigen Minuten die nächste Station zu finden. Dazu bekommen sie an der ersten Station einen Code, den sie an der zweiten Station eingeben müssen. Über diesen Code kann der Arduino ermitteln, wann die Cacher den Code bekommen haben.
Der Trick hierbei ist ganz einfach: Der Code enthält (gut verschlüsselt) die aktuelle Uhrzeit zum Zeitpunkt an der ersten Station. Die zweite Station entschlüsselt den Code und kann jetzt ermitteln, wie lange die Cacher benötigt haben. Dazu haben beide Stationen eine interne Uhr (Das ist ein RTC – RealTimeControl), die die aktuelle Uhrzeit kennen. Daraus lassen sich dann entsprechende Ereignisse erzeugen:
- sei es – die Cacher müssen noch mal Anfangen, da sie zu lange benötigt haben,
- waren knapp entkommen und müssen einen Umweg laufen,
- oder dürfen bei einer guten Zeit direkt zur nächsten Station.
Es lassen sich bestimmt noch mehr Ideen hier einbringen. Ich kann mir auch gut vorstellen, das man so etwas über mehrere Statione durchspielen könnte.
Die benötigten Bauteile (pro Station):
- Ein Arduino (je kleiner, desto besser)
- ein LCD Display (hier ist es ein 2 Zeilen x 20 Zeichen)
- dazu ein PCF 8574
- alternativ, um sich Verdrahtungen zu sparen, ein LCD mit einem I2C Anschluß
- Ein Zeitmodul – am besten ein RTC3221 mit Trockenbatterie
- ein Poti für das LCD (10k Ohm)
- 1 x 220 Ohm
An der ersten Station sind keine Taster nötig. Diese soll nur Text und den Code anzeigen. Die 2. Station benötigt noch ein paar Taster zum eingeben des Codes:
- 1x 2,2k Ohm
- 1x 330 Ohm
- 1x 680 Ohm
- 1x 1k Ohm
- 4 Taster
Die Schaltung:
Für die erste Station kann der Bereich mit den Tastern (linkes Bredboard) einfach weggelassen werden. Beachte: Ich habe die Stromversorgungen der Einfachheit halber nicht mit eingezeichnet!
Zeitmodul und LCD werden über den I2C Bus angesteuert – dies erspart einiges an Verdrahtungsarbeiten. Man kann das LCD auch direkt am Arduino anschließen, aber dann fallen natürlich mehr Lötarbeiten an.
Verdrahtung mit einem PCF 8574:
Beachte: Wenn du ein LCD mit I2C Modul betreibst (gibt es fertig mit eingebautem 8574, google mal nach “arduino lcd i2c”), entfällt das IC 8574. Daraus folgt: erheblich weniger Verdrahtungsaufwand! Der Code für den Arduino bleibt aber (fast) gleich.
Der Code:
Station 1:
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 |
#include <LiquidCrystal_I2C.h> /* * Geocache Zeistation * Liest die Uhrzeit aus einem DS3231 aus * * Und gibt einen Code, der die Uhrzeit (HEX) enhält, auf dem Display zurück */ #include <LiquidCrystal_I2C.h> #include <DS3232RTC.h> //http://github.com/JChristensen/DS3232RTC #include <Time.h> //http://www.arduino.cc/playground/Code/Time #include <Wire.h> LiquidCrystal_I2C lcd(0x38,16,2); /* *Koordinate */ String Nordwert="N 52 00.000"; String Ostwert="E 009 00.000"; boolean esistnacht = true; void setup() { setSyncProvider(RTC.get); lcd.init(); } void loop() { //prüfe, ob Uhrzeit passt! int teststunde=hour(); if (teststunde>=6 && teststunde<=17) { esistnacht=false; } if (esistnacht) { //lcd.setCursor(0,0); //lcd.print(second()); lcd.setCursor(0,0); //lcd.print(getCode()); bool test=getJahr2000(); if (test==true) { lcd.print("Akku leer! Bitte"); lcd.setCursor(1,0); lcd.print("Owner informieren!"); delay(5000); } delay(1000); lcd.clear(); lcd.setCursor(1,0); centerRollIn("Hallo Suchende!",0); delay(500); centerRollIn("Vorsicht!",1); delay(2500); lcd.clear(); centerRollIn("Ihr seid in",0); delay(2500); lcd.clear(); centerRollIn("gro\342er Gefahr!",0); delay(500); centerRollIn("Die Ringgeister",1); delay(2500); lcd.clear(); centerRollIn("suchen euch und",0); delay(500); centerRollIn("sind ganz ",1); delay(2500); lcd.clear(); centerRollIn("Nah! Habt ihr",0); delay(500); centerRollIn("ihren \365blen",1); delay(2500); lcd.clear(); centerRollIn("Geruch bemerkt?",0); delay(500); centerRollIn("Schaut den Weg",1); delay(2500); lcd.clear(); centerRollIn("weiter. Seht ihr",0); delay(500); centerRollIn("sie?",1); delay(3500); lcd.clear(); centerRollIn("Flieht! Lauft!",0); delay(500); centerRollIn("Lauft zur\365ck",1); delay(3500); lcd.clear(); centerRollIn("zur Weggabelung",0); delay(3500); lcd.clear(); centerRollIn("und dann weiter",0); delay(500); centerRollIn("zu diesen Koos:",1); delay(3500); lcd.clear(); centerRollIn(Nordwert,0); delay(500); centerRollIn(Ostwert,1); delay(12000); lcd.clear(); centerRollIn("Ihr habt nur 9",0); delay(500); centerRollIn("Minuten um euch",1); delay(2500); lcd.clear(); centerRollIn("zu retten!",0); delay(500); centerRollIn("Merkt euch den",1); delay(2500); lcd.clear(); centerRollIn("Code:",0); delay(500); centerRollIn(getCode(),1); delay(12000); lcd.clear(); centerRollIn("Jetzt rennt!",0); delay(200); centerRollIn("Oder ihr sterbt!",1); delay(4000); lcd.clear(); centerRollIn("LAUFT!",1); delay(4000); lcd.clear(); delay(9000); } else { lcd.clear(); centerRollIn("?? Was wollt",0); centerRollIn("ihr hier??",1); delay(8000); lcd.clear(); centerRollIn("Dies ist ein",0); centerRollIn("Nachtcache!",1); delay(8000); } } void centerRollIn(String text, int top) { String t; int pos; int i; //breite des displays ist 16 zeichen int textlaenge=text.length(); int position=8; for (i=0; i<=textlaenge; i++) { t=text.substring(0,i); pos=position-(i/2-1)-1; lcd.setCursor(pos,top); lcd.print(t); delay(200); } } String getCode() { String StrStunde=String(hour()*2+2,HEX); String StrMinute=String(minute()*3+3,HEX); String StrSekunde=String(second()+5,HEX); String ret; int stdLang=StrStunde.length(); int minLang=StrMinute.length(); int sekLang=StrSekunde.length(); if (stdLang==1) { StrStunde="0"+StrStunde; } if (minLang==1) { StrMinute="0"+StrMinute; } if (sekLang==1) { StrSekunde="0"+StrSekunde; } ret=StrMinute+StrSekunde+StrStunde; //wenn der akku leer it dann gib diesen code zurück if (getJahr2000()==true) { ret="010010"; } ret.toUpperCase(); return ret; } bool getJahr2000() { //prüfe das Jahr, wenn diese 2000 ist, dann ist der Akku leer! String StrJahr=String(year()); bool ret=false; if (StrJahr=="2000") { ret=true; } return ret; } |
Station 2:
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 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 |
/* * Geocache Zeistation * Liest die Uhrzeit aus einem DS3231 aus * *Codeeingabe aus der Station 1, hierrüber die Zeit *Ermitteln, wie lange die Cacher von 1 zu zwei unterwegs waren */ #include <LiquidCrystal_I2C.h> #include <DS3232RTC.h> //http://github.com/JChristensen/DS3232RTC #include <Time.h> //http://www.arduino.cc/playground/Code/Time #include <Wire.h> LiquidCrystal_I2C lcd(0x38,16,2); /* * Notcode * Wird eingegeben, wenn der Akku in Station 1 oder hier leer ist. */ String Nordwert = "N 52 00.000"; String Ostwert = "E 009 00.000"; String Notcode = "000000"; int toleranzsekunden=45; // tolleranzsekunden, um zeitunterschiede der STationen ein wenig auszugleichen int maxzeit=570; // in sekunden, 300 wären 5 Minuten #define RIGHT_10BIT_ADC 0 // right #define UP_10BIT_ADC 129 // up #define DOWN_10BIT_ADC 312 // down #define LEFT_10BIT_ADC 480 // left #define SELECT_10BIT_ADC 1000 // right #define BUTTONHYSTERESIS 12 // hysteresis for valid button sensing window //return values for ReadButtons() #define BUTTON_ADC_PIN A1 #define BUTTON_NONE 0 // #define BUTTON_RIGHT 1 // #define BUTTON_UP 2 // #define BUTTON_DOWN 3 // #define BUTTON_LEFT 4 // #define BUTTON_SELECT 5 // byte buttonJustPressed = false; //this will be true after a ReadButtons() call if triggered byte buttonJustReleased = false; //this will be true after a ReadButtons() call if triggered byte buttonWas = BUTTON_NONE; //used by ReadButtons() for detection of button events String eingabestring =" "; char eingabewerte[17] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' , 'A' , 'B' , 'C' ,'D' , 'E' ,'F', '\0'}; int postioneingabewert[7]={0, 0, 0, 0, 0, 0, 0}; int eingabeposition =1; // postition 1 pis 6 int eingabevalue =0; // 0 bis 15 int cursorposition =1; boolean ende = false; boolean notcodeerlaubt= false; boolean codeistok = false; void setup() { pinMode( BUTTON_ADC_PIN, INPUT ); digitalWrite( BUTTON_ADC_PIN, LOW ); setSyncProvider(RTC.get); lcd.init(); start(); } void loop() { while(ende==false) { lcd.setCursor(0,0); byte button; byte timestamp; //lcd.print("Code:"); bool test=getJahr2000(); if (test==true) { lcd.clear(); centerRollIn("Akku leer! Bitte",0); centerRollIn("Owner informieren!",1); delay(6000); lcd.clear(); centerRollIn("Eure Zeit kann",0); centerRollIn("nicht ermittelt",1); delay(2500); lcd.clear(); centerRollIn("werden :(",0); lcd.setCursor(1,0); centerRollIn("Deshalb geht nun",1); delay(2500); lcd.clear(); centerRollIn("zu diesen Koos:",0); delay(2500); lcd.clear(); centerRollIn(Nordwert,0); lcd.setCursor(1,0); delay(500); centerRollIn(Ostwert,1); delay(15000); ende=true; } button = ReadButtons(); if( buttonJustPressed || buttonJustReleased ) { //lcd.setCursor( 4, 1 ); //lcd.print( " " ); } switch( button ) { case BUTTON_NONE: { break; } case BUTTON_RIGHT: { //lcd.setCursor( 4, 1 ); //lcd.print( "Up" ); eingabevalue=eingabevalue+1; if (eingabevalue>15) { eingabevalue=0; } schreibe_wert(); break; } case BUTTON_UP: { //lcd.setCursor( 4, 1 ); //lcd.print( "Down" ); eingabevalue=eingabevalue-1; if (eingabevalue<0) { eingabevalue=15; } schreibe_wert(); break; } case BUTTON_DOWN: { //lcd.setCursor( 4, 1 ); //lcd.print( "Right" ); eingabeposition=eingabeposition+1; if (eingabeposition>6) { eingabeposition=1; } eingabevalue=postioneingabewert[eingabeposition]; schreibe_wert(); break; } case BUTTON_LEFT: { int stundenwert=postioneingabewert[5]*16+postioneingabewert[6]; int minutenwert=postioneingabewert[1]*16+postioneingabewert[2]; int sekundenwert=postioneingabewert[3]*16+postioneingabewert[4]; //notcode? 01FF10 if (stundenwert==16 && minutenwert==1 && sekundenwert==255) { lcd.clear(); centerRollIn("Dies war der",0); delay(500); centerRollIn("Notcode.",1); delay(2500); gewonnen(); ende=true; } if (ende==false) { int stundeW=(stundenwert-2)/2; int minuteW=(minutenwert-3)/3; int sekundW=sekundenwert-5; int aktuelleStunde=hour(); int aktuelleMinute=minute(); int aktuelleSkunde=second(); if (stundenwert<=15 || stundenwert>=23) { if (minutenwert>2 && minutenwert<=180 && sekundenwert>4 && sekundenwert<=64) { //Code ist ok codeistok=true; } } if (codeistok==true) { long int zeitaktuell=aktuelleStunde*60*60+aktuelleMinute*60+aktuelleSkunde; long int zeitalt=stundeW*60*60+minuteW*60+sekundW; //tolleranz von x sekunden subrahieren, um zeitdifferenzen halbwegst auszugleichen zeitaktuell=zeitaktuell-toleranzsekunden; if (zeitaktuell<0) { zeitaktuell=(24*60*60)+zeitaktuell; } int zeitdifferenz; //vor 24:00Uhr, oder alte zeit ist nach 24Uhr if (zeitaktuell>=zeitalt) { zeitdifferenz=zeitaktuell-zeitalt; } else { zeitdifferenz=zeitaktuell+24*60*60-zeitalt; } //int zeitInMinuten=zeitdifferenz/60; lcd.clear(); lcd.setCursor( 0, 0 ); lcd.print( "Ben\357tigte Sek:"); lcd.setCursor( 0, 1 ); lcd.print( zeitdifferenz ); if (zeitdifferenz<=maxzeit) { delay(5000); // gechafft in der vorgegebenen Zeit if (zeitdifferenz<120) { lcd.clear(); centerRollIn("Oh! Ihr ward ",0); delay(500); centerRollIn("schnell. Seid",1); delay(2500); lcd.clear(); centerRollIn("ihr etwa",0); centerRollIn("Teleportiert???",1); delay(3500); } gewonnen(); ende=true; } else { //nicht geschafft verloren(); ende=true; } }//ende code ist ok else { lcd.clear(); lcd.setCursor( 0, 0 ); lcd.print("Code Falsch" ); delay(5000); start(); } /* lcd.setCursor( 1, 1 ); lcd.print( zeitInMinuten ); */ break; }// ende id ende==false }//ende case (entertaste) }// ende switch if( buttonJustPressed ) buttonJustPressed = false; if( buttonJustReleased ) buttonJustReleased = false; } } /*Startbildschirm*/ void start() { lcd.clear(); lcd.print("Code:"); lcd.setCursor(10,0); lcd.print("000000"); lcd.setCursor(10,1); lcd.print("^"); for (int i=1; i<=6; i++) { postioneingabewert[i]=0; } eingabeposition =1; eingabevalue =0; cursorposition =1; } void schreibe_wert() { cursorposition=eingabeposition+9; lcd.setCursor( cursorposition, 0 ); lcd.print(eingabewerte[eingabevalue]); postioneingabewert[eingabeposition]=eingabevalue; lcd.setCursor(9,1); lcd.print(" "); lcd.setCursor(cursorposition,1); lcd.print("^"); delay(250); } /* * Tasten auslesen */ byte ReadButtons() { unsigned int buttonVoltage; byte button = BUTTON_NONE; // return no button pressed if the below checks don't write to btn //read the button ADC pin voltage buttonVoltage = analogRead( BUTTON_ADC_PIN ); //analowerte auf LCD ausgeben, um die knöpfe zu kalibrieren /* lcd.setCursor(0,0); lcd.print(buttonVoltage); delay(2500); lcd.setCursor(0,0); lcd.print( " " ); delay(2500); */ // //sense if the voltage falls within valid voltage windows if ( buttonVoltage <= ( RIGHT_10BIT_ADC + BUTTONHYSTERESIS )) { button = BUTTON_RIGHT; } else if( buttonVoltage >= ( UP_10BIT_ADC - BUTTONHYSTERESIS ) && buttonVoltage <= ( UP_10BIT_ADC + BUTTONHYSTERESIS ) ) { button = BUTTON_UP; } else if( buttonVoltage >= ( DOWN_10BIT_ADC - BUTTONHYSTERESIS ) && buttonVoltage <= ( DOWN_10BIT_ADC + BUTTONHYSTERESIS ) ) { button = BUTTON_DOWN; } else if( buttonVoltage >= ( LEFT_10BIT_ADC - BUTTONHYSTERESIS ) && buttonVoltage <= ( LEFT_10BIT_ADC + BUTTONHYSTERESIS ) ) { button = BUTTON_LEFT; } else if( buttonVoltage >= ( SELECT_10BIT_ADC - BUTTONHYSTERESIS ) && buttonVoltage <= ( SELECT_10BIT_ADC + BUTTONHYSTERESIS ) ) { button = BUTTON_SELECT; } //handle button flags for just pressed and just released events if( ( buttonWas == BUTTON_NONE ) && ( button != BUTTON_NONE ) ) { //the button was just pressed, set buttonJustPressed, this can optionally be used to trigger a once-off action for a button press event //it's the duty of the receiver to clear these flags if it wants to detect a new button change event buttonJustPressed = true; buttonJustReleased = false; } if( ( buttonWas != BUTTON_NONE ) && ( button == BUTTON_NONE ) ) { buttonJustPressed = false; buttonJustReleased = true; } //save the latest button value, for change event detection next time round buttonWas = button; return( button ); } void gewonnen() { lcd.clear(); centerRollIn("Boa! Noch mal",0); delay(500); centerRollIn("Gl\365ck gehabt!",1); delay(2500); lcd.clear(); centerRollIn("Ihr seid den",0); delay(500); centerRollIn("Ringgeistern",1); delay(2500); lcd.clear(); centerRollIn("entkommen!",0); delay(500); centerRollIn("Wartet noch ein",1); delay(2500); lcd.clear(); centerRollIn("wenig, bevor ihr",0); delay(500); centerRollIn("weiterzieht.",1); delay(4500); lcd.clear(); centerRollIn("...... Geht",0); centerRollIn("jetzt zu diesen",1); delay(2500); lcd.clear(); centerRollIn("Koordinaten:",0); delay(2500); lcd.clear(); centerRollIn(Nordwert,0); delay(500); centerRollIn(Ostwert,1); delay(15000); } void verloren() { lcd.clear(); centerRollIn("Oh nein!",0); delay(500); centerRollIn("Sie haben euch!",1); delay(2500); lcd.clear(); centerRollIn("Ihr seid gefan-",0); delay(500); centerRollIn("gen worden.",1); delay(2500); lcd.clear(); centerRollIn("Ihr ward zu ",0); delay(500); centerRollIn("langsam.",1); delay(2500); lcd.clear(); centerRollIn("Versucht wieder",0); delay(500); centerRollIn("zu fliehen!",1); delay(2500); lcd.clear(); centerRollIn("Oder erleidet",0); delay(500); centerRollIn("unendlich Qualen.",1); delay(2500); lcd.clear(); centerRollIn("Falls ihr",0); delay(500); centerRollIn("fliehen k\357nnt,",1); delay(2500); lcd.clear(); centerRollIn("versucht es",0); delay(500); centerRollIn("noch einmal...",1); delay(2500); } void centerRollIn(String text, int top) { String t; int pos; int i; //breite des displays ist 16 zeichen int textlaenge=text.length(); int position=8; for (i=0; i<=textlaenge; i++) { t=text.substring(0,i); pos=position-(i/2-1)-1; lcd.setCursor(pos,top); lcd.print(t); delay(200); } } bool getJahr2000() { //prüfe das Jahr, wenn diese 2000 ist, dann ist der Akku leer! String StrJahr=String(year()); bool ret=false; if (StrJahr=="2000") { ret=true; } return ret; } |
Ein fertiges Projekt:
Hier sind wasserdichte Kunstsoffgehäuse mit klarem Deckel im Einsatz. Durch die Schalter und Taster sind die Boxen zwar nicht mehr zu 100% Dicht, aber Feuchtigkeit kann hier so gut wie gar nicht eindringen. Allerdings sind solche Gehäuse etwas teurer.