@GolamMostafa Apologies, that last sketch posted was not the right one. Here is the sketch that will not compile.
#include <bsec.h> // BSEC Library - 4.1 IAQ
#include <MFRC522_I2C.h> // RFID Library
#include <ESP32_Servo.h> // Servo Library
#include <Wire.h> // I2C Library
#include <LiquidCrystal_I2C.h> // LCD Library
#include <WiFi.h> // Wireless Library
#include <ArduinoJson.h> // Json Payload Library
#include <HTTPClient.h> // HTTP Library
#include <esp_sntp.h>
#include <deque>
#include <SPIFFS.h>
#include <math.h>
std::deque<String> logQueue;
unsigned long lastLogSend = 0;
const unsigned long LOG_SEND_INTERVAL = 150; // ms between log POSTs
TaskHandle_t logTaskHandle = NULL;
const char* BSEC_STATE_FILE = "/bsec_state.bin";
unsigned long lastBaselineSave = 0;
const unsigned long BASELINE_SAVE_INTERVAL = 6UL * 60UL * 60UL * 1000UL; // 6 hours
MFRC522_I2C mfrc522(0x28, 0); // RFID Address
LiquidCrystal_I2C lcd(0x27,16,2); // LCD Address
Bsec iaqSensor; // BSEC Address
#define WIFI_SSID "XXX"
#define WIFI_PASSWORD "XXX"
Servo servo_18; // Configure servo pins
const int door_close = 10; // Define the angle of the door servo to close the door, which can be modified according to the actual situation
const int door_open = 105; // Define the angle of the door servo to open the door, it can be modified according to the actual situation
unsigned long lastServoMove = 0; // Last servo position
const int servoStepInterval = 8; // ms beteen steps
const int servoStepSize = 3; // Degrees per step
// --- I2C BUS ID's ---
String getDeviceName(byte address) {
switch (address) {
case 0x27: return "LCD Display";
case 0x28: return "RFID Reader";
case 0x77: return "4.1 Monitor";
// Add more here for expansion
default: return "Unknown Device";
}
}
// ----- LCD State -----
unsigned long lcdStartTime = 0;
unsigned long lcdDuration = 0;
bool lcdActive = false;
String lcdLine1 = "";
String lcdLine2 = "";
String scrollName = "";
int scrollPos = 0;
unsigned long lastScroll = 0;
const unsigned long scrollSpeed = 350; //ms per shift
bool scrollActive = false;
unsigned long lastActivityTime = 0;
bool lcdIsOn = true;
const unsigned long lcdTimeout = 30000; // 60-sec
// ----- Door State -----
bool doorOpenState = false;
unsigned long doorOpenTime = 0;
const unsigned long doorHoldDuration = 2000; // 2 seconds
bool doorJustClosedMessageShown = false;
int doorCurrent = door_close;
int doorTarget = door_close;
// ----- RFID State -----
String rfid_str = ""; // Define the variable of string type to read the card number
String lastUID = ""; // Store last card read
unsigned long lastScanTime = 0; // Last time card scan
const unsigned long scanCooldown = 750; // .75 second
bool cardAlreadyRead = false;
unsigned long lastCardSeen = 0;
const unsigned long cardPresentTimeout = 300; //ms of no card assumes card is gone
bool cardIsPresent = false;
bool deniedCardPresent = false;
String deniedDisplayName = "";
bool lastAuthKnown = false;
bool lastAuthAllowed = false;
String lastAuthName = "";
// ------Sensor State ------------
String output;
float tempOffsetC = 0.0f;
float humidityOffset = 0.0f;
bool offsetsApplied = false;
bsec_virtual_sensor_t sensorList[13] = {
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_STATIC_IAQ,
BSEC_OUTPUT_CO2_EQUIVALENT,
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_RAW_TEMPERATURE,
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
BSEC_OUTPUT_STABILIZATION_STATUS,
BSEC_OUTPUT_RUN_IN_STATUS,
BSEC_OUTPUT_GAS_PERCENTAGE
};
unsigned long sensorWarmup = millis();
const char* divider = "----------------------------------------------";
// -------------------------------------
// Sea-level pressure / altitude helpers
// -------------------------------------
float cachedSLP = 1013.25;
unsigned long lastSLPFetch = 0;
float smoothedAltitude = NAN;
// -----------------------------
// SERIAL PRINT WRAPPER use sysPrint(ln) instead of Serial.print(ln)
// -----------------------------
String serialBuffer = "";
void flushSerialBuffer() {
int newlineIndex;
while ((newlineIndex = serialBuffer.indexOf('\n')) != -1) {
String line = serialBuffer.substring(0, newlineIndex);
serialBuffer.remove(0, newlineIndex + 1);
logQueue.push_back(line);
}
}
void sysPrint(const String msg) {
Serial.print(msg);
serialBuffer += msg;
flushSerialBuffer();
}
void sysPrintln(const String msg) {
Serial.println(msg);
serialBuffer += msg + '\n';
flushSerialBuffer();
}
void sysPrintf(const char *fmt, ...) {
char buffer[512];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
Serial.print(buffer);
serialBuffer += buffer;
flushSerialBuffer();
}
void sendSystemLog(const String &line) {
if (WiFi.status() != WL_CONNECTED) {
return;
}
HTTPClient http;
http.begin("http://SOME.IP.ADDRESS/systemlog");
http.addHeader("Content-Type", "application/json");
String body = "{\"device\":\"ACEBOTT\",\"message\":\"" + line + "\"}";
http.POST(body);
http.end();
}
// ------------------------------
// NON_BLOCKING SYSTEM LOG SENDER
// ------------------------------
void logTask(void *parameter) {
for (;;) {
if (!logQueue.empty()) {
String line = logQueue.front();
logQueue.pop_front();
sendSystemLog(line);
}
vTaskDelay(150 / portTICK_PERIOD_MS);
}
}
void processLogQueue() {
if (logQueue.empty()) return;
if (millis() - lastLogSend < LOG_SEND_INTERVAL) return;
lastLogSend = millis();
String line = logQueue.front();
logQueue.pop_front();
sendSystemLog(line); /// THIS CAN BLOCK - BUT ONLY ONCE PER INTERVAL
}
// -------------------------------
// LOCAL TIME HELPERS
// -------------------------------
// *** Serial Local Time
void printLocalTime() {
time_t now = time(nullptr);
struct tm *t = localtime(&now);
sysPrintf("Local Time: %02d:%02d:%02d %02d/%02d/%04d\n",
t->tm_hour, t->tm_min, t->tm_sec,
t->tm_mon + 1, t->tm_mday, t->tm_year + 1900);
}
// *** LCD Local Time
void lcdShowTime() {
time_t now = time(nullptr);
struct tm *t = localtime(&now);
lcd.setCursor(0, 0);
lcd.printf("%02d:%02d:%02d", t->tm_hour, t->tm_min, t->tm_sec);
lcd.setCursor(0, 1);
lcd.printf("%02d/%02d/%04d", t->tm_mon + 1, t->tm_mday, t->tm_year + 1900);
}
// ------------------------------------------------
// NON-BLOCKING LCD MESSAGE
// ------------------------------------------------
void lcdOn();
void lcdOff();
void showMessage(String line1, String line2, unsigned long duration){
scrollActive = false; //Stop scrolling immediately
lcdOn(); // Turn LCD on immediately
lastActivityTime = millis(); // reset active timer
lcd.clear();
lcd.setCursor(0,0);
lcd.print(line1);
lcd.setCursor(0,1);
lcd.print(line2);
lcdLine1 = line1;
lcdLine2 = line2;
lcdDuration = duration;
lcdStartTime = millis();
lcdActive = true;
}
void updateLCD(){
if(lcdActive && millis() - lcdStartTime >= lcdDuration){
lcd.clear();
lcdActive = false;
}
}
// -------------------------------
// LCD SCROLLING ENGINE
// -------------------------------
void updateNameScroll() {
if (!scrollActive) return;
unsigned long now = millis();
if (now - lastScroll < scrollSpeed) return;
lastScroll = now;
const int windowWidth = 8; // right side of LCD (cols 8–15)
String window = "";
// Build the window using modulo indexing for perfect wrap-around
for (int i = 0; i < windowWidth; i++) {
int idx = (scrollPos + i) % scrollName.length();
window += scrollName[idx];
}
// Print the window
lcd.setCursor(8, 1);
lcd.print(window);
// Advance scroll position with wrap-around
scrollPos = (scrollPos + 1) % scrollName.length();
}
// -----------------------
// LCD POWER FUNCTION
// -----------------------
void lcdOff() {
if (lcdIsOn) {
lcd.noBacklight();
lcdIsOn = false;
}
}
void lcdOn() {
if (!lcdIsOn) {
lcd.backlight();
lcdIsOn = true;
}
}
void updateLCDPower() {
if (millis() - lastActivityTime >= lcdTimeout) {
lcdOff();
}
}
// -------------------------------
// NON-BLOCKING LED
// -------------------------------
void updateLED() {
pinMode(5, OUTPUT);
digitalWrite(5, LOW);
}
void errLeds() {
pinMode(5, OUTPUT);
digitalWrite(5, HIGH);
delay(100);
digitalWrite(5, LOW);
delay(100);
}
// ----------------------------------
// RELIABLE SEA-LEVEL-PRESSURE SOURCE
// ----------------------------------
float fetchSeaLevelPressure() {
WiFiClient client;
HTTPClient http;
String url = "http://api.open-meteo.com/v1/forecast?latitude=36.0&longitude=-86.5¤t=pressure_msl";
http.begin(client, url);
int httpCode = http.GET();
if (httpCode != 200) {
sysPrintln("Failed to fetch SLP");
return 1013.25; // Fallback
}
String payload = http.getString();
http.end();
// Parse JSON
StaticJsonDocument<512> doc;
DeserializationError err = deserializeJson(doc, payload);
if (err) {
sysPrintln("JSON parse error");
return 1013.25;
}
float slp = doc["current"]["pressure_msl"];
return slp; // in hPa
}
// --------------------------
// SEA-LEVEL PRESSURE CACHING
// --------------------------
float getSeaLevelPressure() {
unsigned long now = millis();
if (now - lastSLPFetch > 1800000) { // 30 minutes
float slp = fetchSeaLevelPressure();
if (slp > 800 && slp < 1100) {
cachedSLP = slp;
}
lastSLPFetch = now;
}
return cachedSLP;
}
// -----------------------
// ALTITUDE HELPER
// -----------------------
float computeAltitude(float pressure_hpa, float seaLevel_hpa) {
return 44330.0 * (1.0 - pow(pressure_hpa / seaLevel_hpa, 0.1903));
}
float smoothAltitude(float newAlt, float alpha = 0.1) {
if (isnan(smoothedAltitude)) {
smoothedAltitude = newAlt; // first reading
} else {
smoothedAltitude = (alpha * newAlt) + ((1 - alpha) * smoothedAltitude);
}
return smoothedAltitude;
}
// ----------------------
// DEW POINT HELPERS
// ----------------------
float computeDewPoint(float tempC, float humidity) {
const float a = 17.62;
const float b = 243.12; // °C
float gamma = (a * tempC) / (b + tempC) + log(humidity / 100.0);
return (b * gamma) / (a - gamma);
}
// -----------------------------
// BSEC w/Baselines
// -----------------------------
void loadBsecState() {
if (!SPIFFS.exists(BSEC_STATE_FILE)) {
sysPrintln("No BSEC baseline file found");
return;
}
File f = SPIFFS.open(BSEC_STATE_FILE, "r");
if (!f) {
sysPrintln("Failed to open BSEC baseline file");
return;
}
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
size_t size = f.read(state, sizeof(state));
f.close();
if (size == 0) {
sysPrintln("Empty BSEC baseline file");
return;
}
iaqSensor.setState(state);
sysPrintln("BSEC baseline restored");
}
void saveBsecState() {
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
iaqSensor.getState(state);
File f = SPIFFS.open(BSEC_STATE_FILE, "w");
if (!f) {
sysPrintln("Failed to open BSEC baseline file for write");
return;
}
f.write(state, BSEC_MAX_STATE_BLOB_SIZE);
f.close();
sysPrintln("BSEC baseline saved");
}
void checkIaqSensorStatus() {
if (iaqSensor.bsecStatus != BSEC_OK) {
if (iaqSensor.bsecStatus < BSEC_OK) {
output = "BSEC error code : " + String((int)iaqSensor.bsecStatus);
sysPrintln(output);
for (;;)
errLeds(); /* Halt in case of failure */
} else {
output = "BSEC warning code : " + String((int)iaqSensor.bsecStatus);
sysPrintln(output);
}
}
if (iaqSensor.bme68xStatus != BME68X_OK) {
if (iaqSensor.bme68xStatus < BME68X_OK) {
output = "BME68X error code : " + String((int)iaqSensor.bme68xStatus);
sysPrintln(output);
for (;;)
errLeds(); /* Halt in case of failure */
} else {
output = "BME68X warning code : " + String((int)iaqSensor.bme68xStatus);
sysPrintln(output);
}
}
}
void setupIaqSensor() {
iaqSensor.begin(BME68X_I2C_ADDR_HIGH, Wire);
output = "\nBSEC library version " +
String(iaqSensor.version.major) + "." +
String(iaqSensor.version.minor) + "." +
String(iaqSensor.version.major_bugfix) + "." +
String(iaqSensor.version.minor_bugfix);
sysPrintln(output);
checkIaqSensorStatus();
iaqSensor.updateSubscription(sensorList, 13, BSEC_SAMPLE_RATE_LP);
loadBsecState();
checkIaqSensorStatus();
}
// -------------------
// SENSOR STATE ENUM
// -------------------
enum SensorState {
SENSOR_WARMING,
SENSOR_LEARNING,
SENSOR_CALIBRATING,
SENSOR_READY
};
SensorState getSensorState() {
if (iaqSensor.stabStatus == 0) return SENSOR_WARMING;
if (iaqSensor.iaqAccuracy == 0) return SENSOR_LEARNING;
if (iaqSensor.iaqAccuracy > 0 && iaqSensor.iaqAccuracy < 3) return SENSOR_CALIBRATING;
return SENSOR_READY;
};
const char* sensorStateToString(SensorState s) {
switch (s) {
case SENSOR_WARMING: return "warming";
case SENSOR_LEARNING: return "learning";
case SENSOR_CALIBRATING: return "calibrating";
case SENSOR_READY: return "ready";
default: return "unknown";
}
}
// --------------------------------
// NON-BLOCKING SENSOR UPDATE
// --------------------------------
void sendSensorLog(
float tempCraw, float tempFraw,
float tempC, float tempF,
float humidityRaw, float humidity,
float pressure,
float dewPointC, float dewPointF,
float gasRes, float inAirQ, float static_iaq, float inAirQacc, float stab_status, float gasCal, float gas_percentage,
float slp, float rawAltitude, float altitude,
float co2_eq, float voc_eq,
const char* sensor_state
);
void sendRealTimeSensor(
float tempCraw, float tempFraw,
float tempC, float tempF,
float humidityRaw, float humidity,
float pressure,
float dewPointC, float dewPointF,
float gasRes, float inAirQ, float static_iaq, float inAirQacc, float stab_status, float gasCal, float gas_percentage,
float slp, float rawAltitude, float altitude,
float co2_eq, float voc_eq,
const char* sensor_state
);
void updateSensors() {
static unsigned long lastIAQ = 0;
static int lastLoggedMinute = -1;
static bool pendingReading = false;
static unsigned long readingStartTime = 0;
unsigned long nowMS = millis();
// Get current time
time_t now = time(nullptr);
struct tm *t = localtime(&now);
// --------------------------
// 1-MINUTE LIVE IAQ UPDATE (REAL-TIME Dashboard and BME680 baselines only)
// --------------------------
if (nowMS - lastIAQ >= 30000) {
lastIAQ = nowMS;
if (!iaqSensor.run()) {
checkIaqSensorStatus();
return;
}
//BSEC Mapping
float tempCraw = iaqSensor.rawTemperature;
float tempFraw = tempCraw * 9.0F / 5.0F + 32.0F;
float tempC = iaqSensor.temperature;
float tempF = tempC * 9.0F / 5.0F + 32.0F;
float humidityRaw = iaqSensor.rawHumidity;
float humidity = iaqSensor.humidity;
float pressure = iaqSensor.pressure / 100.0f; // Pa -> hPa
float dewPointC = computeDewPoint(tempC, humidity);
float dewPointF = dewPointC * 9.0F / 5.0F + 32.0F;
float gasRes = iaqSensor.gasResistance;
float inAirQ = iaqSensor.iaq;
float static_iaq = iaqSensor.staticIaq;
uint8_t inAirQacc = iaqSensor.iaqAccuracy;
uint8_t stab_status = iaqSensor.stabStatus;
uint8_t gasCal = iaqSensor.runInStatus;
float gas_percentage = iaqSensor.gasPercentage;
float slp = getSeaLevelPressure();
float rawAltitude = computeAltitude(pressure, slp);
float altitude = smoothAltitude(rawAltitude);
float co2_eq = iaqSensor.co2Equivalent;
float voc_eq = iaqSensor.breathVocEquivalent;
}
if (!offsetsApplied && millis() - sensorWarmup > 10UL * 60UL * 1000UL) {
tempOffsetC = -2.0f;
humidityOffset = 3.0f;
iaqSensor.setTemperatureOffset(tempOffsetC);
offsetsApplied = true;
sysPrintln("Temp/Humidity offsets applied");
}
if (millis() - lastBaselineSave > BASELINE_SAVE_INTERVAL && iaqSensor.iaqAccuracy == 3) {
saveBsecState();
lastBaselineSave = millis();
}
SensorState s = getSensorState();
const char* sensor_state = sensorStateToString(s);
// Send to REAL-TIME Dashboard and store for BME680 baseline accuracies
sendRealTimeSensor(
tempCraw, tempFraw,
tempC, tempF,
humidityRaw, humidity,
pressure,
dewPointC, dewPointF,
gasRes, inAirQ, static_iaq, inAirQacc, stab_status, gasCal, gas_percentage,
slp, rawAltitude, altitude,
co2_eq, voc_eq,
sensor_state
);
// ----------------------------
// 15-MINUTE LOGS (DB STORAGE)
// ----------------------------
// If we hit a quarter-hour and haven't scheduled a reading yet
if (!pendingReading && t->tm_min % 15 == 0 && t->tm_min != lastLoggedMinute) {
pendingReading = true;
readingStartTime = nowMS; // start 5-second timer
lastLoggedMinute = t->tm_min; // prevent retrigger
sysPrintln("\nQuarter-hour reached, scheduled sensor reading in 5 seconds...");
}
// If a reading is scheduled, check if 5 seconds have passed
if (pendingReading && nowMS - readingStartTime >= 5000) {
// Warm up guard
if (millis() - sensorWarmup < 1500) return;
pendingReading = false; // clear flag
if (!iaqSensor.run()) {
checkIaqSensorStatus();
return;
}
//BSEC Mapping
float tempCraw = iaqSensor.rawTemperature;
float tempFraw = tempCraw * 9.0F / 5.0F + 32.0F;
float tempC = iaqSensor.temperature;
float tempF = tempC * 9.0F / 5.0F + 32.0F;
float humidityRaw = iaqSensor.rawHumidity;
float humidity = iaqSensor.humidity;
float pressure = iaqSensor.pressure / 100.0f; // Pa -> hPa
float dewPointC = computeDewPoint(tempC, humidity);
float dewPointF = dewPointC * 9.0F / 5.0F + 32.0F;
float gasRes = iaqSensor.gasResistance;
float inAirQ = iaqSensor.iaq;
float static_iaq = iaqSensor.staticIaq;
uint8_t inAirQacc = iaqSensor.iaqAccuracy;
uint8_t stab_status = iaqSensor.stabStatus;
uint8_t gasCal = iaqSensor.runInStatus;
float gas_percentage = iaqSensor.gasPercentage;
float slp = getSeaLevelPressure();
float rawAltitude = computeAltitude(pressure, slp);
float altitude = smoothAltitude(rawAltitude);
float co2_eq = iaqSensor.co2Equivalent;
float voc_eq = iaqSensor.breathVocEquivalent;
}
sysPrintln(divider);
sysPrint("4.1 Reading successful at: ");
printLocalTime();
sysPrintln(divider);
Serial.print("Temperature (raw): ");
Serial.print(String(tempCraw)); Serial.print(" °C ");
Serial.print(String(tempFraw)); Serial.println(" °F");
Serial.print("Temperature (compensated): ");
Serial.print(String(tempC)); Serial.print(" °C ");
Serial.print(String(tempF)); Serial.println(" °F");
Serial.print("Humidity (raw): ");
Serial.print(String(humidityRaw));
Serial.println(" %");
Serial.print("Humidity (compensated): ");
Serial.print(String(humidity));
Serial.println(" %");
Serial.print("Pressure: ");
Serial.print(String(pressure));
Serial.println(" mbar");
Serial.print("Dew point: ");
Serial.print(String(dewPointC)); Serial.print(" °C ");
Serial.print(String(dewPointF)); Serial.println(" °F");
Serial.print("Gas resistance: ");
Serial.print(String(gasRes));
Serial.println(" ohms");
Serial.print("IAQ: ");
Serial.print(String(inAirQ));
Serial.println(" %");
Serial.print("Static IAQ: ");
Serial.print(String(static_iaq));
Serial.println(" %");
Serial.print("IAQ accuracy: ");
Serial.println(String(inAirQacc));
Serial.print("Stabilization status: ");
Serial.println(String(stab_status));
Serial.print("Gas Calibration Stage: ");
Serial.println(String(gasCal));
Serial.print("Gas stabilization: ");
Serial.println(String(gas_percentage));
Serial.print("Sea Level Pressure: ");
Serial.print(String(slp));
Serial.println(" hPa");
Serial.print("Raw Altitude: ");
Serial.print(String(rawAltitude));
Serial.println(" m");
Serial.print("Adjusted Altitude: ");
Serial.print(String(altitude));
Serial.println(" m");
Serial.print("CO2: ");
Serial.print(String(co2_eq));
Serial.println(" %");
Serial.print("VOC: ");
Serial.print(String(voc_eq));
Serial.println(" %");
Serial.println(divider);
// ---- NEW -----
// Send 15-min log for historicals and send REAL-TIME Dashboard update
// Uncomment when ready
// --------------
sendSensorLog(
tempCraw, tempFraw,
tempC, tempF,
humidityRaw, humidity,
pressure,
dewPointC, dewPointF,
gasRes, inAirQ, static_iaq, inAirQacc, stab_status, gasCal, gas_percentage,
slp, rawAltitude, altitude,
co2_eq, voc_eq,
sensor_state
);
}
// --------------------------------
// NON-BLOCKING DOOR CONTROL
// --------------------------------
void updateServo(){
unsigned long now = millis();
if(now - lastServoMove < servoStepInterval) return;
lastServoMove = now;
// If close enough, snap to target and stop
if(abs(doorCurrent - doorTarget) <= servoStepSize){
doorCurrent = doorTarget;
servo_18.write(doorCurrent);
return;
}
// Otherwise move toward target
if(doorCurrent < doorTarget){
doorCurrent += servoStepSize;
}else if(doorCurrent > doorTarget){
doorCurrent -= servoStepSize;
}
servo_18.write(doorCurrent);
}
void openDoor(){
doorTarget = door_open;
doorOpenState = true;
doorOpenTime = millis();
doorJustClosedMessageShown = false; //reset for next cycle
}
void closeDoor(){
doorTarget = door_close;
}
void updateDoor(){
if(doorOpenState && millis() - doorOpenTime >= doorHoldDuration){
//Start smooth closing
closeDoor();
doorOpenState = false;
// Show "Door Closed"
if(!doorJustClosedMessageShown){
showMessage("Door","Closed", 1500);
doorJustClosedMessageShown = true;
}
}
}
// -----------------------------
// SERVER AUTHORIZATION CHECK
// -----------------------------
bool checkAuthorization(String uid, String &nameOut, bool &allowedOut){
if (WiFi.status() != WL_CONNECTED){
sysPrintln("Wifi not connected");
return false;
}
HTTPClient http;
http.begin("http://SOME.IP.ADDRESS/auth");
http.addHeader("Content-Type", "application/json");
String body = "{\"uid\":\"" + uid + "\"}";
int code = http.POST(body);
if(code <= 0){
sysPrintln("\nHTTP POST failed");
http.end();
return false;
}
String payload = http.getString();
http.end();
// Remove pretty-print newlines
payload.replace("\n", "");
payload.replace("\r", "");
sysPrintln("AUTH RESPONSE: " + payload);
DynamicJsonDocument doc(256);
DeserializationError err = deserializeJson(doc, payload);
if (err){
sysPrintln("JSON parse error");
return false;
}
bool known = doc["known"];
allowedOut = doc["allowed"];
nameOut = doc["name"].as<String>();
return known;
}
// -------------------------
// SERVER LOGGING HELPERS
// -------------------------
// ** RFID READER LOGS **
unsigned long lastRFLog = 0;
const unsigned long RF_LOG_INTERVAL = 2000; /// 2 seconds
void sendLog(String uid, String name, String event, String device){
if (millis() - lastRFLog < RF_LOG_INTERVAL) return;
lastRFLog = millis();
if(WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
http.begin("http://SOME.IP.ADDRESS/logs");
http.addHeader("Content-Type", "application/json");
String body = "{\"uid\":\"" + uid + "\",\"name\":\"" + name + "\",\"event\":\"" + event + "\",\"device\":\"" + device + "\"}";
http.POST(body);
http.end();
}
// ** DB-SENSOR LOGS **
void sendSensorLog(
float tempCraw, float tempFraw,
float tempC, float tempF,
float humidityRaw, float humidity,
float pressure,
float dewPointC, float dewPointF,
float gasRes, float inAirQ, float static_iaq, float inAirQacc, float stab_status, float gasCal, float gas_percentage,
float slp, float rawAltitude, float altitude,
float co2_eq, float voc_eq,
const char* sensor_state
) {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
http.begin("http://SOME.IP.ADDRESS/sensor");
http.addHeader("Content-Type", "application/json");
String body = "{"
"\"device\":\"ACEBOTT\","
"\"temp_c_raw\":" + String(tempCraw) + ","
"\"temp_f_raw\":" + String(tempFraw) + ","
"\"temp_c\":" + String(tempC) + ","
"\"temp_f\":" + String(tempF) + ","
"\"humidity_raw\":" + String(humidityRaw) + ","
"\"humidity\":" + String(humidity) + ","
"\"pressure\":" + String(pressure) + ","
"\"dew_point_c\":" + String(dewPointC) + ","
"\"dew_point_f\":" + String(dewPointF) + ","
"\"gas_resistance\":" + String(gasRes) + ","
"\"iaq\":" + String(inAirQ) + ","
"\"static_iaq\":" + String(static_iaq) + ","
"\"iaq_accuracy\":" + String(inAirQacc) + ","
"\"stab_status\":" + String(stab_status) + ","
"\"gas_calibration\":" + String(gasCal) + ","
"\"gas_percentage\":" + String(gas_percentage) + ","
"\"sea_level_pressure\":" + String(slp) + ","
"\"altitude_raw\":" + String(rawAltitude) + ","
"\"altitude\":" + String(altitude) + ","
"\"co2_eq\":" + String(co2_eq) + ","
"\"voc_eq\":" + String(voc_eq) + ","
"\"sensor_state\":\"" + String(sensor_state) + "\""
"}";
http.POST(body);
http.end();
}
// --------------------------
// REAL-TIME Dashboard
// -- ACTIVE ------------
void sendRealTimeSensor(
float tempCraw, float tempFraw,
float tempC, float tempF,
float humidityRaw, float humidity,
float pressure,
float dewPointC, float dewPointF,
float gasRes, float inAirQ, float static_iaq, float inAirQacc, float stab_status, float gasCal, float gas_percentage,
float slp, float rawAltitude, float altitude,
float co2_eq, float voc_eq,
const char* sensor_state
) {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
http.begin("http://SOME.IP.ADDRESS/sensor-live");
http.addHeader("Content-Type", "applicaton/json");
String body = "{"
"\"device\":\"ACEBOTT\","
"\"temp_c_raw\":" + String(tempCraw) + ","
"\"temp_f_raw\":" + String(tempFraw) + ","
"\"temp_c\":" + String(tempC) + ","
"\"temp_f\":" + String(tempF) + ","
"\"humidity_raw\":" + String(humidityRaw) + ","
"\"humidity\":" + String(humidity) + ","
"\"pressure\":" + String(pressure) + ","
"\"dew_point_c\":" + String(dewPointC) + ","
"\"dew_point_f\":" + String(dewPointF) + ","
"\"gas_resistance\":" + String(gasRes) + ","
"\"iaq\":" + String(inAirQ) + ","
"\"static_iaq\":" + String(static_iaq) + ","
"\"iaq_accuracy\":" + String(inAirQacc) + ","
"\"stab_status\":" + String(stab_status) + ","
"\"gas_calibration_stage\":" + String(gasCal) + ","
"\"gas_percentage\":" + String(gas_percentage) + ","
"\"sea_level_pressure\":" + String(slp) + ","
"\"altitude_raw\":" + String(rawAltitude) + ","
"\"altitude\":" + String(altitude) + ","
"\"co2_eq\":" + String(co2_eq) + ","
"\"voc_eq\":" + String(voc_eq) + ","
"\"sensor_state\":\"" + String(sensor_state) + "\""
"}";
http.POST(body);
http.end();
}
/*
// ------------------
// SYSTEM STATUSES
// ---NOT ACTIVE-----
struct SystemStatus {
bool i2c = false;
bool lcd = false;
bool bme = false;
bool wifi = false;
bool ntp = false;
bool rfid = false;
bool servo = false;
};
SystemStatus sys;
bool deviceExistsOnI2C(byte addr) {
Wire.beginTransmission(addr);
return (Wire.endTransmission() == 0);
}
*/
// ------------------
// MAIN SETUP
// ------------------
void setup() {
xTaskCreatePinnedToCore(
logTask, // Function
"LogTask", // Name
4096, // Stack Size
NULL, // Params
1, // Priority
&logTaskHandle, // Handle
1 // Run on Core 1
);
sensorWarmup = millis();
Serial.begin(115200); //initialize the serial port, see the card number used to
sysPrintln("\nACEBOTT BOOTING...");
sysPrint("Initlializing Bus...");
Wire.begin(); //initialize I2C serial port
sysPrintln("Scanning I2C bus...");
byte error, address;
for(address=1; address<127; address++){
Wire.beginTransmission(address);
error = Wire.endTransmission();
if(error == 0){
sysPrint("Device found at 0x");
sysPrintf("%02X", address);
sysPrint(" -> ");
sysPrint(getDeviceName(address));
sysPrintln(" is now online.");
}
}
sysPrintln("I2C Bus scan complete");
sysPrintln("I2c in Ready state");
sysPrintln("Initializing LCD...");
lcd.init(); //initialize LCD
lcd.backlight(); //turn on LCD backlight
sysPrintln("LCD in Ready state");
if (!SPIFFS.begin(true)) {
sysPrintln("SPIFFS mount failed, no BSEC baseline established");
} else {
sysPrintln("SPIFFS mounted. BSEC baseline established");
}
sysPrintln("Initializing IAQ sensors");
setupIaqSensor();
sysPrint("BSEC status: ");
sysPrintln(String((int)iaqSensor.bsecStatus));
sysPrint("BME680X status: ");
sysPrintln(String((int)iaqSensor.bme68xStatus));
sysPrintln("IAQ sensors in ready state");
updateLCD();
sysPrint("WiFi Loading");
lcd.clear();
lcd.setCursor(0,0);
lcd.print("System Boot");
lcd.setCursor(0,1);
lcd.print("WiFi Loading....");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); //start the wifi
while (WiFi.status() != WL_CONNECTED) { // check if wifi is connected
delay(500);
sysPrint(".");
}
sysPrintln("\nWiFi connected");
sysPrintln("SSID: " + WiFi.SSID());
sysPrintln("IP: " + WiFi.localIP().toString());
sysPrintln("Gateway: " + WiFi.gatewayIP().toString());
sysPrintln("Subnet: " + WiFi.subnetMask().toString());
sysPrintln("DNS: " + WiFi.dnsIP().toString());
sysPrintln("MAC: " + WiFi.macAddress());
sysPrintln("BSSID: " + WiFi.BSSIDstr());
sysPrintln("RSSI: " + String(WiFi.RSSI()));
sysPrintln("Channel: " + String(WiFi.channel()));
lcd.clear();
configTime(-5 * 3600, 0, "pool.ntp.org", "time.nist.gov");
sysPrint("Waiting for NTP servers");
time_t now = time(nullptr);
while (now < 100000) { // Wait until time is valid
delay(500);
sysPrint(".");
now = time(nullptr);
}
sysPrintln("\nNTP server connections established");
printLocalTime();
sysPrintln("Initializing RFID Reader...");
mfrc522.PCD_Init(); // Initialize RFID module
sysPrintln("RFID Reader in Ready state");
sysPrintln("Initializing Servos...");
servo_18.attach(18, 500, 2500); // Initialize servo
servo_18.write(door_close); // Let the servo turn to the initial position
delay(500); // Wait for the end of the servo rotation
sysPrintln("Servos in Ready state");
sysPrintln("All systems Ready");
sysPrintln("SYSTEM BOOT COMPLETE");
}
//--------------
// MAIN LOOP
// -------------
void loop() {
//Always update NON_BLOCKING FUNCTIONS
//processLogQueue();
updateServo();
updateLCD();
updateDoor();
updateNameScroll();
updateSensors();
updateLED();
updateLCDPower();
// ----- NON-BLOCKING RFID SCANNING -----
//Check if card is seen this loop
bool rawPresent = mfrc522.PICC_IsNewCardPresent();
//Update last seen timestamp if card seen
if (rawPresent){
lastCardSeen = millis();
}
//Stable card presense
bool cardPresent = (millis() - lastCardSeen < cardPresentTimeout);
//Keep door open if card is present
if(cardPresent){
doorOpenTime = millis();
}
//Check for card removed
if (!cardPresent) {
lastUID = "";
cardAlreadyRead = false;
}
else {
// Card is present
if (!cardAlreadyRead && mfrc522.PICC_ReadCardSerial()) {
// Mark as read so we don't read again until removed
cardAlreadyRead = true;
// Cooldown
if (millis() - lastScanTime >= scanCooldown) {
lastScanTime = millis();
// Build UID
rfid_str = "";
for (byte i = 0; i < mfrc522.uid.size; i++) {
rfid_str += String(mfrc522.uid.uidByte[i], HEX);
}
rfid_str.toLowerCase();
// Ignore repeated UID while held
if (rfid_str != lastUID) {
lastUID = rfid_str;
sysPrint("\nScanned RF-Key: ");
sysPrintln(rfid_str);
// Reset cached auth for new UID
lastAuthKnown = false;
lastAuthAllowed = false;
lastAuthName = "";
// First-time authorization
String name;
bool allowed;
bool known = checkAuthorization(rfid_str, name, allowed);
lastAuthKnown = known;
lastAuthAllowed = allowed;
lastAuthName = name;
// --- UNKNOWN KEY ---
if(!lastAuthKnown){
sysPrintln("Access Request Denied: UNKNOWN KEY");
sendLog(rfid_str, "Unknown", "Denied - Unknown Key", "ACEBOTT");
deniedCardPresent = true;
deniedDisplayName = "Unknown";
showMessage("Access Denied", "Unknown", 1500);
return;
}
// --- KNOWN NOT ALLOWD KEY ---
if(!lastAuthAllowed){
sysPrintln("Access Denied: " + lastAuthName);
sendLog(rfid_str, lastAuthName, "Denied - Not Allowed", "ACEBOTT");
deniedCardPresent = true;
deniedDisplayName = lastAuthName;
showMessage("Access Denied", lastAuthName, 1500);
return;
}
// --- KNOWN ALLOWED KEY GRANTED ---
lcdOn();
lastActivityTime = millis();
sysPrintln(" Access Granted: " + lastAuthName);
sendLog(rfid_str, lastAuthName, "Access Granted", "ACEBOTT");
//ACCESS GRANTED LCD SCROLL
scrollActive = false;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Access Granted");
lcd.setCursor(0,1);
lcd.print("Welcome:");
if(lastAuthName.length() <= 8){
lcd.setCursor(8,1);
lcd.print(lastAuthName);
}
else{
String gap = " ";
scrollName = lastAuthName + gap;
scrollPos = 0;
scrollActive = true;
}
lcdActive = false;
openDoor();
}
}
}
}
}