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