Refactor platformio.ini for ESP32-S3 and add Ambient Light Sensor functionality with adaptive display contrast
This commit is contained in:
151
src/app/App.cpp
151
src/app/App.cpp
@@ -12,7 +12,9 @@
|
||||
#include "../ui/Display.h"
|
||||
#include "../sensors/MoistureSensor.h"
|
||||
#include "../sensors/BatterySensor.h"
|
||||
#include "../sensors/AmbientLightSensor.h"
|
||||
#include "../ui/FaceRenderer.h"
|
||||
#include <time.h>
|
||||
|
||||
static Settings settings;
|
||||
static WiFiManager wifi;
|
||||
@@ -23,12 +25,29 @@ static WebhookService webhook;
|
||||
static Display display;
|
||||
static MoistureSensor moisture;
|
||||
static BatterySensor battery;
|
||||
static AmbientLightSensor ambient;
|
||||
static FaceRenderer face;
|
||||
|
||||
static unsigned long bootMs = 0;
|
||||
|
||||
static FaceRenderer::Mood lastMood = FaceRenderer::HAPPY;
|
||||
static bool lastDead = false;
|
||||
static bool isNightMode = false;
|
||||
static uint8_t lastContrast = 0xFF;
|
||||
static bool ntpConfigured = false;
|
||||
static bool timeValid = false;
|
||||
static unsigned long nextTimeCheckMs = 0;
|
||||
static bool lastScheduleSleep = false;
|
||||
|
||||
static constexpr float DIM_LUX_MIN = 0.0f;
|
||||
static constexpr float DIM_LUX_MAX = 300.0f;
|
||||
static constexpr uint8_t DIM_CONTRAST_MIN = 0x10;
|
||||
static constexpr uint8_t DIM_CONTRAST_MAX = 0xFF;
|
||||
static constexpr int BED_HOUR = 22;
|
||||
static constexpr int BED_MINUTE = 0;
|
||||
static constexpr int WAKE_HOUR = 7;
|
||||
static constexpr int WAKE_MINUTE = 0;
|
||||
static constexpr int ROUTINE_WINDOW_MIN = 5;
|
||||
|
||||
static PlantEventType moodToEvent(FaceRenderer::Mood m) {
|
||||
if (m == FaceRenderer::DRY) return EVT_DRY;
|
||||
@@ -36,6 +55,123 @@ static PlantEventType moodToEvent(FaceRenderer::Mood m) {
|
||||
return EVT_OK;
|
||||
}
|
||||
|
||||
static float clampFloat(float v, float lo, float hi) {
|
||||
if (v < lo) return lo;
|
||||
if (v > hi) return hi;
|
||||
return v;
|
||||
}
|
||||
|
||||
static uint8_t luxToContrast(float lux) {
|
||||
float clamped = clampFloat(lux, DIM_LUX_MIN, DIM_LUX_MAX);
|
||||
float ratio = (DIM_LUX_MAX <= DIM_LUX_MIN) ? 1.0f
|
||||
: (clamped - DIM_LUX_MIN) / (DIM_LUX_MAX - DIM_LUX_MIN);
|
||||
int value = (int)(DIM_CONTRAST_MIN + ratio * (DIM_CONTRAST_MAX - DIM_CONTRAST_MIN));
|
||||
if (value < 0) value = 0;
|
||||
if (value > 255) value = 255;
|
||||
return (uint8_t)value;
|
||||
}
|
||||
|
||||
struct ScheduleState {
|
||||
bool hasTime = false;
|
||||
bool sleeping = false;
|
||||
FaceRenderer::RoutineAnim routineAnim = FaceRenderer::ROUTINE_NONE;
|
||||
uint16_t routineProgressPermille = 0;
|
||||
};
|
||||
|
||||
static int secondsOfDay(const tm& local) {
|
||||
return local.tm_hour * 3600 + local.tm_min * 60 + local.tm_sec;
|
||||
}
|
||||
|
||||
static bool isInRangeSameDay(int sec, int startSec, int endSec) {
|
||||
return sec >= startSec && sec < endSec;
|
||||
}
|
||||
|
||||
static bool isInOvernightRange(int sec, int startSec, int endSec) {
|
||||
return (sec >= startSec) || (sec < endSec);
|
||||
}
|
||||
|
||||
static void maybeInitTimeSync(bool wifiConnected) {
|
||||
if (!wifiConnected || ntpConfigured) return;
|
||||
configTzTime(PB_TZ, "pool.ntp.org", "time.nist.gov");
|
||||
ntpConfigured = true;
|
||||
Serial.print("[Clock] NTP started, TZ=");
|
||||
Serial.println(PB_TZ);
|
||||
}
|
||||
|
||||
static ScheduleState currentScheduleState() {
|
||||
ScheduleState s;
|
||||
time_t nowEpoch = time(nullptr);
|
||||
if (nowEpoch < 1700000000) return s; // not synced yet
|
||||
|
||||
tm local {};
|
||||
if (!localtime_r(&nowEpoch, &local)) return s;
|
||||
s.hasTime = true;
|
||||
|
||||
const int sec = secondsOfDay(local);
|
||||
const int bedSec = BED_HOUR * 3600 + BED_MINUTE * 60;
|
||||
const int wakeSec = WAKE_HOUR * 3600 + WAKE_MINUTE * 60;
|
||||
const int windDownStartSec = bedSec - ROUTINE_WINDOW_MIN * 60;
|
||||
const int wakeAnimEndSec = wakeSec + ROUTINE_WINDOW_MIN * 60;
|
||||
|
||||
if (isInRangeSameDay(sec, windDownStartSec, bedSec)) {
|
||||
s.routineAnim = FaceRenderer::ROUTINE_SLEEPING_SOON;
|
||||
s.routineProgressPermille = (uint16_t)(((sec - windDownStartSec) * 1000L) / (ROUTINE_WINDOW_MIN * 60L));
|
||||
return s;
|
||||
}
|
||||
|
||||
if (isInOvernightRange(sec, bedSec, wakeSec)) {
|
||||
s.sleeping = true;
|
||||
return s;
|
||||
}
|
||||
|
||||
if (isInRangeSameDay(sec, wakeSec, wakeAnimEndSec)) {
|
||||
s.routineAnim = FaceRenderer::ROUTINE_WAKING_UP;
|
||||
s.routineProgressPermille = (uint16_t)(((sec - wakeSec) * 1000L) / (ROUTINE_WINDOW_MIN * 60L));
|
||||
return s;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void updateScheduleState() {
|
||||
if ((long)(millis() - nextTimeCheckMs) < 0) return;
|
||||
nextTimeCheckMs = millis() + 1000;
|
||||
|
||||
maybeInitTimeSync(wifi.connected());
|
||||
ScheduleState sched = currentScheduleState();
|
||||
timeValid = sched.hasTime;
|
||||
|
||||
face.setRoutineAnimation(sched.routineAnim, sched.routineProgressPermille);
|
||||
|
||||
bool shouldSleep = sched.sleeping;
|
||||
if (!sched.hasTime) shouldSleep = false; // fail-safe: stay awake until time sync exists
|
||||
|
||||
if (shouldSleep != lastScheduleSleep) {
|
||||
Serial.print("[Clock] Schedule ");
|
||||
Serial.println(shouldSleep ? "sleep window entered" : "wake window entered");
|
||||
lastScheduleSleep = shouldSleep;
|
||||
}
|
||||
|
||||
bool wasNightMode = isNightMode;
|
||||
isNightMode = shouldSleep;
|
||||
if (display.ok()) {
|
||||
display.setDisplayEnabled(!isNightMode);
|
||||
if (wasNightMode && !isNightMode) lastContrast = 0xFF; // force contrast refresh when waking
|
||||
}
|
||||
}
|
||||
|
||||
static void updateAmbientDimming() {
|
||||
ambient.loop();
|
||||
if (!ambient.available() || !display.ok() || isNightMode || !display.displayEnabled()) return;
|
||||
|
||||
float lux = ambient.filteredLux();
|
||||
uint8_t contrast = luxToContrast(lux);
|
||||
if (lastContrast == 0xFF || abs((int)contrast - (int)lastContrast) >= 4) {
|
||||
display.setContrast(contrast);
|
||||
lastContrast = contrast;
|
||||
}
|
||||
}
|
||||
|
||||
void App::setup() {
|
||||
bootMs = millis();
|
||||
|
||||
@@ -60,6 +196,7 @@ void App::setup() {
|
||||
|
||||
moisture.begin(settings);
|
||||
battery.begin();
|
||||
ambient.begin();
|
||||
face.begin(display, settings);
|
||||
|
||||
webhook.begin(settings);
|
||||
@@ -81,17 +218,21 @@ void App::loop() {
|
||||
|
||||
wifi.loop();
|
||||
web.loop();
|
||||
updateScheduleState();
|
||||
updateAmbientDimming();
|
||||
|
||||
// Show WiFi status on display during connection attempts
|
||||
bool currentConnected = wifi.connected();
|
||||
if (currentConnected != lastConnected) {
|
||||
if (currentConnected) {
|
||||
Serial.println("[App] WiFi connected - showing on display");
|
||||
display.showStatus("WiFi Connected!", wifi.ssid().c_str());
|
||||
delay(2000);
|
||||
if (!isNightMode) {
|
||||
display.showStatus("WiFi Connected!", wifi.ssid().c_str());
|
||||
delay(2000);
|
||||
}
|
||||
} else if (wifi.mode() == NET_STA) {
|
||||
Serial.println("[App] WiFi disconnected");
|
||||
if (millis() - lastDisplayUpdate > 5000) {
|
||||
if (!isNightMode && millis() - lastDisplayUpdate > 5000) {
|
||||
display.showStatus("WiFi", "Connecting...");
|
||||
lastDisplayUpdate = millis();
|
||||
}
|
||||
@@ -101,7 +242,9 @@ void App::loop() {
|
||||
|
||||
moisture.loop();
|
||||
battery.loop();
|
||||
face.loop(moisture, battery);
|
||||
if (!isNightMode) {
|
||||
face.loop(moisture, battery);
|
||||
}
|
||||
|
||||
// Webhook events on state transitions
|
||||
FaceRenderer::Mood m = face.mood();
|
||||
|
||||
Reference in New Issue
Block a user