Phone Lock Box - Pomodoro Technique
This box is hungry for your phone, noom noom..!
Devices & Components
1
Ultrasonic Sensor HC-SR04
1
LCD display 16x2
1
Rotary Potentiometer, 100 kohm
3
button
1
MG90S 9G Micro Servo Motor
Hardware & Tools
1
Hot Glue Gun
1
Wooden Stick
1
Cardboard box
Software & Tools
Arduino IDE
Project description
Code
Full Code
cpp
Enjoy:)
1#include <LiquidCrystal.h> 2#include <Servo.h> 3 4LiquidCrystal lcd(2, 3, 4, 5, 6, 7); 5Servo myServo; 6 7const int btn1 = 10, btn2 = 9, btn3 = 8; 8const int servoPin = 11, trigPin = 12, echoPin = 13; 9 10const unsigned long STUDY_DURATION_MS = 25ULL * 60ULL * 1000ULL; 11const unsigned long OVERRIDE_HOLD_TIME = 3000; 12const unsigned long SKIP_HOLD_TIME = 4000; 13const unsigned long DEBOUNCE_TIME = 50; 14const unsigned long ANIMATION_FRAME_TIME = 400; 15const unsigned long REMOVAL_BUFFER_TIME = 2000; 16const unsigned long UNLOCK_COUNTDOWN = 5; 17const unsigned long ANGRY_DISPLAY_TIME = 3000; 18const unsigned long LOCKING_COUNTDOWN = 5; 19const unsigned long DISTANCE_CHECK_INTERVAL = 100; 20const unsigned long BLINK_INTERVAL = 3000; 21 22const int DIST_PHONE_REMOVED = 18; 23const int DIST_PHONE_DETECTED = 5; 24const int DIST_IDLE_THRESHOLD = 8; 25 26// State variables 27unsigned long lockStartTime = 0; 28bool isLocked = false; 29bool phoneRemoved = true; 30unsigned long lastDistanceCheck = 0; 31long lastKnownDistance = 999; 32 33int password[3], inputSeq[3], inputIdx = 0; 34bool challengeActive = false; 35 36// Button states 37unsigned long btn1LastPress = 0, btn2LastPress = 0, btn3LastPress = 0; 38bool btn1Pressed = false, btn2Pressed = false, btn3Pressed = false; 39bool btn1JustPressed = false, btn2JustPressed = false, btn3JustPressed = false; 40 41unsigned long overrideStartTime = 0; 42bool overrideActive = false; 43unsigned long skipStartTime = 0; 44bool skipActive = false; 45 46// Animation 47unsigned long lastAnimationUpdate = 0; 48int animationFrame = 0; 49unsigned long lastBlink = 0; 50 51unsigned long removalCheckStart = 0; 52bool checkingRemoval = false; 53unsigned long lockingSequenceStart = 0; 54bool lockingInProgress = false; 55unsigned long angryFaceStart = 0; 56bool showingAngry = false; 57 58unsigned long unlockSequenceStart = 0; 59bool unlockSequenceActive = false; 60bool waitingForSleepAfterTimeUp = false; 61long lastDistanceDuringChallenge = 999; 62long initialDistanceAtChallengeStart = 999; 63long minDistanceDuringChallenge = 999; 64bool phoneWasTakenAfterChallenge = false; 65bool showingAngryInChallenge = false; 66unsigned long angryFaceStartInChallenge = 0; 67 68// --- PIXEL ART --- 69// Eyes (two-row style for blinking) 70byte eyeTop[8] = {0,14,31,31,31,31,31,31}; // Top half of eye 71byte eyeBottom[8] = {31,31,31,31,31,31,14,0}; // Bottom half of eye 72byte eyeClosed[8] = {0,0,0,0,31,31,0,0}; // Closed eye 73 74// Sleeping 75byte sleepEye1[8] = {0,0,0,14,14,0,0,0}; 76byte sleepEye2[8] = {0,0,0,0,31,31,0,0}; 77byte sleepEye3[8] = {0,0,0,7,7,0,0,0}; 78byte tinyMouth[8] = {0,0,0,0,0,0,0,4}; // Tiny dot mouth (one pixel) 79 80// Happy 81byte happyMouth[8] = {0,0,0,17,17,17,10,4}; 82byte cuteMouth[8] = {0,0,0,0,0,0,0,4}; // Tiny cute mouth (smaller than happy) 83byte smallEye[8] = {0,0,0,14,31,31,14,0}; // Smaller round eye 84 85// Angry 86byte angryEyeTop[8] = {0,0,3,7,15,31,31,31}; 87byte angryEyeBottom[8] = {31,31,31,15,7,3,0,0}; 88byte angryMouth[8] = {0,0,0,0,0,4,10,17}; 89 90// Suspicious 91byte suspiciousEyeTop[8] = {0,0,0,7,14,7,0,0}; 92byte suspiciousEyeBottom[8] = {0,0,0,7,14,7,0,0}; 93 94// Surprised 95byte surpriseEyeTop[8] = {0,0,14,31,31,31,31,31}; 96byte surpriseEyeBottom[8] = {31,31,31,31,31,14,0,0}; 97byte surpriseMouth[8] = {0,0,0,14,10,14,0,0}; 98 99// Focus 100byte focusEyeTop[8] = {0,14,31,31,31,31,31,31}; 101byte focusEyeBottom[8] = {31,31,31,31,31,31,14,0}; 102byte neutralMouth[8] = {0,0,0,0,0,0,0,0}; 103 104// Reward (heart eyes) 105byte heartEyeTop[8] = {0,10,31,31,31,31,14,0}; 106byte heartEyeBottom[8] = {0,0,14,31,31,31,10,0}; 107byte rewardMouth[8] = {0,0,0,17,17,17,10,4}; 108 109// Shy 110byte shyEyeTop[8] = {0,0,0,7,14,7,0,0}; 111byte shyEyeBottom[8] = {0,0,0,7,14,7,0,0}; 112 113// Dizzy (X eyes) 114byte xEyeTop[8] = {27,14,4,10,17,10,4,14}; 115byte xEyeBottom[8] = {27,14,4,10,17,10,4,14}; 116 117byte progressBar[8] = {31,31,31,31,31,31,31,31}; 118 119void setup() { 120 lcd.begin(16, 2); 121 randomSeed(analogRead(A1)); 122 myServo.attach(servoPin); 123 pinMode(trigPin, OUTPUT); 124 pinMode(echoPin, INPUT); 125 pinMode(btn1, INPUT_PULLUP); 126 pinMode(btn2, INPUT_PULLUP); 127 pinMode(btn3, INPUT_PULLUP); 128 unlockBox(); 129 lastAnimationUpdate = millis(); 130 lastBlink = millis(); 131} 132 133void loop() { 134 unsigned long currentTime = millis(); 135 136 updateButtonStates(); 137 138 if (currentTime - lastDistanceCheck >= DISTANCE_CHECK_INTERVAL) { 139 long dist = getDistance(); 140 if (dist < 200) lastKnownDistance = dist; 141 lastDistanceCheck = currentTime; 142 } 143 long dist = lastKnownDistance; 144 145 handleMasterOverride(); 146 if (isLocked) handleSkipTimer(); 147 148 // Only show "take phone" if phone is actually detected in box after unlock 149 if (!phoneRemoved && dist <= DIST_PHONE_REMOVED) { 150 showRemovalPrompt(dist, currentTime); 151 return; 152 } 153 154 // If phone was supposed to be removed but it's not detected, go to sleeping 155 if (!phoneRemoved && dist > DIST_PHONE_REMOVED) { 156 phoneRemoved = true; 157 checkingRemoval = false; 158 unlockSequenceActive = false; 159 } 160 161 if (!isLocked && dist > DIST_IDLE_THRESHOLD) { 162 showSleepingAnimation(currentTime); 163 } 164 // FIX: Only start locking sequence if phone is close AND we're NOT in the "just unlocked" state 165 else if (!isLocked && dist <= DIST_PHONE_DETECTED && phoneRemoved && !waitingForSleepAfterTimeUp) { 166 // Check if we just unlocked (phone was recently taken out) 167 // We know the phone was taken out if distance is now short (phone is back) 168 // AND phoneRemoved was true (meaning phone was out) 169 bool canLock = false; 170 171 if (!phoneWasTakenAfterChallenge) { 172 // Normal case - phone never taken during challenge, can lock immediately 173 canLock = true; 174 } else { 175 // After challenge - phone was taken during challenge 176 // Only lock if phone has been back for a while (10 seconds) 177 static unsigned long phoneBackTime = 0; 178 if (dist <= DIST_PHONE_DETECTED) { 179 if (phoneBackTime == 0) { 180 phoneBackTime = currentTime; // Start timer when phone is put back 181 } 182 if (currentTime - phoneBackTime >= 10000) { 183 canLock = true; 184 } 185 } else { 186 // Phone taken out again, reset timer 187 phoneBackTime = 0; 188 } 189 } 190 191 if (canLock) { 192 if (!lockingInProgress) { 193 lockingSequenceStart = currentTime; 194 lockingInProgress = true; 195 lcd.clear(); 196 } 197 runLockingSequence(currentTime); 198 } 199 } else if (isLocked) { 200 unsigned long elapsed = currentTime - lockStartTime; 201 if (elapsed < STUDY_DURATION_MS) { 202 // FIXED: Check for cheating and show angry expression properly 203 if ((btn1JustPressed || btn3JustPressed) && !skipActive && !challengeActive) { 204 // Only show angry if not already showing 205 if (!showingAngry) { 206 showRandomCheatingExpression(); 207 // Don't show studying face when angry is shown 208 return; // ADDED: Return to prevent overwriting angry face 209 } 210 } 211 212 // Only show studying face if not showing angry expression 213 if (!showingAngry) { 214 showStudyingFace(elapsed, currentTime); 215 } 216 217 // Clear angry face after timeout 218 if (showingAngry && (currentTime - angryFaceStart >= ANGRY_DISPLAY_TIME)) { 219 showingAngry = false; 220 lcd.clear(); 221 } 222 } else { 223 handlePasswordChallenge(currentTime, dist); 224 } 225 } 226} 227 228void updateButtonStates() { 229 unsigned long currentTime = millis(); 230 231 // Button 1 232 bool btn1State = (digitalRead(btn1) == LOW); 233 if (btn1State && !btn1Pressed && (currentTime - btn1LastPress > DEBOUNCE_TIME)) { 234 btn1Pressed = true; 235 btn1JustPressed = true; 236 btn1LastPress = currentTime; 237 } else { 238 btn1JustPressed = false; 239 if (!btn1State) btn1Pressed = false; 240 } 241 242 // Button 2 243 bool btn2State = (digitalRead(btn2) == LOW); 244 if (btn2State && !btn2Pressed && (currentTime - btn2LastPress > DEBOUNCE_TIME)) { 245 btn2Pressed = true; 246 btn2JustPressed = true; 247 btn2LastPress = currentTime; 248 } else { 249 btn2JustPressed = false; 250 if (!btn2State) btn2Pressed = false; 251 } 252 253 // Button 3 254 bool btn3State = (digitalRead(btn3) == LOW); 255 if (btn3State && !btn3Pressed && (currentTime - btn3LastPress > DEBOUNCE_TIME)) { 256 btn3Pressed = true; 257 btn3JustPressed = true; 258 btn3LastPress = currentTime; 259 } else { 260 btn3JustPressed = false; 261 if (!btn3State) btn3Pressed = false; 262 } 263} 264 265void handleMasterOverride() { 266 unsigned long currentTime = millis(); 267 if (btn1Pressed && btn3Pressed) { 268 if (!overrideActive) { 269 overrideStartTime = currentTime; 270 overrideActive = true; 271 } else if (currentTime - overrideStartTime >= OVERRIDE_HOLD_TIME) { 272 unlockBox(); 273 overrideActive = false; 274 } 275 } else { 276 overrideActive = false; 277 } 278} 279 280void handleSkipTimer() { 281 unsigned long currentTime = millis(); 282 if (btn2Pressed) { 283 if (!skipActive) { 284 skipStartTime = currentTime; 285 skipActive = true; 286 } else if (currentTime - skipStartTime >= SKIP_HOLD_TIME) { 287 completeStudySession(); 288 skipActive = false; 289 } 290 } else { 291 skipActive = false; 292 } 293} 294 295void completeStudySession() { 296 lockStartTime = millis() - STUDY_DURATION_MS; 297 challengeActive = false; 298 lcd.clear(); 299} 300 301void showRemovalPrompt(long dist, unsigned long currentTime) { 302 // Only show if phone is actually detected (close distance) 303 if (dist > DIST_PHONE_REMOVED) { 304 // Phone already removed, go to sleeping 305 phoneRemoved = true; 306 checkingRemoval = false; 307 unlockSequenceActive = false; 308 return; 309 } 310 311 // Phone is in box - show cute "take phone" face 312 loadCuteFace(); 313 drawFace((currentTime / 300) % 2, "TAKE PHONE!"); 314 315 // Check if phone is being removed 316 if (dist > DIST_PHONE_REMOVED) { 317 if (!checkingRemoval) { 318 removalCheckStart = currentTime; 319 checkingRemoval = true; 320 } else if (currentTime - removalCheckStart >= REMOVAL_BUFFER_TIME) { 321 // Phone confirmed removed - start unlock countdown 322 if (!unlockSequenceActive) { 323 unlockSequenceStart = currentTime; 324 unlockSequenceActive = true; 325 lcd.clear(); 326 } 327 328 unsigned long elapsed = currentTime - unlockSequenceStart; 329 int remaining = UNLOCK_COUNTDOWN - (elapsed / 1000); 330 331 if (remaining > 0) { 332 loadCuteFace(); 333 drawFace(0, "UNLOCKING"); 334 lcd.setCursor(6, 1); 335 lcd.print(remaining); 336 lcd.print("s "); 337 for(int j = 0; j < (UNLOCK_COUNTDOWN - remaining); j++) { 338 lcd.write(byte(4)); 339 } 340 } else { 341 // Unlock complete - go to sleeping 342 phoneRemoved = true; 343 checkingRemoval = false; 344 unlockSequenceActive = false; 345 challengeActive = false; 346 inputIdx = 0; 347 lcd.clear(); 348 } 349 } 350 } else { 351 // Phone still in box - reset removal check 352 checkingRemoval = false; 353 unlockSequenceActive = false; 354 } 355} 356 357void showSleepingAnimation(unsigned long currentTime) { 358 if (currentTime - lastAnimationUpdate >= ANIMATION_FRAME_TIME) { 359 animationFrame = (animationFrame + 1) % 4; 360 lastAnimationUpdate = currentTime; 361 } 362 363 loadSleepingFace(animationFrame); 364 365 int blinkState = 0; 366 if (currentTime - lastBlink < 150) { 367 blinkState = 1; 368 } else if (currentTime - lastBlink >= BLINK_INTERVAL) { 369 lastBlink = currentTime; 370 } 371 372 drawSleepingFace(blinkState, "ZZZ..."); 373} 374 375void runLockingSequence(unsigned long currentTime) { 376 unsigned long elapsed = currentTime - lockingSequenceStart; 377 int remaining = LOCKING_COUNTDOWN - (elapsed / 1000); 378 379 if (remaining > 0) { 380 loadSurprisedFace(); 381 drawTwoRowFace((currentTime / 500) % 2, "LOCKING"); // Use two-row blink 382 lcd.setCursor(6, 1); 383 lcd.print(remaining); 384 lcd.print("s "); 385 for(int j = 0; j < (LOCKING_COUNTDOWN - remaining); j++) { 386 lcd.write(byte(4)); 387 } 388 } else { 389 lockBox(); 390 lockingInProgress = false; 391 phoneWasTakenAfterChallenge = false; // Reset after locking starts 392 } 393} 394 395void showStudyingFace(unsigned long elapsed, unsigned long currentTime) { 396 unsigned long remaining = STUDY_DURATION_MS - elapsed; 397 loadFocusFace(); 398 399 int blinkState = 0; 400 if (currentTime - lastBlink < 150) { 401 blinkState = 1; 402 } else if (currentTime - lastBlink >= BLINK_INTERVAL) { 403 lastBlink = currentTime; 404 } 405 406 drawTwoRowFace(blinkState, "STUDYING"); // Use two-row blink 407 showTime(remaining); 408} 409 410void showRandomCheatingExpression() { 411 int exprType = random(5); 412 String messages[5][2] = { 413 {"NOT YET!!", "FOCUS!"}, 414 {"HEY!", "WHAT R U DOING?"}, 415 {"GO BACK", "TO WORK!"}, 416 {"SHY...", "NO CHEATING!"}, 417 {"DIZZY...", "STAY FOCUS!"} 418 }; 419 420 switch(exprType) { 421 case 0: loadAngryFace(); break; 422 case 1: loadSuspiciousFace(); break; 423 case 2: loadAngryFace(); break; 424 case 3: loadShyFace(); break; 425 case 4: loadDizzyFace(); break; 426 } 427 428 lcd.clear(); 429 drawTwoRowFace(0, messages[exprType][0]); 430 lcd.setCursor(6, 1); 431 lcd.print(messages[exprType][1]); 432 showingAngry = true; 433 angryFaceStart = millis(); // This was already here, keeping it 434} 435 436void handlePasswordChallenge(unsigned long currentTime, long dist) { 437 if (!challengeActive) { 438 // Show reward animation 439 loadRewardFace(); 440 int animFrame = (currentTime / 400) % 3; 441 drawTwoRowFace(animFrame % 2, "TIME UP!"); 442 lcd.setCursor(6, 1); 443 lcd.print("GOOD JOB!"); 444 445 static unsigned long rewardStart = 0; 446 if (rewardStart == 0) { 447 rewardStart = currentTime; 448 initialDistanceAtChallengeStart = dist; 449 minDistanceDuringChallenge = dist; 450 } 451 452 if (currentTime - rewardStart < 3000) return; 453 454 for(int i = 0; i < 3; i++) password[i] = random(1, 4); 455 challengeActive = true; 456 inputIdx = 0; 457 rewardStart = 0; 458 waitingForSleepAfterTimeUp = false; 459 showingAngryInChallenge = false; 460 lcd.clear(); 461 } 462 463 // SIMPLIFIED: Track if phone is taken (distance increases significantly) 464 if (dist < minDistanceDuringChallenge) { 465 minDistanceDuringChallenge = dist; 466 } 467 468 // If phone is taken (distance increased significantly), start sleep countdown 469 if (dist > minDistanceDuringChallenge + 5 && dist > DIST_PHONE_REMOVED) { 470 if (!waitingForSleepAfterTimeUp) { 471 waitingForSleepAfterTimeUp = true; 472 phoneWasTakenAfterChallenge = true; 473 unlockSequenceStart = currentTime; 474 challengeActive = false; 475 showingAngryInChallenge = false; 476 lcd.clear(); 477 } 478 } 479 480 // If waiting for sleep countdown after phone taken 481 if (waitingForSleepAfterTimeUp) { 482 unsigned long elapsed = currentTime - unlockSequenceStart; 483 int remaining = UNLOCK_COUNTDOWN - (elapsed / 1000); 484 485 if (remaining > 0) { 486 loadCuteFace(); 487 drawFace(0, "GOING SLEEP"); 488 lcd.setCursor(6, 1); 489 lcd.print(remaining); 490 lcd.print("s "); 491 for(int j = 0; j < (UNLOCK_COUNTDOWN - remaining); j++) { 492 lcd.write(byte(4)); 493 } 494 return; 495 } else { 496 // Sleep countdown complete - go to sleeping, reset flags 497 waitingForSleepAfterTimeUp = false; 498 phoneRemoved = true; 499 challengeActive = false; 500 inputIdx = 0; 501 phoneWasTakenAfterChallenge = true; // Keep this true so we know phone was taken 502 lcd.clear(); 503 return; 504 } 505 } 506 507 // Show angry expression if showing 508 if (showingAngryInChallenge) { 509 // Keep showing the angry face (it was already drawn) 510 if (currentTime - angryFaceStartInChallenge >= ANGRY_DISPLAY_TIME) { 511 showingAngryInChallenge = false; 512 lcd.clear(); 513 } 514 return; // Don't show code challenge while showing angry 515 } 516 517 // Check for cheating - ANY button press that's not correct code input 518 if (btn1JustPressed || btn2JustPressed || btn3JustPressed) { 519 bool isCorrectInput = false; 520 if (inputIdx < 3) { 521 int expected = password[inputIdx]; 522 if ((btn1JustPressed && expected == 1) || 523 (btn2JustPressed && expected == 2) || 524 (btn3JustPressed && expected == 3)) { 525 isCorrectInput = true; 526 } 527 } 528 529 if (!isCorrectInput) { 530 // Show angry expression 531 int exprType = random(5); 532 String messages[5][2] = { 533 {"NOT YET!!", "FOCUS!"}, 534 {"HEY!", "WHAT R U DOING?"}, 535 {"GO BACK", "TO WORK!"}, 536 {"SHY...", "NO CHEATING!"}, 537 {"DIZZY...", "STAY FOCUS!"} 538 }; 539 540 switch(exprType) { 541 case 0: loadAngryFace(); break; 542 case 1: loadSuspiciousFace(); break; 543 case 2: loadAngryFace(); break; 544 case 3: loadShyFace(); break; 545 case 4: loadDizzyFace(); break; 546 } 547 548 lcd.clear(); 549 drawTwoRowFace(0, messages[exprType][0]); 550 lcd.setCursor(6, 1); 551 lcd.print(messages[exprType][1]); 552 showingAngryInChallenge = true; 553 angryFaceStartInChallenge = currentTime; 554 return; // Don't process as code input 555 } 556 } 557 558 // Show code challenge 559 loadHappyFace(); 560 drawFace(0, "CODE:" + String(password[0]) + String(password[1]) + String(password[2])); 561 562 lcd.setCursor(6, 1); 563 if (inputIdx == 0) lcd.print("___"); 564 else if (inputIdx == 1) lcd.print(String(inputSeq[0]) + "__"); 565 else if (inputIdx == 2) lcd.print(String(inputSeq[0]) + String(inputSeq[1]) + "_"); 566 else lcd.print(String(inputSeq[0]) + String(inputSeq[1]) + String(inputSeq[2])); 567 568 // Process code input 569 int b = 0; 570 if (btn1JustPressed) b = 1; 571 else if (btn2JustPressed) b = 2; 572 else if (btn3JustPressed) b = 3; 573 574 if (b > 0 && inputIdx < 3) { 575 inputSeq[inputIdx++] = b; 576 delay(300); 577 if (inputIdx == 3) { 578 if (inputSeq[0] == password[0] && inputSeq[1] == password[1] && inputSeq[2] == password[2]) { 579 phoneWasTakenAfterChallenge = true; // ADDED THIS LINE 580 unlockBox(); 581 } else { 582 // Wrong code - show angry 583 inputIdx = 0; 584 int exprType = random(5); 585 String messages[5][2] = { 586 {"NOT YET!!", "FOCUS!"}, 587 {"HEY!", "WHAT R U DOING?"}, 588 {"GO BACK", "TO WORK!"}, 589 {"SHY...", "NO CHEATING!"}, 590 {"DIZZY...", "STAY FOCUS!"} 591 }; 592 593 switch(exprType) { 594 case 0: loadAngryFace(); break; 595 case 1: loadSuspiciousFace(); break; 596 case 2: loadAngryFace(); break; 597 case 3: loadShyFace(); break; 598 case 4: loadDizzyFace(); break; 599 } 600 601 lcd.clear(); 602 drawTwoRowFace(0, messages[exprType][0]); 603 lcd.setCursor(6, 1); 604 lcd.print(messages[exprType][1]); 605 showingAngryInChallenge = true; 606 angryFaceStartInChallenge = currentTime; 607 } 608 } 609 } 610} 611// --- FACE DRAWING --- 612// Two-row eyes (for locking/studying) - eyes split across two rows 613void drawTwoRowFace(int blink, String txt) { 614 lcd.clear(); 615 616 // Left Eye - split across two rows 617 lcd.setCursor(0, 0); 618 if(blink) lcd.write(byte(2)); 619 else lcd.write(byte(0)); // Top 620 lcd.setCursor(0, 1); 621 if(blink) lcd.write(byte(2)); 622 else lcd.write(byte(1)); // Bottom 623 624 // Right Eye - split across two rows 625 lcd.setCursor(2, 0); 626 if(blink) lcd.write(byte(2)); 627 else lcd.write(byte(0)); // Top 628 lcd.setCursor(2, 1); 629 if(blink) lcd.write(byte(2)); 630 else lcd.write(byte(1)); // Bottom 631 632 // Mouth centered 633 lcd.setCursor(1, 1); 634 lcd.write(byte(3)); 635 636 // Text 637 lcd.setCursor(4, 0); 638 lcd.print(txt); 639} 640 641// Single row eyes (for other states) 642void drawFace(int blink, String txt) { 643 lcd.clear(); 644 lcd.setCursor(0, 0); 645 if(blink) lcd.write(byte(2)); 646 else lcd.write(byte(0)); 647 lcd.setCursor(2, 0); 648 if(blink) lcd.write(byte(2)); 649 else lcd.write(byte(1)); 650 lcd.setCursor(1, 1); 651 lcd.write(byte(3)); 652 lcd.setCursor(4, 0); 653 lcd.print(txt); 654} 655 656void drawSleepingFace(int blink, String txt) { 657 lcd.clear(); 658 lcd.setCursor(0, 0); 659 if(blink) lcd.write(byte(2)); 660 else lcd.write(byte(0)); 661 lcd.setCursor(2, 0); 662 if(blink) lcd.write(byte(2)); 663 else lcd.write(byte(1)); 664 lcd.setCursor(1, 1); 665 lcd.write(byte(3)); // Tiny dot mouth 666 lcd.setCursor(4, 0); 667 lcd.print(txt); 668} 669 670// --- FACE LOADERS --- 671void loadSleepingFace(int frame) { 672 if (frame == 0 || frame == 2) { 673 lcd.createChar(0, sleepEye1); 674 lcd.createChar(1, sleepEye1); 675 } else if (frame == 1) { 676 lcd.createChar(0, sleepEye2); 677 lcd.createChar(1, sleepEye2); 678 } else { 679 lcd.createChar(0, sleepEye3); 680 lcd.createChar(1, sleepEye3); 681 } 682 lcd.createChar(2, eyeClosed); 683 lcd.createChar(3, tinyMouth); // Tiny dot mouth 684} 685 686void loadHappyFace() { 687 lcd.createChar(0, eyeTop); 688 lcd.createChar(1, eyeBottom); 689 lcd.createChar(2, eyeClosed); 690 lcd.createChar(3, happyMouth); 691} 692 693void loadCuteFace() { 694 // Cuter face with smaller eyes and tiny mouth 695 lcd.createChar(0, smallEye); 696 lcd.createChar(1, smallEye); 697 lcd.createChar(2, eyeClosed); 698 lcd.createChar(3, cuteMouth); // Tiny cute mouth 699} 700 701void loadFocusFace() { 702 lcd.createChar(0, focusEyeTop); 703 lcd.createChar(1, focusEyeBottom); 704 lcd.createChar(2, eyeClosed); 705 lcd.createChar(3, neutralMouth); 706 lcd.createChar(4, progressBar); 707} 708 709void loadAngryFace() { 710 lcd.createChar(0, angryEyeTop); 711 lcd.createChar(1, angryEyeBottom); 712 lcd.createChar(2, eyeClosed); 713 lcd.createChar(3, angryMouth); 714} 715 716void loadSuspiciousFace() { 717 lcd.createChar(0, suspiciousEyeTop); 718 lcd.createChar(1, suspiciousEyeBottom); 719 lcd.createChar(2, eyeClosed); 720 lcd.createChar(3, angryMouth); 721} 722 723void loadSurprisedFace() { 724 lcd.createChar(0, surpriseEyeTop); 725 lcd.createChar(1, surpriseEyeBottom); 726 lcd.createChar(2, eyeClosed); 727 lcd.createChar(3, surpriseMouth); 728 lcd.createChar(4, progressBar); 729} 730 731void loadRewardFace() { 732 lcd.createChar(0, heartEyeTop); 733 lcd.createChar(1, heartEyeBottom); 734 lcd.createChar(2, eyeClosed); 735 lcd.createChar(3, rewardMouth); 736} 737 738void loadShyFace() { 739 lcd.createChar(0, shyEyeTop); 740 lcd.createChar(1, shyEyeBottom); 741 lcd.createChar(2, eyeClosed); 742 lcd.createChar(3, neutralMouth); 743} 744 745void loadDizzyFace() { 746 lcd.createChar(0, xEyeTop); 747 lcd.createChar(1, xEyeBottom); 748 lcd.createChar(2, xEyeTop); 749 lcd.createChar(3, neutralMouth); 750} 751 752// --- HELPERS --- 753void lockBox() { 754 myServo.attach(servoPin); 755 myServo.write(180); 756 delay(1000); 757 myServo.detach(); 758 isLocked = true; 759 lockStartTime = millis(); 760 challengeActive = false; 761 showingAngry = false; 762} 763 764void unlockBox() { 765 myServo.attach(servoPin); 766 myServo.write(0); 767 delay(1000); 768 myServo.detach(); 769 isLocked = false; 770 phoneRemoved = false; // CHANGED: Set to false to show removal prompt 771 challengeActive = false; 772 showingAngry = false; 773 lockingInProgress = false; 774 checkingRemoval = false; 775 unlockSequenceActive = false; 776 waitingForSleepAfterTimeUp = false; 777 // Keep phoneWasTakenAfterChallenge as true to prevent immediate re-locking 778 showingAngryInChallenge = false; 779 inputIdx = 0; 780 lcd.clear(); 781 lastAnimationUpdate = millis(); 782 lastBlink = millis(); 783} 784long getDistance() { 785 digitalWrite(trigPin, LOW); 786 delayMicroseconds(2); 787 digitalWrite(trigPin, HIGH); 788 delayMicroseconds(10); 789 digitalWrite(trigPin, LOW); 790 long duration = pulseIn(echoPin, HIGH); 791 if (duration == 0) return 999; 792 return (duration / 2) / 29.1; 793} 794 795void showTime(unsigned long ms) { 796 unsigned long sec = ms / 1000; 797 unsigned long minutes = sec / 60; 798 unsigned long seconds = sec % 60; 799 if (minutes > 25) minutes = 25; 800 lcd.setCursor(4, 1); 801 if (minutes < 10) lcd.print("0"); 802 lcd.print(minutes); 803 lcd.print("m "); 804 if (seconds < 10) lcd.print("0"); 805 lcd.print(seconds); 806 lcd.print("s "); 807}
Comments
Only logged in users can leave comments