fix readme

This commit is contained in:
feeling001@gmail.com
2026-06-18 15:18:12 +02:00
parent 8bab06b72a
commit c40d7516b1
+266 -185
View File
@@ -1,220 +1,301 @@
/** # Marine Navigation Display
* @file main.cpp
* @brief Marine Navigation Display — application entry point.
*
* Boot sequence
* ─────────────
* 1. Initialise Serial, NeoPixel LED, LittleFS config.
* 2. Check for AP-mode button combo held at boot.
* If combo detected (or first boot): start WiFi AP + web server.
* 3. Otherwise start BLE scanning for the Marine Gateway.
* 4. 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> > Waterproof marine e-ink navigation display based on the **Waveshare ESP32-S3-Zero**
#include "Config.h" > and a 4.2" monochrome e-paper screen, acting as a BLE client to the **Marine Gateway**.
#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 }; ## Hardware Bill of Materials
static AppMode _mode = AppMode::BLE;
static bool _otaInProgress = false;
// Cached WiFi status for LED | Component | Model |
static AppState::WifiStatus _wifiStatus = AppState::WifiStatus::OFF; |---|---|
| Microcontroller | Waveshare ESP32-S3-Zero |
| Display | Waveshare 4.2" E-Ink GDEY042T81 (400×300 px) |
| Touch buttons | 3× capacitive pads on Touch pins 1, 2, 3 |
| Status LED | Built-in WS2812B NeoPixel (GPIO 21) |
| Power | 3.3 V regulated (e.g. waterproof LiPo + TP4056) |
// ============================================================================= ---
// Forward declarations
// =============================================================================
static void enterBleMode();
static void enterApMode();
static void handleTouchEvents();
static void updateDisplay();
// ============================================================================= ## Wiring
// setup()
// =============================================================================
void setup() { ### E-Ink Display (SPI — HSPI bus)
Serial.begin(115200);
delay(200);
Serial.println("\n╔═══════════════════════════════╗");
Serial.println("║ Marine Navigation Display ║");
Serial.printf( "║ v%-28s║\n", APP_VERSION);
Serial.println("╚═══════════════════════════════╝");
// ── Status LED ──────────────────────────────────────────────────────────── | ESP32-S3 GPIO | E-Ink Pin | Signal |
statusLed.begin(); |---|---|---|
statusLed.setColor(0xFFFFFF, 20); // brief white flash on boot | 10 | CLK | SPI Clock |
| 11 | DIN | SPI MOSI |
| 9 | CS | Chip Select |
| 8 | DC | Data / Command |
| 7 | RST | Reset |
| 6 | BUSY | Busy (active LOW) |
| 3V3 | VCC | Power |
| GND | GND | Ground |
// ── Persistent config ───────────────────────────────────────────────────── > If you rewire these pins, update the `Pins::EPD_*` constants in `include/Config.h`.
if (!configManager.begin()) {
Serial.println("[MAIN] FATAL: Config init failed");
// Continue with defaults — non-fatal
}
// ── Display ─────────────────────────────────────────────────────────────── ### Touch Buttons
displayManager.begin(); // Shows splash screen
// ── Touch ───────────────────────────────────────────────────────────────── Connect a metallic rod or pad to each touch GPIO. No pull-up or external components
touchManager.begin(); are required — the ESP32-S3 native touch peripheral handles everything.
// ── Detect boot-time AP combo (hold NEXT+PREV while powering on) ────────── | Button | GPIO | Action |
// Sample touch pads for 500 ms at boot |---|---|---|
uint32_t comboStart = millis(); | NEXT | Touch 1 | Next page (short press) |
bool comboDetected = false; | PREV | Touch 2 | Previous page (short press) |
while (millis() - comboStart < 500) { | ACTION | Touch 3 | Force full e-ink refresh |
if (touchRead(Pins::TOUCH_NEXT) < Touch::THRESHOLD && | NEXT + PREV | — | Hold 3 s → toggle WiFi AP mode |
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();
}
}
// ============================================================================= ## Project Structure
// loop()
// =============================================================================
void loop() { ```
// ── Touch input ─────────────────────────────────────────────────────────── marine-display/
touchManager.update(); ├── platformio.ini # Build system configuration
handleTouchEvents(); ├── partitions/
│ └── custom_ota.csv # Custom flash partition table (OTA + LittleFS)
├── scripts/
│ └── gen_littlefs.py # Pre-build script (PlatformIO extra_script)
├── data/ # LittleFS root — upload with: pio run -t uploadfs
│ └── version.txt # Auto-generated at build time
├── include/
│ ├── Config.h # All constants, UUIDs, data structures
│ ├── ConfigManager.h # Persistent configuration API
│ ├── BleManager.h # BLE central client API
│ ├── DisplayManager.h # E-Ink display driver API
│ ├── WifiManager.h # WiFi AP + web server API
│ ├── TouchManager.h # Capacitive touch API
│ └── StatusLed.h # NeoPixel status LED API
└── src/
├── main.cpp # Application entry point
├── ConfigManager.cpp
├── BleManager.cpp
├── DisplayManager.cpp
├── WifiManager.cpp
├── TouchManager.cpp
└── StatusLed.cpp
```
// ── Status LED ──────────────────────────────────────────────────────────── ---
statusLed.update(bleManager.status(), _wifiStatus, _otaInProgress);
// ── BLE mode tasks ──────────────────────────────────────────────────────── ## Partition Layout
if (_mode == AppMode::BLE) {
bleManager.update();
// Show BLE status banner when not connected | Name | Type | Size | Purpose |
static AppState::BleStatus prevBleStatus = AppState::BleStatus::IDLE; |---|---|---|---|
AppState::BleStatus curStatus = bleManager.status(); | nvs | data/nvs | 20 KB | NimBLE bonds, Preferences |
if (curStatus != prevBleStatus) { | otadata | data/ota | 8 KB | OTA slot selector |
prevBleStatus = curStatus; | app0 | app/ota_0 | 1.5 MB | Running firmware |
if (curStatus != AppState::BleStatus::CONNECTED) { | app1 | app/ota_1 | 1.5 MB | OTA target partition |
displayManager.showBleStatus(curStatus); | littlefs | data/spiffs | 1 MB | Config JSON + web assets |
} | coredump | data/coredump | 64 KB | Crash dump |
}
}
// ── WiFi mode tasks ─────────────────────────────────────────────────────── ---
if (_mode == AppMode::WIFI_AP) {
wifiManager.update();
}
// ── Display render ──────────────────────────────────────────────────────── ## Software Architecture
if (_mode == AppMode::BLE && !_otaInProgress) {
updateDisplay();
}
// Small yield to let background tasks run ```
delay(20); main.cpp
}
├── ConfigManager LittleFS + ArduinoJson
│ └── /config.json (pages, tiles, AP credentials)
├── BleManager NimBLE-Arduino (central role)
│ ├── Scan → connect → pair → subscribe (5 characteristics)
│ ├── Notify callbacks → parse JSON → update BoatState (mutex-protected)
│ └── sendAutopilotCmd / sendAdminCmd
├── DisplayManager GxEPD2_BW
│ ├── Full refresh — page change, boot, timer (5 min)
│ └── Partial refresh — value area only, per-tile change detection
├── WifiManager ESPAsyncWebServer
│ ├── GET / → embedded HTML config page
│ ├── GET /api/config → config JSON
│ ├── POST /api/config → apply new config
│ ├── GET /api/state → live BoatState JSON
│ ├── POST /api/ble/cmd → forward BLE command
│ ├── POST /api/ble/passkey → provide BLE pairing PIN
│ └── POST /update → OTA firmware upload
├── TouchManager ESP32-S3 native touchRead()
│ └── Short press / combo detection (NEXT+PREV → AP mode)
└── StatusLed Adafruit NeoPixel
└── Colour + blink pattern encodes BLE/WiFi status
```
// ============================================================================= ### Mode switching
// Mode transitions
// =============================================================================
static void enterBleMode() { The ESP32-S3 radio is shared between BLE and WiFi. The display operates in one
Serial.println("[MAIN] Entering BLE mode"); mode at a time:
_mode = AppMode::BLE;
_wifiStatus = AppState::WifiStatus::OFF;
if (wifiManager.isActive()) wifiManager.stop(); ```
Boot
├── NEXT+PREV held? → WiFi AP mode
└── Normal boot → BLE mode
bleManager.begin(); BLE mode: BLE scanning → connect → live data display
displayManager.forceFullRefresh(); WiFi AP mode: AP started → web server → config / OTA
}
static void enterApMode() { Toggle at runtime: hold NEXT+PREV for 3 seconds.
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(); ## Display Layout
wifiManager.begin(cfg.apSSID, cfg.apPassword);
displayManager.showApScreen(cfg.apSSID, WifiManager::AP_IP);
}
// ============================================================================= ```
// Touch event dispatcher ┌────────────────────┬────────────────────┐
// ============================================================================= │ ████ TILE 0 ████ │ ████ TILE 1 ████ │
│ │ │
│ 5.2 │ 47.2° │
│ kn │ │
├────────────────────┼────────────────────┤
│ ████ TILE 2 ████ │ ████ TILE 3 ████ │
│ │ │
│ 10.1 │ 85 % │
│ kn │ │
└────────────────────┴────────────────────┘
400 px wide × 300 px tall
Each tile: 200 × 150 px
Label bar: 22 px (inverted — white text on black)
Value area: 128 px tall — large 24pt font, centred
```
static void handleTouchEvents() { ### Refresh strategy
// AP combo (NEXT + PREV held 3 s)
if (touchManager.apCombo()) {
if (_mode == AppMode::BLE) {
enterApMode();
} else {
enterBleMode();
}
return;
}
if (_mode == AppMode::BLE) { | Event | Refresh type | Duration |
if (touchManager.nextPressed()) { |---|---|---|
configManager.nextPage(); | Data value changed | Partial (value area only) | ~200 ms |
displayManager.forceFullRefresh(); | Page change | Full | ~1500 ms |
Serial.printf("[MAIN] Page → %u\n", configManager.activePage()); | Manual (ACTION button) | Full | ~1500 ms |
} | Timer (every 5 min) | Full | ~1500 ms |
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() { ## BLE Protocol
BoatState state;
bleManager.getBoatState(state);
const AppConfig& cfg = configManager.config(); The display connects to the **Marine Gateway** (`MarineGateway`) as a BLE
const PageConfig& page = cfg.pages[cfg.activePage]; central client. Full protocol documentation is in `BLE_Client_Documentation.md`.
displayManager.render(state, page); ### Pairing
}
1. The Marine Gateway displays a 6-digit PIN on its dashboard.
2. Submit the PIN via the web UI at `http://192.168.4.1/`**BLE Pairing PIN**,
or via the Serial console (planned).
3. The bond is saved; subsequent reconnections are automatic.
### Subscribed characteristics
| Service | Characteristic | Update rate |
|---|---|---|
| Navigation | NavData | 1 Hz |
| Wind | WindData | 1 Hz |
| Autopilot | AutopilotData | 1 Hz |
| Sail Performance | PerformanceData | 1 Hz |
| Admin | AdminData | 1 Hz |
---
## Building and Flashing
### Requirements
- [PlatformIO Core](https://docs.platformio.org/en/latest/core/installation/) ≥ 6.x
- USB-C cable to the ESP32-S3-Zero (native USB CDC)
### First flash
```bash
# Clone / open the project
cd marine-display
# Build and flash firmware
pio run -t upload
# Upload the LittleFS filesystem (web UI assets + default config placeholder)
pio run -t uploadfs
```
### OTA update (subsequent flashes)
1. Ensure the device is in **WiFi AP mode** (hold NEXT+PREV 3 s or at boot).
2. Connect your computer to the `MarineDisplay` WiFi network.
3. Open `http://192.168.4.1/` in a browser.
4. Scroll to **Firmware Update**, select the `.bin` file, click **Flash Firmware**.
The firmware binary is at `.pio/build/esp32s3_marine_display/firmware.bin` after a build.
Alternatively, configure OTA upload in `platformio.ini`:
```ini
upload_protocol = espota
upload_port = 192.168.4.1
```
Then use `pio run -t upload` over WiFi.
---
## Configuration Web UI
Connect to `MarineDisplay` WiFi (default password: `marine123`) and open
`http://192.168.4.1/` in a browser.
### Tile configuration
Each display page has 4 tiles arranged in a 2×2 grid. For each tile, select:
- **Data Field** — which BLE data to display (SOG, TWA, VMG, Depth, etc.)
- **Label override** — custom short label (max 15 chars); leave blank for auto
Up to **8 pages** can be configured. Navigate with NEXT/PREV buttons.
### Gateway control
From the web UI you can:
- Switch the **Marine Gateway** to STA mode (connect it to your boat WiFi)
- Restart the Marine Gateway remotely
- Submit a BLE pairing PIN
---
## Customisation
### Adding a new data field
1. Add an entry to the `DataField` enum in `include/Config.h`.
2. Add the field to `fieldName()` and `fieldUnit()` inline functions.
3. Add the field extraction in `DisplayManager::_fieldToString()`.
4. Add parsing in the appropriate `BleManager::_parseXxx()` method and struct.
### Changing the display model
Replace `GxEPD2_420_GDEY042T81` in `include/DisplayManager.h` with the correct
GxEPD2 model class for your panel. Update `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` in
`platformio.ini` accordingly.
### Changing pin assignments
All pin constants live in `include/Config.h` under the `Pins::` namespace, or
can be overridden via `build_flags` in `platformio.ini`.
---
## Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Display shows only splash | BLE not connected | Wait for scan; check gateway is advertising |
| Values show `---` | NMEA data stale (>10 s) | Check gateway serial input |
| Ghosting on display | Partial refresh accumulation | Press ACTION button for full refresh |
| Can't see web UI | Not in AP mode | Hold NEXT+PREV 3 s |
| OTA fails | Wrong .bin file | Use `firmware.bin` from `.pio/build/` |
| Touch not responding | Threshold too high/low | Adjust `Touch::THRESHOLD` in `Config.h` |
| BLE bond stale | Old bond data | Clear NVS: `pio run -t erase` then re-flash |
---
## Licence
MIT — see `LICENSE` file.