8.7 KiB
/**
- @file main.cpp
- @brief Marine Navigation Display — application entry point.
- Boot sequence
- ─────────────
-
- Initialise Serial, NeoPixel LED, LittleFS config.
-
- Check for AP-mode button combo held at boot.
- If combo detected (or first boot): start WiFi AP + web server.
-
- Otherwise start BLE scanning for the Marine Gateway.
-
- main loop:
- a. Poll touch buttons.
- b. Handle page navigation events.
- c. Check BLE status → update LED.
- d. Render display (partial refresh where possible).
- e. Run WiFi server tasks.
- WiFi AP ↔ BLE co-existence
- ──────────────────────────
- The ESP32-S3 radio is shared. We run in one of two mutually exclusive modes:
- MODE_BLE — BLE client active, WiFi off.
- MODE_WIFI — WiFi AP active, BLE off (configuration / OTA mode).
- The user switches modes via the NEXT+PREV button combo (hold 3 s). */
#include <Arduino.h> #include "Config.h" #include "ConfigManager.h" #include "BleManager.h" #include "DisplayManager.h" #include "WifiManager.h" #include "TouchManager.h" #include "StatusLed.h"
// ============================================================================= // Application state // =============================================================================
enum class AppMode { BLE, WIFI_AP }; static AppMode _mode = AppMode::BLE; static bool _otaInProgress = false;
// Cached WiFi status for LED static AppState::WifiStatus _wifiStatus = AppState::WifiStatus::OFF;
// ============================================================================= // Forward declarations // ============================================================================= static void enterBleMode(); static void enterApMode(); static void handleTouchEvents(); static void updateDisplay();
// ============================================================================= // setup() // =============================================================================
void setup() { Serial.begin(115200); delay(200); Serial.println("\n╔═══════════════════════════════╗"); Serial.println("║ Marine Navigation Display ║"); Serial.printf( "║ v%-28s║\n", APP_VERSION); Serial.println("╚═══════════════════════════════╝");
// ── Status LED ────────────────────────────────────────────────────────────
statusLed.begin();
statusLed.setColor(0xFFFFFF, 20); // brief white flash on boot
// ── Persistent config ─────────────────────────────────────────────────────
if (!configManager.begin()) {
Serial.println("[MAIN] FATAL: Config init failed");
// Continue with defaults — non-fatal
}
// ── Display ───────────────────────────────────────────────────────────────
displayManager.begin(); // Shows splash screen
// ── Touch ─────────────────────────────────────────────────────────────────
touchManager.begin();
// ── Detect boot-time AP combo (hold NEXT+PREV while powering on) ──────────
// Sample touch pads for 500 ms at boot
uint32_t comboStart = millis();
bool comboDetected = false;
while (millis() - comboStart < 500) {
if (touchRead(Pins::TOUCH_NEXT) < Touch::THRESHOLD &&
touchRead(Pins::TOUCH_PREV) < Touch::THRESHOLD) {
comboDetected = true;
break;
}
delay(10);
}
if (comboDetected) {
Serial.println("[MAIN] Boot combo detected → entering AP mode");
enterApMode();
} else {
enterBleMode();
}
}
// ============================================================================= // loop() // =============================================================================
void loop() { // ── Touch input ─────────────────────────────────────────────────────────── touchManager.update(); handleTouchEvents();
// ── Status LED ────────────────────────────────────────────────────────────
statusLed.update(bleManager.status(), _wifiStatus, _otaInProgress);
// ── BLE mode tasks ────────────────────────────────────────────────────────
if (_mode == AppMode::BLE) {
bleManager.update();
// Show BLE status banner when not connected
static AppState::BleStatus prevBleStatus = AppState::BleStatus::IDLE;
AppState::BleStatus curStatus = bleManager.status();
if (curStatus != prevBleStatus) {
prevBleStatus = curStatus;
if (curStatus != AppState::BleStatus::CONNECTED) {
displayManager.showBleStatus(curStatus);
}
}
}
// ── WiFi mode tasks ───────────────────────────────────────────────────────
if (_mode == AppMode::WIFI_AP) {
wifiManager.update();
}
// ── Display render ────────────────────────────────────────────────────────
if (_mode == AppMode::BLE && !_otaInProgress) {
updateDisplay();
}
// Small yield to let background tasks run
delay(20);
}
// ============================================================================= // Mode transitions // =============================================================================
static void enterBleMode() { Serial.println("[MAIN] Entering BLE mode"); _mode = AppMode::BLE; _wifiStatus = AppState::WifiStatus::OFF;
if (wifiManager.isActive()) wifiManager.stop();
bleManager.begin();
displayManager.forceFullRefresh();
}
static void enterApMode() { Serial.println("[MAIN] Entering WiFi AP mode"); _mode = AppMode::WIFI_AP; _wifiStatus = AppState::WifiStatus::AP_ACTIVE;
// BLE and WiFi share the radio; stop BLE first (NimBLE deinit is implicit
// when WiFi takes over, but we force a clean state)
// Note: NimBLEDevice::deinit(true) could be called here if needed.
const AppConfig& cfg = configManager.config();
wifiManager.begin(cfg.apSSID, cfg.apPassword);
displayManager.showApScreen(cfg.apSSID, WifiManager::AP_IP);
}
// ============================================================================= // Touch event dispatcher // =============================================================================
static void handleTouchEvents() { // AP combo (NEXT + PREV held 3 s) if (touchManager.apCombo()) { if (_mode == AppMode::BLE) { enterApMode(); } else { enterBleMode(); } return; }
if (_mode == AppMode::BLE) {
if (touchManager.nextPressed()) {
configManager.nextPage();
displayManager.forceFullRefresh();
Serial.printf("[MAIN] Page → %u\n", configManager.activePage());
}
if (touchManager.prevPressed()) {
configManager.prevPage();
displayManager.forceFullRefresh();
Serial.printf("[MAIN] Page ← %u\n", configManager.activePage());
}
if (touchManager.actionPressed()) {
// Force a full e-ink refresh (clears ghosting)
displayManager.forceFullRefresh();
Serial.println("[MAIN] Manual full refresh");
}
}
}
// ============================================================================= // Display update // =============================================================================
static void updateDisplay() { BoatState state; bleManager.getBoatState(state);
const AppConfig& cfg = configManager.config();
const PageConfig& page = cfg.pages[cfg.activePage];
displayManager.render(state, page);
}