#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <WiFi.h>
#include <FirebaseESP32.h>
#include <ArduinoJson.h>
// Pin definitions
const int BTN_PINS[] = {26, 25, 33, 32}; // Green, Yellow, Red, Blue
const int RELAY_PINS[] = {13, 12, 14, 27}; // Green, Yellow, Red, Blue
const int NUM_COLORS = 4;
// LCD setup
LiquidCrystal_I2C lcd(0x27, 16, 2);
// RTC setup
RTC_DS1307 rtc;
// Variables
const char* COLOR_NAMES[] = {"GREEN", "YELLOW", "RED", "BLUE"};
int currentColor = 0; // Start with green (index 0)
unsigned long stopwatchStart = 0;
unsigned long lastUpdate = 0;
unsigned long lastSyncTime = 0;
// Variables to store previous values for comparison
char prevStopwatchStr[9] = "";
char prevBottomLine[17] = "";
// Unique device ID
const char* DEVICE_ID = "Anduino_000";
// WiFi credentials
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASSWORD = "";
// Firebase credentials
#define FIREBASE_HOST "https://anduin0-default-rtdb.asia-southeast1.firebasedatabase.app/"
#define FIREBASE_AUTH "4uafIesosxANhjkIALFmz1doNjgObCVKd4OVr449"
FirebaseData firebaseData;
FirebaseJson json;
// Device attributes
String deviceName = "Engine Rebuild";
String status = "online";
String ipAddress;
int firmwareVersion = 1;
void setup() {
Serial.begin(115200);
// WiFi setup
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
ipAddress = WiFi.localIP().toString();
// Firebase setup
Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
Firebase.reconnectWiFi(true);
for (int i = 0; i < NUM_COLORS; i++) {
pinMode(BTN_PINS[i], INPUT_PULLUP);
pinMode(RELAY_PINS[i], OUTPUT);
digitalWrite(RELAY_PINS[i], LOW);
}
Wire.begin();
lcd.init();
lcd.backlight();
if (!rtc.begin()) {
lcd.print("RTC failed");
while (1);
}
// Set initial color to green
setColor(0);
updateDisplay();
updateFirebase();
}
void loop() {
if (json.get(jsonData, "color")) {
String newColor = jsonData.stringValue;
for (int i = 0; i < NUM_COLORS; i++) {
if (newColor == COLOR_NAMES[i]) {
setColor(i);
break;
}
}
}
if (json.get(jsonData, "name")) {
deviceName = jsonData.stringValue;
}
if (json.get(jsonData, "status")) {
status = jsonData.stringValue;
}
}
}
for (int i = 0; i < NUM_COLORS; i++) {
if (digitalRead(BTN_PINS[i]) == LOW) {
delay(50); // Debounce
if (digitalRead(BTN_PINS[i]) == LOW) {
setColor(i);
updateFirebase();
while (digitalRead(BTN_PINS[i]) == LOW); // Wait for release
}
}
}
if (millis() - lastUpdate >= 1000) {
updateDisplay();
lastUpdate = millis();
}
// Sync with Firebase every minute
if (millis() - lastSyncTime >= 60000) {
updateFirebase();
lastSyncTime = millis();
}
}
void setColor(int color) {
digitalWrite(RELAY_PINS[currentColor], LOW);
currentColor = color;
digitalWrite(RELAY_PINS[currentColor], HIGH);
stopwatchStart = millis();
lastSyncTime = millis(); // Reset sync timer when color changes
updateDisplay();
updateFirebase();
}
void updateDisplay() {
// Stopwatch
unsigned long elapsed = (millis() - stopwatchStart) / 1000;
int hours = elapsed / 3600;
int minutes = (elapsed % 3600) / 60;
int seconds = elapsed % 60;
char stopwatchStr[9];
sprintf(stopwatchStr, "%02d:%02d:%02d", hours, minutes, seconds);
// Color and current time
DateTime now = rtc.now();
// Format bottom line with color on the left, --- in the middle, and time on the right
char bottomLine[17];
snprintf(bottomLine, sizeof(bottomLine), "%-6s --- %02d:%02d", COLOR_NAMES[currentColor], now.hour(), now.minute());
// Update the LCD only if the content has changed
if (strcmp(stopwatchStr, prevStopwatchStr) != 0) {
lcd.setCursor((16 - strlen(stopwatchStr)) / 2, 0);
lcd.print(stopwatchStr);
strcpy(prevStopwatchStr, stopwatchStr);
}
if (strcmp(bottomLine, prevBottomLine) != 0) {
lcd.setCursor(0, 1);
lcd.print(bottomLine);
strcpy(prevBottomLine, bottomLine);
}
}
void updateFirebase() {
unsigned long elapsedTime = (millis() - stopwatchStart) / 1000;
DateTime now = rtc.now();
json.clear();
json.set("name", deviceName);
json.set("color", COLOR_NAMES[currentColor]);
json.set("status", status);
json.set("stopwatch_start", stopwatchStart);
json.set("elapsed_time", elapsedTime);
json.set("device_time", now.unixtime());
json.set("last_update", now.unixtime());
json.set("ip_address", ipAddress);
json.set("firmware_version", firmwareVersion);
String devicePath = "devices/" + String(DEVICE_ID);
if (Firebase.updateNode(firebaseData, devicePath, json)) {
Serial.println("Firebase update successful");
} else {
Serial.println("Firebase update failed: " + firebaseData.errorReason());
}
// Update history
String date = String(now.year()) + "-" + String(now.month()) + "-" + String(now.day());
String historyPath = "history/" + String(DEVICE_ID) + "/" + date + "/color_changes";
FirebaseJson colorChange;
colorChange.set("timestamp", now.unixtime());
colorChange.set("color", COLOR_NAMES[currentColor]);
colorChange.set("elapsed_time", elapsedTime);
if (Firebase.pushJSON(firebaseData, historyPath, colorChange)) {
Serial.println("History update successful");
} else {
Serial.println("History update failed: " + firebaseData.errorReason());
}
// Update occurrences
String occurrencesPath = "history/" + String(DEVICE_ID) + "/" + date + "/occurrences/" + COLOR_NAMES[currentColor];
if (Firebase.getInt(firebaseData, occurrencesPath)) {
int occurrences = firebaseData.intData();
if (Firebase.setInt(firebaseData, occurrencesPath, occurrences + 1)) {
Serial.println("Occurrences update successful");
} else {
Serial.println("Occurrences update failed: " + firebaseData.errorReason());
}
} else {
if (Firebase.setInt(firebaseData, occurrencesPath, 1)) {
Serial.println("Occurrences initialized");
} else {
Serial.println("Occurrences initialization failed: " + firebaseData.errorReason());
}
}
// Update total_runtime
String totalRuntimePath = "history/" + String(DEVICE_ID) + "/" + date + "/total_runtime";
if (Firebase.getInt(firebaseData, totalRuntimePath)) {
int totalRuntime = firebaseData.intData();
if (Firebase.setInt(firebaseData, totalRuntimePath, totalRuntime + 60)) { // Add 60 seconds (1 minute)
Serial.println("Total runtime update successful");
} else {
Serial.println("Total runtime update failed: " + firebaseData.errorReason());
}
} else {
if (Firebase.setInt(firebaseData, totalRuntimePath, 60)) {
Serial.println("Total runtime initialized");
} else {
Serial.println("Total runtime initialization failed: " + firebaseData.errorReason());
}
}
}