Sfoglia il codice sorgente

Convert HUD to portrait mode with skull display and improved button handling

- Changed display orientation to portrait mode (U8G2_R1)
- Added skull graphic when disconnected instead of "Waiting..." text
- Changed boot screen from "tangerine" to "PEV" (top) and "HUD" (bottom)
- Redesigned UI layout for 64x128 portrait orientation:
  - Vertical battery bars filling from bottom up
  - Reorganized speed, power, and voltage displays
  - Adjusted all element positions for portrait layout

Button handling improvements:
- Reduced debounce time from 175ms to 50ms for better responsiveness
- Changed interrupts from CHANGE to FALLING edge (50% less overhead)
- Added hardware verification with 100μs delay to filter noise
- Implemented atomic operations to prevent race conditions
- Maintained 700ms sequence timeout for hub compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Seb 5 mesi fa
parent
commit
4eaa866a99
7 ha cambiato i file con 759 aggiunte e 113 eliminazioni
  1. 5 0
      .gitignore
  2. 10 0
      .vscode/extensions.json
  3. 9 0
      claude.md
  4. 14 4
      platformio.ini
  5. 18 0
      platformio.ini.backup
  6. 294 109
      src/main.cpp
  7. 409 0
      src/main.cpp.backup

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+.pio
+.vscode/.browse.c_cpp.db*
+.vscode/c_cpp_properties.json
+.vscode/launch.json
+.vscode/ipch

+ 10 - 0
.vscode/extensions.json

@@ -0,0 +1,10 @@
+{
+    // See http://go.microsoft.com/fwlink/?LinkId=827846
+    // for the documentation about the extensions.json format
+    "recommendations": [
+        "platformio.platformio-ide"
+    ],
+    "unwantedRecommendations": [
+        "ms-vscode.cpptools-extension-pack"
+    ]
+}

+ 9 - 0
claude.md

@@ -0,0 +1,9 @@
+- HUD esp32-c3 xaio with waveshare oled display SSD1309 128x64 connected via SPI https://www.waveshare.com/wiki/1.51inch_Transparent_OLED the pins are 
+
+#define OLED_CLK  7   // D5 - SPI Clock (SCL/SCK)
+#define OLED_MOSI 6   // D4 - SPI Data (SDA/MOSI)
+#define OLED_RES  3   // D1 - Reset
+#define OLED_DC   4   // D2 - Data/Command
+#define OLED_CS   5    // D3 - Chip Select
+
+This device receive EUC battery voltage and current speed from the HUB. It also has 3 buttons left, middle and right respectively attached to pin d06, d07 and d08

+ 14 - 4
platformio.ini

@@ -1,18 +1,28 @@
 ; PlatformIO Project Configuration File for Mirror HUD Display
 ;
-; ESP32 with 1.51" Transparent OLED (SSD1309)
+; XIAO ESP32-C3 with 1.51 Transparent OLED SSD1309
 ; BLE Server receiving battery and speed data from glove
 
 [platformio]
-default_envs = esp32dev
+default_envs = seeed_xiao_esp32c3
 
-[env:esp32dev]
+[env:seeed_xiao_esp32c3]
 platform = espressif32
-board = esp32dev
+board = seeed_xiao_esp32c3
 framework = arduino
 monitor_speed = 115200
 lib_deps = 
     olikraus/U8g2@^2.34.22
+    h2zero/NimBLE-Arduino @ ^1.4.1
 build_flags = 
     -DCORE_DEBUG_LEVEL=3
     -DCONFIG_ESP_COEX_SW_COEXIST_ENABLE
+    -DARDUINO_USB_CDC_ON_BOOT=1
+    -DCONFIG_BT_NIMBLE_ENABLED=1
+    -DCONFIG_BT_BLE_ENABLED=1
+    -DCONFIG_BLUEDROID_ENABLED=0
+    -DCONFIG_BT_BLUEDROID_ENABLED=0
+lib_ignore = 
+    BLE
+upload_protocol = esptool
+monitor_filters = esp32_exception_decoder

+ 18 - 0
platformio.ini.backup

@@ -0,0 +1,18 @@
+; PlatformIO Project Configuration File for Mirror HUD Display
+;
+; ESP32 with 1.51" Transparent OLED (SSD1309)
+; BLE Server receiving battery and speed data from glove
+
+[platformio]
+default_envs = esp32dev
+
+[env:esp32dev]
+platform = espressif32
+board = esp32dev
+framework = arduino
+monitor_speed = 115200
+lib_deps = 
+    olikraus/U8g2@^2.34.22
+build_flags = 
+    -DCORE_DEBUG_LEVEL=3
+    -DCONFIG_ESP_COEX_SW_COEXIST_ENABLE

+ 294 - 109
src/main.cpp

@@ -1,18 +1,17 @@
 #include <Arduino.h>
-#include <BLEDevice.h>
-#include <BLEServer.h>
-#include <BLEUtils.h>
-#include <BLE2902.h>
+#include <NimBLEDevice.h>
+#include <NimBLEServer.h>
+#include <NimBLEUtils.h>
 #include <SPI.h>
 #include <U8g2lib.h>
 
 // OLED Display Configuration for Waveshare 1.51" Transparent OLED
-// SSD1309 128x64 SPI Interface
-#define OLED_CLK  18   // SPI Clock (SCL/SCK)
-#define OLED_MOSI 23   // SPI Data (SDA/MOSI) 
-#define OLED_RES  16   // Reset
-#define OLED_DC   17   // Data/Command
-#define OLED_CS   5    // Chip Select
+// SSD1309 128x64 SPI Interface - XIAO ESP32-C3 Pin Mapping
+#define OLED_CLK  7   // D5 - SPI Clock (SCL/SCK)
+#define OLED_MOSI 6   // D4 - SPI Data (SDA/MOSI) 
+#define OLED_RES  3   // D1 - Reset
+#define OLED_DC   4   // D2 - Data/Command
+#define OLED_CS   5    // D3 - Chip Select
 
 // BLE Configuration
 #define SERVICE_UUID        "12345678-1234-1234-1234-123456789abc"
