Thank you for the clarifications. I already edited it.
Note: That code (POST 16) is not actually the actual code I'm using for my project; I made that sample sketch because that is the problem I encountered while doing my project. I would like to ask for your opinion and idea about this.
If you want to see the whole code for my project, I can paste it here.
The project components/devices I'm using are:
SD Card Module?
Esp32 S3 N16R8
Buzzer
RGB Led
Pzem-004t
MFRC522 Reader
4 channel Realy
Buck Converter (Step Down - 12v - 5v)
"THE ONLY PROBLEM WITH THIS IS THE SD CARD AND MFRC522 SPI CONFLICT"
this is my original code:
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include <MFRC522.h>
#include <SPI.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <PZEM004Tv30.h>
#include <SD.h>
#include <HX711.h>
#include <ArduinoOTA.h>
#include <HTTPClient.h>
#include <ESPmDNS.h>
#include "FS.h"
#include "addons/TokenHelper.h"
#include "addons/RTDBHelper.h"
#include <esp_task_wdt.h> // For watchdog timer
// ----------------------
// WiFi Configuration
// ----------------------
#define WIFI_SSID
#define WIFI_PASSWORD
// ----------------------
// Firebase Configuration
// ----------------------
#define API_KEY
#define DATABASE_URL
// Firebase Variables
FirebaseData fbdo;
FirebaseConfig config;
FirebaseAuth auth;
// ----------------------
// RFID Configuration (Dedicated HSPI bus)
// ----------------------
#define RFID_RST_PIN 8
#define RFID_CS_PIN 9
#define RFID_SCK_PIN 6
#define RFID_MISO_PIN 7
#define RFID_MOSI_PIN 5
SPIClass rfidSPI(HSPI);
MFRC522 rfid(RFID_CS_PIN, RFID_RST_PIN);
// Duplicate scan prevention
String lastUID = "";
unsigned long lastScanTime = 0;
const unsigned long duplicateScanInterval = 5000; // 5 seconds
// ----------------------
// GPIO Configuration
// ----------------------
#define LED_R 2
#define LED_G 3
#define LED_B 4
#define RELAY1 14
#define RELAY2 15 // AC Light is now wired to Relay2
#define RELAY3 16
#define RELAY4 17
#define BUZZER 1
// ----------------------
// Reset Button (optional)
// ----------------------
#define RESET_BUTTON 0 // Active LOW
// ----------------------
// LDR Sensor (MH Sensor Digital Out)
// ----------------------
#define LDR_DO_PIN 19
// ----------------------
// SD Card Configuration (Separate FSPI bus)
// ----------------------
#define SD_CS_PIN 10
#define SD_SCK_PIN 12
#define SD_MISO_PIN 13
#define SD_MOSI_PIN 11
SPIClass sdSPI(FSPI);
File logFile;
// ----------------------
// PZEM-004T Configuration
// ----------------------
HardwareSerial pzemSerial(2);
#define PZEM_RX 47
#define PZEM_TX 35 // Changed TX to GPIO35
PZEM004Tv30 pzem(pzemSerial, PZEM_TX, PZEM_RX);
// ----------------------
// HX711 Weight Sensors Configuration
// Shared SCK on GPIO18, each with a unique DOUT.
#define HX711_SCK 18
#define HX711_DOUT1 39
#define HX711_DOUT2 36
#define HX711_DOUT3 38
HX711 scale1;
HX711 scale2;
HX711 scale3;
// Calibration factors (update after calibration)
float calibrationFactor1 = 2035.5;
float calibrationFactor2 = 2035.5;
float calibrationFactor3 = 2035.5;
// Smoothing parameters
const int numSamples = 30;
float weightBuffer1[numSamples] = {0};
float weightBuffer2[numSamples] = {0};
float weightBuffer3[numSamples] = {0};
int bufferIndex1 = 0, bufferIndex2 = 0, bufferIndex3 = 0;
float maxWeight1 = 0;
float maxWeight2 = 0;
float maxWeight3 = 0;
// ----------------------
// Time & NTP
// ----------------------
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 8 * 3600, 60000); // UTC+8
// ----------------------
// Global Timers & Flags
// ----------------------
unsigned long relay2ActivatedTime = 0;
bool isRelay2On = false;
unsigned long RELAY2_ON_DURATION = 45000; // ms
unsigned long lastPZEMRead = 0; // For periodic PZEM readings
// Remote config variables (from /SystemConfig)
float remoteWeightThreshold = 10.0;
unsigned long remoteSessionDuration = 45000;
unsigned long remoteConfigLastUpdate = 0;
// SD Log Rotation Timer
unsigned long lastLogCheckTime = 0;
const unsigned long logCheckInterval = 60000; // 60 seconds
// ----------------------
// Global Variables for Instructor Session
// ----------------------
String activeInstructorUID = "";
unsigned long activeInstructorSessionStart = 0; // milliseconds
// ----------------------
// System-Failure Monitoring
// ----------------------
bool wifiOK = false;
bool firebaseOK = false;
bool sdOK = false;
const unsigned long SYSTEM_FAILURE_TIMEOUT = 30000; // 30 seconds
unsigned long systemFailureStart = 0;
// ----------------------
// SPI Settings
// ----------------------
SPISettings sdSPISettings(4000000, MSBFIRST, SPI_MODE0); // SD card
SPISettings rfidSPISettings(10000000, MSBFIRST, SPI_MODE0); // RFID
// Voltage threshold to decide "no electricity"
const float NO_ELECTRICITY_THRESHOLD = 1.0;
// ----------------------
// Forward Declarations
// ----------------------
bool handleUnregisteredUID(String uid);
void logToSD(String logMessage);
String sanitizeUID(String uid);
void checkLogFileSize();
void processRFIDCard(String uid);
void activateRelays();
void logAccess(String rolePath, String uid, bool isStudent = false);
bool checkClassSchedule();
bool checkInstructorSchedule(String uid);
void checkSystemFailures();
void reinitSD();
void checkWiFiWithBackoff();
// ----------------------
// HX711 Helper Functions (Smoothing)
// ----------------------
float getSmoothedWeight(float buffer[], int &index, HX711 &scale) {
float rawWeight = scale.get_units(1);
if (rawWeight < 0) rawWeight = 0;
buffer[index] = rawWeight;
index = (index + 1) % numSamples;
float sum = 0;
for (int i = 0; i < numSamples; i++) {
sum += buffer[i];
}
return sum / numSamples;
}
void processSensor(HX711 &scale, float buffer[], int &index, float &maxWeight, const char *label) {
if (scale.is_ready()) {
float smoothedWeight = getSmoothedWeight(buffer, index, scale);
if (smoothedWeight < 0.05) smoothedWeight = 0;
if (smoothedWeight > maxWeight) {
maxWeight = smoothedWeight;
}
if (smoothedWeight < (maxWeight * 0.8)) {
maxWeight = smoothedWeight;
}
Serial.print(label);
Serial.print(" Weight: ");
Serial.print(maxWeight, 2);
Serial.println(" kg");
} else {
Serial.print(label);
Serial.println(" not ready. Check connections.");
}
}
// ----------------------
// Other Helper Functions
// ----------------------
String getTimestamp() {
timeClient.update();
unsigned long epochTime = timeClient.getEpochTime();
time_t rawtime = (time_t)epochTime;
struct tm *timeinfo = localtime(&rawtime);
char buffer[20];
strftime(buffer, sizeof(buffer), "%m-%d-%Y %H:%M:%S", timeinfo);
return String(buffer);
}
void uploadOfflineLogs() {
if (!SD.exists("/log.txt")) return;
sdSPI.beginTransaction(sdSPISettings);
logFile = SD.open("/log.txt", FILE_READ);
if (logFile) {
while (logFile.available()) {
String logEntry = logFile.readStringUntil('\n');
if (Firebase.RTDB.pushString(&fbdo, "/OfflineLogs", logEntry))
Serial.println("Uploaded offline log: " + logEntry);
else
Serial.println("Failed to upload offline log: " + logEntry);
}
logFile.close();
SD.remove("/log.txt");
}
sdSPI.endTransaction();
}
void resetRFID() {
digitalWrite(RFID_CS_PIN, HIGH);
rfid.PCD_Init();
Serial.println("RFID reader reset.");
}
bool waitForWeightVerification(float threshold, unsigned long timeout) {
unsigned long startTime = millis();
while (millis() - startTime < timeout) {
processSensor(scale1, weightBuffer1, bufferIndex1, maxWeight1, "Sensor 1");
processSensor(scale2, weightBuffer2, bufferIndex2, maxWeight2, "Sensor 2");
processSensor(scale3, weightBuffer3, bufferIndex3, maxWeight3, "Sensor 3");
if (maxWeight1 >= threshold || maxWeight2 >= threshold || maxWeight3 >= threshold)
return true;
delay(100);
}
return false;
}
void setNeutral() {
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, HIGH);
}
void indicateAccessGranted() {
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
tone(BUZZER, 1000, 200);
delay(200);
noTone(BUZZER);
}
void indicateAccessDenied() {
digitalWrite(LED_R, HIGH);
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
tone(BUZZER, 400, 200);
delay(200);
noTone(BUZZER);
}
void streamCallback(FirebaseStream data) {
Serial.println("Data changed in Firebase!");
String uid = data.stringData();
Serial.println("Updated UID: " + uid);
}
void streamTimeoutCallback(bool timeout) {
if (timeout) {
Serial.println("Stream timeout, trying to reconnect...");
Firebase.RTDB.beginStream(&fbdo, "/UIDs/");
}
}
// ----------------------
// WiFi with Exponential Backoff
// ----------------------
void checkWiFiWithBackoff() {
if (WiFi.status() == WL_CONNECTED) {
wifiOK = true;
return;
}
Serial.println("WiFi disconnected, attempting reconnection...");
WiFi.disconnect();
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int retry = 0;
const int maxRetry = 5;
unsigned long delayTime = 500;
while (WiFi.status() != WL_CONNECTED && retry < maxRetry) {
Serial.print(".");
delay(delayTime);
retry++;
delayTime *= 2;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\nFailed to reconnect to WiFi.");
wifiOK = false;
} else {
Serial.println("\nReconnected to WiFi: " + WiFi.localIP().toString());
wifiOK = true;
}
}
void readRemoteConfig() {
if (millis() - remoteConfigLastUpdate < 60000) return;
remoteConfigLastUpdate = millis();
if (WiFi.status() == WL_CONNECTED) {
FirebaseJson json;
if (Firebase.RTDB.getJSON(&fbdo, "/SystemConfig")) {
FirebaseJson &jsonObj = fbdo.jsonObject();
FirebaseJsonData jsonData;
if (jsonObj.get(jsonData, "WeightThreshold", false) && jsonData.success) {
remoteWeightThreshold = jsonData.intValue;
Serial.print("Remote Weight Threshold updated to: ");
Serial.println(remoteWeightThreshold);
}
if (jsonObj.get(jsonData, "SessionDuration", false) && jsonData.success) {
remoteSessionDuration = jsonData.intValue;
Serial.print("Remote Session Duration updated to: ");
Serial.println(remoteSessionDuration);
}
} else {
Serial.println("Failed to read remote config from Firebase.");
}
}
}
void checkLogFileSize() {
sdSPI.beginTransaction(sdSPISettings);
File logFile = SD.open("/log.txt");
if (logFile) {
if (logFile.size() > 50000) {
logFile.close();
SD.remove("/log.txt");
Serial.println("Log file rotated due to size limit.");
} else {
logFile.close();
}
}
sdSPI.endTransaction();
}
void reinitSD() {
sdSPI.beginTransaction(sdSPISettings);
if (!SD.begin(SD_CS_PIN, sdSPI)) {
Serial.println("Reinitializing SD card failed.");
sdOK = false;
} else {
Serial.println("SD card reinitialized successfully.");
sdOK = true;
}
sdSPI.endTransaction();
}
// ----------------------
// Missing Function Implementations
// ----------------------
// Activate all relays (example implementation)
void activateRelays() {
relay2ActivatedTime = millis();
isRelay2On = true;
// Activate relays (active LOW)
digitalWrite(RELAY1, LOW);
digitalWrite(RELAY2, LOW);
digitalWrite(RELAY3, LOW);
digitalWrite(RELAY4, LOW);
delay(1000);
// Turn Relay1 off to signal end of activation
digitalWrite(RELAY1, HIGH);
setNeutral();
Serial.println("Relays activated.");
}
// Log access data to Firebase (or fallback to SD if needed)
void logAccess(String rolePath, String uid, bool isStudent) {
String timestamp = getTimestamp();
String logPath = isStudent ? rolePath + "/AttendanceRecords" : rolePath + "/AccessLogs";
if (Firebase.RTDB.setString(&fbdo, logPath, timestamp))
Serial.println("Access logged for UID " + uid + " at " + timestamp);
else {
Serial.println("Error logging access for UID " + uid);
logToSD("Access log failed for UID: " + uid + " at " + timestamp);
}
}
// Check instructor schedule (placeholder, return true if allowed)
bool checkInstructorSchedule(String uid) {
Serial.println("Checking schedule for instructor UID: " + uid);
return true;
}
// Check class schedule (placeholder, return true if allowed)
bool checkClassSchedule() {
Serial.println("Checking global class schedule.");
return true;
}
// Check if UID is registered in Firebase
bool handleUnregisteredUID(String uid) {
Serial.println("Checking UID in Firebase...");
String adminPath = "/Admin/" + uid;
String instructorPath = "/Instructors/" + uid;
String studentPath = "/Students/" + uid;
bool isRegistered = false;
if (Firebase.RTDB.getJSON(&fbdo, adminPath))
isRegistered = true;
else if (Firebase.RTDB.getJSON(&fbdo, instructorPath))
isRegistered = true;
else if (Firebase.RTDB.getJSON(&fbdo, studentPath))
isRegistered = true;
if (!isRegistered) {
Serial.println("UID not found in Firebase. Storing unregistered UID in Firebase...");
String timestamp = getTimestamp();
FirebaseJson json;
json.add("uid", uid);
json.add("timestamp", timestamp);
if (Firebase.RTDB.pushJSON(&fbdo, "/UnregisteredUIDs", &json))
Serial.println("Unregistered UID stored successfully.");
else
Serial.println("Failed to store unregistered UID in Firebase.");
return true;
}
return false;
}
// ----------------------
// [NEW LOGIC] logToSD Implementation
// ----------------------
void logToSD(String logMessage) {
sdSPI.beginTransaction(sdSPISettings);
File logFile = SD.open("/log.txt", FILE_APPEND);
if (logFile) {
logFile.println(logMessage);
logFile.close();
Serial.println("Logged to SD: " + logMessage);
} else {
Serial.println("Failed to open log file for writing.");
}
sdSPI.endTransaction();
}
// ----------------------
// [NEW LOGIC] Safe Offline Log Function
// Turns LED green during SD write and temporarily disables RFID SPI.
void safeOfflineLog(String message) {
// Disable RFID to avoid SPI conflict
rfid.PCD_AntennaOff();
rfidSPI.end();
digitalWrite(RFID_CS_PIN, HIGH);
Serial.println("RFID disabled for SD logging.");
// Set LED to green as processing indicator
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
// Enable SD card if not already enabled
sdSPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
sdSPI.beginTransaction(sdSPISettings);
if (!SD.begin(SD_CS_PIN, sdSPI)) {
Serial.println("Failed to enable SD card for safeOfflineLog.");
sdSPI.endTransaction();
return;
}
sdSPI.endTransaction();
sdOK = true;
Serial.println("SD card enabled for safeOfflineLog.");
// Write log to SD
sdSPI.beginTransaction(sdSPISettings);
File logFile = SD.open("/log.txt", FILE_APPEND);
if (logFile) {
logFile.println(message);
logFile.close();
Serial.println("Logged to SD (safeOfflineLog): " + message);
} else {
Serial.println("Failed to open log file in safeOfflineLog.");
}
sdSPI.endTransaction();
// Revert LED to neutral (blue)
setNeutral();
// Optionally disable SD card after logging
SD.end();
sdSPI.end();
digitalWrite(SD_CS_PIN, HIGH);
sdOK = false;
Serial.println("SD card disabled after safeOfflineLog.");
// Re-enable RFID
rfidSPI.begin(RFID_SCK_PIN, RFID_MISO_PIN, RFID_MOSI_PIN, RFID_CS_PIN);
rfid.PCD_Init();
rfid.PCD_AntennaOn();
Serial.println("RFID re-enabled after safeOfflineLog.");
}
// ----------------------
// Process RFID Card
// ----------------------
void processRFIDCard(String uid) {
Serial.println("Processing card: " + uid);
if (WiFi.status() != WL_CONNECTED) {
String timestamp = getTimestamp();
String offlineLog = "Offline Attendance - UID: " + uid + ", Time: " + timestamp;
safeOfflineLog(offlineLog);
Serial.println("WiFi not connected. Access Denied for UID: " + uid);
indicateAccessDenied();
resetRFID();
return;
}
if (handleUnregisteredUID(uid)) {
Serial.println("Card unregistered. Access Denied for UID: " + uid);
indicateAccessDenied();
resetRFID();
return;
}
String path = "/Admin/" + uid;
if (Firebase.RTDB.getJSON(&fbdo, path)) {
Serial.println("Admin access granted for UID: " + uid);
activateRelays();
indicateAccessGranted();
logAccess(path, uid, false);
resetRFID();
return;
}
path = "/Instructors/" + uid;
if (Firebase.RTDB.getJSON(&fbdo, path)) {
Serial.println("Instructor access for UID: " + uid);
if (!checkInstructorSchedule(uid)) {
Serial.println("Access denied due to scheduling restrictions for Instructor UID: " + uid);
indicateAccessDenied();
resetRFID();
return;
}
activeInstructorUID = uid;
activeInstructorSessionStart = millis();
activateRelays();
indicateAccessGranted();
logAccess(path, uid, false);
resetRFID();
return;
}
path = "/Students/" + uid;
if (Firebase.RTDB.getJSON(&fbdo, path)) {
Serial.println("Student detected for UID: " + uid);
if (!checkClassSchedule()) {
Serial.println("Access denied due to scheduling restrictions for Student UID: " + uid);
indicateAccessDenied();
resetRFID();
return;
}
Serial.println("Initiating weight sensor verification for UID: " + uid);
if (waitForWeightVerification(remoteWeightThreshold, 10000)) {
Serial.println("Weight verification successful. Access granted for UID: " + uid);
activateRelays();
indicateAccessGranted();
logAccess(path, uid, true);
} else {
Serial.println("Weight verification failed. Access Denied for UID: " + uid);
indicateAccessDenied();
}
resetRFID();
return;
}
Serial.println("Unauthorized card. Access Denied for UID: " + uid);
indicateAccessDenied();
resetRFID();
}
// ----------------------
// SD Card File System Functions (wrapped with SPI transactions)
// ----------------------
void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
sdSPI.beginTransaction(sdSPISettings);
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
sdSPI.endTransaction();
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
sdSPI.endTransaction();
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.name(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
unsigned long fSize = file.size();
Serial.print(fSize);
Serial.print(" bytes (");
Serial.print((float)fSize / 1048576.0, 3);
Serial.println(" MB)");
}
file = root.openNextFile();
}
sdSPI.endTransaction();
}
void createDir(fs::FS &fs, const char * path) {
sdSPI.beginTransaction(sdSPISettings);
Serial.printf("Creating Dir: %s\n", path);
if (fs.mkdir(path))
Serial.println("Dir created");
else
Serial.println("mkdir failed");
sdSPI.endTransaction();
}
void removeDir(fs::FS &fs, const char * path) {
sdSPI.beginTransaction(sdSPISettings);
Serial.printf("Removing Dir: %s\n", path);
if (fs.rmdir(path))
Serial.println("Dir removed");
else
Serial.println("rmdir failed");
sdSPI.endTransaction();
}
void readFile(fs::FS &fs, const char * path) {
sdSPI.beginTransaction(sdSPISettings);
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
sdSPI.endTransaction();
return;
}
Serial.print("Read from file: ");
while (file.available()) {
Serial.write(file.read());
}
Serial.println();
file.close();
sdSPI.endTransaction();
}
void writeFile(fs::FS &fs, const char * path, const char * message) {
sdSPI.beginTransaction(sdSPISettings);
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
sdSPI.endTransaction();
return;
}
if (file.print(message))
Serial.println("File written");
else
Serial.println("Write failed");
file.close();
sdSPI.endTransaction();
}
void appendFile(fs::FS &fs, const char * path, const char * message) {
sdSPI.beginTransaction(sdSPISettings);
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if (!file) {
Serial.println("Failed to open file for appending");
sdSPI.endTransaction();
return;
}
if (file.print(message))
Serial.println("Message appended");
else
Serial.println("Append failed");
file.close();
sdSPI.endTransaction();
}
void renameFile(fs::FS &fs, const char * path1, const char * path2) {
sdSPI.beginTransaction(sdSPISettings);
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (fs.rename(path1, path2))
Serial.println("File renamed");
else
Serial.println("Rename failed");
sdSPI.endTransaction();
}
void deleteFile(fs::FS &fs, const char * path) {
sdSPI.beginTransaction(sdSPISettings);
Serial.printf("Deleting file: %s\n", path);
if (fs.remove(path))
Serial.println("File deleted");
else
Serial.println("Delete failed");
sdSPI.endTransaction();
}
void testFileIO(fs::FS &fs, const char * path) {
sdSPI.beginTransaction(sdSPISettings);
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if (file) {
len = file.size();
size_t flen = len;
start = millis();
while (len) {
size_t toRead = (len > 512) ? 512 : len;
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read in %u ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
sdSPI.endTransaction();
return;
}
start = millis();
for (size_t i = 0; i < 2048; i++) {
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written in %u ms\n", 2048 * 512, end);
file.close();
sdSPI.endTransaction();
}
// -------------------------------------------------------------------
// Setup
// -------------------------------------------------------------------
void setup() {
Serial.begin(115200);
// Initialize watchdog with a 30-second timeout
esp_task_wdt_config_t wdt_config;
wdt_config.timeout_ms = 30000;
esp_task_wdt_init(&wdt_config);
esp_task_wdt_add(NULL);
// --------------------------
// RFID SPI Initialization
// --------------------------
pinMode(RFID_CS_PIN, OUTPUT);
digitalWrite(RFID_CS_PIN, HIGH); // Deassert CS
rfidSPI.begin(RFID_SCK_PIN, RFID_MISO_PIN, RFID_MOSI_PIN, RFID_CS_PIN);
rfid.PCD_Init();
rfid.PCD_DumpVersionToSerial();
// --------------------------
// SD SPI Initialization
// --------------------------
pinMode(SD_CS_PIN, OUTPUT);
digitalWrite(SD_CS_PIN, HIGH);
sdSPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
// --------------------------
// GPIO Setup
// --------------------------
pinMode(LED_R, OUTPUT);
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(RELAY1, OUTPUT);
pinMode(RELAY2, OUTPUT);
pinMode(RELAY3, OUTPUT);
pinMode(RELAY4, OUTPUT);
pinMode(BUZZER, OUTPUT);
setNeutral();
noTone(BUZZER);
// --------------------------
// Reset Button
// --------------------------
pinMode(RESET_BUTTON, INPUT_PULLUP);
// --------------------------
// LDR Digital Input
// --------------------------
pinMode(LDR_DO_PIN, INPUT);
// --------------------------
// Relays default to OFF
// --------------------------
digitalWrite(RELAY1, HIGH);
digitalWrite(RELAY2, HIGH);
digitalWrite(RELAY3, HIGH);
digitalWrite(RELAY4, HIGH);
// --------------------------
// SD Card Check (initial test)
// --------------------------
Serial.println("Initializing SD card...");
if (!SD.begin(SD_CS_PIN, sdSPI)) {
Serial.println("SD card initialization failed.");
sdOK = false;
} else {
Serial.println("SD card initialized successfully!");
sdOK = true;
sdSPI.beginTransaction(sdSPISettings);
File testFile = SD.open("/test.txt", FILE_WRITE);
if (testFile) {
testFile.println("Test file write successful!");
testFile.close();
Serial.println("Test file successfully created.");
} else {
Serial.println("Test file write failed!");
}
sdSPI.endTransaction();
listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
deleteFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
// --------------------------
// [SD Standby after Initialization]
// --------------------------
// Now that the SD card test is complete,
// disable the SD card (put into standby mode) to free up the SPI bus for MFRC522.
SD.end();
sdSPI.end();
digitalWrite(SD_CS_PIN, HIGH);
sdOK = false;
Serial.println("SD card switched to standby mode.");
}
// --------------------------
// Wi-Fi
// --------------------------
Serial.print("Connecting to Wi-Fi");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
checkWiFiWithBackoff();
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\nWiFi connection failed.");
wifiOK = false;
} else {
Serial.println("\nConnected to Wi-Fi");
Serial.println("IP Address: " + WiFi.localIP().toString());
wifiOK = true;
}
// --------------------------
// mDNS & OTA
// --------------------------
if (!MDNS.begin("esp32s3")) {
Serial.println("Error setting up MDNS responder!");
} else {
Serial.println("MDNS responder started.");
}
ArduinoOTA.onStart([]() {
Serial.println("OTA Update starting...");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nOTA Update finished.");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("OTA Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("OTA Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("OTA Ready");
// --------------------------
// Firebase
// --------------------------
config.api_key = API_KEY;
config.database_url = DATABASE_URL;
if (!Firebase.signUp(&config, &auth, "", "")) {
Serial.printf("Firebase signup error: %s\n", config.signer.signupError.message.c_str());
Serial.println("Firebase authentication failed.");
firebaseOK = false;
} else {
Serial.println("Firebase authenticated successfully.");
firebaseOK = true;
}
config.token_status_callback = tokenStatusCallback;
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
// Set Firebase stream callback
Firebase.RTDB.setStreamCallback(&fbdo, streamCallback, streamTimeoutCallback);
// --------------------------
// NTP
// --------------------------
timeClient.begin();
// --------------------------
// PZEM
// --------------------------
pzemSerial.begin(9600, SERIAL_8N1, PZEM_RX, PZEM_TX);
// --------------------------
// HX711 Sensors Initialization (Shared SCK on GPIO18)
// --------------------------
Serial.println("Initializing HX711 sensors...");
scale1.begin(HX711_DOUT1, HX711_SCK);
scale2.begin(HX711_DOUT2, HX711_SCK);
scale3.begin(HX711_DOUT3, HX711_SCK);
while (!scale1.is_ready() || !scale2.is_ready() || !scale3.is_ready()) {
Serial.println("Waiting for HX711 sensors to stabilize...");
delay(500);
}
scale1.set_scale(calibrationFactor1);
scale2.set_scale(calibrationFactor2);
scale3.set_scale(calibrationFactor3);
Serial.println("Taring the scales... Ensure no weight is on them.");
delay(5000);
scale1.tare();
scale2.tare();
scale3.tare();
Serial.println("HX711 Tare complete. Ready for measurement.");
Serial.println("Setup complete.\n");
}
// -------------------------------------------------------------------
// Check System Failures
// -------------------------------------------------------------------
void checkSystemFailures() {
bool allOK = wifiOK && firebaseOK && sdOK;
if (!allOK) {
if (systemFailureStart == 0) {
systemFailureStart = millis();
} else {
if (millis() - systemFailureStart >= SYSTEM_FAILURE_TIMEOUT) {
Serial.println("System failure detected for too long. Restarting...");
ESP.restart();
}
}
} else {
systemFailureStart = 0;
}
}
// -------------------------------------------------------------------
// Main Loop
// -------------------------------------------------------------------
void loop() {
ArduinoOTA.handle();
esp_task_wdt_reset(); // Reset watchdog timer
// Optional Software Reset Button (Active LOW)
if (digitalRead(RESET_BUTTON) == LOW) {
Serial.println("Reset button pressed! Restarting...");
delay(500);
ESP.restart();
}
// Check Wi-Fi / Firebase
checkWiFiWithBackoff();
if (!Firebase.ready()) {
Serial.println("Firebase not ready!");
firebaseOK = false;
} else {
firebaseOK = true;
}
// Remote Config
readRemoteConfig();
// [NEW LOGIC] Read PZEM Voltage and enable/disable SD accordingly
float voltage = pzem.voltage();
if (voltage < NO_ELECTRICITY_THRESHOLD) {
if (!sdOK) {
sdSPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
sdSPI.beginTransaction(sdSPISettings);
if (SD.begin(SD_CS_PIN, sdSPI)) {
Serial.println("SD card enabled due to power outage.");
sdOK = true;
} else {
Serial.println("Failed to enable SD card in power outage.");
}
sdSPI.endTransaction();
}
} else {
if (sdOK) {
SD.end();
sdSPI.end();
digitalWrite(SD_CS_PIN, HIGH);
sdOK = false;
Serial.println("SD card disabled (normal power present).");
}
}
// Upload Offline Logs if Wi-Fi is connected
if (WiFi.status() == WL_CONNECTED) {
uploadOfflineLogs();
}
// Check SD Log File Size Periodically
if (millis() - lastLogCheckTime > logCheckInterval) {
lastLogCheckTime = millis();
checkLogFileSize();
}
// Read PZEM Sensor Data (every 10 seconds)
if (millis() - lastPZEMRead > 10000) {
lastPZEMRead = millis();
float current = pzem.current();
float power = pzem.power();
float energy = pzem.energy();
FirebaseJson sensorJson;
sensorJson.add("voltage", voltage);
sensorJson.add("current", current);
sensorJson.add("power", power);
sensorJson.add("energy", energy);
sensorJson.add("timestamp", getTimestamp());
if (activeInstructorUID != "") {
sensorJson.add("uid", activeInstructorUID);
}
String sensorDataStr = "Voltage: " + String(voltage, 2) + " V, " +
"Current: " + String(current, 2) + " A, " +
"Power: " + String(power, 2) + " W, " +
"Energy: " + String(energy, 2) + " kWh";
if (voltage < 1.0)
sensorDataStr += " (No Electricity)";
Serial.println("PZEM Data: " + sensorDataStr);
if (activeInstructorUID != "") {
String sensorPath = "/SensorData/" + activeInstructorUID;
if (Firebase.RTDB.pushJSON(&fbdo, sensorPath, &sensorJson)) {
Serial.println("Sensor data for instructor " + activeInstructorUID + " uploaded to Firebase.");
} else {
Serial.println("Failed to upload sensor data for instructor " + activeInstructorUID);
safeOfflineLog("PZEM OFFLINE: " + sensorDataStr);
}
}
else {
if (Firebase.RTDB.setJSON(&fbdo, "/SensorData", &sensorJson)) {
Serial.println("Global sensor data uploaded to Firebase.");
} else {
Serial.println("Failed to upload sensor data to Firebase.");
safeOfflineLog("PZEM OFFLINE: " + sensorDataStr);
}
}
}
// Instructor Session Timeout
if (activeInstructorUID != "") {
if (millis() - activeInstructorSessionStart > remoteSessionDuration) {
Serial.println("Instructor session expired for UID: " + activeInstructorUID);
activeInstructorUID = "";
}
}
// Set LED to Neutral
setNeutral();
// RFID Reading
digitalWrite(RFID_CS_PIN, HIGH); // Deassert CS
rfidSPI.beginTransaction(rfidSPISettings);
bool cardPresent = rfid.PICC_IsNewCardPresent();
bool readCard = cardPresent && rfid.PICC_ReadCardSerial();
rfidSPI.endTransaction();
if (readCard) {
String uid = "";
for (byte i = 0; i < rfid.uid.size; i++) {
uid += String(rfid.uid.uidByte[i], HEX);
}
uid.toUpperCase();
Serial.println("Card UID: " + uid);
// Check for duplicate scans
if (uid == lastUID && (millis() - lastScanTime < duplicateScanInterval)) {
Serial.println("Duplicate scan detected. Ignoring.");
resetRFID();
delay(500);
return;
}
lastUID = uid;
lastScanTime = millis();
processRFIDCard(uid);
delay(500);
}
// LDR Light Control (only if instructor present)
if (activeInstructorUID != "") {
int ldrState = digitalRead(LDR_DO_PIN);
if (ldrState == HIGH) {
digitalWrite(RELAY2, HIGH); // Light OFF
Serial.println("LDR sees bright => Light OFF");
} else {
digitalWrite(RELAY2, LOW); // Light ON
Serial.println("LDR sees dark => Light ON");
}
} else {
digitalWrite(RELAY2, HIGH);
}
// Process HX711 sensors (smoothing)
processSensor(scale1, weightBuffer1, bufferIndex1, maxWeight1, "Sensor 1");
processSensor(scale2, weightBuffer2, bufferIndex2, maxWeight2, "Sensor 2");
processSensor(scale3, weightBuffer3, bufferIndex3, maxWeight3, "Sensor 3");
// System Failure Check
checkSystemFailures();
delay(100); // Loop pacing
}