| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- #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);
- }
|