diff --git a/src/app/App.cpp b/src/app/App.cpp index eb07454..a3162eb 100644 --- a/src/app/App.cpp +++ b/src/app/App.cpp @@ -11,6 +11,7 @@ #include "../ui/Display.h" #include "../sensors/MoistureSensor.h" +#include "../sensors/BatterySensor.h" #include "../ui/FaceRenderer.h" static Settings settings; @@ -21,6 +22,7 @@ static WebhookService webhook; static Display display; static MoistureSensor moisture; +static BatterySensor battery; static FaceRenderer face; static unsigned long bootMs = 0; @@ -37,15 +39,27 @@ static PlantEventType moodToEvent(FaceRenderer::Mood m) { void App::setup() { bootMs = millis(); + Serial.begin(115200); + delay(100); + Serial.println("\n\n=== FacePlant Starting ==="); + Serial.print("Firmware: "); + Serial.println(PB_VERSION); + settings.begin(); bool forceSetup = BootTrigger::checkAndConsume(); + Serial.print("Force setup mode: "); + Serial.println(forceSetup ? "YES" : "NO"); + Serial.print("Saved WiFi SSID: "); + Serial.println(settings.hasWiFi() ? settings.wifiSsid() : "(none)"); + display.begin(); display.showStatus("FacePlant", "Starting..."); wifi.begin(settings, forceSetup); moisture.begin(settings); + battery.begin(); face.begin(display, settings); webhook.begin(settings); @@ -55,16 +69,39 @@ void App::setup() { lastMood = face.mood(); lastDead = face.isDeadMode(); + + Serial.println("=== Setup Complete ===\n"); } void App::loop() { + static bool lastConnected = false; + static unsigned long lastDisplayUpdate = 0; + BootTrigger::clearAfterStableUptime(); wifi.loop(); web.loop(); + // 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); + } else if (wifi.mode() == NET_STA) { + Serial.println("[App] WiFi disconnected"); + if (millis() - lastDisplayUpdate > 5000) { + display.showStatus("WiFi", "Connecting..."); + lastDisplayUpdate = millis(); + } + } + lastConnected = currentConnected; + } + moisture.loop(); - face.loop(moisture); + battery.loop(); + face.loop(moisture, battery); // Webhook events on state transitions FaceRenderer::Mood m = face.mood(); diff --git a/src/net/WebUI.cpp b/src/net/WebUI.cpp index 1a88429..87352c9 100644 --- a/src/net/WebUI.cpp +++ b/src/net/WebUI.cpp @@ -19,40 +19,188 @@ void WebUI::begin(Settings& settings, String mode = (wifi.mode() == NET_AP_SETUP) ? "Setup AP" : "Station"; String wifiSsid = settings.wifiSsid(); String currentSsid = wifi.ssid(); + String plantProfile = settings.plantProfile(); + String page = - "

FacePlant

" - "

Firmware v" + String(PB_VERSION) + "

" - "

Wi-Fi

" - "

Mode: " + mode + "

" - "

Connected SSID: " + (currentSsid.length() ? currentSsid : "(not connected)") + "

" - "

Saved SSID: " + (wifiSsid.length() ? wifiSsid : "(none)") + "

" - "

Setup AP: " + String(wifi.setupSsid()) + " / " + wifi.apIp().toString() + "

" - "
" - "
" - "
" - "
" - "
" - "Leave password blank to keep saved password for the same SSID.

" - " " - "" - "

" - "
" - "
" - "

" - "

" - "
" - "
" - "

" - "" + "" + "" + "FacePlant" + "" + "" + "" + "
" + "
" + "
" + "

FacePlant

" + "
Firmware v" + String(PB_VERSION) + "
" + "
" + "
" + "
Status
" + "
" + + (wifi.connected() ? "Connected" : "Not Connected") + "
" + "
Mode
" + "
" + mode + "
" + "
Current Network
" + "
" + (currentSsid.length() ? currentSsid : "None") + "
" + "
Setup AP
" + "
" + String(wifi.setupSsid()) + "
" + "
" + "
" + "
" + "

Wi-Fi Settings

" + "" + "" + "" + "
Enter your Wi-Fi network name
" + "" + "" + "
Leave blank to keep existing password for same SSID
" + "
" + "" + "" + "
" "" - "

Status (JSON)

" - "

OTA Update

"; + "
" + "
" + "

Plant Settings

