Add Motion Sensor functionality and integrate timezone, bedtime, and wake time settings
This commit is contained in:
121
src/app/App.cpp
121
src/app/App.cpp
@@ -13,6 +13,7 @@
|
||||
#include "../sensors/MoistureSensor.h"
|
||||
#include "../sensors/BatterySensor.h"
|
||||
#include "../sensors/AmbientLightSensor.h"
|
||||
#include "../sensors/MotionSensor.h"
|
||||
#include "../ui/FaceRenderer.h"
|
||||
#include <time.h>
|
||||
|
||||
@@ -26,6 +27,7 @@ static Display display;
|
||||
static MoistureSensor moisture;
|
||||
static BatterySensor battery;
|
||||
static AmbientLightSensor ambient;
|
||||
static MotionSensor motion;
|
||||
static FaceRenderer face;
|
||||
|
||||
static unsigned long bootMs = 0;
|
||||
@@ -38,16 +40,15 @@ static bool ntpConfigured = false;
|
||||
static bool timeValid = false;
|
||||
static unsigned long nextTimeCheckMs = 0;
|
||||
static bool lastScheduleSleep = false;
|
||||
static String lastConfiguredTz;
|
||||
static unsigned long motionWakeUntilMs = 0;
|
||||
|
||||
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 constexpr unsigned long MOTION_WAKE_MS = 30000UL;
|
||||
|
||||
static PlantEventType moodToEvent(FaceRenderer::Mood m) {
|
||||
if (m == FaceRenderer::DRY) return EVT_DRY;
|
||||
@@ -90,12 +91,44 @@ 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");
|
||||
static int wrapDaySeconds(int sec) {
|
||||
const int day = 24 * 3600;
|
||||
while (sec < 0) sec += day;
|
||||
while (sec >= day) sec -= day;
|
||||
return sec;
|
||||
}
|
||||
|
||||
static bool parseClockHHMM(const String& hhmm, int& hour, int& minute) {
|
||||
if (hhmm.length() != 5 || hhmm.charAt(2) != ':') return false;
|
||||
int h = hhmm.substring(0, 2).toInt();
|
||||
int m = hhmm.substring(3, 5).toInt();
|
||||
if (h < 0 || h > 23 || m < 0 || m > 59) return false;
|
||||
hour = h;
|
||||
minute = m;
|
||||
return true;
|
||||
}
|
||||
|
||||
static String normalizeTzForEsp(const String& tz) {
|
||||
if (tz == "America/New_York") return "EST5EDT,M3.2.0,M11.1.0";
|
||||
if (tz == "America/Chicago") return "CST6CDT,M3.2.0,M11.1.0";
|
||||
if (tz == "America/Denver") return "MST7MDT,M3.2.0,M11.1.0";
|
||||
if (tz == "America/Los_Angeles") return "PST8PDT,M3.2.0,M11.1.0";
|
||||
return tz;
|
||||
}
|
||||
|
||||
static void ensureTimeSyncConfigured(bool wifiConnected) {
|
||||
if (!wifiConnected) return;
|
||||
|
||||
String tz = settings.timezone();
|
||||
if (tz.length() == 0) tz = String(PB_TZ);
|
||||
String tzForEsp = normalizeTzForEsp(tz);
|
||||
if (ntpConfigured && tzForEsp == lastConfiguredTz) return;
|
||||
|
||||
configTzTime(tzForEsp.c_str(), "pool.ntp.org", "time.nist.gov");
|
||||
ntpConfigured = true;
|
||||
Serial.print("[Clock] NTP started, TZ=");
|
||||
Serial.println(PB_TZ);
|
||||
lastConfiguredTz = tzForEsp;
|
||||
Serial.print("[Clock] NTP started/reconfigured, TZ=");
|
||||
Serial.println(tz);
|
||||
}
|
||||
|
||||
static ScheduleState currentScheduleState() {
|
||||
@@ -108,14 +141,24 @@ static ScheduleState currentScheduleState() {
|
||||
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;
|
||||
int bedHour = 22, bedMinute = 0;
|
||||
int wakeHour = 7, wakeMinute = 0;
|
||||
parseClockHHMM(settings.bedtime(), bedHour, bedMinute);
|
||||
parseClockHHMM(settings.wakeTime(), wakeHour, wakeMinute);
|
||||
|
||||
if (isInRangeSameDay(sec, windDownStartSec, bedSec)) {
|
||||
const int bedSec = bedHour * 3600 + bedMinute * 60;
|
||||
const int wakeSec = wakeHour * 3600 + wakeMinute * 60;
|
||||
const int windDownStartSec = wrapDaySeconds(bedSec - ROUTINE_WINDOW_MIN * 60);
|
||||
const int wakeAnimEndSec = wrapDaySeconds(wakeSec + ROUTINE_WINDOW_MIN * 60);
|
||||
|
||||
bool inWindDown = (windDownStartSec <= bedSec)
|
||||
? isInRangeSameDay(sec, windDownStartSec, bedSec)
|
||||
: isInOvernightRange(sec, windDownStartSec, bedSec);
|
||||
if (inWindDown) {
|
||||
int elapsed = (sec - windDownStartSec);
|
||||
if (elapsed < 0) elapsed += 24 * 3600;
|
||||
s.routineAnim = FaceRenderer::ROUTINE_SLEEPING_SOON;
|
||||
s.routineProgressPermille = (uint16_t)(((sec - windDownStartSec) * 1000L) / (ROUTINE_WINDOW_MIN * 60L));
|
||||
s.routineProgressPermille = (uint16_t)((elapsed * 1000L) / (ROUTINE_WINDOW_MIN * 60L));
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -124,9 +167,14 @@ static ScheduleState currentScheduleState() {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (isInRangeSameDay(sec, wakeSec, wakeAnimEndSec)) {
|
||||
bool inWakeAnim = (wakeSec <= wakeAnimEndSec)
|
||||
? isInRangeSameDay(sec, wakeSec, wakeAnimEndSec)
|
||||
: isInOvernightRange(sec, wakeSec, wakeAnimEndSec);
|
||||
if (inWakeAnim) {
|
||||
int elapsed = (sec - wakeSec);
|
||||
if (elapsed < 0) elapsed += 24 * 3600;
|
||||
s.routineAnim = FaceRenderer::ROUTINE_WAKING_UP;
|
||||
s.routineProgressPermille = (uint16_t)(((sec - wakeSec) * 1000L) / (ROUTINE_WINDOW_MIN * 60L));
|
||||
s.routineProgressPermille = (uint16_t)((elapsed * 1000L) / (ROUTINE_WINDOW_MIN * 60L));
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -137,7 +185,7 @@ static void updateScheduleState() {
|
||||
if ((long)(millis() - nextTimeCheckMs) < 0) return;
|
||||
nextTimeCheckMs = millis() + 1000;
|
||||
|
||||
maybeInitTimeSync(wifi.connected());
|
||||
ensureTimeSyncConfigured(wifi.connected());
|
||||
ScheduleState sched = currentScheduleState();
|
||||
timeValid = sched.hasTime;
|
||||
|
||||
@@ -152,12 +200,7 @@ static void updateScheduleState() {
|
||||
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() {
|
||||
@@ -197,6 +240,7 @@ void App::setup() {
|
||||
moisture.begin(settings);
|
||||
battery.begin();
|
||||
ambient.begin();
|
||||
motion.begin();
|
||||
face.begin(display, settings);
|
||||
|
||||
webhook.begin(settings);
|
||||
@@ -218,7 +262,32 @@ void App::loop() {
|
||||
|
||||
wifi.loop();
|
||||
web.loop();
|
||||
|
||||
motion.loop();
|
||||
if (motion.available()) {
|
||||
face.setTiltEffects(motion.eyeOffsetX(), motion.pupilSizeDelta());
|
||||
if (motion.consumePickupEvent()) {
|
||||
face.triggerSurprised();
|
||||
}
|
||||
if (motion.isMoving()) {
|
||||
motionWakeUntilMs = millis() + MOTION_WAKE_MS;
|
||||
}
|
||||
} else {
|
||||
face.setTiltEffects(0, 0);
|
||||
}
|
||||
|
||||
updateScheduleState();
|
||||
|
||||
bool motionWakeActive = ((long)(millis() - motionWakeUntilMs) < 0);
|
||||
bool displaySleeping = isNightMode && !motionWakeActive;
|
||||
|
||||
static bool lastDisplaySleeping = false;
|
||||
if (displaySleeping != lastDisplaySleeping && display.ok()) {
|
||||
display.setDisplayEnabled(!displaySleeping);
|
||||
if (lastDisplaySleeping && !displaySleeping) lastContrast = 0xFF;
|
||||
lastDisplaySleeping = displaySleeping;
|
||||
}
|
||||
|
||||
updateAmbientDimming();
|
||||
|
||||
// Show WiFi status on display during connection attempts
|
||||
@@ -226,13 +295,13 @@ void App::loop() {
|
||||
if (currentConnected != lastConnected) {
|
||||
if (currentConnected) {
|
||||
Serial.println("[App] WiFi connected - showing on display");
|
||||
if (!isNightMode) {
|
||||
if (!displaySleeping) {
|
||||
display.showStatus("WiFi Connected!", wifi.ssid().c_str());
|
||||
delay(2000);
|
||||
}
|
||||
} else if (wifi.mode() == NET_STA) {
|
||||
Serial.println("[App] WiFi disconnected");
|
||||
if (!isNightMode && millis() - lastDisplayUpdate > 5000) {
|
||||
if (!displaySleeping && millis() - lastDisplayUpdate > 5000) {
|
||||
display.showStatus("WiFi", "Connecting...");
|
||||
lastDisplayUpdate = millis();
|
||||
}
|
||||
@@ -242,7 +311,7 @@ void App::loop() {
|
||||
|
||||
moisture.loop();
|
||||
battery.loop();
|
||||
if (!isNightMode) {
|
||||
if (!displaySleeping) {
|
||||
face.loop(moisture, battery);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user