#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. HTTPClient http; #ifdef LOCAL_ACL void localAcl(String cardID) { 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(false); } #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 #ifdef WEB_SERVER AsyncWebServer server(80); #endif #ifdef WIFI String getESP32ChipID() { uint64_t chipId = ESP.getEfuseMac(); // Get the ESP32 chip ID return String(chipId, HEX); } void connectToWiFi(String ssid, String password) { String hostname = "MX-" + getESP32ChipID() + "-RDR"; WiFi.hostname(hostname.c_str()); #ifdef SERIAL_DEBUG Serial.println("Connecting to WiFi..."); #endif WiFi.begin(ssid.c_str(), password.c_str()); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 30) { delay(1000); #ifdef SERIAL_DEBUG Serial.print("."); #endif attempts++; } if (WiFi.status() == WL_CONNECTED) { #ifdef SERIAL_DEBUG Serial.println("\nConnected to WiFi"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); #endif } else { #ifdef SERIAL_DEBUG Serial.println("\nFailed to connect to WiFi. Rebooting..."); #endif delay(1000); 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(ssid, password); } void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info){ #ifdef SERIAL_DEBUG Serial.println("Connected to AP successfully!"); #endif #ifdef NET_FAIL_SAFE lockDoor(true); #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) { if (bitCount < 34) { cardData <<= 1; cardData |= bitValue; newDataAvailable = true; bitCount++; // Increment the bit count } } if (bitCount <= 34) { lastInterruptTime = interruptTime; lastDataTime = millis(); // Update the time of last received data } } void handleData0Interrupt() { handleInterrupt(0); } void handleData1Interrupt() { handleInterrupt(1); } #ifdef TINANCE2_BACKEND class Tinance2HttpClient { public: Tinance2HttpClient() {} std::pair sendHttpRequest(String url, String method, String payload) { if (!http.begin(url)) { #ifdef SERIAL_DEBUG Serial.println("Failed to begin HTTP request"); #endif http.end(); return std::make_pair("", -1); } // Set the headers http.addHeader("Content-Type", "application/json"); http.addHeader("X-Identifier", tinance2_reader_identifer); http.addHeader("X-Access-Key", tinance2_reader_key); // Set the HTTP timeout to 5 seconds http.setTimeout(5000); // Send the POST request int httpResponseCode; if (method == "POST") { httpResponseCode = http.POST(payload); } else if (method == "GET") { httpResponseCode = http.GET(); } else { // Handle invalid method #ifdef SERIAL_DEBUG Serial.println("Invalid HTTP method"); #endif http.end(); return std::make_pair("", -1); } if (httpResponseCode <= 0) { #ifdef SERIAL_DEBUG Serial.println("Request Failed (response less than 0)"); #endif http.end(); return std::make_pair("", httpResponseCode); } String responseBody = http.getString(); // Cleanup http.end(); return std::make_pair(responseBody, httpResponseCode); } // Function to decode JSON response DynamicJsonDocument decodeJsonResponse(const String& json) { DynamicJsonDocument doc(1024); // Adjust the size as per your JSON data DeserializationError error = deserializeJson(doc, json); if (error) { #ifdef SERIAL_DEBUG Serial.print("Failed to parse JSON: "); Serial.println(error.c_str()); #endif } return doc; } }; // Function to send the authentication request to Tinance2 void tinance2authrequest(String fullCardID, String cardID) { #ifdef SERIAL_DEBUG Serial.println("WIFI Status: " + String(WiFi.status())); #endif if (WiFi.status() == WL_CONNECTED) { #ifdef SERIAL_DEBUG Serial.println("Sending Request to Tinance2 for card: " + fullCardID); #endif // Create the JSON payload String payload = "{\"full_card_id\":\"" + String(fullCardID) + "\"}"; // Send the HTTP request and get the response Tinance2HttpClient httpClient; std::pair responsePair = httpClient.sendHttpRequest(tinance2_url_validatecard, "POST", payload); String response = responsePair.first; int httpResponseCode = responsePair.second; #ifdef SERIAL_DEBUG // Print the response Serial.println("HTTP Response: " + response); #endif // Process the response if (httpResponseCode != 200 && httpResponseCode != 401 && httpResponseCode != 402 && httpResponseCode != 403) { #ifdef LOCAL_ACL #ifdef SERIAL_DEBUG Serial.println("Got unexpected http response using offline auth."); #endif localAcl(String(cardID)); #endif return; } // Check the response code if (httpResponseCode == 200) { Serial.println("Tinance2 Door Access Granted"); #ifdef LATCH_DOOR if (!settings.DoorDisabled()) { unlockDoor(false); delay(RELAY_DELAY); lockDoor(false); } #endif #ifdef TOGGLE_DOOR if (!settings.DoorDisabled()) { toggleDoor(); delay(RELAY_DELAY); } #endif } else if (httpResponseCode == 400 || httpResponseCode == 401 || httpResponseCode == 402 || httpResponseCode == 403) { #ifdef SERIAL_DEBUG Serial.println("Tinance2 Door Access Denied"); #endif #ifdef BUZZER denied_beep(); #endif } } else { #ifdef SERIAL_DEBUG Serial.println("Tinance2 Wifi Disconnected using offline processes."); #endif #ifdef LOCAL_ACL localAcl(String(cardID)); #endif } } #ifdef TINANCE2_BACKEND_SYNC #include #include void tinance2SyncTaskFunction(void *parameter) { while (true) { // Your code here #ifdef SERIAL_DEBUG Serial.println("Syncing Tinance2"); #endif vTaskDelay(pdMS_TO_TICKS(15000)); // Delay for 15 seconds Tinance2HttpClient httpClient; std::pair responsePair = httpClient.sendHttpRequest(tinance2_url_readerinfo, "GET", ""); String response = responsePair.first; int httpResponseCode = responsePair.second; #ifdef SERIAL_DEBUG // Print the response Serial.println("HTTP Response: " + response); Serial.println("HTTP Response Code: " + String(httpResponseCode)); #endif // Process the response DynamicJsonDocument json = httpClient.decodeJsonResponse(response); if (json.containsKey("enabled")) { bool DisableDoor = json["enabled"].as(); settings.setDisableDoor(!DisableDoor); #ifdef SERIAL_DEBUG Serial.println("JSON Reader Enabled: " + json["enabled"].as()); #endif } } } #endif #endif void setup() { // allow reuse (if server supports it) http.setReuse(true); #if defined SERIAL_DEBUG || defined SERIAL_ACL Serial.begin(9600); #endif #ifdef WEB_SERVER WiFi.disconnect(true); #endif #ifdef TINANCE2_BACKEND_SYNC xTaskCreatePinnedToCore(tinance2SyncTaskFunction, "Task", 10000, NULL, 1, NULL, 1); #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 BIT_MODE_WG34 pinMode(BIT_MODE_WG34_PIN, OUTPUT); digitalWrite(BIT_MODE_WG34_PIN, HIGH); #endif #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(ssid, password); #endif #ifdef RELAY1 #ifdef SERIAL_DEBUG Serial.println("Enabling Relay: Locking Door"); #endif lockDoor(true); #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 bool previousDoorDisabled = false; // Declare the missing variable bool isFirstRun = true; // Declare the missing variable void updateDoorStatus() { bool previousDoorDisabled = false; // Declare the missing variable bool isFirstRun = true; // Declare the missing variable if (settings.DoorDisabled() != previousDoorDisabled && !isFirstRun) { #ifdef SERIAL_DEBUG Serial.print("DoorDisabled setting changed to: "); Serial.println(settings.DoorDisabled()); Serial.print("Previous DoorDisabled setting: "); Serial.println(previousDoorDisabled); Serial.print("isFirstRun: "); Serial.println(isFirstRun); #endif if (settings.DoorDisabled()) { #ifdef SERIAL_DEBUG Serial.println("Disabling Door due to DoorDisabled setting"); #endif unlockDoor(false); // Provide the required argument } else { #ifdef SERIAL_DEBUG Serial.println("Enabling Door due to DoorDisabled setting"); #endif lockDoor(false); } } // Update the previousDoorDisabled variable previousDoorDisabled = settings.DoorDisabled(); isFirstRun = false; } #endif /** * @brief The main loop function that runs repeatedly in the program. * * This function is responsible for processing the card reader data and performing necessary actions based on the received data. * It checks for new data availability, validates the data, and performs actions accordingly. * If valid card data is received, it extracts the facility ID and card ID, and prints them to the Serial or WebSerial (if enabled). * It then calls the appropriate functions based on the defined macros to handle the card data. * If no valid card data is received within the specified time, it resets the card data and bit count. * * @note This function assumes the availability of certain macros like TINANCE2_BACKEND, LOCAL_ACL, SERIAL_ACL, SERIAL_DEBUG, and WEB_SERIAL_DEBUG. * * @note This function assumes the availability of certain variables like newDataAvailable, lastDataTime, displayDelay, cardData, bitCount, and maxReaderWaitTime. * * @note This function assumes the availability of certain functions like updateDoorStatus, checkSerialCommand, tinance2authrequest, and localAcl. */ void loop() { #ifdef TINANCE2_BACKEND updateDoorStatus(); #endif #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 && (bitCount == 26 || bitCount == 34)) { uint64_t facilityID = (cardData >> 17) & 0xFFFF; uint64_t cardID = (cardData >> 1) & 0xFFFF; #ifdef SERIAL_DEBUG Serial.println("--- Reader Input Processed ---"); 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 #ifdef TINANCE2_BACKEND tinance2authrequest(String(fullCardID), String(cardID)); #endif #ifdef LOCAL_ACL #ifndef TINANCE2_BACKEND localAcl(String(cardID)); #endif #endif cardData = 0; // Reset the card data lastDataTime = millis(); // Reset the time of last received data bitCount = 0; // Reset the bit count } else if (millis() - lastDataTime >= maxReaderWaitTime && cardData != 0) { uint64_t facilityID = (cardData >> 17) & 0xFFFF; uint64_t cardID = (cardData >> 1) & 0xFFFF; #ifdef SERIAL_DEBUG Serial.println("--- Reader Input Error ---"); 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 cardData = 0; // Reset the card data lastDataTime = millis(); // Reset the time of last received data bitCount = 0; // Reset the bit count } }