@@ -20,7 +19,12 @@
 #define SPEED_CHAR_UUID     "11111111-2222-3333-4444-555555555555"
 #define POWER_CHAR_UUID     "22222222-3333-4444-5555-666666666666"
 #define LIGHT_CHAR_UUID     "33333333-4444-5555-6666-777777777777"
-#define DEVICE_NAME         "mirror-hud"
+#define BUTTON_CHAR_UUID    "44444444-5555-6666-7777-888888888888"
+#define DEVICE_NAME         "PEV-HUD"
+
+// Button Configuration - Using GPIO 20 and 21
+#define BUTTON_1_PIN 20  // GPIO 20 - Button 1
+#define BUTTON_2_PIN 21  // GPIO 21 - Button 2
 
 // Display object for SSD1309 - Hardware SPI
 U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, OLED_CS, OLED_DC, OLED_RES);
@@ -30,15 +34,33 @@ U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, OLED_CS, OLED_DC, OLED_RES
 // U8G2_SSD1309_128X64_NONAME0_F_4W_SW_SPI u8g2_alt2(U8G2_R0, OLED_CLK, OLED_MOSI, OLED_CS, OLED_DC, OLED_RES);
 
 // BLE Variables
-BLEServer* pServer = nullptr;
-BLEService* pService = nullptr;
-BLECharacteristic* pBatteryCharacteristic = nullptr;
-BLECharacteristic* pSpeedCharacteristic = nullptr;
-BLECharacteristic* pPowerCharacteristic = nullptr;
-BLECharacteristic* pLightCharacteristic = nullptr;
+NimBLEServer* pServer = nullptr;
+NimBLEService* pService = nullptr;
+NimBLECharacteristic* pBatteryCharacteristic = nullptr;
+NimBLECharacteristic* pSpeedCharacteristic = nullptr;
+NimBLECharacteristic* pPowerCharacteristic = nullptr;
+NimBLECharacteristic* pLightCharacteristic = nullptr;
+NimBLECharacteristic* pButtonCharacteristic = nullptr;
 bool deviceConnected = false;
 bool oldDeviceConnected = false;
 
+// Button handling variables
+volatile bool button1Pressed = false;
+volatile bool button2Pressed = false;
+unsigned long button1LastTime = 0;
+unsigned long button2LastTime = 0;
+const unsigned long DEBOUNCE_TIME = 50; // 50ms debounce for better responsiveness
+
+// Sequence detection variables
+String buttonSequence = "";
+unsigned long sequenceStartTime = 0;
+const unsigned long SEQUENCE_TIMEOUT = 700; // 700ms timeout (match hub)
+
+// Sequence display variables
+String currentSequence = "";
+unsigned long sequenceDisplayTime = 0;
+const unsigned long SEQUENCE_DISPLAY_DURATION = 1000; // Show sequence for 1 second
+
 // Data variables
 float batteryVoltage = 0.0; // Voltage instead of percentage
 float speed = 0.0;          // Speed in km/h
@@ -47,22 +69,51 @@ bool lightOn = false;       // Headlight status
 unsigned long lastUpdate = 0;
 const unsigned long UPDATE_INTERVAL = 250; // Update display every 250ms (reduce load)
 bool dataReceived = false;  // Flag to track if we've received data from glove
+
+// Button interrupt handlers - using FALLING edge for efficiency
+void IRAM_ATTR button1ISR() {
+    unsigned long now = millis();
+    if (now - button1LastTime > DEBOUNCE_TIME) {
+        // Verify button is still pressed after small delay
+        delayMicroseconds(100);
+        if (digitalRead(BUTTON_1_PIN) == LOW) {
+            button1Pressed = true;
+            button1LastTime = now;
+        }
+    }
+}
+
+void IRAM_ATTR button2ISR() {
+    unsigned long now = millis();
+    if (now - button2LastTime > DEBOUNCE_TIME) {
+        // Verify button is still pressed after small delay
+        delayMicroseconds(100);
+        if (digitalRead(BUTTON_2_PIN) == LOW) {
+            button2Pressed = true;
+            button2LastTime = now;
+        }
+    }
+}
 // BLE Server Callbacks
