#include #include #include #include #include // --- Configurable Constants --- #define PASSCODE_FILE "/pass.txt" #define DEFAULT_CODE "#D36" #define HOLD_TIME_MS 2500 #define RELAY_PIN 5 // Relay successfully moved to safe GPIO 5 #define RELAY_ON_TIME_MS 3000 // Relay trigger duration (3 seconds) // --- Keypad Setup --- const byte ROWS = 4; const byte COLS = 4; char keys[ROWS][COLS] = { {'1','2','3','A'}, {'4','5','6','B'}, {'7','8','9','C'}, {'*','0','#','D'} }; byte rowPins[ROWS] = {13, 12, 14, 27}; byte colPins[COLS] = {26, 25, 33, 32}; Keypad customKeypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS); // --- TFT Setup --- TFT_eSPI tft = TFT_eSPI(); // --- State Machine --- enum SystemState { STATE_IDLE, STATE_ENTER_PASSCODE, STATE_AUTH_FOR_CHANGE, STATE_CHANGE_PASSCODE, STATE_VERIFY_PASSCODE }; SystemState currentState = STATE_IDLE; String currentPasscode = DEFAULT_CODE; String enteredInput = ""; String newPasscodeAttempt = ""; // Timing variables for holding '#' unsigned long hashPressStartTime = 0; bool isHashBeingHeld = false; void setup() { Serial.begin(115200); // Set up the hardware relay pin control pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, LOW); // Start with relay deactivated (Locked) tft.init(); tft.setRotation(0); if (!LittleFS.begin(true)) { Serial.println("LittleFS Mount Failed!"); } else { loadPasscode(); } showIdleScreen(); } void loop() { char key = customKeypad.getKey(); KeyState kstate = customKeypad.getState(); // --- 1. SPECIAL '#' HANDLING IN IDLE STATE --- if (currentState == STATE_IDLE) { if (key == '#' && kstate == PRESSED) { isHashBeingHeld = true; hashPressStartTime = millis(); } if (isHashBeingHeld && (kstate == HOLD || kstate == PRESSED)) { if (millis() - hashPressStartTime >= HOLD_TIME_MS) { isHashBeingHeld = false; enteredInput = ""; currentState = STATE_AUTH_FOR_CHANGE; updateDisplay(); return; } } if (kstate == RELEASED && isHashBeingHeld) { isHashBeingHeld = false; currentState = STATE_ENTER_PASSCODE; enteredInput = "#"; updateDisplay(); return; } } // --- 2. PROCESS NORMAL KEYPRESSES --- if (key != NO_KEY) { if (currentState == STATE_IDLE && key != '#') { currentState = STATE_ENTER_PASSCODE; enteredInput = String(key); updateDisplay(); return; } if (currentState == STATE_ENTER_PASSCODE || currentState == STATE_AUTH_FOR_CHANGE || currentState == STATE_CHANGE_PASSCODE || currentState == STATE_VERIFY_PASSCODE) { if (enteredInput.length() < 4) { enteredInput += key; updateDisplay(); } if (enteredInput.length() == 4) { delay(400); if (currentState == STATE_ENTER_PASSCODE) { if (enteredInput == currentPasscode) { triggerRelay(); } else { flashMessage("WRONG PASSCODE", TFT_RED); } resetToIdle(); } else if (currentState == STATE_AUTH_FOR_CHANGE) { if (enteredInput == currentPasscode) { enteredInput = ""; currentState = STATE_CHANGE_PASSCODE; updateDisplay(); } else { flashMessage("WRONG PASSCODE", TFT_RED); resetToIdle(); } } else if (currentState == STATE_CHANGE_PASSCODE) { // Safe local matching string newPasscodeAttempt = enteredInput; enteredInput = ""; currentState = STATE_VERIFY_PASSCODE; updateDisplay(); } else if (currentState == STATE_VERIFY_PASSCODE) { if (enteredInput == newPasscodeAttempt) { currentPasscode = newPasscodeAttempt; savePasscode(currentPasscode); tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.setTextDatum(MC_DATUM); tft.drawString("Passcode Changed", 120, 100, 4); tft.drawString(currentPasscode, 120, 140, 4); delay(2500); resetToIdle(); } else { flashMessage("MISMATCH! TRY AGAIN", TFT_RED); enteredInput = ""; currentState = STATE_CHANGE_PASSCODE; updateDisplay(); } } } } } } // --- 3. Hardware Interfacing Actions --- void triggerRelay() { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.setTextDatum(MC_DATUM); tft.drawString("ACCESS GRANTED", 120, 120, 4); digitalWrite(RELAY_PIN, HIGH); // Turn relay ON (Unlock fence) delay(RELAY_ON_TIME_MS); // Hold for 3 seconds digitalWrite(RELAY_PIN, LOW); // Turn relay OFF (Lock fence) } // --- 4. Screen Drawing --- void showIdleScreen() { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setTextDatum(MC_DATUM); tft.drawString("10:08", 120, 120, 7); } void updateDisplay() { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setTextDatum(TC_DATUM); if (currentState == STATE_ENTER_PASSCODE) { tft.drawString("Enter Passcode", 120, 50, 4); } else if (currentState == STATE_AUTH_FOR_CHANGE) { tft.drawString("Enter Passcode", 120, 35, 4); tft.drawString("To Change Passcode", 120, 65, 2); } else if (currentState == STATE_CHANGE_PASSCODE) { tft.drawString("Change Passcode", 120, 50, 4); } else if (currentState == STATE_VERIFY_PASSCODE) { tft.drawString("Re-enter Passcode", 120, 50, 4); } int startX = 60; int spacing = 40; tft.setTextDatum(MC_DATUM); for (int i = 0; i < 4; i++) { int currentX = startX + (i * spacing); if (i < enteredInput.length()) { tft.drawString("*", currentX, 130, 4); } else { tft.drawString("_", currentX, 130, 4); } } } void flashMessage(String msg, uint16_t color) { tft.fillScreen(TFT_BLACK); tft.setTextColor(color, TFT_BLACK); tft.setTextDatum(MC_DATUM); tft.drawString(msg, 120, 120, 4); delay(2000); } void resetToIdle() { enteredInput = ""; newPasscodeAttempt = ""; isHashBeingHeld = false; currentState = STATE_IDLE; showIdleScreen(); } // --- 5. Storage Persistence Layer --- void loadPasscode() { if (LittleFS.exists(PASSCODE_FILE)) { File file = LittleFS.open(PASSCODE_FILE, "r"); if (file) { String saved = file.readStringUntil('\n'); saved.trim(); if (saved.length() == 4) { currentPasscode = saved; Serial.println("Loaded custom passcode: " + currentPasscode); } file.close(); } } } void savePasscode(String newCode) { File file = LittleFS.open(PASSCODE_FILE, "w"); if (file) { file.println(newCode); file.close(); Serial.println("Saved custom passcode to LittleFS: " + newCode); } }