diff --git a/boards/esp32-s3-lcd-43/esp32-s3-lcd-43.ino b/boards/esp32-s3-lcd-43/esp32-s3-lcd-43.ino index b09fa5d..f914a09 100644 --- a/boards/esp32-s3-lcd-43/esp32-s3-lcd-43.ino +++ b/boards/esp32-s3-lcd-43/esp32-s3-lcd-43.ino @@ -20,52 +20,45 @@ void setup() { logic.finishBoot(); } -// void loop() { -TouchEvent evt = display.readTouch(); +void loop() { + TouchEvent evt = display.readTouch(); -logic.update(); -display.render(logic.getScreenState()); + logic.update(); + display.render(logic.getScreenState()); -const ScreenState& st = logic.getScreenState(); + const ScreenState& st = logic.getScreenState(); -if(st.deviceState == DeviceState::ALERTING) { - HoldState h = display.updateHold(HOLD_TO_SILENCE_MS); - if(h.completed) { - logic.silenceAlert(); - } - if(!h.active) { - display.updateHint(); - } -} else if(evt.pressed) { - if(st.screen == ScreenID::OFF) { - // Tap in OFF mode → wake to DASHBOARD - Serial.println("[TOUCH] OFF → DASHBOARD"); - logic.setScreen(ScreenID::DASHBOARD); - display.setBacklight(true); - } else if(st.screen == ScreenID::DASHBOARD) { - int tile = display.dashboardTouch(evt.x, evt.y); - if(tile >= 0) { - Serial.printf("[DASH] Tile %d tapped\n", tile); - // TODO: Handle tile actions + if(st.deviceState == DeviceState::ALERTING) { + HoldState h = display.updateHold(HOLD_TO_SILENCE_MS); + if(h.completed) { + logic.silenceAlert(); } - } else if(st.screen == ScreenID::ALERT) { - // Tap in ALERT mode → could dismiss or show more info - Serial.println("[TOUCH] ALERT tap"); - // For now, let's make tap do nothing (hold to silence only) - // Or: logic.dismissAlert(); + if(!h.active) { + display.updateHint(); + } + } else if(evt.pressed) { + if(st.screen == ScreenID::OFF) { + // Tap in OFF mode → wake to DASHBOARD + Serial.println("[TOUCH] OFF → DASHBOARD"); + logic.setScreen(ScreenID::DASHBOARD); + display.setBacklight(true); + } else if(st.screen == ScreenID::DASHBOARD) { + int tile = display.dashboardTouch(evt.x, evt.y); + if(tile >= 0) { + Serial.printf("[DASH] Tile %d tapped\n", tile); + // TODO: Handle tile actions + } + } else if(st.screen == ScreenID::ALERT) { + // Tap in ALERT mode → could dismiss or show more info + Serial.println("[TOUCH] ALERT tap"); + // For now, let's make tap do nothing (hold to silence only) + // Or: logic.dismissAlert(); + } + } + if(Serial.available()) { + String cmd = Serial.readStringUntil('\n'); + cmd.trim(); + if(cmd.length() > 0) + logic.onSerialCommand(cmd); } } - -// Serial console (unchanged) -if(Serial.available()) { - // ... -} -} -// ── Serial console ── -if(Serial.available()) { - String cmd = Serial.readStringUntil('\n'); - cmd.trim(); - if(cmd.length() > 0) - logic.onSerialCommand(cmd); -} -} diff --git a/libraries/KlubhausCore/src/DoorbellLogic.cpp b/libraries/KlubhausCore/src/DoorbellLogic.cpp index 48458cf..1481dee 100644 --- a/libraries/KlubhausCore/src/DoorbellLogic.cpp +++ b/libraries/KlubhausCore/src/DoorbellLogic.cpp @@ -48,13 +48,13 @@ void DoorbellLogic::begin(const char* version, const char* boardName, String sfx = _debug ? "_test" : ""; _statusUrl = String("https://") + NTFY_SERVER + "/" + STATUS_TOPIC + sfx; - Serial.printf("[CONFIG] ALERT_URL: %s\n", _alertUrl.c_str()); - Serial.printf("[CONFIG] SILENCE_URL: %s\n", _silenceUrl.c_str()); - Serial.printf("[CONFIG] ADMIN_URL: %s\n", _adminUrl.c_str()); + Serial.printf("[CONFIG] ALERT_URL: %s\n", _alertUrl.c_str()); + Serial.printf("[CONFIG] SILENCE_URL: %s\n", _silenceUrl.c_str()); + Serial.printf("[CONFIG] ADMIN_URL: %s\n", _adminUrl.c_str()); // Boot status - flushStatus(String("BOOTED — ") + _net.getSSID() + " " - + _net.getIP() + " RSSI:" + String(_net.getRSSI())); + flushStatus(String("BOOTED — ") + _net.getSSID() + " " + _net.getIP() + + " RSSI:" + String(_net.getRSSI())); } void DoorbellLogic::finishBoot() { @@ -73,114 +73,124 @@ void DoorbellLogic::update() { uint32_t now = millis(); _state.uptimeMs = now; - if (_net.isConnected()) { + if(_net.isConnected()) { _state.wifiRssi = _net.getRSSI(); _state.wifiSsid = _net.getSSID(); - _state.ipAddr = _net.getIP(); + _state.ipAddr = _net.getIP(); } - if (!_net.checkConnection()) return; + if(!_net.checkConnection()) + return; // Poll - if (now - _lastPollMs >= POLL_INTERVAL_MS) { + if(now - _lastPollMs >= POLL_INTERVAL_MS) { _lastPollMs = now; pollTopics(); } // Heartbeat - if (now - _lastHeartbeatMs >= HEARTBEAT_INTERVAL_MS) { + if(now - _lastHeartbeatMs >= HEARTBEAT_INTERVAL_MS) { _lastHeartbeatMs = now; heartbeat(); } // Auto-transitions - switch (_state.deviceState) { - case DeviceState::ALERTING: - if (now - _state.alertStartMs > ALERT_TIMEOUT_MS) { - Serial.println("[STATE] Alert timed out → SILENT"); - transition(DeviceState::SILENT); - _state.screen = ScreenID::OFF; - _display->setBacklight(false); - _state.backlightOn = false; - } - break; - case DeviceState::SILENCED: - if (now - _state.silenceStartMs > SILENCE_DISPLAY_MS) { - Serial.println("[STATE] Silence display done → SILENT"); - transition(DeviceState::SILENT); - _state.screen = ScreenID::OFF; - _display->setBacklight(false); - _state.backlightOn = false; - } - break; - default: break; + switch(_state.deviceState) { + case DeviceState::ALERTING: + if(now - _state.alertStartMs > ALERT_TIMEOUT_MS) { + Serial.println("[STATE] Alert timed out → SILENT"); + transition(DeviceState::SILENT); + _state.screen = ScreenID::OFF; + _display->setBacklight(false); + _state.backlightOn = false; + } + break; + case DeviceState::SILENCED: + if(now - _state.silenceStartMs > SILENCE_DISPLAY_MS) { + Serial.println("[STATE] Silence display done → SILENT"); + transition(DeviceState::SILENT); + _state.screen = ScreenID::OFF; + _display->setBacklight(false); + _state.backlightOn = false; + } + break; + default: + break; } } // ── Polling ───────────────────────────────────────────────── void DoorbellLogic::pollTopics() { - pollTopic(_alertUrl, "ALERT"); + pollTopic(_alertUrl, "ALERT"); pollTopic(_silenceUrl, "SILENCE"); - pollTopic(_adminUrl, "ADMIN"); + pollTopic(_adminUrl, "ADMIN"); _state.lastPollMs = millis(); } void DoorbellLogic::pollTopic(const String& url, const char* label) { String body; int code = _net.httpGet(url.c_str(), body); - if (code != 200 || body.length() == 0) return; + if(code != 200 || body.length() == 0) + return; // ntfy returns newline-delimited JSON int pos = 0; - while (pos < (int)body.length()) { + while(pos < (int)body.length()) { int nl = body.indexOf('\n', pos); - if (nl < 0) nl = body.length(); + if(nl < 0) + nl = body.length(); String line = body.substring(pos, nl); line.trim(); pos = nl + 1; - if (line.length() == 0) continue; + if(line.length() == 0) + continue; JsonDocument doc; - if (deserializeJson(doc, line)) continue; + if(deserializeJson(doc, line)) + continue; const char* evt = doc["event"] | ""; - if (strcmp(evt, "message") != 0) continue; + if(strcmp(evt, "message") != 0) + continue; - const char* title = doc["title"] | ""; + const char* title = doc["title"] | ""; const char* message = doc["message"] | ""; - Serial.printf("[%s] title=\"%s\" message=\"%s\"\n", - label, title, message); + Serial.printf("[%s] title=\"%s\" message=\"%s\"\n", label, title, message); - if (strcmp(label, "ALERT") == 0) onAlert(String(title), String(message)); - else if (strcmp(label, "SILENCE") == 0) onSilence(); - else if (strcmp(label, "ADMIN") == 0) onAdmin(String(message)); + if(strcmp(label, "ALERT") == 0) + onAlert(String(title), String(message)); + else if(strcmp(label, "SILENCE") == 0) + onSilence(); + else if(strcmp(label, "ADMIN") == 0) + onAdmin(String(message)); } } // ── Event handlers ────────────────────────────────────────── void DoorbellLogic::onAlert(const String& title, const String& body) { - if (millis() < _bootGraceEnd) { + if(millis() < _bootGraceEnd) { Serial.println("[ALERT] Ignored (boot grace)"); return; } Serial.printf("[ALERT] %s: %s\n", title.c_str(), body.c_str()); - _state.alertTitle = title; - _state.alertBody = body; + _state.alertTitle = title; + _state.alertBody = body; _state.alertStartMs = millis(); transition(DeviceState::ALERTING); - _state.screen = ScreenID::ALERT; + _state.screen = ScreenID::ALERT; _display->setBacklight(true); _state.backlightOn = true; flushStatus("ALERT: " + title); } void DoorbellLogic::onSilence() { - if (_state.deviceState != DeviceState::ALERTING) return; + if(_state.deviceState != DeviceState::ALERTING) + return; Serial.println("[SILENCE] Alert silenced"); _state.silenceStartMs = millis(); transition(DeviceState::SILENCED); - _state.screen = ScreenID::OFF; + _state.screen = ScreenID::OFF; _display->setBacklight(false); _state.backlightOn = false; flushStatus("SILENCED"); @@ -190,20 +200,20 @@ void DoorbellLogic::silenceAlert() { onSilence(); } void DoorbellLogic::onAdmin(const String& cmd) { Serial.printf("[ADMIN] %s\n", cmd.c_str()); - if (cmd == "reboot") { + if(cmd == "reboot") { flushStatus("REBOOTING (admin)"); delay(500); ESP.restart(); - } else if (cmd == "dashboard") { - _state.screen = ScreenID::DASHBOARD; + } else if(cmd == "dashboard") { + _state.screen = ScreenID::DASHBOARD; _display->setBacklight(true); _state.backlightOn = true; - } else if (cmd == "off") { - _state.screen = ScreenID::OFF; + } else if(cmd == "off") { + _state.screen = ScreenID::OFF; _display->setBacklight(false); _state.backlightOn = false; - } else if (cmd == "status") { - heartbeat(); // re-uses heartbeat message format + } else if(cmd == "status") { + heartbeat(); // re-uses heartbeat message format } } @@ -219,9 +229,8 @@ void DoorbellLogic::flushStatus(const String& message) { void DoorbellLogic::heartbeat() { String m = String("HEARTBEAT ") + deviceStateStr(_state.deviceState) - + " up:" + String(millis() / 1000) + "s" - + " RSSI:" + String(_net.getRSSI()) - + " heap:" + String(ESP.getFreeHeap()); + + " up:" + String(millis() / 1000) + "s" + " RSSI:" + String(_net.getRSSI()) + + " heap:" + String(ESP.getFreeHeap()); #ifdef BOARD_HAS_PSRAM m += " psram:" + String(ESP.getFreePsram()); #endif @@ -237,32 +246,34 @@ void DoorbellLogic::transition(DeviceState s) { void DoorbellLogic::onSerialCommand(const String& cmd) { Serial.printf("[CMD] %s\n", cmd.c_str()); - if (cmd == "alert") onAlert("Test Alert", "Serial test"); - else if (cmd == "silence") onSilence(); - else if (cmd == "reboot") ESP.restart(); - else if (cmd == "dashboard") onAdmin("dashboard"); - else if (cmd == "off") onAdmin("off"); - else if (cmd == "status") { - Serial.printf("[STATE] %s screen:%s bl:%s\n", - deviceStateStr(_state.deviceState), - screenIdStr(_state.screen), - _state.backlightOn ? "ON" : "OFF"); + if(cmd == "alert") + onAlert("Test Alert", "Serial test"); + else if(cmd == "silence") + onSilence(); + else if(cmd == "reboot") + ESP.restart(); + else if(cmd == "dashboard") + onAdmin("dashboard"); + else if(cmd == "off") + onAdmin("off"); + else if(cmd == "status") { + Serial.printf("[STATE] %s screen:%s bl:%s\n", deviceStateStr(_state.deviceState), + screenIdStr(_state.screen), _state.backlightOn ? "ON" : "OFF"); Serial.printf("[MEM] heap:%d", ESP.getFreeHeap()); #ifdef BOARD_HAS_PSRAM Serial.printf(" psram:%d", ESP.getFreePsram()); #endif Serial.println(); - Serial.printf("[NET] %s RSSI:%d IP:%s\n", - _state.wifiSsid.c_str(), _state.wifiRssi, - _state.ipAddr.c_str()); - } - else Serial.println(F("[CMD] alert|silence|reboot|dashboard|off|status")); + Serial.printf("[NET] %s RSSI:%d IP:%s\n", _state.wifiSsid.c_str(), _state.wifiRssi, + _state.ipAddr.c_str()); + } else + Serial.println(F("[CMD] alert|silence|reboot|dashboard|off|status")); } void DoorbellLogic::setScreen(ScreenID s) { Serial.printf("[SCREEN] Set to %s\n", screenIdStr(s)); _state.screen = s; - + // Auto-manage backlight based on screen bool needsBacklight = (s != ScreenID::OFF); _display->setBacklight(needsBacklight);