Rebuilding a Legacy Entry-Logging System on Arduino Mega
We get a lot of calls about "smart" products, but some of our most interesting work happens when a client brings us a machine that has been running for a decade and needs to be brought back from the dead.
Devices & Components
1
Arduino Micro
1
4 relay module 5VDC 10A (assembled)
1
Arduino Mega 2560 Rev3
1
Grove - OLED Display 0.96"
Software & Tools
Arduino IDE
Project description
Code
QR_Entry_Logging_Mega2560
1#include <SPI.h> 2#include <Wire.h> 3#include <RTClib.h> 4#include <Adafruit_GFX.h> 5#include <Adafruit_SH110X.h> 6 7// ---------------- SD Card Enable/Disable ---------------- 8#define ENABLE_SD_LOGGING 1 9 10#if ENABLE_SD_LOGGING 11#include <SD.h> 12#endif 13 14// ---------------- Pin / Serial Definitions ---------------- 15#define SD_CS_PIN 10 16#define LED_PIN 7 17#define BUZZER_PIN 8 18 19#define BARCODE_SERIAL Serial1 // Mega hardware UART1: RX1=19, TX1=18 20#define BARCODE_BAUD 115200 21 22// ---------------- OLED Settings ---------------- 23#define SCREEN_WIDTH 128 24#define SCREEN_HEIGHT 64 25#define OLED_ADDR 0x3C 26 27Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); 28 29// ---------------- Objects ---------------- 30RTC_DS3231 rtc; 31 32#if ENABLE_SD_LOGGING 33File logFile; 34const char ENTRY_LOG_FILE[] = "ENTRYLOG.TXT"; 35const char START_LOG_FILE[] = "STARTLOG.TXT"; 36bool sdReady = false; 37#endif 38 39// ---------------- Temporary RAM toggle table (used ONLY while SD is disabled) 40#if !ENABLE_SD_LOGGING 41#define MAX_TEMP_USERS 60 42String tempIDs[MAX_TEMP_USERS]; 43bool tempCheckedIn[MAX_TEMP_USERS]; 44int tempUserCount = 0; 45 46// ---------------- Temporary RAM record buffer ------------- 47struct ScanRecord { 48 String id; 49 String date; 50 String time; 51 String status; 52}; 53#define MAX_BUFFER_RECORDS 40 54ScanRecord recordBuffer[MAX_BUFFER_RECORDS]; 55int recordCount = 0; 56#endif 57 58// ---------------- Debounce ---------------- 59String lastScannedID = ""; 60unsigned long lastScanTime = 0; 61const unsigned long SCAN_COOLDOWN = 3000; 62 63// ---------------- Serial input buffer ---------------- 64String qrBuffer = ""; 65unsigned long lastByteTime = 0; 66const unsigned long LINE_TIMEOUT = 150; 67 68void setup() { 69 Serial.begin(115200); 70 BARCODE_SERIAL.begin(BARCODE_BAUD); 71 72 pinMode(LED_PIN, OUTPUT); 73 pinMode(BUZZER_PIN, OUTPUT); 74 digitalWrite(LED_PIN, LOW); 75 digitalWrite(BUZZER_PIN, LOW); 76 77 // ---------------- I2C / OLED Init ---------------- 78 Wire.begin(); 79 if (!display.begin(OLED_ADDR, true)) { 80 Serial.println(F("SH1106 OLED not found!")); 81 while (1); 82 } 83 display.clearDisplay(); 84 display.display(); 85 showMessage("QR Entry System", "Booting...", "", 1500); 86 87 // ---------------- SD Init (only if enabled) ---------------- 88#if ENABLE_SD_LOGGING 89 SPI.begin(); 90 showMessage("SD Card", "Initializing...", "", 0); 91 pinMode(SD_CS_PIN, OUTPUT); 92 sdReady = SD.begin(SD_CS_PIN); 93 if (!sdReady) { 94 Serial.println(F("SD Card init failed!")); 95 showMessage("SD Card", "INIT FAILED!", "Check wiring/card", 3000); 96 } else { 97 Serial.println(F("SD Card init done.")); 98 showMessage("SD Card", "Init Done", "", 1000); 99 if (!SD.exists(ENTRY_LOG_FILE)) { 100 logFile = SD.open(ENTRY_LOG_FILE, FILE_WRITE); 101 if (logFile) { 102 logFile.println(F("ID,Date,Time,Status")); 103 logFile.close(); 104 } 105 } 106 } 107#else 108 Serial.println(F("SD logging disabled (module not connected).")); 109#endif 110 111 // ---------------- RTC Init ---------------- 112 if (!rtc.begin()) { 113 Serial.println(F("Couldn't find RTC")); 114 showMessage("RTC Error", "DS3231 not found!", "Check wiring", 0); 115 while (1); 116 } 117 if (rtc.lostPower()) { 118 Serial.println(F("RTC lost power, setting time to compile time")); 119 rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 120 } 121 122 // ---------------- Log system start time to SD (only if enabled) 123#if ENABLE_SD_LOGGING 124 if (sdReady) { 125 DateTime now = rtc.now(); 126 logFile = SD.open(START_LOG_FILE, FILE_WRITE); 127 if (logFile) { 128 logFile.print(F("System started: ")); 129 logFile.print(formatDate(now)); 130 logFile.print(F(" ")); 131 logFile.println(formatTime(now)); 132 logFile.close(); 133 Serial.println(F("Start time logged to SD.")); 134 } 135 } 136#endif 137 138 showMessage("System Ready", "Scan your QR code", "", 1500); 139 Serial.println(F("System ready. Waiting for QR/barcode scan...")); 140} 141 142void loop() { 143 readBarcodeScanner(); 144 145 if (qrBuffer.length() > 0 && (millis() - lastByteTime) > LINE_TIMEOUT) { 146 processScan(qrBuffer); 147 qrBuffer = ""; 148 } 149} 150 151// ---------------- Read data from the scanner module ---------------- 152void readBarcodeScanner() { 153 while (BARCODE_SERIAL.available()) { 154 char c = BARCODE_SERIAL.read(); 155 lastByteTime = millis(); 156 157 // Most scanner modules terminate each scan with CR and/or LF 158 if (c == '\r' || c == '\n') { 159 if (qrBuffer.length() > 0) { 160 processScan(qrBuffer); 161 qrBuffer = ""; 162 } 163 } else { 164 qrBuffer += c; 165 if (qrBuffer.length() > 60) qrBuffer = ""; 166 } 167 } 168} 169 170void processScan(String rawID) { 171 rawID.trim(); 172 if (rawID.length() == 0) return; 173 174 // Debounce: ignore the same code scanned again within cooldown window 175 if (rawID == lastScannedID && (millis() - lastScanTime) < SCAN_COOLDOWN) { 176 return; 177 } 178 lastScannedID = rawID; 179 lastScanTime = millis(); 180 181 Serial.print(F("Scanned ID: ")); 182 Serial.println(rawID); 183 184 DateTime now = rtc.now(); 185 bool checkingIn; 186 187#if ENABLE_SD_LOGGING 188 checkingIn = true; 189 if (sdReady) { 190 String lastStatus = getLastStatusFromSD(rawID); 191 checkingIn = (lastStatus != "CHECK-IN"); 192 } 193#else 194 checkingIn = tempToggleStatus(rawID); 195#endif 196 197 const char* statusText = checkingIn ? "CHECK-IN" : "CHECK-OUT"; 198 199 bool logged = false; 200#if ENABLE_SD_LOGGING 201 if (sdReady) { 202 logged = logToSD(rawID, now, statusText); 203 if (logged) { 204 verifyLastSDWrite(rawID, statusText); 205 } 206 } 207#else 208 storeInBuffer(rawID, formatDate(now), formatTime(now), statusText); 209 logged = true; 210#endif 211 212 // Print full scan details to the Serial Monitor: ID, status, date, time 213 Serial.println(F("---- QR Scan ----")); 214 Serial.print(F("User ID : ")); Serial.println(rawID); 215 Serial.print(F("Status : ")); Serial.println(statusText); 216 Serial.print(F("Date : ")); Serial.println(formatDate(now)); 217 Serial.print(F("Time : ")); Serial.println(formatTime(now)); 218 Serial.println(F("------------------")); 219 220 displayResult(rawID, now, statusText, logged); 221 feedbackSignal(checkingIn); 222 223 delay(5000); 224 showMessage("System Ready", "Scan your QR code", "", 0); 225} 226 227#if !ENABLE_SD_LOGGING 228 229// ---------------- Temporary RAM-based toggle (SD disabled) ---------------- 230bool tempToggleStatus(String id) { 231 for (int i = 0; i < tempUserCount; i++) { 232 if (tempIDs[i] == id) { 233 tempCheckedIn[i] = !tempCheckedIn[i]; 234 return tempCheckedIn[i]; 235 } 236 } 237 // New ID seen for the first time -> treat as CHECK-IN 238 if (tempUserCount < MAX_TEMP_USERS) { 239 tempIDs[tempUserCount] = id; 240 tempCheckedIn[tempUserCount] = true; 241 tempUserCount++; 242 } else { 243 Serial.println(F("Warning: MAX_TEMP_USERS reached, increase the limit.")); 244 } 245 return true; 246} 247 248// Stores one scan record into the RAM buffer (overwrites oldest once full). 249void storeInBuffer(String id, String date, String time, String status) { 250 int idx = recordCount % MAX_BUFFER_RECORDS; 251 recordBuffer[idx].id = id; 252 recordBuffer[idx].date = date; 253 recordBuffer[idx].time = time; 254 recordBuffer[idx].status = status; 255 recordCount++; 256} 257 258void printBuffer() { 259 int total = min(recordCount, MAX_BUFFER_RECORDS); 260 Serial.println(F("===== RAM Buffer Contents =====")); 261 for (int i = 0; i < total; i++) { 262 Serial.print(recordBuffer[i].id); Serial.print(F(", ")); 263 Serial.print(recordBuffer[i].date); Serial.print(F(", ")); 264 Serial.print(recordBuffer[i].time); Serial.print(F(", ")); 265 Serial.println(recordBuffer[i].status); 266 } 267 Serial.println(F("================================")); 268} 269#endif 270 271#if ENABLE_SD_LOGGING 272// ---------------- Look up an ID's last status from the SD log ---------------- 273String getLastStatusFromSD(String id) { 274 String result = ""; 275 File f = SD.open(ENTRY_LOG_FILE, FILE_READ); 276 if (!f) return result; 277 278 while (f.available()) { 279 String line = f.readStringUntil('\n'); 280 line.trim(); 281 if (line.length() == 0) continue; 282 if (line.startsWith("ID,")) continue; // skip header row 283 284 int c1 = line.indexOf(','); 285 if (c1 == -1) continue; 286 String lineID = line.substring(0, c1); 287 288 if (lineID == id) { 289 int lastComma = line.lastIndexOf(','); 290 if (lastComma != -1) { 291 result = line.substring(lastComma + 1); 292 result.trim(); 293 } 294 } 295 } 296 f.close(); 297 return result; 298} 299 300// ---------------- SD Logging ---------------- 301bool logToSD(String id, DateTime now, const char* status) { 302 logFile = SD.open(ENTRY_LOG_FILE, FILE_WRITE); 303 if (logFile) { 304 logFile.print(id); 305 logFile.print(","); 306 logFile.print(formatDate(now)); 307 logFile.print(","); 308 logFile.print(formatTime(now)); 309 logFile.print(","); 310 logFile.println(status); 311 logFile.close(); 312 Serial.println(F("Logged to SD card successfully.")); 313 return true; 314 } else { 315 Serial.print(F("Error opening ")); 316 Serial.println(ENTRY_LOG_FILE); 317 return false; 318 } 319} 320 321bool verifyLastSDWrite(String expectedID, const char* expectedStatus) { 322 File f = SD.open(ENTRY_LOG_FILE, FILE_READ); 323 if (!f) { 324 Serial.println(F("Verify FAILED: could not reopen log file.")); 325 return false; 326 } 327 328 String lastLine = ""; 329 while (f.available()) { 330 String line = f.readStringUntil('\n'); 331 line.trim(); 332 if (line.length() > 0) lastLine = line; 333 } 334 f.close(); 335 336 if (lastLine.length() == 0) { 337 Serial.println(F("Verify FAILED: log file empty.")); 338 return false; 339 } 340 341 Serial.print(F("Verify: last SD line -> ")); 342 Serial.println(lastLine); 343 344 bool idMatch = lastLine.startsWith(expectedID + ","); 345 bool statusMatch = lastLine.endsWith(String(expectedStatus)); 346 347 if (idMatch && statusMatch) { 348 Serial.println(F("Verify OK: entry matches what was just scanned.")); 349 return true; 350 } else { 351 Serial.println(F("Verify MISMATCH: last line does not match expected scan!")); 352 return false; 353 } 354} 355#endif 356 357// ---------------- Formatting Helpers ---------------- 358String formatDate(DateTime now) { 359 char buf[12]; 360 sprintf(buf, "%02d/%02d/%04d", now.day(), now.month(), now.year()); 361 return String(buf); 362} 363 364String formatTime(DateTime now) { 365 char buf[15]; 366 int hour = now.hour(); 367 int hour12; 368 const char* ampm; 369 370 // Convert to 12-hour format 371 if (hour == 0) { 372 hour12 = 12; 373 ampm = "AM"; 374 } else if (hour >= 1 && hour < 12) { 375 hour12 = hour; 376 ampm = "AM"; 377 } else if (hour == 12) { 378 hour12 = 12; 379 ampm = "PM"; 380 } else { // hour > 12 381 hour12 = hour - 12; 382 ampm = "PM"; 383 } 384 sprintf(buf, "%02d:%02d:%02d %s", hour12, now.minute(), now.second(), ampm); 385 return String(buf); 386} 387 388// ---------------- OLED Display ---------------- 389void displayResult(String id, DateTime now, const char* status, bool logged) { 390 display.clearDisplay(); 391 display.setTextColor(SH110X_WHITE); 392 display.setTextSize(1); 393 394 display.setCursor(0, 0); 395 display.println(status); 396 display.drawLine(0, 10, SCREEN_WIDTH, 10, SH110X_WHITE); 397 398 display.setCursor(0, 16); 399 display.println("ID:"); 400 display.setCursor(0, 26); 401 display.println(id); 402 403 display.setCursor(0, 40); 404 display.print("Date: "); 405 display.println(formatDate(now)); 406 407 display.setCursor(0, 50); 408 display.print("Time: "); 409 display.println(formatTime(now)); 410 411#if ENABLE_SD_LOGGING 412 if (!logged) { 413 display.setCursor(100, 0); 414 display.print("(!)"); 415 } 416#endif 417 418 display.display(); 419} 420 421void showMessage(const char* line1, const char* line2, const char* line3, unsigned int holdMs) { 422 display.clearDisplay(); 423 display.setTextColor(SH110X_WHITE); 424 display.setTextSize(1); 425 426 display.setCursor(0, 0); 427 display.println(line1); 428 display.drawLine(0, 10, SCREEN_WIDTH, 10, SH110X_WHITE); 429 430 display.setCursor(0, 20); 431 display.println(line2); 432 433 if (line3 != NULL && strlen(line3) > 0) { 434 display.setCursor(0, 32); 435 display.println(line3); 436 } 437 438 display.display(); 439 if (holdMs > 0) delay(holdMs); 440} 441 442// ---------------- LED + Buzzer Feedback ---------------- 443void feedbackSignal(bool checkIn) { 444 if (checkIn) { 445 digitalWrite(LED_PIN, HIGH); 446 tone(BUZZER_PIN, 2000); 447 delay(200); 448 noTone(BUZZER_PIN); 449 delay(600); 450 digitalWrite(LED_PIN, LOW); 451 } else { 452 for (int i = 0; i < 4; i++) { 453 digitalWrite(LED_PIN, HIGH); 454 delay(150); 455 digitalWrite(LED_PIN, LOW); 456 delay(150); 457 } 458 for (int i = 0; i < 2; i++) { 459 tone(BUZZER_PIN, 1200); 460 delay(150); 461 noTone(BUZZER_PIN); 462 delay(150); 463 } 464 } 465}
Downloadable files
schemtic
schemtic.png

Comments
Only logged in users can leave comments