/*
* Project Name: Sentinel
* Designed For: ESP32 S3 BOX 3
*
*
* License: GPL3+
* This project is licensed under the GNU General Public License v3.0 or later.
* You are free to use, modify, and distribute this software under the terms
* of the GPL, as long as you preserve the original license and credit the original
* author. For more details, see <https://www.gnu.org/licenses/gpl-3.0.en.html>.
*
* Copyright (C) 2026 Ameya Angadi
*
* Code Created And Maintained By: Ameya Angadi
* Last Modified On: March 30, 2026
* Version: 1.0.0
*
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <time.h>
#include <Preferences.h>
#include <esp_display_panel.hpp>
#include <lvgl.h>
#include <ui.h>
#include "lvgl_v8_port.h"
using namespace esp_panel::drivers;
using namespace esp_panel::board;
// --- CONFIGURATION ---
const char* ssid = "Galaxy A14 5G 5C4D";
const char* password = "randompassword4321567890";
#define RXD1 40
#define TXD1 41
WebServer server(80);
Preferences prefs;
Board *board = nullptr;
// Global Data
float g_temp = 0, g_hum = 0, g_pres = 0;
float g_x = 0, g_y = 0, g_z = 0, g_vibe = 0;
String g_status = "WAITING";
String g_last_maint = "01/01/2026";
String g_next_maint = "01/07/2026";
int currentBrightness = 100;
unsigned long lastWifiCheck = 0;
// Web Server Background Color Logic (Vibrant Shades)
void handleRoot() {
String bg = "#1a1a1a"; // Dark Gray (default)
if (g_status == "DANGER") bg = "#ff4b2b"; // Vibrant Safety Red
else if (g_status == "CAUTION") bg = "#ff9800"; // Vibrant Safety Orange
String html = "<html><head><style>";
html += "body { font-family: 'Segoe UI', sans-serif; background: " + bg + "; color: white; padding: 25px; margin: 0; transition: background 0.4s; }";
html += ".card { background: rgba(0,0,0,0.75); padding: 30px; border-radius: 20px; border: 1px solid rgba(255,255,255,0.2); max-width: 450px; margin: auto; box-shadow: 0 10px 30px rgba(0,0,0,0.5); }";
html += "h2 { margin: 0; text-transform: uppercase; letter-spacing: 2px; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); }";
html += ".metric { margin: 18px 0; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 10px; }";
html += ".val { font-weight: bold; font-size: 1.6em; color: white; }";
html += ".label { color: #bbb; display: block; font-size: 0.85em; text-transform: uppercase; margin-bottom: 5px; }";
html += "</style></head><body><div class='card'>";
html += "<h2>SYSTEM " + g_status + "</h2><br>";
html += "<div class='metric'><span class='label'>Vibration Intensity</span><span class='val'>" + String(g_vibe) + " G</span></div>";
html += "<div class='metric'><span class='label'>Temperature</span><span class='val'>" + String(g_temp) + " C</span></div>";
html += "<div>" + String(g_hum) + "% Humidity | " + String(g_pres) + " hPa</div><br>";
html += "<div style='font-size: 0.8em; color: #aaa;'>Service: " + g_last_maint + " | Due: " + g_next_maint + "</div>";
html += "</div></body></html>";
server.send(200, "text/html", html);
}
// --- EVENT HANDLERS ---
void onBrightnessChange(lv_event_t * e) {
lv_obj_t * slider = lv_event_get_target(e);
int val = lv_slider_get_value(slider);
if (val < 10) val = 10;
currentBrightness = val;
board->getBacklight()->setBrightness(currentBrightness);
}
void updateSettingsScreenUI() {
if (millis() - lastWifiCheck > 2000) {
lastWifiCheck = millis();
String ssidStr = WiFi.SSID();
if(ssidStr.isEmpty()) ssidStr = "Searching...";
lv_label_set_text(ui_LabelWifiSSID, ("SSID: " + ssidStr).c_str());
if (WiFi.status() == WL_CONNECTED) {
lv_label_set_text(ui_LabelWifiStatus, "Status: Connected");
lv_obj_set_style_text_color(ui_LabelWifiStatus, lv_color_hex(0x00FF00), 0);
} else {
lv_label_set_text(ui_LabelWifiStatus, "Status: Disconnected");
lv_obj_set_style_text_color(ui_LabelWifiStatus, lv_color_hex(0xFF0000), 0);
}
}
}
void onMaintBtnClicked(lv_event_t * e) {
struct tm info;
if (getLocalTime(&info)) {
char buf[15];
strftime(buf, sizeof(buf), "%d/%m/%Y", &info);
g_last_maint = String(buf);
info.tm_mon += 6;
mktime(&info);
strftime(buf, sizeof(buf), "%d/%m/%Y", &info);
g_next_maint = String(buf);
prefs.begin("maint", false);
prefs.putString("last", g_last_maint);
prefs.putString("next", g_next_maint);
prefs.end();
lv_label_set_text(ui_LabelLastMaintainaceDate, g_last_maint.c_str());
lv_label_set_text(ui_LabelNextMaintDate, g_next_maint.c_str());
}
}
void setup() {
Serial.begin(115200);
Serial1.begin(115200, SERIAL_8N1, RXD1, TXD1);
Serial1.setTimeout(50);
board = new Board();
board->init();
assert(board->begin());
lvgl_port_init(board->getLCD(), board->getTouch());
lvgl_port_lock(-1);
ui_init();
if(ui_ButtonUpdateMaint) lv_obj_add_event_cb(ui_ButtonUpdateMaint, onMaintBtnClicked, LV_EVENT_CLICKED, NULL);
if (ui_SliderBrightness) {
lv_slider_set_range(ui_SliderBrightness, 10, 100);
lv_slider_set_value(ui_SliderBrightness, 100, LV_ANIM_OFF);
lv_obj_add_event_cb(ui_SliderBrightness, onBrightnessChange, LV_EVENT_VALUE_CHANGED, NULL);
}
prefs.begin("maint", true);
g_last_maint = prefs.getString("last", "01/01/2026");
g_next_maint = prefs.getString("next", "01/07/2026");
prefs.end();
lv_label_set_text(ui_LabelLastMaintainaceDate, g_last_maint.c_str());
lv_label_set_text(ui_LabelNextMaintDate, g_next_maint.c_str());
lvgl_port_unlock();
WiFi.begin(ssid, password);
server.on("/", handleRoot);
server.begin();
configTime(19800, 0, "pool.ntp.org");
}
void loop() {
server.handleClient();
static unsigned long lastClock = 0;
struct tm timeinfo;
if (lv_scr_act() == ui_SettingScreen) {
lvgl_port_lock(-1);
updateSettingsScreenUI();
lvgl_port_unlock();
}
if (Serial1.available() > 0) {
String raw = Serial1.readStringUntil('\n');
if (raw.startsWith("$[")) {
String data = raw.substring(raw.indexOf("$[") + 2, raw.indexOf("]"));
float v[7]; int count = 0;
char* ptr = strtok((char*)data.c_str(), ",");
while (ptr != NULL && count < 7) { v[count++] = atof(ptr); ptr = strtok(NULL, ","); }
if (count == 7) {
g_temp = v[0]; g_hum = v[1]; g_pres = v[2];
g_x = v[3]; g_y = v[4]; g_z = v[5]; g_vibe = v[6];
lvgl_port_lock(-1);
uint32_t s_color = 0x00FF00;
if (g_temp > 55.0 || g_vibe > 0.70) { g_status = "DANGER"; s_color = 0xFF0000; }
else if (g_temp > 42.0 || g_vibe > 0.15) { g_status = "CAUTION"; s_color = 0xFFA500; }
else { g_status = "HEALTHY"; s_color = 0x00FF00; }
lv_label_set_text(ui_LabelHealthStatus, g_status.c_str());
lv_obj_set_style_text_color(ui_LabelHealthStatus, lv_color_hex(s_color), 0);
char b[16];
snprintf(b, sizeof(b), "%.1f C", g_temp);
lv_label_set_text(ui_LabelTemp, b); lv_label_set_text(ui_LabelTempES, b);
snprintf(b, sizeof(b), "%.0f %%", g_hum);
lv_label_set_text(ui_LabelHum, b); lv_label_set_text(ui_LabelHumES, b);
snprintf(b, sizeof(b), "%.1f hPa", g_pres); lv_label_set_text(ui_LabelBMPressure, b);
snprintf(b, sizeof(b), "X: %.2f G", g_x); lv_label_set_text(ui_LabelXaxis, b);
snprintf(b, sizeof(b), "Y: %.2f G", g_y); lv_label_set_text(ui_LabelYaxis, b);
snprintf(b, sizeof(b), "Z: %.2f G", g_z); lv_label_set_text(ui_LabelZaxis, b);
snprintf(b, sizeof(b), "%.2f G", g_vibe); lv_label_set_text(ui_LabelCombinedMPU, b);
lvgl_port_unlock();
}
}
}
if (millis() - lastClock > 1000) {
lastClock = millis();
if (getLocalTime(&timeinfo)) {
char t_b[10], p_b[5], d_b[15];
strftime(t_b, sizeof(t_b), "%I:%M", &timeinfo);
strftime(p_b, sizeof(p_b), "%p", &timeinfo);
strftime(d_b, sizeof(d_b), "%d/%m/%Y", &timeinfo);
lvgl_port_lock(-1);
lv_label_set_text(ui_LabelTime12Hr, t_b);
lv_label_set_text(ui_LabelTimeAMPM, p_b);
lv_label_set_text(ui_LabelDate, d_b);
lvgl_port_unlock();
}
}
delay(2);
}