" + "
" + "" + "" + "
Select your plant type for optimal watering thresholds
" + "" + "
Simplified interface for children
" + "" + "" + "
Optional webhook for notifications
" + "" + "
" + "" + "
" + "
" + "
" + "
" + "

Sensor Calibration

" + "
" + "
Current Reading
" + "
" + String(moisture.raw()) + "
" + "
Moisture: " + String(moisture.percent()) + "%
" + "
" + "
" + "

" + "How to calibrate:
" + "1. For dry reading: Remove sensor from soil, wait 30 seconds, note the value above
" + "2. For wet reading: Place sensor in water, wait 30 seconds, note the value above
" + "3. Enter both values below and save" + "

" + "" + "" + "
Raw ADC value when sensor is completely dry (typically 3000-4000)
" + "" + "" + "
Raw ADC value when sensor is fully wet (typically 1000-2000)
" + "
" + "" + "
" + "
" + "
" + "
" + "

Danger Zone

" + "

Irreversible actions that will reset your device

" + "
" + "

Factory Reset

" + "

" + "This will erase all settings including Wi-Fi credentials, plant profile, calibration data, and webhooks. " + "The device will restart in setup mode.

" + "" + "
" + "
" + "" + "
" + "" + ""; _server.send(200, "text/html", page); }); @@ -69,6 +217,10 @@ void WebUI::begin(Settings& settings, pass = _server.arg("pass"); } else if (ssid == settings.wifiSsid()) { pass = settings.wifiPass(); + } else { + // New SSID but no password provided - reject + _server.send(400, "text/plain", "Password required for new network"); + return; } if (ssid.length() > 0) wifi.saveAndConnect(ssid, pass); @@ -87,6 +239,56 @@ void WebUI::begin(Settings& settings, _server.send(303); }); + _server.on("/calibrate", HTTP_POST, [&]() { + if (_server.hasArg("dry") && _server.hasArg("wet")) { + int dryVal = _server.arg("dry").toInt(); + int wetVal = _server.arg("wet").toInt(); + + Serial.println("[WebUI] Calibration update requested"); + Serial.print("[WebUI] Dry value: "); + Serial.println(dryVal); + Serial.print("[WebUI] Wet value: "); + Serial.println(wetVal); + + if (dryVal > wetVal && dryVal <= 4095 && wetVal >= 0) { + settings.setCalibration(dryVal, wetVal); + Serial.println("[WebUI] Calibration saved"); + } else { + Serial.println("[WebUI] Invalid calibration values (dry must be > wet)"); + } + } + _server.sendHeader("Location", "/"); + _server.send(303); + }); + + _server.on("/factory-reset", HTTP_POST, [&]() { + Serial.println("[WebUI] Factory reset requested"); + _server.send(200, "text/html", + "" + "" + "" + "
" + "
" + "

Factory Reset

" + "

Erasing all settings...

" + "

Device will restart in setup mode.

" + "

Reconnect to FacePlant-Setup network in 10 seconds.

