#include <WiFi.h>
#include <SPI.h>
#include <MFRC522.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#define RFID_SS_PIN 5
#define RFID_RST_PIN 21
#define RELAY_PIN 27
const int RELAY_LOCKED_LEVEL = LOW;
const int RELAY_UNLOCKED_LEVEL = HIGH;
const unsigned long UNLOCK_DURATION_MS = 5000;
const unsigned long HEARTBEAT_INTERVAL_MS = 15000;
const unsigned long WIFI_CONNECT_TIMEOUT_MS = 15000;
const unsigned long WIFI_RETRY_INTERVAL_MS = 5000;
const unsigned long MQTT_RECONNECT_INTERVAL_MS = 5000;
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASSWORD = "";
const char* MQTT_SERVER = "mqtt.thingsboard.cloud";
const int MQTT_PORT = 1883;
const char* THINGSBOARD_TOKEN = "8emu4e3m6onqybdo8pqk";
const char* TELEMETRY_TOPIC =
"v1/devices/me/telemetry";
const char* RPC_REQUEST_TOPIC =
"v1/devices/me/rpc/request/+";
const char* RPC_REQUEST_PREFIX =
"v1/devices/me/rpc/request/";
const char* RPC_RESPONSE_PREFIX =
"v1/devices/me/rpc/response/";
const String BLUE_CARD_UID = "01:02:03:04";
const String YELLOW_CARD_UID = "55:66:77:88";
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
MFRC522 rfid(RFID_SS_PIN, RFID_RST_PIN);
unsigned long scanCount = 0;
unsigned long unlockStartedAt = 0;
unsigned long lastHeartbeatAt = 0;
unsigned long lastWiFiReconnectAttempt = 0;
unsigned long lastMqttReconnectAttempt = 0;
bool doorUnlocked = false;
String getCardUID() {
String uid = "";
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) {
uid += "0";
}
uid += String(rfid.uid.uidByte[i], HEX);
if (i < rfid.uid.size - 1) {
uid += ":";
}
}
uid.toUpperCase();
return uid;
}
String getCardName(const String& uid) {
if (uid == BLUE_CARD_UID) {
return "BLUE CARD";
}
if (uid == YELLOW_CARD_UID) {
return "YELLOW CARD";
}
return "UNKNOWN CARD";
}
bool isAuthorizedCard(const String& uid) {
return uid == BLUE_CARD_UID ||
uid == YELLOW_CARD_UID;
}
bool connectWiFi() {
WiFi.mode(WIFI_OFF);
delay(250);
WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, 6);
Serial.print("Connecting to WiFi");
unsigned long connectionStartedAt = millis();
while (
WiFi.status() != WL_CONNECTED &&
millis() - connectionStartedAt <
WIFI_CONNECT_TIMEOUT_MS
) {
delay(250);
Serial.print(".");
}
Serial.println();
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi connection timeout");
Serial.println("Automatic retry is enabled");
return false;
}
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Signal strength: ");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
return true;
}
void maintainWiFiConnection() {
if (WiFi.status() == WL_CONNECTED) {
return;
}
if (
lastWiFiReconnectAttempt != 0 &&
millis() - lastWiFiReconnectAttempt <
WIFI_RETRY_INTERVAL_MS
) {
return;
}
lastWiFiReconnectAttempt = millis();
if (mqttClient.connected()) {
mqttClient.disconnect();
}
Serial.println("WiFi disconnected");
Serial.println("Trying to reconnect...");
connectWiFi();
}
bool publishTelemetry(JsonDocument& document) {
if (!mqttClient.connected()) {
Serial.println(
"Telemetry not sent: MQTT disconnected"
);
return false;
}
char payload[512];
size_t payloadLength = serializeJson(
document,
payload,
sizeof(payload)
);
if (payloadLength == 0) {
Serial.println("Telemetry serialization failed");
return false;
}
bool published = mqttClient.publish(
TELEMETRY_TOPIC,
payload
);
if (published) {
Serial.print("Telemetry sent: ");
Serial.println(payload);
} else {
Serial.println("Telemetry publish failed");
}
return published;
}
void publishDoorState() {
JsonDocument document;
document["doorUnlocked"] = doorUnlocked;
document["relayState"] = doorUnlocked;
publishTelemetry(document);
}
void publishSystemStatus() {
JsonDocument document;
document["deviceStatus"] = "online";
document["doorUnlocked"] = doorUnlocked;
document["relayState"] = doorUnlocked;
document["wifiRSSI"] = WiFi.RSSI();
publishTelemetry(document);
}
void publishCardEvent(
const String& uid,
const String& cardName,
bool accessGranted
) {
JsonDocument document;
document["cardUID"] = uid;
document["cardName"] = cardName;
document["accessGranted"] = accessGranted;
document["accessStatus"] =
accessGranted ? "AUTHORIZED" : "UNAUTHORIZED";
document["scanCount"] = scanCount;
document["doorUnlocked"] = doorUnlocked;
document["relayState"] = doorUnlocked;
publishTelemetry(document);
}
void lockDoor() {
digitalWrite(
RELAY_PIN,
RELAY_LOCKED_LEVEL
);
doorUnlocked = false;
Serial.println("Door state: LOCKED");
publishDoorState();
}
void unlockDoor() {
digitalWrite(
RELAY_PIN,
RELAY_UNLOCKED_LEVEL
);
doorUnlocked = true;
unlockStartedAt = millis();
Serial.println("Door state: UNLOCKED");
Serial.println(
"The door will lock automatically after 5 seconds."
);
publishDoorState();
}
void updateDoorLock() {
if (!doorUnlocked) {
return;
}
if (
millis() - unlockStartedAt >=
UNLOCK_DURATION_MS
) {
lockDoor();
Serial.println("--------------------------");
Serial.println("Scan another card...");
}
}
void sendRpcResponse(
const String& requestId,
bool success,
const String& message
) {
JsonDocument document;
document["success"] = success;
document["message"] = message;
document["doorUnlocked"] = doorUnlocked;
document["relayState"] = doorUnlocked;
char payload[256];
size_t payloadLength = serializeJson(
document,
payload,
sizeof(payload)
);
if (payloadLength == 0) {
Serial.println("RPC response serialization failed");
return;
}
String responseTopic =
String(RPC_RESPONSE_PREFIX) + requestId;
bool published = mqttClient.publish(
responseTopic.c_str(),
payload
);
if (published) {
Serial.print("RPC response sent: ");
Serial.println(payload);
} else {
Serial.println("RPC response failed");
}
}
bool readRequestedRelayState(
JsonVariant params,
bool& requestedState
) {
if (params.is<bool>()) {
requestedState = params.as<bool>();
return true;
}
if (params.is<int>()) {
requestedState = params.as<int>() != 0;
return true;
}
if (
params.is<JsonObject>() &&
params["value"].is<bool>()
) {
requestedState = params["value"].as<bool>();
return true;
}
if (params.is<const char*>()) {
String value = params.as<String>();
value.toLowerCase();
if (value == "true" || value == "on") {
requestedState = true;
return true;
}
if (value == "false" || value == "off") {
requestedState = false;
return true;
}
}
return false;
}
void mqttCallback(
char* topic,
byte* payload,
unsigned int length
) {
Serial.println();
Serial.print("RPC topic: ");
Serial.println(topic);
Serial.print("RPC payload: ");
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
JsonDocument document;
DeserializationError error = deserializeJson(
document,
payload,
length
);
String topicString = String(topic);
String requestId = topicString.substring(
String(RPC_REQUEST_PREFIX).length()
);
if (error) {
Serial.print("RPC JSON error: ");
Serial.println(error.c_str());
sendRpcResponse(
requestId,
false,
"Invalid JSON"
);
return;
}
const char* methodValue =
document["method"] | "";
String method = String(methodValue);
if (method != "setRelay") {
Serial.print("Unknown RPC method: ");
Serial.println(method);
sendRpcResponse(
requestId,
false,
"Unknown method"
);
return;
}
bool requestedState = false;
bool validParams = readRequestedRelayState(
document["params"],
requestedState
);
if (!validParams) {
Serial.println("Invalid setRelay parameters");
sendRpcResponse(
requestId,
false,
"Invalid parameters"
);
return;
}
if (requestedState) {
Serial.println("Remote command: UNLOCK");
unlockDoor();
sendRpcResponse(
requestId,
true,
"Door unlocked"
);
} else {
Serial.println("Remote command: LOCK");
lockDoor();
sendRpcResponse(
requestId,
true,
"Door locked"
);
}
}
String createMqttClientId() {
String clientId = "ESP32-RFID-";
clientId += WiFi.macAddress();
clientId.replace(":", "");
return clientId;
}
void maintainMQTTConnection() {
if (mqttClient.connected()) {
return;
}
if (WiFi.status() != WL_CONNECTED) {
return;
}
if (
lastMqttReconnectAttempt != 0 &&
millis() - lastMqttReconnectAttempt <
MQTT_RECONNECT_INTERVAL_MS
) {
return;
}
lastMqttReconnectAttempt = millis();
String clientId = createMqttClientId();
Serial.print(
"Connecting to ThingsBoard MQTT..."
);
bool connected = mqttClient.connect(
clientId.c_str(),
THINGSBOARD_TOKEN,
""
);
if (!connected) {
Serial.println(" failed");
Serial.print("MQTT state: ");
Serial.println(mqttClient.state());
return;
}
Serial.println(" connected");
Serial.println("MQTT connection established");
bool subscribed = mqttClient.subscribe(
RPC_REQUEST_TOPIC
);
if (subscribed) {
Serial.println(
"Subscribed to ThingsBoard RPC requests"
);
} else {
Serial.println(
"RPC topic subscription failed"
);
}
publishSystemStatus();
}
void setup() {
Serial.begin(115200);
delay(1000);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(
RELAY_PIN,
RELAY_LOCKED_LEVEL
);
SPI.begin(
18,
19,
23,
RFID_SS_PIN
);
rfid.PCD_Init();
delay(100);
Serial.println();
Serial.println("RFID Access Control System");
Serial.println("MFRC522 Ready");
Serial.println("Door state: LOCKED");
connectWiFi();
mqttClient.setServer(
MQTT_SERVER,
MQTT_PORT
);
mqttClient.setCallback(mqttCallback);
mqttClient.setBufferSize(512);
Serial.println("Scan a card...");
}
void loop() {
maintainWiFiConnection();
maintainMQTTConnection();
mqttClient.loop();
updateDoorLock();
if (
mqttClient.connected() &&
millis() - lastHeartbeatAt >=
HEARTBEAT_INTERVAL_MS
) {
lastHeartbeatAt = millis();
publishSystemStatus();
}
if (!rfid.PICC_IsNewCardPresent()) {
return;
}
if (!rfid.PICC_ReadCardSerial()) {
return;
}
scanCount++;
String currentUID = getCardUID();
String cardName = getCardName(currentUID);
bool accessGranted =
isAuthorizedCard(currentUID);
Serial.println();
Serial.print("Card UID: ");
Serial.println(currentUID);
Serial.print("Card name: ");
Serial.println(cardName);
Serial.print("Scan count: ");
Serial.println(scanCount);
if (accessGranted) {
Serial.println(
"Access status: AUTHORIZED"
);
Serial.println("Access granted");
unlockDoor();
} else {
Serial.println(
"Access status: UNAUTHORIZED"
);
Serial.println("Access denied");
Serial.print("Door state: ");
Serial.println(
doorUnlocked ? "UNLOCKED" : "LOCKED"
);
Serial.println("--------------------------");
Serial.println("Scan another card...");
}
publishCardEvent(
currentUID,
cardName,
accessGranted
);
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}