#include "main.h" volatile uint64_t cardData = 0; volatile bool newDataAvailable = false; unsigned long lastDataTime = 0; const unsigned long displayDelay = 1000; // Delay in milliseconds after which the data is displayed const unsigned long wifiRebootTimeout = 20000; // Delay before reboot after disconnect unsigned int bitCount = 0; // Variable to keep track of the bit count unsigned int maxReaderWaitTime = 9000; // Variable to timeout reader after too long of no data. #ifdef WEB_SERVER AsyncWebServer server(80); #endif #ifdef WIFI void connectToWiFi() { const int maxAttempts = 3; // Maximum number of attempts to connect to WiFi int attemptCount = 0; // Counter for the number of connection attempts while (attemptCount < maxAttempts) { int strongestSignal = -100; // Variable to store the strongest signal strength String strongestSSID; // Variable to store the SSID with the strongest signal // Scan for available networks int numNetworks = WiFi.scanNetworks(); // Find the SSID with the strongest signal for (int i = 0; i < numNetworks; i++) { int signalStrength = WiFi.RSSI(i); String networkSSID = WiFi.SSID(i); if (signalStrength > strongestSignal) { strongestSignal = signalStrength; strongestSSID = networkSSID; } } // Connect to the provided SSID and password if (strongestSignal > -100 && strongestSSID.equals(ssid)) { WiFi.begin(ssid, password); // Wait for WiFi connection while (WiFi.status() != WL_CONNECTED) { delay(1000); } // Print network information #ifdef SERIAL_DEBUG Serial.print("[*] Connected to network: "); Serial.println(strongestSSID); Serial.print("[+] Signal Strength: "); Serial.println(strongestSignal); Serial.print("[+] IP Address: "); Serial.println(WiFi.localIP()); #endif return; // Exit the function after successful connection } attemptCount++; // Increment the attempt count } // Reboot if no networks are available or connection attempts have failed #ifdef SERIAL_DEBUG Serial.println("[!] No networks available or connection attempts failed. Rebooting..."); #endif delay(1004); // Delay before attempting to reconnect ESP.restart(); } void GetWifiInfo(){ if(WiFi.status() == WL_CONNECTED) { #ifdef SERIAL_DEBUG Serial.print("[*] Network information for "); Serial.println(ssid); Serial.println("[+] BSSID : " + WiFi.BSSIDstr()); Serial.print("[+] Gateway IP : "); Serial.println(WiFi.gatewayIP()); Serial.print("[+] Subnet Mask : "); Serial.println(WiFi.subnetMask()); Serial.println((String)"[+] RSSI : " + WiFi.RSSI() + " dB"); Serial.print("[+] ESP32 IP : "); Serial.println(WiFi.localIP()); #endif } } void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){ #ifdef NET_FAIL_SAFE unlockDoor(true); #endif #ifdef SERIAL_DEBUG Serial.println("Disconnected from WiFi access point"); Serial.print("WiFi lost connection. Reason: "); Serial.println(info.wifi_sta_disconnected.reason); Serial.println("Trying to Reconnect"); #endif connectToWiFi(); } void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info){ #ifdef SERIAL_DEBUG Serial.println("Connected to AP successfully!"); #endif #ifdef NET_FAIL_SAFE lockDoor(); #endif } void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info){ #ifdef SERIAL_DEBUG #ifdef WIFI GetWifiInfo(); #endif #endif } #endif #ifdef WEB_SERVER #endif void handleInterrupt(int bitValue) { static unsigned long lastInterruptTime = 0; unsigned long interruptTime = micros(); if (interruptTime - lastInterruptTime > 200) { cardData <<= 1; cardData |= bitValue; newDataAvailable = true; bitCount++; // Increment the bit count } lastInterruptTime = interruptTime; lastDataTime = millis(); // Update the time of last received data } void handleData0Interrupt() { handleInterrupt(0); } void handleData1Interrupt() { handleInterrupt(1); } void setup() { #if defined SERIAL_DEBUG || defined SERIAL_ACL Serial.begin(9600); #endif #ifdef WEB_SERVER WiFi.disconnect(true); #endif #ifdef LOCAL_ACL acl.loadFromEEPROM(); #endif settings.loadFromEEPROM(); // Initialize SPIFFS #ifdef WEB_SERVER if(!SPIFFS.begin(true)){ #ifdef SERIAL_DEBUG Serial.println("An Error has occurred while mounting SPIFFS"); #endif return; } #ifdef WEB_OTA_UPDATE AsyncElegantOTA.begin(&server, http_username, http_password); #endif #ifdef WEB_SERIAL_DEBUG WebSerial.begin(&server); #endif #endif pinMode(DATA0_PIN, INPUT_PULLUP); pinMode(DATA1_PIN, INPUT_PULLUP); #ifdef LEDCTL_PIN pinMode(LEDCTL_PIN, OUTPUT); #endif #ifdef RELAY1 pinMode(RELAY1_PIN, OUTPUT); #endif attachInterrupt(digitalPinToInterrupt(DATA0_PIN), handleData0Interrupt, FALLING); attachInterrupt(digitalPinToInterrupt(DATA1_PIN), handleData1Interrupt, FALLING); #ifdef WIFI WiFi.onEvent(WiFiStationConnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED); WiFi.onEvent(WiFiGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); WiFi.onEvent(WiFiStationDisconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); connectToWiFi(); #endif #ifdef RELAY1 #ifdef SERIAL_DEBUG Serial.println("Enabling Relay: Locking Door"); #endif lockDoor(); #endif #ifdef RELAY2 pinMode(RELAY2_PIN, OUTPUT); #endif #ifdef BUZZER pinMode(ALARM_PIN, OUTPUT); digitalWrite(ALARM_PIN, HIGH); // Do not set to low or it will constantly beep. #endif #ifdef WEB_SERVER MainWebServer.begin(&server, http_username, http_password); #ifdef LOCAL_ACL #ifdef LOCAL_ACL_API ACLWebServer.begin(&server, http_username, http_password); #endif #endif server.begin(); #endif } #ifdef LOCAL_ACL #ifdef SERIAL_ACL struct WordList { char** words; int length; }; WordList splitString(const char* inputString, const char* delimiter = " ") { WordList result; result.words = NULL; result.length = 0; // Create a temporary copy of the input string since strtok modifies the string char* inputCopy = strdup(inputString); // Get the first word char* word = strtok(inputCopy, delimiter); // Split the string and store each word in the dynamic list while (word != NULL) { result.length++; // Reallocate memory for the dynamic list result.words = (char**)realloc(result.words, (result.length + 1) * sizeof(char*)); // Allocate memory for the current word and copy it into the list result.words[result.length - 1] = (char*)malloc(strlen(word) + 1); strcpy(result.words[result.length - 1], word); // Set the next element in the list to NULL for termination result.words[result.length] = NULL; // Get the next word word = strtok(NULL, delimiter); } // Free the temporary copy of the input string free(inputCopy); return result; } void freeWordList(char** wordList) { for (int i = 0; wordList[i] != NULL; i++) { free(wordList[i]); } free(wordList); } void checkSerialCommand() { if (Serial.available() > 0) { String command = Serial.readStringUntil('\n'); // Read the incoming command until a newline character is received Serial.println("Received command: " + command); // Echo back the received command command.trim(); // Remove any leading or trailing whitespaces if (command.length() == 0) { Serial.println("Invalid command"); // If the command is not recognized } else { WordList wordData = splitString(command.c_str()); char** wordList = wordData.words; int wordCount = wordData.length; String prefix = wordList[0]; // Command Validate Access if (prefix.equals("validateAccess")) { if (wordCount == 2) { bool result = acl.validateAccess(String(wordList[1])); if (result) { Serial.println("Access granted!"); } else { Serial.println("Access denied!"); } } else { Serial.println("validateAccess requires exactly 1 arguments (cardId)"); return; } } // Command Add User if (prefix.equals("addUser")) { if (wordCount == 3) { acl.addUser(wordList[1], wordList[2]); acl.saveToEEPROM(); Serial.println("User Added!"); } else { Serial.println("addUser requires exactly 2 arguments (cardId, Description)"); return; } } // Command Update User if (prefix.equals("updateUser")) { if (wordCount == 4) { acl.updateUser(wordList[1], wordList[2], wordList[3]); acl.saveToEEPROM(); Serial.println("User Updated!"); } else { Serial.println("userUser requires exactly 2 arguments (cardId, newCardId, newDescription)"); return; } } // Command Remove User if (prefix.equals("removeUser")) { if (wordCount == 2) { acl.removeUser(wordList[1]); acl.saveToEEPROM(); Serial.println("User Removed!!"); } else { Serial.println("removeUser requires exactly 1 argument (cardId)"); return; } } // Command List Uses if (prefix.equals("listUsers")) { if (wordCount == 1) { // Retrieve the ACL data using the getter function const User* aclData = acl.getACL(); // Iterate over each user in the ACL and add it to the JSON array for (int i = 0; i < acl.getACLSize(); i++) { Serial.print(aclData[i].cardId); Serial.print(" : "); Serial.println(aclData[i].desc); } } else { Serial.println("removeUser requires exactly 0 arguments"); return; } } freeWordList(wordList); } } } #endif #endif #ifdef TINANCE2_BACKEND #include // Function to send the authentication request to the endpoint void tinance2authrequest(String fullCardID) { #ifdef SERIAL_DEBUG Serial.println("Sending Request to Tinance2 for card: " + fullCardID); #endif HTTPClient http; // Set the endpoint URL String url = tinance2_url; http.begin(url); // Set the headers http.addHeader("Content-Type", "application/json"); http.addHeader("X-Identifier", tinance2_reader_identifer); http.addHeader("X-Access-Key", tinance2_reader_key); // Create the JSON payload String payload = "{\"full_card_id\":\"" + String(fullCardID) + "\"}"; #ifdef SERIAL_DEBUG Serial.println("Payload: " + payload); #endif // Send the POST request int httpResponseCode = http.POST(payload); #ifdef SERIAL_DEBUG Serial.println("response code: " + httpResponseCode); Serial.println("response body: " + http.getString()); #endif // Check the response code if (httpResponseCode == 200) { // Unlock the door unlockDoor(false); delay(RELAY_DELAY); lockDoor(); } else { #ifdef SERIAL_DEBUG Serial.println("Tinance2 Door Access Denied"); #endif // Perform actions for denied access } #ifdef SERIAL_DEBUG // Print the response code Serial.print("HTTP Response Code: "); Serial.println(httpResponseCode); // Print the response body String responseBody = http.getString(); Serial.print("HTTP Response Body: "); Serial.println(responseBody); Serial.println("End of Tinance2 Request"); #endif // Cleanup http.end(); } #endif void loop() { #ifdef LOCAL_ACL #ifdef SERIAL_ACL checkSerialCommand(); #endif #endif if (newDataAvailable) { newDataAvailable = false; lastDataTime = millis(); // Reset the time of last received data } if (millis() - lastDataTime >= displayDelay && cardData != 0) { uint64_t facilityID = (cardData >> 17) & 0xFFFF; uint64_t cardID = (cardData >> 1) & 0xFFFF; #ifdef SERIAL_DEBUG Serial.print("Facility ID (Decimal): "); Serial.println(facilityID); Serial.print("Facility ID (Binary): "); Serial.println(facilityID, BIN); Serial.print("Card ID (Decimal): "); Serial.println(cardID); Serial.print("Card ID (Binary): "); Serial.println(cardID, BIN); Serial.print("Card Data (Binary): "); Serial.println(cardData, BIN); Serial.print("Total Bits Received: "); Serial.println(bitCount); #endif #ifdef WEB_SERIAL_DEBUG WebSerial.print("Facility ID (Decimal): "); WebSerial.println(facilityID); WebSerial.print("Facility ID (Binary): "); WebSerial.println(facilityID, BIN); WebSerial.print("Card ID (Decimal): "); WebSerial.println(cardID); WebSerial.print("Card ID (Binary): "); WebSerial.println(cardID, BIN); WebSerial.print("Card Data (Binary): "); WebSerial.println(cardData, BIN); WebSerial.print("Total Bits Received: "); WebSerial.println(bitCount); #endif String fullCardID = String(facilityID)+":"+String(cardID); #ifdef SERIAL_DEBUG Serial.print("Full Card (Tinance2): "); Serial.println(fullCardID); #endif #ifdef WEB_SERIAL_DEBUG WebSerial.print("Full Card (Tinance2): "); WebSerial.println(fullCardID); #endif cardData = 0; // Reset the card data lastDataTime = millis(); // Reset the time of last received data bitCount = 0; // Reset the bit count #ifdef TINANCE2_BACKEND tinance2authrequest(String(fullCardID)); #endif #ifdef LOCAL_ACL if (acl.validateAccess(String(cardID))) { #ifdef SERIAL_DEBUG Serial.println("LOCAL_AUTH: Access granted!"); #endif // Perform actions for authorized access #ifdef LATCH_DOOR if (!settings.DoorDisabled()) { unlockDoor(false); delay(RELAY_DELAY); lockDoor(); } #endif #ifdef TOGGLE_DOOR if (!settings.DoorDisabled()) { toggleDoor(); delay(RELAY_DELAY); } #endif } else { #ifdef SERIAL_DEBUG Serial.println("LOCAL_AUTH: Access denied!"); #endif // Perform actions for denied access #ifdef BUZZER denied_beep(); #endif } #endif } else if (millis() - lastDataTime >= maxReaderWaitTime) { cardData = 0; // Reset the card data lastDataTime = millis(); // Reset the time of last received data bitCount = 0; // Reset the bit count } }