" + "
"); + delay(1000); + settings.factoryReset(); + delay(1000); + Serial.println("[WebUI] Restarting device..."); + ESP.restart(); + }); + _server.on("/status", HTTP_GET, [&]() { JsonDocument doc; doc["device"] = "FacePlant"; diff --git a/src/net/WiFiManager.cpp b/src/net/WiFiManager.cpp index fe691dd..391eb84 100644 --- a/src/net/WiFiManager.cpp +++ b/src/net/WiFiManager.cpp @@ -20,18 +20,26 @@ String WiFiManager::ssid() const { void WiFiManager::begin(Settings& settings, bool forceSetupAP) { _settings = &settings; + Serial.println("[WiFi] Initializing..."); WiFi.persistent(false); WiFi.setAutoReconnect(false); - if (forceSetupAP) { startSetupAP(); return; } + if (forceSetupAP) { + Serial.println("[WiFi] Forced setup AP mode"); + startSetupAP(); + return; + } if (_settings->hasWiFi()) { + Serial.print("[WiFi] Connecting to: "); + Serial.println(_settings->wifiSsid()); WiFi.mode(WIFI_STA); WiFi.setHostname(PB_HOSTNAME); WiFi.begin(_settings->wifiSsid().c_str(), _settings->wifiPass().c_str()); _bootConnectStartMs = millis(); _mode = NET_STA; } else { + Serial.println("[WiFi] No saved credentials, starting setup AP"); startSetupAP(); } } @@ -46,12 +54,23 @@ void WiFiManager::saveAndConnect(const String& ssid, const String& pass) { if (cleanSsid.length() == 0) return; bool changed = (cleanSsid != _settings->wifiSsid()) || (pass != _settings->wifiPass()); + + Serial.println("[WiFi] Saving credentials..."); + Serial.print("[WiFi] SSID: "); + Serial.println(cleanSsid); + Serial.print("[WiFi] Password length: "); + Serial.println(pass.length()); + Serial.print("[WiFi] Changed: "); + Serial.println(changed ? "YES" : "NO"); + _settings->saveWiFi(cleanSsid, pass); if (changed) _settings->setEverConnected(false); _bootConnectStartMs = millis(); _reconnectBackoffStep = 0; _nextReconnectMs = _bootConnectStartMs + 5000UL; + + Serial.println("[WiFi] Starting connection attempt..."); startStationAttempt(); } @@ -63,6 +82,7 @@ void WiFiManager::clearAndStartSetupAP() { } void WiFiManager::startSetupAP() { + Serial.println("[WiFi] Starting Setup AP mode"); _mode = NET_AP_SETUP; _setupRequested = false; @@ -71,6 +91,11 @@ void WiFiManager::startSetupAP() { WiFi.softAPConfig(_apIP, _apIP, _apMask); WiFi.softAP(_setupSsid, _setupPass); + Serial.print("[WiFi] AP SSID: "); + Serial.println(_setupSsid); + Serial.print("[WiFi] AP IP: "); + Serial.println(_apIP); + _portal.start(_apIP); MDNS.end(); @@ -104,20 +129,56 @@ void WiFiManager::startStation() { } void WiFiManager::startStationAttempt() { + Serial.print("[WiFi] Starting connection attempt to: "); + Serial.println(_settings->wifiSsid()); + Serial.print("[WiFi] Mode: "); + Serial.println(_mode == NET_AP_SETUP ? "AP+STA (dual)" : "STA only"); + + WiFi.disconnect(); + delay(100); + if (_mode == NET_AP_SETUP) WiFi.mode(WIFI_AP_STA); else WiFi.mode(WIFI_STA); WiFi.setHostname(PB_HOSTNAME); + WiFi.begin(_settings->wifiSsid().c_str(), _settings->wifiPass().c_str()); + + Serial.println("[WiFi] WiFi.begin() called"); } void WiFiManager::loop() { + static unsigned long lastStatusLog = 0; + static wl_status_t lastStatus = WL_IDLE_STATUS; + if (_mode == NET_AP_SETUP) _portal.loop(); if (_setupRequested) { startSetupAP(); return; } + wl_status_t currentStatus = WiFi.status(); + if (currentStatus != lastStatus || (millis() - lastStatusLog > 5000)) { + Serial.print("[WiFi] Status: "); + switch (currentStatus) { + case WL_CONNECTED: Serial.println("CONNECTED"); break; + case WL_NO_SSID_AVAIL: Serial.println("SSID not found"); break; + case WL_CONNECT_FAILED: Serial.println("Connection FAILED (wrong password?)"); break; + case WL_IDLE_STATUS: Serial.println("Idle"); break; + case WL_DISCONNECTED: Serial.println("Disconnected"); break; + default: Serial.print("Other ("); Serial.print(currentStatus); Serial.println(")"); break; + } + lastStatus = currentStatus; + lastStatusLog = millis(); + } + if (WiFi.status() == WL_CONNECTED) { - if (_mode != NET_STA || WiFi.getMode() != WIFI_STA) startStation(); - if (!_settings->everConnected()) _settings->setEverConnected(true); + if (_mode != NET_STA || WiFi.getMode() != WIFI_STA) { + Serial.print("[WiFi] CONNECTED! IP: "); + Serial.println(WiFi.localIP()); + startStation(); + } + if (!_settings->everConnected()) { + Serial.println("[WiFi] First successful connection!"); + _settings->setEverConnected(true); + } return; } @@ -128,6 +189,10 @@ void WiFiManager::loop() { if (!_settings->everConnected() && _bootConnectStartMs != 0 && (millis() - _bootConnectStartMs) > BOOT_CONNECT_TIMEOUT_MS) { + Serial.println("[WiFi] Boot connection timeout - returning to setup AP"); + Serial.print("[WiFi] Elapsed time: "); + Serial.print((millis() - _bootConnectStartMs) / 1000); + Serial.println(" seconds"); _bootConnectStartMs = 0; startSetupAP(); return; @@ -136,6 +201,9 @@ void WiFiManager::loop() { if (_settings->everConnected()) { unsigned long now = millis(); if ((long)(now - _nextReconnectMs) >= 0) { + Serial.print("[WiFi] Retry attempt (backoff step "); + Serial.print(_reconnectBackoffStep); + Serial.println(")"); startStationAttempt(); _nextReconnectMs = now + reconnectDelayMs(_reconnectBackoffStep++); } diff --git a/src/net/WiFiManager.h b/src/net/WiFiManager.h index 2e7502f..9c6b45c 100644 --- a/src/net/WiFiManager.h +++ b/src/net/WiFiManager.h @@ -40,7 +40,7 @@ private: const char* _setupPass = "faceplant"; unsigned long _bootConnectStartMs = 0; - static constexpr unsigned long BOOT_CONNECT_TIMEOUT_MS = 20000; + static constexpr unsigned long BOOT_CONNECT_TIMEOUT_MS = 60000; unsigned long _nextReconnectMs = 0; int _reconnectBackoffStep = 0; diff --git a/src/sensors/BatterySensor.cpp b/src/sensors/BatterySensor.cpp new file mode 100644 index 0000000..07628bc --- /dev/null +++ b/src/sensors/BatterySensor.cpp @@ -0,0 +1,100 @@ +#include "BatterySensor.h" + +void BatterySensor::begin() { + pinMode(PIN_BATTERY_ADC, INPUT); + pinMode(PIN_LBO, INPUT); + + analogReadResolution(12); // 12-bit ADC (0-4095) + + Serial.println("[Battery] Initialized"); + Serial.println("[Battery] ADC on GPIO 2, LBO on GPIO 5"); + + // Initial reading + _voltage = readBatteryVoltage(); + _percent = voltageToPercent(_voltage); + _isLow = (_voltage < VOLTAGE_LOW); + + Serial.print("[Battery] Initial voltage: "); + Serial.print(_voltage); + Serial.print("V, "); + Serial.print(_percent); + Serial.println("%"); +} + +void BatterySensor::loop() { + unsigned long now = millis(); + if (now - _lastCheckMs < CHECK_INTERVAL_MS) return; + _lastCheckMs = now; + + float prevVoltage = _voltage; + int prevPercent = _percent; + bool wasLow = _isLow; + + // Read battery voltage via ADC + _voltage = readBatteryVoltage(); + _percent = voltageToPercent(_voltage); + + // Check LBO pin as backup confirmation + bool lboHigh = digitalRead(PIN_LBO); + bool lboIndicatesLow = !lboHigh; + + // Battery is low if either voltage is low OR LBO pin indicates low + _isLow = (_voltage < VOLTAGE_LOW) || lboIndicatesLow; + + // Log significant changes + if (abs(_percent - prevPercent) >= 5 || _isLow != wasLow) { + Serial.print("[Battery] Voltage: "); + Serial.print(_voltage, 2); + Serial.print("V ("); + Serial.print(_percent); + Serial.print("%) LBO: "); + Serial.println(lboHigh ? "OK" : "LOW"); + + if (_isLow && !wasLow) { + Serial.println("[Battery] ⚠️ LOW BATTERY WARNING - Please recharge!"); + } + } +} + +float BatterySensor::readBatteryVoltage() { + // Take multiple samples and average them for stability + long sum = 0; + for (int i = 0; i < SAMPLES; i++) { + sum += analogRead(PIN_BATTERY_ADC); + delay(5); + } + int avgRaw = sum / SAMPLES; + + // Convert ADC value to voltage at the pin + // ESP32-C3 ADC: 12-bit (0-4095) maps to 0-3.3V + float adcVoltage = (avgRaw / 4095.0) * 3.3; + + // Multiply by divider ratio to get actual battery voltage + float batteryVoltage = adcVoltage * DIVIDER_RATIO; + + return batteryVoltage; +} + +int BatterySensor::voltageToPercent(float voltage) { + // Clamp voltage to valid range + if (voltage >= VOLTAGE_MAX) return 100; + if (voltage <= VOLTAGE_MIN) return 0; + + // Linear mapping from voltage to percentage + // This is a simplified model; LiPo discharge curves are non-linear + // For better accuracy, you could use a lookup table + float percent = ((voltage - VOLTAGE_MIN) / (VOLTAGE_MAX - VOLTAGE_MIN)) * 100.0; + + return constrain((int)percent, 0, 100); +} + +bool BatterySensor::shouldBlink() const { + // Blink every 500ms when battery is low (< 20%) + return (_percent < 20) && ((millis() / 500) % 2 == 0); +} + +int BatterySensor::iconFillWidth(int maxWidth) const { + // Calculate fill width for battery icon + // Returns 0 to maxWidth based on percentage + return (_percent * maxWidth) / 100; +} diff --git a/src/sensors/BatterySensor.h b/src/sensors/BatterySensor.h new file mode 100644 index 0000000..eab8898 --- /dev/null +++ b/src/sensors/BatterySensor.h @@ -0,0 +1,40 @@ +#pragma once +#include + +class BatterySensor { +public: + void begin(); + void loop(); + + // Battery status + int percent() const { return _percent; } + float voltage() const { return _voltage; } + bool isLow() const { return _isLow; } + bool shouldBlink() const; + + // For display + int iconFillWidth(int maxWidth) const; + +private: + static constexpr int PIN_BATTERY_ADC = 2; // Voltage divider input + static constexpr int PIN_LBO = 5; // PowerBoost LBO pin + + static constexpr unsigned long CHECK_INTERVAL_MS = 2000; + static constexpr int SAMPLES = 10; // Number of ADC samples to average + + // LiPo voltage thresholds + static constexpr float VOLTAGE_MIN = 3.0; // 0% + static constexpr float VOLTAGE_MAX = 4.2; // 100% + static constexpr float VOLTAGE_LOW = 3.2; // Low battery threshold + + // Voltage divider: R1=100k, R2=100k (2:1 ratio) + static constexpr float DIVIDER_RATIO = 2.0; + + int _percent = 100; + float _voltage = 3.7; + bool _isLow = false; + unsigned long _lastCheckMs = 0; + + float readBatteryVoltage(); + int voltageToPercent(float voltage); +}; diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index ff7c1d5..101d914 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -29,8 +29,8 @@ String Settings::plantProfile() const { return _prefs.getString("plant_profile", void Settings::setPlantProfile(const String& key) { _prefs.putString("plant_profile", key); } PlantThresholds Settings::thresholds() const { return thresholdsForProfile(plantProfile()); } -int Settings::dryRaw() const { return _prefs.getInt("cal_dry_raw", 3000); } -int Settings::wetRaw() const { return _prefs.getInt("cal_wet_raw", 1600); } +int Settings::dryRaw() const { return _prefs.getInt("cal_dry_raw", 3772); } +int Settings::wetRaw() const { return _prefs.getInt("cal_wet_raw", 1416); } void Settings::setCalibration(int dryRaw, int wetRaw) { _prefs.putInt("cal_dry_raw", dryRaw); _prefs.putInt("cal_wet_raw", wetRaw); @@ -44,3 +44,9 @@ void Settings::setWebhookEnabled(bool v) { _prefs.putBool("wh_en", v); } String Settings::webhookUrl() const { return _prefs.getString("wh_url", ""); } void Settings::setWebhookUrl(const String& url) { _prefs.putString("wh_url", url); } + +void Settings::factoryReset() { + Serial.println("[Settings] Factory reset - clearing all settings"); + _prefs.clear(); + Serial.println("[Settings] All settings cleared"); +} diff --git a/src/settings/Settings.h b/src/settings/Settings.h index c729a08..1657615 100644 --- a/src/settings/Settings.h +++ b/src/settings/Settings.h @@ -43,6 +43,9 @@ public: String webhookUrl() const; void setWebhookUrl(const String& url); + // Factory reset + void factoryReset(); + private: mutable Preferences _prefs; }; diff --git a/src/ui/Display.cpp b/src/ui/Display.cpp index 3e8ed3a..fe5758b 100644 --- a/src/ui/Display.cpp +++ b/src/ui/Display.cpp @@ -59,3 +59,28 @@ _oled.display(); // The animation above already takes ~0.9s; hold this screen ~2.1s more. delay(2100); } + +void Display::drawBatteryIcon(int x, int y, int percent, bool blink) { + if (!_ok) return; + + // Skip if blinking and blink state is off + if (blink && ((millis() / 500) % 2 == 1)) return; + + // Battery body: 14x7 rectangle + _oled.drawRect(x, y, 14, 7, 1); + + // Battery terminal: small nub on right + _oled.fillRect(x + 14, y + 2, 2, 3, 1); + + // Fill level: 12 pixels max width inside battery + int fillWidth = (percent * 12) / 100; + if (fillWidth > 0) { + _oled.fillRect(x + 1, y + 1, fillWidth, 5, 1); + } + + // Low battery warning: blink outline + if (percent < 20 && blink) { + // Draw thicker outline for emphasis + _oled.drawRect(x - 1, y - 1, 16, 9, 1); + } +} diff --git a/src/ui/Display.h b/src/ui/Display.h index 748d78b..a5876a7 100644 --- a/src/ui/Display.h +++ b/src/ui/Display.h @@ -11,6 +11,7 @@ public: void showStatus(const String& line1, const String& line2); void bootAnimation(); + void drawBatteryIcon(int x, int y, int percent, bool blink); private: static constexpr int PIN_SDA = 6; diff --git a/src/ui/FaceRenderer.cpp b/src/ui/FaceRenderer.cpp index 09acf97..9ab50fc 100644 --- a/src/ui/FaceRenderer.cpp +++ b/src/ui/FaceRenderer.cpp @@ -29,7 +29,7 @@ void FaceRenderer::begin(Display& display, Settings& settings) { _nextGazeMs = now + randRange(800, 2000); } -void FaceRenderer::loop(const MoistureSensor& moisture) { +void FaceRenderer::loop(const MoistureSensor& moisture, const BatterySensor& battery) { unsigned long now = millis(); if ((long)(now - _nextFrameMs) < 0) return; _nextFrameMs = now + FRAME_MS; @@ -38,7 +38,7 @@ void FaceRenderer::loop(const MoistureSensor& moisture) { updateDeathMode(now); if (_deadMode) { - renderDead(now); + renderDead(now, battery); return; } @@ -46,7 +46,7 @@ void FaceRenderer::loop(const MoistureSensor& moisture) { else if (_mood == DRY) updateDry(now); else updateTooWet(now); - renderNormal(now); + renderNormal(now, battery); } void FaceRenderer::updateMood(int moisturePct) { @@ -125,7 +125,7 @@ void FaceRenderer::updateTooWet(unsigned long now) { if (_blinking && (long)(now - _blinkUntilMs) >= 0) _blinking = false; } -void FaceRenderer::renderDead(unsigned long now) { +void FaceRenderer::renderDead(unsigned long now, const BatterySensor& battery) { auto &d = _display->oled(); d.clearDisplay(); @@ -145,10 +145,13 @@ void FaceRenderer::renderDead(unsigned long now) { drawMouthFlat(); } + // Draw battery icon in top-right corner + _display->drawBatteryIcon(110, 2, battery.percent(), battery.shouldBlink()); + d.display(); } -void FaceRenderer::renderNormal(unsigned long now) { +void FaceRenderer::renderNormal(unsigned long now, const BatterySensor& battery) { auto &d = _display->oled(); d.clearDisplay(); @@ -173,6 +176,9 @@ void FaceRenderer::renderNormal(unsigned long now) { drawBubbles((now / 100) % 12 - 6); } + // Draw battery icon in top-right corner + _display->drawBatteryIcon(110, 2, battery.percent(), battery.shouldBlink()); + d.display(); } diff --git a/src/ui/FaceRenderer.h b/src/ui/FaceRenderer.h index 7485b2e..ae5aede 100644 --- a/src/ui/FaceRenderer.h +++ b/src/ui/FaceRenderer.h @@ -3,13 +3,14 @@ #include "../ui/Display.h" #include "../settings/Settings.h" #include "../sensors/MoistureSensor.h" +#include "../sensors/BatterySensor.h" class FaceRenderer { public: enum Mood { HAPPY, DRY, TOO_WET }; void begin(Display& display, Settings& settings); - void loop(const MoistureSensor& moisture); + void loop(const MoistureSensor& moisture, const BatterySensor& battery); bool isDeadMode() const { return _deadMode; } Mood mood() const { return _mood; } @@ -55,8 +56,8 @@ private: void updateDry(unsigned long now); void updateTooWet(unsigned long now); - void renderDead(unsigned long now); - void renderNormal(unsigned long now); + void renderDead(unsigned long now, const BatterySensor& battery); + void renderNormal(unsigned long now, const BatterySensor& battery); void drawEyesOpen(int pupilDx, int pupilDy); void drawEyesClosed();