main.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. #include <Arduino.h>
  2. #include <BLEDevice.h>
  3. #include <BLEServer.h>
  4. #include <BLEUtils.h>
  5. #include <BLE2902.h>
  6. #include <SPI.h>
  7. #include <U8g2lib.h>
  8. // OLED Display Configuration for Waveshare 1.51" Transparent OLED
  9. // SSD1309 128x64 SPI Interface
  10. #define OLED_CLK 18 // SPI Clock (SCL/SCK)
  11. #define OLED_MOSI 23 // SPI Data (SDA/MOSI)
  12. #define OLED_RES 16 // Reset
  13. #define OLED_DC 17 // Data/Command
  14. #define OLED_CS 5 // Chip Select
  15. // BLE Configuration
  16. #define SERVICE_UUID "12345678-1234-1234-1234-123456789abc"
  17. #define BATTERY_CHAR_UUID "87654321-4321-4321-4321-cba987654321"
  18. #define SPEED_CHAR_UUID "11111111-2222-3333-4444-555555555555"
  19. #define POWER_CHAR_UUID "22222222-3333-4444-5555-666666666666"
  20. #define LIGHT_CHAR_UUID "33333333-4444-5555-6666-777777777777"
  21. #define DEVICE_NAME "mirror-hud"
  22. // Display object for SSD1309 - Hardware SPI
  23. U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, OLED_CS, OLED_DC, OLED_RES);
  24. // Alternative display objects to try if the above doesn't work:
  25. // U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2_alt1(U8G2_R0, OLED_CS, OLED_DC, OLED_RES);
  26. // U8G2_SSD1309_128X64_NONAME0_F_4W_SW_SPI u8g2_alt2(U8G2_R0, OLED_CLK, OLED_MOSI, OLED_CS, OLED_DC, OLED_RES);
  27. // BLE Variables
  28. BLEServer* pServer = nullptr;
  29. BLEService* pService = nullptr;
  30. BLECharacteristic* pBatteryCharacteristic = nullptr;
  31. BLECharacteristic* pSpeedCharacteristic = nullptr;
  32. BLECharacteristic* pPowerCharacteristic = nullptr;
  33. BLECharacteristic* pLightCharacteristic = nullptr;
  34. bool deviceConnected = false;
  35. bool oldDeviceConnected = false;
  36. // Data variables
  37. float batteryVoltage = 0.0; // Voltage instead of percentage
  38. float speed = 0.0; // Speed in km/h
  39. float power = 0.0; // Power in watts
  40. bool lightOn = false; // Headlight status
  41. unsigned long lastUpdate = 0;
  42. const unsigned long UPDATE_INTERVAL = 250; // Update display every 250ms (reduce load)
  43. bool dataReceived = false; // Flag to track if we've received data from glove
  44. // BLE Server Callbacks
  45. class MyServerCallbacks: public BLEServerCallbacks {
  46. void onConnect(BLEServer* pServer) {
  47. deviceConnected = true;
  48. Serial.println("BLE Client connected to mirror-hud");
  49. }
  50. void onDisconnect(BLEServer* pServer) {
  51. deviceConnected = false;
  52. Serial.println("BLE Client disconnected from mirror-hud");
  53. }
  54. };
  55. // Battery Level Characteristic Callback
  56. class BatteryCallbacks: public BLECharacteristicCallbacks {
  57. void onWrite(BLECharacteristic* pCharacteristic) {
  58. std::string value = pCharacteristic->getValue();
  59. if (value.length() == 4) { // Expecting 4 bytes for float
  60. memcpy(&batteryVoltage, value.data(), sizeof(float));
  61. dataReceived = true; // Mark that we've received data
  62. Serial.print("Received battery voltage: ");
  63. Serial.print(batteryVoltage);
  64. Serial.println("V");
  65. }
  66. }
  67. };
  68. // Speed Characteristic Callback
  69. class SpeedCallbacks: public BLECharacteristicCallbacks {
  70. void onWrite(BLECharacteristic* pCharacteristic) {
  71. std::string value = pCharacteristic->getValue();
  72. if (value.length() == 4) { // Expecting 4 bytes for float
  73. memcpy(&speed, value.data(), sizeof(float));
  74. dataReceived = true; // Mark that we've received data
  75. Serial.print("Received speed: ");
  76. Serial.print(speed);
  77. Serial.println(" km/h");
  78. }
  79. }
  80. };
  81. // Power Characteristic Callback
  82. class PowerCallbacks: public BLECharacteristicCallbacks {
  83. void onWrite(BLECharacteristic* pCharacteristic) {
  84. std::string value = pCharacteristic->getValue();
  85. if (value.length() == 4) { // Expecting 4 bytes for float
  86. memcpy(&power, value.data(), sizeof(float));
  87. dataReceived = true; // Mark that we've received data
  88. Serial.print("Received power: ");
  89. Serial.print(power);
  90. Serial.println(" W");
  91. }
  92. }
  93. };
  94. // Light Characteristic Callback
  95. class LightCallbacks: public BLECharacteristicCallbacks {
  96. void onWrite(BLECharacteristic* pCharacteristic) {
  97. std::string value = pCharacteristic->getValue();
  98. if (value.length() == 1) { // Expecting 1 byte for boolean
  99. lightOn = (value[0] != 0);
  100. dataReceived = true; // Mark that we've received data
  101. Serial.print("Received light status: ");
  102. Serial.println(lightOn ? "ON" : "OFF");
  103. }
  104. }
  105. };
  106. void initBLE() {
  107. Serial.println("Initializing BLE...");
  108. // Initialize BLE
  109. BLEDevice::init(DEVICE_NAME);
  110. // Create BLE Server
  111. pServer = BLEDevice::createServer();
  112. pServer->setCallbacks(new MyServerCallbacks());
  113. // Create BLE Service
  114. pService = pServer->createService(SERVICE_UUID);
  115. // Create Battery Characteristic
  116. pBatteryCharacteristic = pService->createCharacteristic(
  117. BATTERY_CHAR_UUID,
  118. BLECharacteristic::PROPERTY_READ |
  119. BLECharacteristic::PROPERTY_WRITE |
  120. BLECharacteristic::PROPERTY_NOTIFY
  121. );
  122. pBatteryCharacteristic->setCallbacks(new BatteryCallbacks());
  123. pBatteryCharacteristic->addDescriptor(new BLE2902());
  124. // Create Speed Characteristic
  125. pSpeedCharacteristic = pService->createCharacteristic(
  126. SPEED_CHAR_UUID,
  127. BLECharacteristic::PROPERTY_READ |
  128. BLECharacteristic::PROPERTY_WRITE |
  129. BLECharacteristic::PROPERTY_NOTIFY
  130. );
  131. pSpeedCharacteristic->setCallbacks(new SpeedCallbacks());
  132. pSpeedCharacteristic->addDescriptor(new BLE2902());
  133. // Create Power Characteristic
  134. pPowerCharacteristic = pService->createCharacteristic(
  135. POWER_CHAR_UUID,
  136. BLECharacteristic::PROPERTY_READ |
  137. BLECharacteristic::PROPERTY_WRITE |
  138. BLECharacteristic::PROPERTY_NOTIFY
  139. );
  140. pPowerCharacteristic->setCallbacks(new PowerCallbacks());
  141. pPowerCharacteristic->addDescriptor(new BLE2902());
  142. // Create Light Characteristic
  143. pLightCharacteristic = pService->createCharacteristic(
  144. LIGHT_CHAR_UUID,
  145. BLECharacteristic::PROPERTY_READ |
  146. BLECharacteristic::PROPERTY_WRITE |
  147. BLECharacteristic::PROPERTY_NOTIFY
  148. );
  149. pLightCharacteristic->setCallbacks(new LightCallbacks());
  150. pLightCharacteristic->addDescriptor(new BLE2902());
  151. // Start the service
  152. pService->start();
  153. // Start advertising
  154. BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  155. pAdvertising->addServiceUUID(SERVICE_UUID);
  156. pAdvertising->setScanResponse(false);
  157. pAdvertising->setMinPreferred(0x0);
  158. BLEDevice::startAdvertising();
  159. Serial.println("BLE Server started, advertising as 'mirror-hud'");
  160. }
  161. void initDisplay() {
  162. Serial.println("=== OLED Display Initialization (SPI) ===");
  163. // Configure SPI pins
  164. Serial.println("Configuring SPI pins...");
  165. pinMode(OLED_CS, OUTPUT);
  166. pinMode(OLED_DC, OUTPUT);
  167. pinMode(OLED_RES, OUTPUT);
  168. // Manual reset sequence
  169. Serial.println("Performing manual reset...");
  170. digitalWrite(OLED_RES, LOW);
  171. delay(100);
  172. digitalWrite(OLED_RES, HIGH);
  173. delay(100);
  174. // Initialize SPI
  175. Serial.println("Initializing SPI...");
  176. SPI.begin(OLED_CLK, -1, OLED_MOSI, OLED_CS); // CLK, MISO(-1), MOSI, CS
  177. Serial.print("SPI pins - CLK:");
  178. Serial.print(OLED_CLK);
  179. Serial.print(" MOSI:");
  180. Serial.print(OLED_MOSI);
  181. Serial.print(" CS:");
  182. Serial.print(OLED_CS);
  183. Serial.print(" DC:");
  184. Serial.print(OLED_DC);
  185. Serial.print(" RES:");
  186. Serial.println(OLED_RES);
  187. // Initialize U8g2 with SPI
  188. Serial.println("Initializing U8g2 library for SPI...");
  189. bool displayInit = u8g2.begin();
  190. if (displayInit) {
  191. Serial.println("✓ U8g2 SPI initialization successful!");
  192. } else {
  193. Serial.println("✗ U8g2 SPI initialization failed, but continuing...");
  194. }
  195. // Force display setup
  196. u8g2.clearBuffer();
  197. u8g2.setDisplayRotation(U8G2_R0);
  198. u8g2.setPowerSave(0); // Wake up display
  199. // Test with simple elements
  200. Serial.println("Testing SPI display...");
  201. u8g2.clearBuffer();
  202. u8g2.drawPixel(64, 32); // Center pixel
  203. u8g2.sendBuffer();
  204. delay(500);
  205. // Test with rectangle
  206. u8g2.clearBuffer();
  207. u8g2.drawBox(10, 10, 20, 20);
  208. u8g2.sendBuffer();
  209. delay(500);
  210. // Show "tangerine" boot screen
  211. Serial.println("Displaying tangerine boot screen via SPI...");
  212. u8g2.clearBuffer();
  213. u8g2.setFont(u8g2_font_ncenB18_tr);
  214. u8g2.drawStr(15, 35, "tangerine");
  215. // Add border to confirm display is working
  216. u8g2.drawFrame(0, 0, 128, 64);
  217. u8g2.sendBuffer();
  218. Serial.println("SPI OLED should now show 'tangerine'!");
  219. delay(3000); // Show "tangerine" for 3 seconds
  220. Serial.println("=== SPI OLED Display Initialization Complete ===");
  221. }
  222. void updateDisplay() {
  223. u8g2.clearBuffer();
  224. if (!dataReceived) {
  225. // Show "tangerine" screen when no data received
  226. u8g2.setFont(u8g2_font_ncenB18_tr);
  227. u8g2.drawStr(15, 35, "tangerine");
  228. // Show connection status
  229. u8g2.setFont(u8g2_font_6x10_tr);
  230. if (deviceConnected) {
  231. u8g2.drawStr(30, 55, "Connected");
  232. } else {
  233. u8g2.drawStr(25, 55, "Waiting...");
  234. }
  235. } else {
  236. // Show EUC data when received from glove
  237. // Connection status indicator
  238. if (deviceConnected) {
  239. u8g2.setFont(u8g2_font_4x6_tr);
  240. u8g2.drawStr(100, 8, "CONN");
  241. } else {
  242. u8g2.setFont(u8g2_font_4x6_tr);
  243. u8g2.drawStr(95, 8, "DISC");
  244. }
  245. // Speed Display - Large and prominent
  246. u8g2.setFont(u8g2_font_ncenB24_tn); // Increased from 18 to 24
  247. char speedStr[10];
  248. dtostrf(speed, 4, 1, speedStr);
  249. u8g2.drawStr(5, 35, speedStr); // Moved left to make room for larger font
  250. // "K" instead of "km/h"
  251. u8g2.setFont(u8g2_font_ncenB12_tr); // Larger K
  252. u8g2.drawStr(85, 35, "K");
  253. // Power Display where km/h used to be
  254. u8g2.setFont(u8g2_font_ncenB08_tr);
  255. char powerStr[15];
  256. sprintf(powerStr, "%.0fW", power);
  257. u8g2.drawStr(100, 35, powerStr);
  258. // Battery Voltage Display
  259. u8g2.setFont(u8g2_font_ncenB08_tr);
  260. char voltStr[15];
  261. sprintf(voltStr, "%.1fV", batteryVoltage);
  262. u8g2.drawStr(10, 55, voltStr);
  263. // 5-Bar Battery Display based on actual voltage range
  264. // 134.4V = 100%, 117.7V = 44%
  265. float minVoltage = 104.58; // Calculated 0% voltage
  266. float maxVoltage = 134.4; // 100% voltage
  267. // Calculate number of bars to display (0-5)
  268. int numBars = 0;
  269. if (batteryVoltage >= 134.40) numBars = 5; // 100%+
  270. else if (batteryVoltage >= 128.44) numBars = 4; // 80%+
  271. else if (batteryVoltage >= 122.47) numBars = 3; // 60%+
  272. else if (batteryVoltage >= 116.51) numBars = 2; // 40%+
  273. else if (batteryVoltage >= 110.54) numBars = 1; // 20%+
  274. else numBars = 0; // <20%
  275. // Draw 5 individual battery bars (each 8px wide, 2px spacing)
  276. int barX = 70; // Starting X position
  277. int barY = 48; // Y position
  278. int barWidth = 8;
  279. int barHeight = 8;
  280. int barSpacing = 2;
  281. for (int i = 0; i < 5; i++) {
  282. int x = barX + i * (barWidth + barSpacing);
  283. // Draw bar outline
  284. u8g2.drawFrame(x, barY, barWidth, barHeight);
  285. // Fill bar if it should be active
  286. if (i < numBars) {
  287. u8g2.drawBox(x + 1, barY + 1, barWidth - 2, barHeight - 2);
  288. }
  289. }
  290. // Show percentage for debugging
  291. float percentage = ((batteryVoltage - minVoltage) / (maxVoltage - minVoltage)) * 100.0;
  292. if (percentage < 0) percentage = 0;
  293. if (percentage > 100) percentage = 100;
  294. char percentStr[8];
  295. sprintf(percentStr, "%.0f%%", percentage);
  296. u8g2.setFont(u8g2_font_4x6_tr);
  297. u8g2.drawStr(70, 63, percentStr);
  298. // Headlight icon at bottom left
  299. if (lightOn) {
  300. // Draw filled headlight icon when on
  301. u8g2.drawCircle(8, 58, 4); // Outer circle
  302. u8g2.drawDisc(8, 58, 2); // Inner filled circle
  303. u8g2.drawLine(8, 54, 8, 50); // Light beam line
  304. u8g2.drawLine(6, 55, 4, 53); // Left beam
  305. u8g2.drawLine(10, 55, 12, 53); // Right beam
  306. } else {
  307. // Draw outline headlight icon when off
  308. u8g2.drawCircle(8, 58, 4); // Outer circle only
  309. u8g2.drawCircle(8, 58, 2); // Inner circle outline
  310. }
  311. }
  312. // Send buffer to display
  313. u8g2.sendBuffer();
  314. }
  315. void setup() {
  316. Serial.begin(115200);
  317. delay(1000); // Give time for serial to initialize
  318. Serial.println();
  319. Serial.println("=== Mirror HUD Display Starting ===");
  320. Serial.println("ESP32 Standard Board");
  321. Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
  322. // Initialize OLED display first
  323. initDisplay();
  324. // Initialize BLE
  325. initBLE();
  326. Serial.println("=== Mirror HUD Ready! ===");
  327. Serial.printf("Free heap after init: %d bytes\n", ESP.getFreeHeap());
  328. }
  329. void loop() {
  330. // Handle BLE disconnection
  331. if (!deviceConnected && oldDeviceConnected) {
  332. delay(500); // Give time for BLE stack
  333. pServer->startAdvertising(); // Restart advertising
  334. Serial.println("Restarting BLE advertising...");
  335. oldDeviceConnected = deviceConnected;
  336. }
  337. // Handle BLE connection
  338. if (deviceConnected && !oldDeviceConnected) {
  339. oldDeviceConnected = deviceConnected;
  340. }
  341. // Update display at regular intervals
  342. if (millis() - lastUpdate > UPDATE_INTERVAL) {
  343. updateDisplay();
  344. lastUpdate = millis();
  345. }
  346. delay(10);
  347. }