#include <WiFi.h>
#include <BLEDevice.h>
#include <BLEScan.h>
#include <time.h>
#include <Firebase_ESP_Client.h>
#include "addons/TokenHelper.h" // This file already defines tokenStatusCallback.
#include "addons/RTDBHelper.h"
#include <vector>
#include <map>
using namespace std;
#define WIFI_SSID "*****"
#define WIFI_PASSWORD "******"
// cred
#define API_KEY "*********"
#define DATABASE_URL "********"
// RSSI
#define RSSI_THRESHOLD -90
// ntp settings
const char* ntpServer = "time.google.com";
const long gmtOffset_sec = 19800;
const int daylightOffset_sec = 0;
// ble objects
BLEScan* pBLEScan;
// rtc var
time_t rtcStartTime = 0; // Changed to time_t (64-bit)
//unsigned long rtcStartTime = 0;
unsigned long systemStartMillis = 0;
bool rtcSynced = false;
// tag data struct
struct TagData {
String mac;
String inTime;
String outTime;
unsigned long lastSeen;
};
vector<TagData> tagBuffer;
//firebase objects
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;
// Use std::map explicitly to avoid conflict with Arduino's built-in map() function.
std::map<String, int> sessionSequence;
bool connectToWiFi();
bool initializeRTC();
String getFormattedRTCTime();
String getFormattedDate();
int getCurrentHour();
String getFirebasePath(const String &mac, int sequence);
void pushTagData(const TagData &tag);
void pushAllTagData();
void checkForTagTimeouts();
// wifi con
bool connectToWiFi() {
// If already connected, nothing to do
if (WiFi.status() == WL_CONNECTED) return true;
Serial.println("Attempting to connect to WiFi...");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) {
delay(500);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nConnected to Wifi");
return true;
} else {
Serial.println("\nFailed to connect to Wifi");
return false;
}
}
// rtc init
bool initializeRTC() {
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
struct tm timeinfo;
for (int i = 0; i < 5; i++) {
if (getLocalTime(&timeinfo)) {
rtcStartTime = time(nullptr); // Store as 64-bit time_t
systemStartMillis = millis();
rtcSynced = true;
Serial.print("RTC Synced: ");
Serial.println(ctime(&rtcStartTime)); // Correct print
return true;
}
Serial.println("Retrying RTC sync");
delay(2000);
}
rtcSynced = false;
Serial.println("Failed to sync RTC.");
return false;
}
String getFormattedRTCTime() {
struct tm timeinfo;
time_t now = rtcStartTime + (millis() - systemStartMillis) / 1000;
if (localtime_r(&now, &timeinfo)) {
char timeString[9];
strftime(timeString, sizeof(timeString), "%H:%M:%S", &timeinfo);
return String(timeString);
}
return "00:00:00";
}
String getFormattedDate() {
time_t now = time(nullptr);
struct tm timeinfo;
if (localtime_r(&now, &timeinfo)) {
char dateStr[11]; // "YYYY-MM-DD"
strftime(dateStr, sizeof(dateStr), "%Y-%m-%d", &timeinfo);
return String(dateStr);
}
return "0000-00-00";
}
int getCurrentHour() {
time_t now = time(nullptr);
struct tm timeinfo;
if (localtime_r(&now, &timeinfo)) {
return timeinfo.tm_hour;
}
return 0;
}
String getFirebasePath(const String &mac, int sequence) {
String date = getFormattedDate();
int hour = getCurrentHour();
String period = (hour < 10) ? "morning" : "afternoon_evening";
String path = "/tags/" + mac + "/" + date + "/" + period + "/" + String(sequence);
return path;
}
//push data firebase
void pushTagData(const TagData &tag) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected. Data push skipped for tag: " + tag.mac);
return;
}
// Ensure Firebase is authenticated
if (!Firebase.ready()) {
Serial.println("Firebase not ready. Trying to refresh token...");
Firebase.refreshToken(&config);
delay(1000); // Give some time to refresh
if (!Firebase.ready()) {
Serial.println("Firebase still not ready. Skipping push.");
return;
}
}
FirebaseJson json;
json.set("mac", tag.mac);
json.set("inTime", tag.inTime);
json.set("outTime", tag.outTime);
json.set("lastSeen", tag.lastSeen);
String key = tag.mac + "_" + getFormattedDate();
int seq = 1;
if (sessionSequence.find(key) != sessionSequence.end()) {
seq = sessionSequence[key] + 1;
}
sessionSequence[key] = seq;
String path = getFirebasePath(tag.mac, seq);
// Clear FirebaseData object before each use
fbdo.clear();
if (Firebase.RTDB.setJSON(&fbdo, path.c_str(), &json)) {
Serial.println("Firebase push success for tag: " + tag.mac + " at path: " + path);
} else {
Serial.println("Firebase push failed for tag " + tag.mac + ": " + fbdo.errorReason());
// If error is due to authentication, refresh token and retry once
if (fbdo.errorReason().indexOf("token is not ready") >= 0) {
Serial.println("Refreshing Firebase token...");
Firebase.refreshToken(&config);
delay(1000);
if (Firebase.RTDB.setJSON(&fbdo, path.c_str(), &json)) {
Serial.println("Firebase push retry successful for tag: " + tag.mac);
} else {
Serial.println("Firebase push failed again: " + fbdo.errorReason());
}
}
}
}
// Iterate over tagBuffer, finalize each session and push the data.
void pushAllTagData() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Wifi not connected. Cannot push.");
return;
}
for (auto &tag : tagBuffer) {
if (tag.outTime == "") {
tag.outTime = getFormattedRTCTime();
Serial.println("Setting outTime for tag " + tag.mac + " as " + tag.outTime);
}
pushTagData(tag);
}
tagBuffer.clear();
}
void checkForTagTimeouts() {
unsigned long currentMillis = millis();
for (auto &tag : tagBuffer) {
if (tag.outTime == "" && (currentMillis - tag.lastSeen) > 60000) { // 60 sec timeout
tag.outTime = getFormattedRTCTime();
Serial.println("Tag timeout: " + tag.mac + ", OutTime: " + tag.outTime);
}
}
}
// BLE Advertised Device Callback
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
if (!rtcSynced) {
Serial.println("Skipping ble scan due to rtc sync failure.");
return;
}
int rssi = advertisedDevice.getRSSI();
String mac = advertisedDevice.getAddress().toString().c_str();
String name = advertisedDevice.getName().c_str();
// Filter for devices whose name starts with "ZT" and meet the RSSI threshold.
if (name.startsWith("ZT") && rssi >= RSSI_THRESHOLD) {
bool activeSessionFound = false;
for (auto &tag : tagBuffer) {
if (tag.mac == mac && tag.outTime == "") { // active session exists
tag.lastSeen = millis();
activeSessionFound = true;
break;
}
}
if (!activeSessionFound) {
TagData newTag = { mac, getFormattedRTCTime(), "", millis() };
tagBuffer.push_back(newTag);
Serial.println("New session started for tag: " + mac + ", InTime: " + newTag.inTime);
}
}
}
};
void setup() {
Serial.begin(115200);
connectToWiFi();
initializeRTC(); // Sync RTC once
config.api_key = API_KEY;
config.database_url = DATABASE_URL;
auth.user.email = "******";
auth.user.password = "*******";
Firebase.reconnectWiFi(true);
Firebase.begin(&config, &auth);
// Wait for Firebase connection
Serial.print("Connecting to Firebase");
unsigned long start = millis();
while (!Firebase.ready() && millis() - start < 15000) {
Serial.print(".");
delay(500);
}
if (Firebase.ready()) {
Serial.println("\nFirebase ready!");
} else {
Serial.println("\nFailed to connect to Firebase.");
}
BLEDevice::init("");
pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected. Attempting to reconnect...");
connectToWiFi();
if (WiFi.status() == WL_CONNECTED) {
// Sync RTC only if it wasn't synced before
if (!rtcSynced) {
initializeRTC();
}
}
}
if (WiFi.status() == WL_CONNECTED) {
// Check Firebase connection before pushing data
if (!Firebase.ready()) {
Serial.println("Firebase not ready, reconnecting");
Firebase.begin(&config, &auth);
delay(1000);
}
if (Firebase.ready()) {
Serial.println("wifi conn. push data to firebase.");
pushAllTagData();
} else {
Serial.println("firebase conn failed.");
}
delay(10000);
} else {
Serial.println("wifi not connected. Scanning ble");
pBLEScan->start(30, false);
checkForTagTimeouts();
delay(5000);
}
}
Use the stack trace decoder to find out more about what’s going on and triggered the issue
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.