-class MyServerCallbacks: public BLEServerCallbacks {
-    void onConnect(BLEServer* pServer) {
+class MyServerCallbacks: public NimBLEServerCallbacks {
+    void onConnect(NimBLEServer* pServer) {
         deviceConnected = true;
-        Serial.println("BLE Client connected to mirror-hud");
+        Serial.println("=== HUD: BLE CLIENT CONNECTED ===");
+        Serial.println("BLE Client connected to PEV-HUD");
+        Serial.println("deviceConnected = TRUE");
     }
 
-    void onDisconnect(BLEServer* pServer) {
+    void onDisconnect(NimBLEServer* pServer) {
         deviceConnected = false;
-        Serial.println("BLE Client disconnected from mirror-hud");
+        Serial.println("=== HUD: BLE CLIENT DISCONNECTED ===");
+        Serial.println("BLE Client disconnected from PEV-HUD");
+        Serial.println("deviceConnected = FALSE");
     }
 };
 
 // Battery Level Characteristic Callback
-class BatteryCallbacks: public BLECharacteristicCallbacks {
-    void onWrite(BLECharacteristic* pCharacteristic) {
+class BatteryCallbacks: public NimBLECharacteristicCallbacks {
+    void onWrite(NimBLECharacteristic* pCharacteristic) {
         std::string value = pCharacteristic->getValue();
         
         if (value.length() == 4) { // Expecting 4 bytes for float
@@ -76,8 +127,8 @@ class BatteryCallbacks: public BLECharacteristicCallbacks {
 };
 
 // Speed Characteristic Callback
-class SpeedCallbacks: public BLECharacteristicCallbacks {
-    void onWrite(BLECharacteristic* pCharacteristic) {
+class SpeedCallbacks: public NimBLECharacteristicCallbacks {
+    void onWrite(NimBLECharacteristic* pCharacteristic) {
         std::string value = pCharacteristic->getValue();
         
         if (value.length() == 4) { // Expecting 4 bytes for float
@@ -91,8 +142,8 @@ class SpeedCallbacks: public BLECharacteristicCallbacks {
 };
 
 // Power Characteristic Callback
-class PowerCallbacks: public BLECharacteristicCallbacks {
-    void onWrite(BLECharacteristic* pCharacteristic) {
+class PowerCallbacks: public NimBLECharacteristicCallbacks {
+    void onWrite(NimBLECharacteristic* pCharacteristic) {
         std::string value = pCharacteristic->getValue();
         
         if (value.length() == 4) { // Expecting 4 bytes for float
@@ -106,8 +157,8 @@ class PowerCallbacks: public BLECharacteristicCallbacks {
 };
 
 // Light Characteristic Callback
-class LightCallbacks: public BLECharacteristicCallbacks {
-    void onWrite(BLECharacteristic* pCharacteristic) {
+class LightCallbacks: public NimBLECharacteristicCallbacks {
+    void onWrite(NimBLECharacteristic* pCharacteristic) {
         std::string value = pCharacteristic->getValue();
         
         if (value.length() == 1) { // Expecting 1 byte for boolean
@@ -122,10 +173,10 @@ void initBLE() {
     Serial.println("Initializing BLE...");
     
     // Initialize BLE
-    BLEDevice::init(DEVICE_NAME);
+    NimBLEDevice::init(DEVICE_NAME);
     
     // Create BLE Server
-    pServer = BLEDevice::createServer();
+    pServer = NimBLEDevice::createServer();
     pServer->setCallbacks(new MyServerCallbacks());
     
     // Create BLE Service
@@ -134,54 +185,144 @@ void initBLE() {
     // Create Battery Characteristic
     pBatteryCharacteristic = pService->createCharacteristic(
         BATTERY_CHAR_UUID,
-        BLECharacteristic::PROPERTY_READ |
-        BLECharacteristic::PROPERTY_WRITE |
-        BLECharacteristic::PROPERTY_NOTIFY
+        NIMBLE_PROPERTY::READ |
+        NIMBLE_PROPERTY::WRITE |
+        NIMBLE_PROPERTY::NOTIFY
     );
     pBatteryCharacteristic->setCallbacks(new BatteryCallbacks());
-    pBatteryCharacteristic->addDescriptor(new BLE2902());
     
     // Create Speed Characteristic
     pSpeedCharacteristic = pService->createCharacteristic(
         SPEED_CHAR_UUID,
-        BLECharacteristic::PROPERTY_READ |
-        BLECharacteristic::PROPERTY_WRITE |
-        BLECharacteristic::PROPERTY_NOTIFY
+        NIMBLE_PROPERTY::READ |
+        NIMBLE_PROPERTY::WRITE |
+        NIMBLE_PROPERTY::NOTIFY
     );
     pSpeedCharacteristic->setCallbacks(new SpeedCallbacks());
-    pSpeedCharacteristic->addDescriptor(new BLE2902());
     
     // Create Power Characteristic
     pPowerCharacteristic = pService->createCharacteristic(
         POWER_CHAR_UUID,
-        BLECharacteristic::PROPERTY_READ |
-        BLECharacteristic::PROPERTY_WRITE |
-        BLECharacteristic::PROPERTY_NOTIFY
+        NIMBLE_PROPERTY::READ |
+        NIMBLE_PROPERTY::WRITE |
+        NIMBLE_PROPERTY::NOTIFY
     );
     pPowerCharacteristic->setCallbacks(new PowerCallbacks());
-    pPowerCharacteristic->addDescriptor(new BLE2902());
     
     // Create Light Characteristic
     pLightCharacteristic = pService->createCharacteristic(
         LIGHT_CHAR_UUID,
-        BLECharacteristic::PROPERTY_READ |
-        BLECharacteristic::PROPERTY_WRITE |
-        BLECharacteristic::PROPERTY_NOTIFY
+        NIMBLE_PROPERTY::READ |
+        NIMBLE_PROPERTY::WRITE |
+        NIMBLE_PROPERTY::NOTIFY
     );
     pLightCharacteristic->setCallbacks(new LightCallbacks());
-    pLightCharacteristic->addDescriptor(new BLE2902());
+    
+    // Create Button Characteristic (for sending button data to hub)
+    pButtonCharacteristic = pService->createCharacteristic(
+        BUTTON_CHAR_UUID,
+        NIMBLE_PROPERTY::READ |
+        NIMBLE_PROPERTY::NOTIFY
+    );
+    // CCCD descriptor automatically created by NimBLE for NOTIFY characteristics
     
     // Start the service
     pService->start();
     
     // Start advertising
-    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
     pAdvertising->addServiceUUID(SERVICE_UUID);
     pAdvertising->setScanResponse(false);
     pAdvertising->setMinPreferred(0x0);
-    BLEDevice::startAdvertising();
+    NimBLEDevice::startAdvertising();
     
-    Serial.println("BLE Server started, advertising as 'mirror-hud'");
+    Serial.println("BLE Server started, advertising as 'PEV-HUD'");
+}
+
+void initButtons() {
+    Serial.println("Initializing buttons...");
+    
+    // Configure button pins as input with pullup
+    pinMode(BUTTON_1_PIN, INPUT_PULLUP);
+    pinMode(BUTTON_2_PIN, INPUT_PULLUP);
+    
+    // Attach interrupts (trigger on falling edge for efficiency)
+    attachInterrupt(digitalPinToInterrupt(BUTTON_1_PIN), button1ISR, FALLING);
+    attachInterrupt(digitalPinToInterrupt(BUTTON_2_PIN), button2ISR, FALLING);
+    
+    Serial.printf("Buttons initialized on GPIO %d and %d\n", BUTTON_1_PIN, BUTTON_2_PIN);
+}
+
+void handleButtons() {
+    // Atomic read and clear of button flags to prevent race conditions
+    bool b1 = false, b2 = false;
+    noInterrupts();
+    if (button1Pressed) {
+        b1 = true;
+        button1Pressed = false;
+    }
+    if (button2Pressed) {
+        b2 = true;
+        button2Pressed = false;
+    }
+    interrupts();
+    
+    // Process button 1 outside critical section
+    if (b1) {
+        // Add to sequence
+        buttonSequence += "1";
+        sequenceStartTime = millis();
+        
+        Serial.println("=== BUTTON 1 PRESSED ===");
+        Serial.println("Button 1 pressed, sequence now: " + buttonSequence);
+        Serial.println("Sequence start time: " + String(sequenceStartTime));
+    }
+    
+    // Process button 2 outside critical section
+    if (b2) {
+        // Add to sequence
+        buttonSequence += "2";
+        sequenceStartTime = millis();
+        
+        Serial.println("=== BUTTON 2 PRESSED ===");
+        Serial.println("Button 2 pressed, sequence now: " + buttonSequence);
+        Serial.println("Sequence start time: " + String(sequenceStartTime));
+    }
+    
+    // Check if sequence timeout has elapsed
+    if (buttonSequence.length() > 0 && 
+        (millis() - sequenceStartTime) > SEQUENCE_TIMEOUT) {
+        
+        Serial.println("=== SEQUENCE TIMEOUT ===");
+        Serial.println("Sequence timeout - sending: " + buttonSequence);
+        Serial.println("Device connected: " + String(deviceConnected ? "YES" : "NO"));
+        Serial.println("Button characteristic: " + String(pButtonCharacteristic ? "YES" : "NO"));
+        
+        // Send sequence via BLE
+        if (deviceConnected && pButtonCharacteristic) {
+            Serial.println("=== SENDING BLE NOTIFICATION ===");
+            Serial.println("HUD: Sending notification - " + buttonSequence);
+            Serial.printf("HUD: String length: %d\n", buttonSequence.length());
+            
+            // Method 1: Send as byte array with explicit length
+            pButtonCharacteristic->setValue((uint8_t*)buttonSequence.c_str(), buttonSequence.length());
+            pButtonCharacteristic->notify();
+            Serial.println("HUD: Notification sent successfully");
+            
+            // Set sequence display
+            currentSequence = buttonSequence;
+            sequenceDisplayTime = millis();
+        } else {
+            Serial.println("=== ERROR: CANNOT SEND ===");
+            Serial.println("HUD: Cannot send - not connected or no characteristic");
+            if (!deviceConnected) Serial.println("Reason: Device not connected");
+            if (!pButtonCharacteristic) Serial.println("Reason: No button characteristic");
+        }
+        
+        // Reset sequence
+        Serial.println("Resetting sequence buffer");
+        buttonSequence = "";
+    }
 }
 void initDisplay() {
     Serial.println("=== OLED Display Initialization (SPI) ===");
@@ -226,7 +367,7 @@ void initDisplay() {
     
     // Force display setup
     u8g2.clearBuffer();
-    u8g2.setDisplayRotation(U8G2_R0);
+    u8g2.setDisplayRotation(U8G2_R1);  // Portrait mode (90 degrees)
     u8g2.setPowerSave(0);  // Wake up display
     
     // Test with simple elements
@@ -242,14 +383,19 @@ void initDisplay() {
     u8g2.sendBuffer();
     delay(500);
     
-    // Show "tangerine" boot screen
-    Serial.println("Displaying tangerine boot screen via SPI...");
+    // Show "PEV/HUD" boot screen
+    Serial.println("Displaying PEV/HUD boot screen via SPI...");
     u8g2.clearBuffer();
+    
+    // "PEV" at top
     u8g2.setFont(u8g2_font_ncenB18_tr);
-    u8g2.drawStr(15, 35, "tangerine");
+    u8g2.drawStr(16, 25, "PEV");
+    
+    // "HUD" at bottom
+    u8g2.drawStr(16, 110, "HUD");
     
     // Add border to confirm display is working
-    u8g2.drawFrame(0, 0, 128, 64);
+    u8g2.drawFrame(0, 0, 64, 128);
     u8g2.sendBuffer();
     
     Serial.println("SPI OLED should now show 'tangerine'!");
@@ -261,85 +407,120 @@ void updateDisplay() {
     u8g2.clearBuffer();
     
     if (!dataReceived) {
-        // Show "tangerine" screen when no data received
-        u8g2.setFont(u8g2_font_ncenB18_tr);
-        u8g2.drawStr(15, 35, "tangerine");
-        
-        // Show connection status
-        u8g2.setFont(u8g2_font_6x10_tr);
         if (deviceConnected) {
-            u8g2.drawStr(30, 55, "Connected");
+            // Show "PEV/HUD" screen when connected but no data
+            u8g2.setFont(u8g2_font_ncenB18_tr);
+            u8g2.drawStr(16, 25, "PEV");
+            u8g2.drawStr(16, 110, "HUD");
+            
+            u8g2.setFont(u8g2_font_6x10_tr);
+            u8g2.drawStr(15, 64, "Connected");
         } else {
-            u8g2.drawStr(25, 55, "Waiting...");
+            // Draw skull when not connected (portrait 64x128)
+            // Skull outline
+            u8g2.drawEllipse(32, 45, 20, 25);  // Head
+            
+            // Eye sockets
+            u8g2.drawDisc(26, 38, 4);  // Left eye
+            u8g2.drawDisc(38, 38, 4);  // Right eye
+            
+            // Nasal cavity
+            u8g2.drawTriangle(32, 42, 28, 52, 36, 52);
+            
+            // Mouth/teeth
+            u8g2.drawLine(22, 55, 42, 55);  // Top jaw line
+            u8g2.drawLine(24, 55, 24, 60);  // Tooth 1
+            u8g2.drawLine(28, 55, 28, 58);  // Tooth 2
+            u8g2.drawLine(32, 55, 32, 60);  // Tooth 3
+            u8g2.drawLine(36, 55, 36, 58);  // Tooth 4
+            u8g2.drawLine(40, 55, 40, 60);  // Tooth 5
+            
+            // Status text
+            u8g2.setFont(u8g2_font_6x10_tr);
+            u8g2.drawStr(8, 85, "DISCONNECTED");
         }
     } else {
-        // Show EUC data when received from glove
+        // Show EUC data when received from glove (Portrait mode: 64x128)
         
-        // Connection status indicator
+        // Connection status indicator at top
         if (deviceConnected) {
             u8g2.setFont(u8g2_font_4x6_tr);
-            u8g2.drawStr(100, 8, "CONN");
+            u8g2.drawStr(25, 8, "CONN");
         } else {
             u8g2.setFont(u8g2_font_4x6_tr);
-            u8g2.drawStr(95, 8, "DISC");
+            u8g2.drawStr(20, 8, "DISC");
         }
         
         // Speed Display - Large and prominent
-        u8g2.setFont(u8g2_font_ncenB24_tn);  // Increased from 18 to 24
+        u8g2.setFont(u8g2_font_ncenB24_tn);
         char speedStr[10];
         dtostrf(speed, 4, 1, speedStr);
-        u8g2.drawStr(5, 35, speedStr);  // Moved left to make room for larger font
+        u8g2.drawStr(5, 35, speedStr);
         
-        // "K" instead of "km/h"
-        u8g2.setFont(u8g2_font_ncenB12_tr);  // Larger K
-        u8g2.drawStr(85, 35, "K");
+        // "K" for km/h
+        u8g2.setFont(u8g2_font_ncenB10_tr);
+        u8g2.drawStr(50, 35, "K");
         
-        // Power Display where km/h used to be
+        // Power Display
         u8g2.setFont(u8g2_font_ncenB08_tr);
-        char powerStr[15];
-        sprintf(powerStr, "%.0fW", power);
-        u8g2.drawStr(100, 35, powerStr);
+        
+        // Check if we should display sequence instead of power
+        bool showSequence = (currentSequence.length() > 0) && 
+                           (millis() - sequenceDisplayTime < SEQUENCE_DISPLAY_DURATION);
+        
+        if (showSequence) {
+            u8g2.drawStr(45, 50, currentSequence.c_str());
+        } else {
+            // Clear sequence if time has elapsed
+            if (currentSequence.length() > 0 && millis() - sequenceDisplayTime >= SEQUENCE_DISPLAY_DURATION) {
+                currentSequence = "";
+            }
+            // Show normal power display
+            char powerStr[15];
+            sprintf(powerStr, "%.0fW", power);
+            u8g2.drawStr(20, 50, powerStr);
+        }
         
         // Battery Voltage Display
         u8g2.setFont(u8g2_font_ncenB08_tr);
         char voltStr[15];
         sprintf(voltStr, "%.1fV", batteryVoltage);
-        u8g2.drawStr(10, 55, voltStr);
+        u8g2.drawStr(15, 70, voltStr);
         
-        // 5-Bar Battery Display based on actual voltage range
-        // 134.4V = 100%, 117.7V = 44% 
-        float minVoltage = 104.58;   // Calculated 0% voltage
-        float maxVoltage = 134.4;    // 100% voltage
+        // 5-Bar Battery Display (vertical for portrait)
+        // Inmotion Adventure: 32S battery pack (134.4V nominal)
+        float minVoltage = 112.0;    // 32S * 3.5V (empty/low)
+        float maxVoltage = 134.4;    // 32S * 4.2V (full charge)
         
         // Calculate number of bars to display (0-5)
         int numBars = 0;
-        if (batteryVoltage >= 134.40) numBars = 5;      // 100%+
-        else if (batteryVoltage >= 128.44) numBars = 4; // 80%+
-        else if (batteryVoltage >= 122.47) numBars = 3; // 60%+
-        else if (batteryVoltage >= 116.51) numBars = 2; // 40%+
-        else if (batteryVoltage >= 110.54) numBars = 1; // 20%+
-        else numBars = 0;                               // <20%
+        if (batteryVoltage >= 130.0) numBars = 5;       // 80%+ (full)
+        else if (batteryVoltage >= 126.0) numBars = 4;  // 62%+ (high)
+        else if (batteryVoltage >= 122.0) numBars = 3;  // 45%+ (med)
+        else if (batteryVoltage >= 118.0) numBars = 2;  // 27%+ (low)
+        else if (batteryVoltage >= 114.0) numBars = 1;  // 9%+ (very low)
+        else numBars = 0;                               // <9% (critical)
         
-        // Draw 5 individual battery bars (each 8px wide, 2px spacing)
-        int barX = 70;  // Starting X position
-        int barY = 48;  // Y position
-        int barWidth = 8;
-        int barHeight = 8;
+        // Draw 5 vertical battery bars (portrait mode)
+        int barX = 10;  // X position
+        int barY = 85;  // Starting Y position
+        int barWidth = 10;
+        int barHeight = 6;
         int barSpacing = 2;
         
         for (int i = 0; i < 5; i++) {
-            int x = barX + i * (barWidth + barSpacing);
+            int y = barY + i * (barHeight + barSpacing);
             
             // Draw bar outline
-            u8g2.drawFrame(x, barY, barWidth, barHeight);
+            u8g2.drawFrame(barX, y, barWidth, barHeight);
             
-            // Fill bar if it should be active
-            if (i < numBars) {
-                u8g2.drawBox(x + 1, barY + 1, barWidth - 2, barHeight - 2);
+            // Fill bar if it should be active (fill from bottom up)
+            if ((4 - i) < numBars) {
+                u8g2.drawBox(barX + 1, y + 1, barWidth - 2, barHeight - 2);
             }
         }
         
-        // Show percentage for debugging
+        // Show percentage
         float percentage = ((batteryVoltage - minVoltage) / (maxVoltage - minVoltage)) * 100.0;
         if (percentage < 0) percentage = 0;
         if (percentage > 100) percentage = 100;
@@ -347,20 +528,18 @@ void updateDisplay() {
         char percentStr[8];
         sprintf(percentStr, "%.0f%%", percentage);
         u8g2.setFont(u8g2_font_4x6_tr);
-        u8g2.drawStr(70, 63, percentStr);
+        u8g2.drawStr(25, 95, percentStr);
         
-        // Headlight icon at bottom left
+        // Headlight icon at bottom
         if (lightOn) {
             // Draw filled headlight icon when on
-            u8g2.drawCircle(8, 58, 4);      // Outer circle
-            u8g2.drawDisc(8, 58, 2);        // Inner filled circle
-            u8g2.drawLine(8, 54, 8, 50);    // Light beam line
-            u8g2.drawLine(6, 55, 4, 53);    // Left beam
-            u8g2.drawLine(10, 55, 12, 53);  // Right beam
+            u8g2.drawDisc(32, 115, 4);      // Filled outer circle
+            u8g2.drawLine(32, 111, 32, 107); // Light beam line
+            u8g2.drawLine(30, 112, 28, 110); // Left beam
+            u8g2.drawLine(34, 112, 36, 110); // Right beam
         } else {
             // Draw outline headlight icon when off
-            u8g2.drawCircle(8, 58, 4);      // Outer circle only
-            u8g2.drawCircle(8, 58, 2);      // Inner circle outline
+            u8g2.drawCircle(32, 115, 4);    // Outer circle only (outline)
         }
     }
     
@@ -372,7 +551,7 @@ void setup() {
     delay(1000);  // Give time for serial to initialize
     Serial.println();
     Serial.println("=== Mirror HUD Display Starting ===");
-    Serial.println("ESP32 Standard Board");
+    Serial.println("XIAO ESP32-C3 Board Configuration");
     Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
     
     // Initialize OLED display first
@@ -381,6 +560,9 @@ void setup() {
     // Initialize BLE
     initBLE();
     
+    // Initialize buttons
+    initButtons();
+    
     Serial.println("=== Mirror HUD Ready! ===");
     Serial.printf("Free heap after init: %d bytes\n", ESP.getFreeHeap());
 }
@@ -399,6 +581,9 @@ void loop() {
         oldDeviceConnected = deviceConnected;
     }
     
+    // Handle button presses
+    handleButtons();
+    
     // Update display at regular intervals
     if (millis() - lastUpdate > UPDATE_INTERVAL) {
         updateDisplay();

+ 409 - 0
src/main.cpp.backup

@@ -0,0 +1,409 @@
+#include <Arduino.h>
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <SPI.h>
+#include <U8g2lib.h>
+
+// OLED Display Configuration for Waveshare 1.51" Transparent OLED
+// SSD1309 128x64 SPI Interface
+#define OLED_CLK  18   // SPI Clock (SCL/SCK)
+#define OLED_MOSI 23   // SPI Data (SDA/MOSI) 
+#define OLED_RES  16   // Reset
+#define OLED_DC   17   // Data/Command
+#define OLED_CS   5    // Chip Select
+
+// BLE Configuration
+#define SERVICE_UUID        "12345678-1234-1234-1234-123456789abc"
+#define BATTERY_CHAR_UUID   "87654321-4321-4321-4321-cba987654321"
+#define SPEED_CHAR_UUID     "11111111-2222-3333-4444-555555555555"
+#define POWER_CHAR_UUID     "22222222-3333-4444-5555-666666666666"
+#define LIGHT_CHAR_UUID     "33333333-4444-5555-6666-777777777777"
+#define DEVICE_NAME         "mirror-hud"
+
+// Display object for SSD1309 - Hardware SPI
+U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, OLED_CS, OLED_DC, OLED_RES);
+
+// Alternative display objects to try if the above doesn't work:
+// U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2_alt1(U8G2_R0, OLED_CS, OLED_DC, OLED_RES);
+// U8G2_SSD1309_128X64_NONAME0_F_4W_SW_SPI u8g2_alt2(U8G2_R0, OLED_CLK, OLED_MOSI, OLED_CS, OLED_DC, OLED_RES);
+
+// BLE Variables
+BLEServer* pServer = nullptr;
+BLEService* pService = nullptr;
+BLECharacteristic* pBatteryCharacteristic = nullptr;
+BLECharacteristic* pSpeedCharacteristic = nullptr;
+BLECharacteristic* pPowerCharacteristic = nullptr;
+BLECharacteristic* pLightCharacteristic = nullptr;
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Data variables
+float batteryVoltage = 0.0; // Voltage instead of percentage
+float speed = 0.0;          // Speed in km/h
+float power = 0.0;          // Power in watts
+bool lightOn = false;       // Headlight status
+unsigned long lastUpdate = 0;
+const unsigned long UPDATE_INTERVAL = 250; // Update display every 250ms (reduce load)
+bool dataReceived = false;  // Flag to track if we've received data from glove
+// BLE Server Callbacks
+class MyServerCallbacks: public BLEServerCallbacks {
+    void onConnect(BLEServer* pServer) {
+        deviceConnected = true;
+        Serial.println("BLE Client connected to mirror-hud");
+    }
+
+    void onDisconnect(BLEServer* pServer) {
+        deviceConnected = false;
+        Serial.println("BLE Client disconnected from mirror-hud");
+    }
+};
+
+// Battery Level Characteristic Callback
+class BatteryCallbacks: public BLECharacteristicCallbacks {
+    void onWrite(BLECharacteristic* pCharacteristic) {
+        std::string value = pCharacteristic->getValue();
+        
+        if (value.length() == 4) { // Expecting 4 bytes for float
+            memcpy(&batteryVoltage, value.data(), sizeof(float));
+            dataReceived = true;  // Mark that we've received data
+            Serial.print("Received battery voltage: ");
+            Serial.print(batteryVoltage);
+            Serial.println("V");
+        }
+    }
+};
+
+// Speed Characteristic Callback
+class SpeedCallbacks: public BLECharacteristicCallbacks {
+    void onWrite(BLECharacteristic* pCharacteristic) {
+        std::string value = pCharacteristic->getValue();
+        
+        if (value.length() == 4) { // Expecting 4 bytes for float
+            memcpy(&speed, value.data(), sizeof(float));
+            dataReceived = true;  // Mark that we've received data
+            Serial.print("Received speed: ");
+            Serial.print(speed);
+            Serial.println(" km/h");
+        }
+    }
+};
+
+// Power Characteristic Callback
+class PowerCallbacks: public BLECharacteristicCallbacks {
+    void onWrite(BLECharacteristic* pCharacteristic) {
+        std::string value = pCharacteristic->getValue();
+        
+        if (value.length() == 4) { // Expecting 4 bytes for float
+            memcpy(&power, value.data(), sizeof(float));
+            dataReceived = true;  // Mark that we've received data
+            Serial.print("Received power: ");
+            Serial.print(power);
+            Serial.println(" W");
+        }
+    }
+};
+
+// Light Characteristic Callback
+class LightCallbacks: public BLECharacteristicCallbacks {
+    void onWrite(BLECharacteristic* pCharacteristic) {
+        std::string value = pCharacteristic->getValue();
+        
+        if (value.length() == 1) { // Expecting 1 byte for boolean
+            lightOn = (value[0] != 0);
+            dataReceived = true;  // Mark that we've received data
+            Serial.print("Received light status: ");
+            Serial.println(lightOn ? "ON" : "OFF");
+        }
+    }
+};
+void initBLE() {
+    Serial.println("Initializing BLE...");
+    
+    // Initialize BLE
+    BLEDevice::init(DEVICE_NAME);
+    
+    // Create BLE Server
+    pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new MyServerCallbacks());
+    
+    // Create BLE Service
+    pService = pServer->createService(SERVICE_UUID);
+    
+    // Create Battery Characteristic
+    pBatteryCharacteristic = pService->createCharacteristic(
+        BATTERY_CHAR_UUID,
+        BLECharacteristic::PROPERTY_READ |
+        BLECharacteristic::PROPERTY_WRITE |
+        BLECharacteristic::PROPERTY_NOTIFY
+    );
+    pBatteryCharacteristic->setCallbacks(new BatteryCallbacks());
+    pBatteryCharacteristic->addDescriptor(new BLE2902());
+    
+    // Create Speed Characteristic
+    pSpeedCharacteristic = pService->createCharacteristic(
+        SPEED_CHAR_UUID,
+        BLECharacteristic::PROPERTY_READ |
+        BLECharacteristic::PROPERTY_WRITE |
+        BLECharacteristic::PROPERTY_NOTIFY
+    );
+    pSpeedCharacteristic->setCallbacks(new SpeedCallbacks());
+    pSpeedCharacteristic->addDescriptor(new BLE2902());
+    
+    // Create Power Characteristic
+    pPowerCharacteristic = pService->createCharacteristic(
+        POWER_CHAR_UUID,
+        BLECharacteristic::PROPERTY_READ |
+        BLECharacteristic::PROPERTY_WRITE |
+        BLECharacteristic::PROPERTY_NOTIFY
+    );
+    pPowerCharacteristic->setCallbacks(new PowerCallbacks());
+    pPowerCharacteristic->addDescriptor(new BLE2902());
+    
+    // Create Light Characteristic
+    pLightCharacteristic = pService->createCharacteristic(
+        LIGHT_CHAR_UUID,
+        BLECharacteristic::PROPERTY_READ |
+        BLECharacteristic::PROPERTY_WRITE |
+        BLECharacteristic::PROPERTY_NOTIFY
+    );
+    pLightCharacteristic->setCallbacks(new LightCallbacks());
+    pLightCharacteristic->addDescriptor(new BLE2902());
+    
+    // Start the service
+    pService->start();
+    
+    // Start advertising
+    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    pAdvertising->addServiceUUID(SERVICE_UUID);
+    pAdvertising->setScanResponse(false);
+    pAdvertising->setMinPreferred(0x0);
+    BLEDevice::startAdvertising();
+    
+    Serial.println("BLE Server started, advertising as 'mirror-hud'");
+}
+void initDisplay() {
+    Serial.println("=== OLED Display Initialization (SPI) ===");
+    
+    // Configure SPI pins
+    Serial.println("Configuring SPI pins...");
+    pinMode(OLED_CS, OUTPUT);
+    pinMode(OLED_DC, OUTPUT);
+    pinMode(OLED_RES, OUTPUT);
+    
+    // Manual reset sequence
+    Serial.println("Performing manual reset...");
+    digitalWrite(OLED_RES, LOW);
+    delay(100);
+    digitalWrite(OLED_RES, HIGH);
+    delay(100);
+    
+    // Initialize SPI
+    Serial.println("Initializing SPI...");
+    SPI.begin(OLED_CLK, -1, OLED_MOSI, OLED_CS); // CLK, MISO(-1), MOSI, CS
+    
+    Serial.print("SPI pins - CLK:");
+    Serial.print(OLED_CLK);
+    Serial.print(" MOSI:");
+    Serial.print(OLED_MOSI);
+    Serial.print(" CS:");
+    Serial.print(OLED_CS);
+    Serial.print(" DC:");
+    Serial.print(OLED_DC);
+    Serial.print(" RES:");
+    Serial.println(OLED_RES);
+    
+    // Initialize U8g2 with SPI
+    Serial.println("Initializing U8g2 library for SPI...");
+    
+    bool displayInit = u8g2.begin();
+    if (displayInit) {
+        Serial.println("✓ U8g2 SPI initialization successful!");
+    } else {
+        Serial.println("✗ U8g2 SPI initialization failed, but continuing...");
+    }
+    
+    // Force display setup
+    u8g2.clearBuffer();
+    u8g2.setDisplayRotation(U8G2_R0);
+    u8g2.setPowerSave(0);  // Wake up display
+    
+    // Test with simple elements
+    Serial.println("Testing SPI display...");
+    u8g2.clearBuffer();
+    u8g2.drawPixel(64, 32);  // Center pixel
+    u8g2.sendBuffer();
+    delay(500);
+    
+    // Test with rectangle
+    u8g2.clearBuffer();
+    u8g2.drawBox(10, 10, 20, 20);
+    u8g2.sendBuffer();
+    delay(500);
+    
+    // Show "tangerine" boot screen
+    Serial.println("Displaying tangerine boot screen via SPI...");
+    u8g2.clearBuffer();
+    u8g2.setFont(u8g2_font_ncenB18_tr);
+    u8g2.drawStr(15, 35, "tangerine");
+    
+    // Add border to confirm display is working
+    u8g2.drawFrame(0, 0, 128, 64);
+    u8g2.sendBuffer();
+    
+    Serial.println("SPI OLED should now show 'tangerine'!");
+    delay(3000);  // Show "tangerine" for 3 seconds
+    
+    Serial.println("=== SPI OLED Display Initialization Complete ===");
+}
+void updateDisplay() {
+    u8g2.clearBuffer();
+    
+    if (!dataReceived) {
+        // Show "tangerine" screen when no data received
+        u8g2.setFont(u8g2_font_ncenB18_tr);
+        u8g2.drawStr(15, 35, "tangerine");
+        
+        // Show connection status
+        u8g2.setFont(u8g2_font_6x10_tr);
+        if (deviceConnected) {
+            u8g2.drawStr(30, 55, "Connected");
+        } else {
+            u8g2.drawStr(25, 55, "Waiting...");
+        }
+    } else {
+        // Show EUC data when received from glove
+        
+        // Connection status indicator
+        if (deviceConnected) {
+            u8g2.setFont(u8g2_font_4x6_tr);
+            u8g2.drawStr(100, 8, "CONN");
+        } else {
+            u8g2.setFont(u8g2_font_4x6_tr);
+            u8g2.drawStr(95, 8, "DISC");
+        }
+        
+        // Speed Display - Large and prominent
+        u8g2.setFont(u8g2_font_ncenB24_tn);  // Increased from 18 to 24
+        char speedStr[10];
+        dtostrf(speed, 4, 1, speedStr);
+        u8g2.drawStr(5, 35, speedStr);  // Moved left to make room for larger font
+        
+        // "K" instead of "km/h"
+        u8g2.setFont(u8g2_font_ncenB12_tr);  // Larger K
+        u8g2.drawStr(85, 35, "K");
+        
+        // Power Display where km/h used to be
+        u8g2.setFont(u8g2_font_ncenB08_tr);
+        char powerStr[15];
+        sprintf(powerStr, "%.0fW", power);
+        u8g2.drawStr(100, 35, powerStr);
+        
+        // Battery Voltage Display
+        u8g2.setFont(u8g2_font_ncenB08_tr);
+        char voltStr[15];
+        sprintf(voltStr, "%.1fV", batteryVoltage);
+        u8g2.drawStr(10, 55, voltStr);
+        
+        // 5-Bar Battery Display based on actual voltage range
+        // 134.4V = 100%, 117.7V = 44% 
+        float minVoltage = 104.58;   // Calculated 0% voltage
+        float maxVoltage = 134.4;    // 100% voltage
+        
+        // Calculate number of bars to display (0-5)
+        int numBars = 0;
+        if (batteryVoltage >= 134.40) numBars = 5;      // 100%+
+        else if (batteryVoltage >= 128.44) numBars = 4; // 80%+
+        else if (batteryVoltage >= 122.47) numBars = 3; // 60%+
+        else if (batteryVoltage >= 116.51) numBars = 2; // 40%+
+        else if (batteryVoltage >= 110.54) numBars = 1; // 20%+
+        else numBars = 0;                               // <20%
+        
+        // Draw 5 individual battery bars (each 8px wide, 2px spacing)
+        int barX = 70;  // Starting X position
+        int barY = 48;  // Y position
+        int barWidth = 8;
+        int barHeight = 8;
+        int barSpacing = 2;
+        
+        for (int i = 0; i < 5; i++) {
+            int x = barX + i * (barWidth + barSpacing);
+            
+            // Draw bar outline
+            u8g2.drawFrame(x, barY, barWidth, barHeight);
+            
+            // Fill bar if it should be active
+            if (i < numBars) {
+                u8g2.drawBox(x + 1, barY + 1, barWidth - 2, barHeight - 2);
+            }
+        }
+        
+        // Show percentage for debugging
+        float percentage = ((batteryVoltage - minVoltage) / (maxVoltage - minVoltage)) * 100.0;
+        if (percentage < 0) percentage = 0;
+        if (percentage > 100) percentage = 100;
+        
+        char percentStr[8];
+        sprintf(percentStr, "%.0f%%", percentage);
+        u8g2.setFont(u8g2_font_4x6_tr);
+        u8g2.drawStr(70, 63, percentStr);
+        
+        // Headlight icon at bottom left
+        if (lightOn) {
+            // Draw filled headlight icon when on
+            u8g2.drawCircle(8, 58, 4);      // Outer circle
+            u8g2.drawDisc(8, 58, 2);        // Inner filled circle
+            u8g2.drawLine(8, 54, 8, 50);    // Light beam line
+            u8g2.drawLine(6, 55, 4, 53);    // Left beam
+            u8g2.drawLine(10, 55, 12, 53);  // Right beam
+        } else {
+            // Draw outline headlight icon when off
+            u8g2.drawCircle(8, 58, 4);      // Outer circle only
+            u8g2.drawCircle(8, 58, 2);      // Inner circle outline
+        }
+    }
+    
+    // Send buffer to display
+    u8g2.sendBuffer();
+}
+void setup() {
+    Serial.begin(115200);
+    delay(1000);  // Give time for serial to initialize
+    Serial.println();
+    Serial.println("=== Mirror HUD Display Starting ===");
+    Serial.println("ESP32 Standard Board");
+    Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
+    
+    // Initialize OLED display first
+    initDisplay();
+    
+    // Initialize BLE
+    initBLE();
+    
+    Serial.println("=== Mirror HUD Ready! ===");
+    Serial.printf("Free heap after init: %d bytes\n", ESP.getFreeHeap());
+}
+
+void loop() {
+    // Handle BLE disconnection
+    if (!deviceConnected && oldDeviceConnected) {
+        delay(500); // Give time for BLE stack
+        pServer->startAdvertising(); // Restart advertising
+        Serial.println("Restarting BLE advertising...");
+        oldDeviceConnected = deviceConnected;
+    }
+    
+    // Handle BLE connection
+    if (deviceConnected && !oldDeviceConnected) {
+        oldDeviceConnected = deviceConnected;
+    }
+    
+    // Update display at regular intervals
+    if (millis() - lastUpdate > UPDATE_INTERVAL) {
+        updateDisplay();
+        lastUpdate = millis();
+    }
+    
+    delay(10);
+}