|
|
@@ -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();
|