ESP8266 Door Access Control System
Loading...
Searching...
No Matches
esp1_rfid_motion.ino
Go to the documentation of this file.
1/**
2 * @file esp1_rfid_motion.ino
3 * @brief RFID + PIN based access controller with LCD, motion sensing, and MQTT.
4 *
5 * @defgroup esp1 ESP1 - RFID & Motion
6 * @{
7 *
8 * @details
9 * This firmware runs on an ESP-based Arduino-compatible board and implements
10 * the first stage of a distributed door access control system.
11 *
12 * Hardware components:
13 * - MFRC522 RFID reader (RC522)
14 * - I2C LCD display
15 * - PIR motion sensor
16 *
17 * Functional responsibilities:
18 * - Detects motion to manage LCD backlight and user interaction
19 * - Reads RFID tags and initiates authentication
20 * - Displays system and authentication status on the LCD
21 * - Communicates authentication requests and results via MQTT
22 *
23 * RFID authentication must succeed before the keypad stage is enabled.
24 */
25
26
27
28
29#include <Arduino.h>
30#include <string.h>
31#include <ArduinoJson.h>
32
33#include <SPI.h>
34#include <MFRC522.h>
35#include <Wire.h>
36#include <LiquidCrystal_I2C.h>
37
38#include <WiFiMqttClient.h>
39
40// ---------------- Network configuration ----------------
41
42/** @brief WiFi + MQTT client wrapper */
44
45/** @brief WiFi SSID */
46constexpr char WIFI_SSID[] = "Mathias2.4";
47/** @brief WiFi password */
48constexpr char WIFI_PASS[] = "mrbombasticcallmefantastic";
49
50/** @brief MQTT broker hostname */
51constexpr char MQTT_HOST[] = "maqiatto.com";
52/** @brief MQTT broker port */
53constexpr uint16_t MQTT_PORT = 1883;
54/** @brief MQTT username */
55constexpr char MQTT_USER[] = "hectorfoss@gmail.com";
56/** @brief MQTT password */
57constexpr char MQTT_PASS[] = "potter";
58
59/** @brief Unique device identifier used in MQTT topics */
60constexpr char DEVICE_ID[] = "door1";
61
62// -----------------------------------------------------------------------------
63// Pin configuration | esp1&2.fzz
64// -----------------------------------------------------------------------------
65
66/** @brief RC522 SPI Slave Select pin (D8 / GPIO15). */
67constexpr uint8_t RFID_SS_PIN = 15;
68
69/** @brief RC522 Reset pin (D0 / GPIO16). */
70constexpr uint8_t RFID_RST_PIN = 16;
71
72/** @brief Motion sensor pin (D3 / GPIO0). */
73constexpr uint8_t MOTION_PIN = 0;
74
75/** @brief I2C SDA pin for LCD (D2 / GPIO4). */
76constexpr uint8_t I2C_SDA_PIN = 4;
77
78/** @brief I2C SCL pin for LCD (D1 / GPIO5). */
79constexpr uint8_t I2C_SCL_PIN = 5;
80
81// -----------------------------------------------------------------------------
82// LCD configuration
83// -----------------------------------------------------------------------------
84
85/** @brief Number of LCD columns. */
86constexpr uint8_t LCD_COLUMNS = 16;
87
88/** @brief Number of LCD rows. */
89constexpr uint8_t LCD_ROWS = 2;
90
91/** @brief I2C address of the LCD module. */
92constexpr uint8_t LCD_ADDRESS = 0x27;
93
94// -----------------------------------------------------------------------------
95// Timing configuration
96// -----------------------------------------------------------------------------
97
98/** @brief Duration (ms) to display text before resetting. */
99constexpr uint32_t DISPLAY_MS = 3000;
100
101/** @brief Duration (ms) to keep LCD backlight on after motion. */
102constexpr uint32_t DISPLAY_BACKLIGHT_MS = 5000;
103
104/** @brief Time window (ms) for entering PIN after RFID success. */
105constexpr uint32_t PIN_TIME_MS = 15000;
106
107/** @brief Door unlock display duration (ms). */
108constexpr uint32_t UNLOCK_TIME_MS = 5000;
109
110/** @brief Main loop polling delay (ms). */
111constexpr uint32_t POLL_MS = 30;
112
113// -----------------------------------------------------------------------------
114// Global objects
115// -----------------------------------------------------------------------------
116
117/** @brief MFRC522 RFID reader instance. */
119
120/** @brief I2C LCD instance. */
121LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS);
122
123// -----------------------------------------------------------------------------
124// Global state
125// -----------------------------------------------------------------------------
126
127/**
128 * @brief Access result enumeration.
129 */
130enum class AccessResult : uint8_t {
131 Denied, /**< Access denied */
132 Granted /**< Access granted */
133};
134
135/** @brief Result of RFID authentication. */
137
138/** @brief Result of PIN verification. */
140
141/** @brief Indicates whether a status message is currently displayed. */
142bool textshown = false;
143
144/** @brief Timestamp (ms) when the displayed text should expire. */
145uint32_t showTextUntil = 0;
146
147/** @brief Timestamp (ms) when LCD backlight should turn off. */
148uint32_t showDisplayUntil = 0;
149
150/** @brief Indicates whether motion is currently considered active. */
151bool motionActive = false;
152
153/**
154 * @brief Masked PIN buffer shown on LCD (max 4 digits + null terminator).
155 *
156 * Filled with '*' for entered digits and spaces for remaining slots.
157 */
158static char enteredPins[5] = " ";
159
160// -----------------------------------------------------------------------------
161// Helper functions
162// -----------------------------------------------------------------------------
163
164/**
165 * @brief Clears an LCD line and prints a flash-resident string.
166 *
167 * @param msg Flash string (use F("...")).
168 * @param line LCD row index.
169 */
170void lcdPrintLine(const __FlashStringHelper* msg, uint8_t line) {
171 lcd.setCursor(0, line);
172 lcd.print(" "); // Overwrite entire row to remove old content
173 lcd.setCursor(0, line);
174 lcd.print(msg);
175}
176
177/**
178 * @brief Clears an LCD line and prints a RAM-resident string.
179 *
180 * @param msg C-string to print.
181 * @param line LCD row index.
182 */
183void lcdPrintLine(const char* msg, uint8_t line) {
184 lcd.setCursor(0, line);
185 lcd.print(" "); // Clear row to avoid leftover characters
186 lcd.setCursor(0, line);
187 lcd.print(msg);
188}
189
190/**
191 * @brief Forces the system back into locked idle state.
192 *
193 * Resets access state, clears temporary flags,
194 * and restores the default idle LCD message.
195 */
196static void forceLock() {
197 textshown = false; // Exit message display mode
198 rfidAccess = AccessResult::Denied; // Clear RFID authorization state
199 accessGranted = AccessResult::Denied; // Clear PIN authorization state
200 lcdPrintLine(F("Scan RFID card"), 0); // Restore idle prompt
201}
202
203/**
204 * @brief Builds a masked PIN string for LCD display.
205 *
206 * Converts a numeric PIN length into a visual representation
207 * using '*' characters and trailing spaces.
208 *
209 * @param pinLength Number of digits entered so far.
210 */
211void makeEnteredPins(uint8_t pinLength)
212{
213 // Clamp PIN length to display capacity (4 digits)
214 if (pinLength > 4) {
215 pinLength = 4;
216 }
217
218 // Fill entered portion with masking characters
219 memset(enteredPins, '*', pinLength);
220
221 // Fill remaining positions with spaces to clear old characters
222 memset(enteredPins + pinLength, ' ', 4 - pinLength);
223
224 // Ensure string is null-terminated
225 enteredPins[4] = '\0';
226}
227
228/**
229 * @brief MQTT message callback handler.
230 *
231 * Parses incoming JSON messages and dispatches logic based on topic:
232 * - RFID access decision
233 * - Keypad PIN validation result
234 * - Keypad keypress feedback
235 *
236 * @param topic MQTT topic string.
237 * @param payload Raw payload bytes.
238 * @param length Payload length.
239 */
240void callback(char* topic, byte* payload, unsigned int length) {
241 // Ignore empty MQTT messages
242 if (length == 0) return;
243
244 StaticJsonDocument<512> doc;
245
246 // Deserialize JSON payload into document
247 DeserializationError err = deserializeJson(doc, payload, length);
248
249 // Abort if JSON parsing fails
250 if (err) {
251 Serial.print("JSON parse failed: ");
252 Serial.println(err.c_str());
253 return;
254 }
255
256 // ---------------------------------------------------------------------------
257 // RFID access response
258 // ---------------------------------------------------------------------------
259 if (strcmp(topic, net.makeTopic("access/response").c_str()) == 0) {
260
261 // Timestamp when request was sent (provided by backend)
262 uint32_t requestMs = doc["sent_ts_ms"] | 0;
263
264 // Compute round-trip delay to detect stale responses
265 uint32_t deltaMs = millis() - requestMs;
266
267 Serial.printf(
268 "Request/Response time diff: %lu ms\n",
269 deltaMs
270 );
271
272 // Ignore responses that took too long to arrive
273 if (deltaMs > DISPLAY_MS) {
274 Serial.println("Took too long to respond");
275 return;
276 }
277
278 // Extract access decision from JSON payload
279 rfidAccess = (doc["response"]["hasAccess"] | false)
282
283 Serial.println("UID match: waiting for PIN...");
284
285 // Handle denied RFID access immediately
287 lcdPrintLine(F("Access Denied"), 0);
288 textshown = true;
289 showTextUntil = millis() + DISPLAY_MS;
290 return;
291 }
292
293 // Prompt user to enter PIN after successful RFID
294 lcdPrintLine(F("Enter PIN:"), 0);
295 textshown = true;
296 showTextUntil = millis() + PIN_TIME_MS;
297
298 }
299 // ---------------------------------------------------------------------------
300 // Keypad PIN verification response
301 // ---------------------------------------------------------------------------
302 else if (strcmp(topic, net.makeTopic("access/keypad_response").c_str()) == 0) {
303
304 // Ignore PIN responses if RFID was not authorized
305 if (rfidAccess != AccessResult::Granted) return;
306
307 // Extract PIN validation result
308 accessGranted = (doc["response"]["accessGranted"] | false)
311
312 // Handle incorrect PIN
314 Serial.println("Access Denied");
315 lcdPrintLine(F("Access Denied"), 0);
316 textshown = true;
317 showTextUntil = millis() + DISPLAY_MS;
318 return;
319 }
320
321 // Handle successful authentication
322 Serial.println("Access Granted");
323 lcdPrintLine(F("Access Granted"), 0);
324 textshown = true;
325 showTextUntil = millis() + UNLOCK_TIME_MS;
326
327 }
328 // ---------------------------------------------------------------------------
329 // Keypad visual feedback (keypress)
330 // ---------------------------------------------------------------------------
331 else if (strcmp(topic, net.makeTopic("keypad/beep").c_str()) == 0) {
332
333 // Only show keypad feedback after successful RFID
334 if (rfidAccess != AccessResult::Granted) return;
335
336 // Number of digits entered so far
337 uint8_t pinLength = doc["data"]["pinlength"] | 0;
338
339 // Update masked PIN display
340 makeEnteredPins(pinLength);
342 }
343}
344
345/**
346 * @brief Checks whether the LCD backlight should remain active.
347 *
348 * Uses signed time comparison to safely handle millis() wraparound.
349 *
350 * @param now Current time in milliseconds.
351 * @return true if display is active, false otherwise.
352 */
353bool isDisplayActive(uint32_t now) {
354 return (int32_t)(now - showDisplayUntil) < 0;
355}
356
357/**
358 * @brief Handles motion-detected events.
359 *
360 * Activates LCD backlight and refreshes display timeout.
361 *
362 * @param now Current time in milliseconds.
363 */
364void onMotionDetected(uint32_t now) {
365 if (!motionActive) {
366 motionActive = true; // Mark motion as active
367 Serial.println(F("Motion detected"));
368 lcd.backlight(); // Turn on LCD backlight
369 }
370
371 // Extend backlight timeout while motion persists
373}
374
375/**
376 * @brief Handles motion-idle state.
377 *
378 * Turns off LCD backlight when motion has ceased
379 * and the timeout has expired.
380 *
381 * @param now Current time in milliseconds.
382 */
383void onMotionIdle(uint32_t now) {
384 if (motionActive && !isDisplayActive(now)) {
385 motionActive = false; // Mark motion as inactive
386 lcd.noBacklight(); // Turn off LCD backlight
387 }
388}
389
390/**
391 * @brief Updates motion state based on PIR sensor input.
392 *
393 * Dispatches to motion-detected or motion-idle handlers.
394 *
395 * @param now Current time in milliseconds.
396 */
397void updateMotionState(uint32_t now) {
398 const bool motion = digitalRead(MOTION_PIN); // Read PIR sensor state
399
400 if (motion) {
401 onMotionDetected(now);
402 } else {
403 onMotionIdle(now);
404 }
405}
406
407/**
408 * @brief Converts an RFID UID to a hexadecimal string.
409 *
410 * Each UID byte is encoded as two uppercase hexadecimal characters.
411 *
412 * @param uid MFRC522 UID structure.
413 * @param output Destination buffer.
414 * @param outputSize Size of destination buffer.
415 */
416void uidToHexString(const MFRC522::Uid& uid, char* output, size_t outputSize) {
417
418 // Ensure output buffer is large enough:
419 // 2 hex characters per byte + null terminator
420 if (outputSize < (uid.size * 2 + 1)) {
421 output[0] = '\0'; // Fail safely with empty string
422 return;
423 }
424
425 // Convert each UID byte into two hex characters
426 for (byte i = 0; i < uid.size; i++) {
427 sprintf(&output[i * 2], "%02X", uid.uidByte[i]);
428 }
429
430 // Explicitly terminate C-string
431 output[uid.size * 2] = '\0';
432}
433
434/**
435 * @brief Handles RFID card detection and request publishing.
436 *
437 * Detects new cards, reads UID, updates LCD,
438 * and sends an MQTT access request.
439 */
441
442 // Exit if no new card is present
443 if (!mfrc522.PICC_IsNewCardPresent()) {
444 return;
445 }
446
447 // Exit if card UID could not be read
448 if (!mfrc522.PICC_ReadCardSerial()) {
449 return;
450 }
451
452 char uidString[21];
453 uidToHexString(mfrc522.uid, uidString, sizeof(uidString));
454
455 Serial.println(uidString);
456 lcdPrintLine(F("Connecting..."), 0);
457
458 textshown = true;
459 showTextUntil = millis() + DISPLAY_MS;
460
461 // Build JSON payload for access request
462 StaticJsonDocument<64> data;
463 data["uid"] = uidString;
464 data["event"] = "RFID_Try";
465
466 // Publish access request via MQTT
467 bool ok = net.publishJson("access/request", data);
468 Serial.println(ok ? "MQTT publish OK" : "MQTT publish FAILED");
469
470 // Properly halt card communication
471 mfrc522.PICC_HaltA();
472 mfrc522.PCD_StopCrypto1();
473}
474
475/**
476 * @brief Arduino setup function.
477 *
478 * Initializes hardware peripherals, LCD, WiFi,
479 * MQTT client, and topic subscriptions.
480 */
481void setup() {
482 delay(100); // Allow hardware to stabilize
483 Serial.begin(115200);
484
486
487 Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
488
489 lcd.init();
490 lcd.noBacklight();
491 lcd.clear();
492 lcdPrintLine(F("Scan RFID card"), 0);
493
494 SPI.begin();
495 mfrc522.PCD_Init();
496
497 Serial.println("RC522 initialized");
498
499 pinMode(MOTION_PIN, INPUT);
500
501 net.begin(
502 WIFI_SSID,
503 WIFI_PASS,
504 MQTT_HOST,
505 MQTT_PORT,
506 MQTT_USER,
507 MQTT_PASS,
508 DEVICE_ID,
509 "site1"
510 );
511
512 Serial.println("MQTT ready");
513
514 net.setCallback(callback);
515
516 // Subscribe to required MQTT topics
517 Serial.printf("access/response MQTT subscribe %s\n",
518 net.subscribe(net.makeTopic("access/response").c_str()) ? "OK" : "FAILED");
519
520 Serial.printf("access/keypad_response MQTT subscribe %s\n",
521 net.subscribe(net.makeTopic("access/keypad_response").c_str()) ? "OK" : "FAILED");
522
523 Serial.printf("keypad/beep MQTT subscribe %s\n",
524 net.subscribe(net.makeTopic("keypad/beep").c_str()) ? "OK" : "FAILED");
525}
526
527/**
528 * @brief Arduino main loop.
529 *
530 * Executes non-blocking system logic:
531 * - MQTT client processing
532 * - Display timeout handling
533 * - Motion-based backlight control
534 * - RFID polling when active
535 */
536void loop() {
537 net.loop(); // Process MQTT traffic
538 yield(); // Allow background WiFi tasks
539
540 const uint32_t now = millis();
541
542 // If a message is currently displayed, wait for timeout
543 if (textshown) {
544 if ((int32_t)(now - showTextUntil) >= 0) {
545 forceLock(); // Reset system when timeout expires
546 }
547 delay(POLL_MS);
548 return;
549 }
550
551 // Update LCD backlight based on motion sensor
553
554 // Only allow RFID scans when display is active
555 if (isDisplayActive(now)) {
556 handleRFID();
557 }
558}
Lightweight WiFi + MQTT helper wrapper for ESP-based Arduino systems.
Combined WiFi and MQTT client abstraction.
constexpr char WIFI_PASS[]
WiFi password.
uint32_t showTextUntil
Timestamp (ms) when the displayed text should expire.
constexpr char DEVICE_ID[]
Unique device identifier used in MQTT topics.
WifiMqttClient net
WiFi + MQTT client wrapper.
constexpr uint8_t LCD_COLUMNS
Number of LCD columns.
constexpr uint32_t PIN_TIME_MS
Time window (ms) for entering PIN after RFID success.
void lcdPrintLine(const __FlashStringHelper *msg, uint8_t line)
Clears an LCD line and prints a flash-resident string.
MFRC522 mfrc522(RFID_SS_PIN, RFID_RST_PIN)
MFRC522 RFID reader instance.
uint32_t showDisplayUntil
Timestamp (ms) when LCD backlight should turn off.
constexpr char MQTT_USER[]
MQTT username.
constexpr char MQTT_PASS[]
MQTT password.
AccessResult accessGranted
Result of PIN verification.
constexpr char MQTT_HOST[]
MQTT broker hostname.
void setup()
Arduino setup function.
constexpr uint8_t RFID_RST_PIN
RC522 Reset pin (D0 / GPIO16).
bool motionActive
Indicates whether motion is currently considered active.
constexpr uint8_t I2C_SDA_PIN
I2C SDA pin for LCD (D2 / GPIO4).
AccessResult
Access result enumeration.
constexpr uint32_t UNLOCK_TIME_MS
Door unlock display duration (ms).
constexpr uint8_t RFID_SS_PIN
RC522 SPI Slave Select pin (D8 / GPIO15).
bool textshown
Indicates whether a status message is currently displayed.
constexpr uint32_t DISPLAY_BACKLIGHT_MS
Duration (ms) to keep LCD backlight on after motion.
constexpr uint32_t DISPLAY_MS
Duration (ms) to display text before resetting.
void updateMotionState(uint32_t now)
Updates motion state based on PIR sensor input.
constexpr uint8_t MOTION_PIN
Motion sensor pin (D3 / GPIO0).
constexpr uint16_t MQTT_PORT
MQTT broker port.
constexpr uint8_t I2C_SCL_PIN
I2C SCL pin for LCD (D1 / GPIO5).
constexpr uint8_t LCD_ADDRESS
I2C address of the LCD module.
LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS)
I2C LCD instance.
void uidToHexString(const MFRC522::Uid &uid, char *output, size_t outputSize)
Converts an RFID UID to a hexadecimal string.
void callback(char *topic, byte *payload, unsigned int length)
MQTT message callback handler.
void makeEnteredPins(uint8_t pinLength)
Builds a masked PIN string for LCD display.
void onMotionIdle(uint32_t now)
Handles motion-idle state.
void onMotionDetected(uint32_t now)
Handles motion-detected events.
constexpr char WIFI_SSID[]
WiFi SSID.
constexpr uint8_t LCD_ROWS
Number of LCD rows.
void handleRFID()
Handles RFID card detection and request publishing.
static char enteredPins[5]
Masked PIN buffer shown on LCD (max 4 digits + null terminator).
AccessResult rfidAccess
Result of RFID authentication.
constexpr uint32_t POLL_MS
Main loop polling delay (ms).
bool isDisplayActive(uint32_t now)
Checks whether the LCD backlight should remain active.
void loop()
Arduino main loop.
static void forceLock()
Forces the system back into locked idle state.