diff --git a/README.md b/README.md index ad95e31..8a3186d 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,55 @@ # KIA ENIRO DASHBOARD -OBDII dashboard for TTGO-T4 module (ESP32) + OBD BLE4.0 adapter. Developed for my KIA ENIRO 2020, but could work on Hyundai Kona EV. Contact me if not. +OBDII dashboard for TTGO-T4 module (ESP32) + OBD BLE4.0 adapter. Working on Kia e-NIRO (EV) and Hyundai Kona. +Use it at your own risk! +Author: nick.n17@gmail.com (Lubos Petrovic / Slovakia) -Author: nick.n17@gmail.com (Lubos Petrovic) +## Hardware +- LILYGO TTGO T4 v1.3 + ~USD $30 https://www.banggood.com/LILYGO-TTGO-T-Watcher-BTC-Ticker-ESP32-For-Bitcoin-Price-Program-4M-SPI-Flash-Psram-LCD-Display-Module-p-1345292.html +- OBD BLE4.0 adapter. + Officialy supported is only this model... + Vgate iCar Pro Bluetooth 4.0 (BLE) OBD2 ~USD $30 -## Hardware and software -- TTGO-T4. I used this from banggood (T4 v1.3) ~ USD $30 https://www.banggood.com/LILYGO-TTGO-T-Watcher-BTC-Ticker-ESP32-For-Bitcoin-Price-Program-4M-SPI-Flash-Psram-LCD-Display-Module-p-1345292.html -- OBD BLE4.0 adapter. Ex. Vgate iCar Pro Bluetooth 4.0 (BLE) OBD2 ~ USD $30 -- Software is written for Arduino IDE (ESP32). +## Quick installation with ESP32 flash tool -## Supporting +Guide is here +https://docs.google.com/document/d/1nEezrtXY-8X6mQ1hiZVWDjBVse1sXQg1SlnizaRmJwU/edit?usp=sharing + +## Supporting me - Buy Me a Beer via paypal https://www.paypal.me/nickn17 - EU companies can support me via IBAN/Invoice (my company is non-VAT payer in Slovakia). Thank you for supporting me. +## Screens and shortcuts +- Middle button - menu +- Left button - toggle screens + +Screen list +- no0. blank screen, lcd off +- no1. summary info (default) +- no2. speed kmh + kwh/100km (or kw for discharge) +- no3. battery cells + battery module temperatures +- no4. charging graph +- no5. conspumption table. Can be used to measure available battery capacity! + ![image](https://github.com/nickn17/enirodashboard/blob/master/screenshots/v1.jpg) [![Watch the video](https://github.com/nickn17/enirodashboard/blob/master/screenshots/v0.9.jpg)](https://www.youtube.com/watch?v=Jg5VP2P58Yg&) -## Hardware and software -- TTGO-T4. I used this from banggood (T4 v1.3) ~ USD $30 https://www.banggood.com/LILYGO-TTGO-T-Watcher-BTC-Ticker-ESP32-For-Bitcoin-Price-Program-4M-SPI-Flash-Psram-LCD-Display-Module-p-1345292.html -- OBD BLE4.0 adapter. Ex. Vgate iCar Pro Bluetooth 4.0 (BLE) OBD2 ~ USD $30 -- Software is written for Arduino IDE (ESP32). - ## Release notes +### v1.4 2020-05-29 +- added menu +- Pairing with VGATE iCar Pro BLE4 adapter via menu! +- Installation with flash tool. You don't have to install Arduino and compile sources :) +- New screen 5. Conspumption... Can be used to measure available battery capacity! +- Load/Save settings +- Flip screen vertical option +- Several different improvements + ### v1.1 2020-04-12 - added new screens (switch via left button) - screen 0. (blank screen, lcd off) @@ -44,7 +67,7 @@ Thank you for supporting me. ESP32-TTGO-T4 https://github.com/fdufnews/ESP32-TTGO-T4 -## Installation guide +## Installation from sources - install arduino IDE + ESP32 support - https://github.com/Bodmer/TFT_eSPI - display library - Configure TFT eSPI diff --git a/dist/boot_app0.bin b/dist/boot_app0.bin new file mode 100644 index 0000000..13562ca Binary files /dev/null and b/dist/boot_app0.bin differ diff --git a/dist/bootloader_qio_80m.bin b/dist/bootloader_qio_80m.bin new file mode 100644 index 0000000..944f494 Binary files /dev/null and b/dist/bootloader_qio_80m.bin differ diff --git a/dist/enirodashboard.ino.esp32.bin b/dist/enirodashboard.ino.esp32.bin new file mode 100644 index 0000000..92aa3b7 Binary files /dev/null and b/dist/enirodashboard.ino.esp32.bin differ diff --git a/dist/enirodashboard.ino.partitions.bin b/dist/enirodashboard.ino.partitions.bin new file mode 100644 index 0000000..0e52b4c Binary files /dev/null and b/dist/enirodashboard.ino.partitions.bin differ diff --git a/enirodashboard.ino b/enirodashboard.ino index e751dd5..1554a18 100644 --- a/enirodashboard.ino +++ b/enirodashboard.ino @@ -1,11 +1,13 @@ /* - KIA eNiro Dashboard 1.01, 2020-04-12 - working only with OBD BLE 4.0 adapters ex. Vgate ICar Pro (BLE4.0 version) + KIA eNiro Dashboard 1.4, 2020-05-29 + !! working only with OBD BLE 4.0 adapters + !! Supported adapter is Vgate ICar Pro (must be BLE4.0 version) + !! Not working with standard BLUETOOTH 3 adapters IMPORTANT Replace HM_MAC, serviceUUID, charTxUUID, charRxUUID as described below - !! How to obtain MAC + 3x UUID? (I want to add pairing via buttons later) + !! How to obtain MAC + 3x UUID? You don't need it for Vgate iCar Pro BLE 4.0 adapter Run Android BLE scanner - choose IOS-VLINK device @@ -15,25 +17,24 @@ set charTxUUID with UUID from NOTIFY section set charRxUUID with UUID from WRITE section - Example. - #define HM_MAC "dd:0d:30:50:ed:63" - static BLEUUID serviceUUID("000018f0-0000-1000-8000-00805f9b34fb"); - static BLEUUID charTxUUID("00002af0-0000-1000-8000-00805f9b34fb"); - static BLEUUID charRxUUID("00002af1-0000-1000-8000-00805f9b34fb"); + --- + eNiro/Kona chargins limits depending on battery temperature (min.value of 01-04 battery module) + >= 35°C BMS allows max 180A + >= 25°C without limit 200A + >= 15°C BMS allows max 120A + >= 5°C BMS allows max 90A + >= 1°C BMS allows max 60A + <= 0°C BMS allows max 40A */ #include "SPI.h" #include "TFT_eSPI.h" #include "BLEDevice.h" +#include +#include // PLEASE CHANGE THIS SETTING for your BLE4 uint32_t PIN = 1234; -// Temporary moved to initSettings(). Preparing to load/save settings from flash memory -//#define HM_MAC "dd:0d:30:50:ed:63" // mac ios-vlink cez nRf connect -//static BLEUUID serviceUUID("000018f0-0000-1000-8000-00805f9b34fb"); // nRf connect to ios.vlink / client / dblclick on unknown service - this is service UUID -//static BLEUUID charTxUUID("00002af0-0000-1000-8000-00805f9b34fb"); // UUID from NOTIFY section (one of custom characteristics under unknown service) -//static BLEUUID charRxUUID("00002af1-0000-1000-8000-00805f9b34fb"); // UUID from WRITE section (one of custom characteristics under unknown service) -/////////////////////////////////////////////// // LILYGO TTGO T4 v1.3 BUTTONS #define BUTTON_MIDDLE 37 @@ -74,15 +75,15 @@ char tmpStr2[20]; char tmpStr3[20]; char tmpStr4[20]; -// Main -#define displayScreenCount 4 -byte displayScreen = 1; // 0 - blank screen, 1 - dash board (default), 2 - big speed + kwh/100, 3 - battery cells, 4 - charging graph +// Screens, buttons +#define displayScreenCount 5 +byte displayScreen = 1; // 0 - blank screen, 1 - dash board (default), 2 - big speed + kwh/100, 3 - battery cells, 4 - charging graph, 5 - soc10% CED table bool btnLeftPressed = true; bool btnMiddlePressed = true; bool btnRightPressed = true; // Commands loop -#define commandQueueCount 23 +#define commandQueueCount 26 #define commandQueueLoopFrom 7 String responseRow; String responseRowMerged; @@ -94,71 +95,118 @@ String commandQueue[commandQueueCount] = { "AT I", // Print the version ID "AT E0", // Echo off "AT L0", // Linefeeds off + "AT S0", // Printing of spaces on //"AT SP 6", // Select protocol to ISO 15765-4 CAN (11 bit ID, 500 kbit/s) //"AT AL", // Allow Long (>7 byte) messages //"AT AR", // Automatically receive //"AT H1", // Headers on (debug only) - "AT S0", // Printing of spaces on //"AT D1", // Display of the DLC on //"AT CAF0", // Automatic formatting off "AT DP", - "atst16", + "AT ST16", // Loop from (KIA ENIRO) - "atsh7e4", // BMS + // BMS + "atsh7e4", "220101", // power kw, ... "220102", // cell voltages, screen 3 only "220103", // cell voltages, screen 3 only "220104", // cell voltages, screen 3 only - "220105", // soh, soc, .. - //"220106", - - "atsh7e2", // VMCU + "220105", // soh, soc, .. + "220106", // cooling water temp + + // VMCU + "atsh7e2", "2101", // speed, ... "2102", // aux, ... - + //"atsh7df", //"2106", //"220106", - - "atsh7b3", + + // ECU - Aircondition + "atsh7b3", "220100", // in/out temp - - "atsh7a0", // TMPS? + "220102", // coolant temp1, 2 + + // BCM / TPMS + "atsh7a0", "22c00b", // tire pressure/temp + + // CLUSTER MODULE + "atsh7c6", + "22B002", // odo }; // Menu id/parent/title typedef struct { - uint8_t lang; - char* sound; - char* value; -} menuItem; + int16_t id; + int16_t parentId; + int16_t targetParentId; + char title[50]; + char obdMacAddress[20]; + char serviceUUID[40]; +} MENU_ITEM; -String menu[] = { - "1/0/Vehicle type", - "100/1/Kia eNiro", - "101/1/Hyundai Kona EV", - "2/0/Pair OBD2 BLE adapter", - "3/0/Other", - "30/3/Screen rotation", - "300/30/Normal", - "301/30/Flip vertical", - "4/0/Units", - "40/4/Distance", - "400/40/Kilometers", - "401/40/Miles", - "41/4/Temperature", - "410/41/Celsius", - "411/41/Fahrenheit", - "42/4/Pressure", - "420/42/Bar", - "421/42/Psi", +#define menuItemsCount 24 +bool menuVisible = false; +uint16_t menuCurrent = 0; +uint8_t menuItemSelected = 0; +uint16_t scanningDeviceIndex = 0; +MENU_ITEM menuItems[menuItemsCount] = { + + {0, 0, 0, "<- exit menu"}, + {1, 0, -1, "Vehicle type"}, + {2, 0, -1, "Select OBD2BLE adapter"}, + {3, 0, -1, "Others"}, + //{4, 0, -1, "Units"}, + {8, 0, -1, "Factory reset"}, + {9, 0, -1, "Save settings"}, + + {100, 1, 0, "<- parent menu"}, + {101, 1, -1, "Kia eNiro 2020"}, + + {300, 3, 0, "<- parent menu"}, + {301, 3, -1, "Screen rotation"}, + + /*{400, 4, 0, "<- parent menu"}, + {401, 4, -1, "Distance"}, + {402, 4, -1, "Temperature"}, + {403, 4, -1, "Pressure"}, + */ + {3010, 301, 3, "<- parent menu"}, + {3011, 301, -1, "Normal"}, + {3012, 301, -1, "Flip vertical"}, + /* + {4010, 401, 4, "<- parent menu"}, + {4011, 401, -1, "Kilometers"}, + {4012, 401, -1, "Miles"}, + + {4020, 402, 4, "<- parent menu"}, + {4021, 402, -1, "Celsius"}, + {4022, 402, -1, "Fahrenheit"}, + + {4030, 403, 4, "<- parent menu"}, + {4031, 403, -1, "Bar"}, + {4032, 403, -1, "Psi"},*/ + + {9999, 9998, 0, "List of BLE devices"}, + {10000, 9999, -1, "empty list"}, + {10001, 9999, -1, "-"}, + {10002, 9999, -1, "-"}, + {10003, 9999, -1, "-"}, + {10004, 9999, -1, "-"}, + {10005, 9999, -1, "-"}, + {10006, 9999, -1, "-"}, + {10007, 9999, -1, "-"}, + {10008, 9999, -1, "-"}, + {10009, 9999, -1, "-"}, }; // Structure with realtime values -struct strucParams { +typedef struct { float speedKmh; + float odoKm; float socPerc; float sohPerc; float cumulativeEnergyChargedKWh; @@ -180,6 +228,9 @@ struct strucParams { float batModule02TempC; float batModule03TempC; float batModule04TempC; + float coolingWaterTempC; + float coolantTemp1C; + float coolantTemp2C; float auxPerc; float auxCurrentAmp; float auxVoltage; @@ -194,42 +245,112 @@ struct strucParams { float tireRearRightTempC; float tireRearRightPressureBar; float cellVoltage[98]; // 1..98 has index 0..97 + // Screen - charging graph float chargingGraphKw[101]; // 0..100% .. how many HW in each step float chargingGraphMinTempC[101]; // 0..100% .. Min temp in.C float chargingGraphMaxTempC[101]; // 0..100% .. Max temp in.C -}; + // Screen - consumption info + float soc10ced[11]; // 0..10 (5%, 10%, 20%, 30%, 40%).. (never discharged soc% to 0) + float soc10cec[11]; // 0..10 (5%, 10%, 20%, 30%, 40%).. + float soc10odo[11]; // odo history + time_t soc10time[11]; // time for avg speed +} PARAMS_STRUC; // Setting stored to flash -struct strucSettings { +typedef struct { byte initFlag; // 183 value byte settingsVersion; // 1 + uint16_t carType; // 0 - Kia eNiro 2020 char obdMacAddress[20]; char serviceUUID[40]; char charTxUUID[40]; char charRxUUID[40]; -}; + byte displayRotation; // 0 portrait, 1 landscape, 2.., 3.. + char distanceUnit; // k - kilometers + char temperatureUnit; // c - celsius + char pressureUnit; // b - bar +} SETTINGS_STRUC; -strucParams params; // Realtime sensor values -strucParams oldParams; // Old states used for change detection (draw optimization) -strucSettings settings; // Settings stored into flash +PARAMS_STRUC params; // Realtime sensor values +PARAMS_STRUC oldParams; // Old states used for change detection (draw optimization) +SETTINGS_STRUC settings, tmpSettings; // Settings stored into flash /** - Init settings + Load setting from flash memory, upgrade structure if version differs */ -bool initSettings() { +bool loadSettings() { String tmpStr; + // Init settings.initFlag = 183; settings.settingsVersion = 1; - tmpStr = "dd:0d:30:50:ed:63"; + settings.carType = 0; + + // Default OBD adapter MAC and UUID's + tmpStr = "00:00:00:00:00:00"; // Pair via menu (middle button) tmpStr.toCharArray(settings.obdMacAddress, 18); tmpStr = "000018f0-0000-1000-8000-00805f9b34fb"; tmpStr.toCharArray(settings.serviceUUID, 37); - tmpStr = "00002af0-0000-1000-8000-00805f9b34fb"; + tmpStr = "00002af0-0000-1000-8000-00805f9b34fb"; tmpStr.toCharArray(settings.charTxUUID, 37); - tmpStr = "00002af1-0000-1000-8000-00805f9b34fb"; + tmpStr = "00002af1-0000-1000-8000-00805f9b34fb"; tmpStr.toCharArray(settings.charRxUUID, 37); + + settings.displayRotation = 1; // 1,3 + settings.distanceUnit = 'k'; + settings.temperatureUnit = 'c'; + settings.pressureUnit = 'b'; + + // Load settings and replace default values + Serial.println("Reading settings from eeprom."); + EEPROM.begin(sizeof(SETTINGS_STRUC)); + EEPROM.get(0, tmpSettings); + + // Init flash with default settings + if (tmpSettings.initFlag != 183) { + Serial.println("Settings not found. Initialization."); + saveSettings(); + } else { + Serial.print("Loaded settings ver.: "); + Serial.println(settings.settingsVersion); + // Save version? No need to upgrade structure + if (settings.settingsVersion == tmpSettings.settingsVersion) { + settings = tmpSettings; + } + } + + return true; +} + +/** + Load settings from flash memory, upgrade structure if version differs +*/ +bool saveSettings() { + + // Flash to memory + Serial.println("Settings saved to eeprom."); + EEPROM.put(0, settings); + EEPROM.commit(); + + return true; +} + +/** + Reset settings (factory reset) +*/ +bool resetSettings() { + + // Flash to memory + Serial.println("Factory reset."); + settings.initFlag = 1; + EEPROM.put(0, settings); + EEPROM.commit(); + + displayMessage("Settings erased", "Restarting in 5 seconds"); + + delay(5000); + ESP.restart(); return true; } @@ -240,6 +361,7 @@ bool initSettings() { bool initStructure() { params.speedKmh = -1; + params.odoKm = -1; params.socPerc = -1; params.sohPerc = -1; params.cumulativeEnergyChargedKWh = -1; @@ -261,6 +383,9 @@ bool initStructure() { params.batModule02TempC = -1; params.batModule03TempC = -1; params.batModule04TempC = -1; + params.coolingWaterTempC = -1; + params.coolantTemp1C = -1; + params.coolantTemp2C = -1; params.auxPerc = -1; params.auxCurrentAmp = -1; params.auxVoltage = -1; @@ -274,6 +399,10 @@ bool initStructure() { params.tireRearLeftPressureBar = -1; params.tireRearRightTempC = -1; params.tireRearRightPressureBar = -1; + for (int i = 0; i <= 10; i++) { + params.soc10ced[i] = params.soc10cec[i] = params.soc10odo[i] = -1; + params.soc10time[i] = 0; + } for (int i = 0; i < 98; i++) { params.cellVoltage[i] = 0; } @@ -317,6 +446,21 @@ float hexToDec(String hexString, byte bytes = 2, bool signedNum = true) { return (decValue > 32767 ? (float)decValue - 65536.0 : decValue); } +/** + Clear screen a display two lines message +*/ +bool displayMessage(const char* row1, const char* row2) { + + tft.fillScreen(TFT_BLACK); + tft.setTextDatum(ML_DATUM); + tft.setTextColor(TFT_WHITE, TFT_BLACK); + tft.setFreeFont(&Roboto_Thin_24); + tft.setTextDatum(BL_DATUM); + tft.drawString(row1, 0, 240 / 2, GFXFF); + tft.drawString(row2, 0, (240 / 2) + 30, GFXFF); + return true; +} + /** Draw cell on dashboard */ @@ -429,7 +573,7 @@ bool showTires(int32_t x, int32_t y, int32_t w, int32_t h, const char* topleft, } /** - Main screen (Screen 0) + Main screen (Screen 1) */ bool drawSceneMain(bool force) { @@ -476,7 +620,7 @@ bool drawSceneMain(bool force) { if (force || params.batPowerKwh100 != oldParams.batPowerKwh100) { sprintf(tmpStr1, "%01.01f", params.batPowerKwh100); monitoringRect(1, 1, 2, 2, tmpStr1, "KWH/100KM", (params.batPowerKwh100 >= 0 ? TFT_DARKGREEN2 : (params.batPowerKwh100 < -30.0 ? TFT_RED : TFT_DARKRED)), TFT_WHITE); - oldParams.speedKmh = params.batPowerKwh100; + oldParams.batPowerKwh100 = params.batPowerKwh100; } } else { // batPowerAmp on chargers (under 10kmh) @@ -567,7 +711,7 @@ bool drawSceneMain(bool force) { } /** - Speed + kwh/100km (Screen 1) + Speed + kwh/100km (Screen 2) */ bool drawSceneSpeed(bool force) { @@ -586,18 +730,23 @@ bool drawSceneSpeed(bool force) { posy = 140; tft.setTextDatum(TC_DATUM); // Top center tft.setTextSize(1); - if (params.speedKmh > 25 && params.batPowerKw > 0) { + if (params.speedKmh > 25 && params.batPowerKw < 0) { sprintf(tmpStr3, " %01.01f ", params.batPowerKwh100); } else { sprintf(tmpStr3, " %01.01f ", params.batPowerKw); } tft.drawString(tmpStr3, posx, posy, 7); - + // Bottom 2 numbers with charged/discharged kWh from start + tft.setFreeFont(&Roboto_Thin_24); posx = 10; + posy = 10; + sprintf(tmpStr3, "%01.00fkm ", params.odoKm); + tft.setTextDatum(TL_DATUM); + tft.drawString(tmpStr3, posx, posy, GFXFF); + posy = 240 - 10; sprintf(tmpStr3, "-%01.01f ", params.cumulativeEnergyDischargedKWh - params.cumulativeEnergyDischargedKWhStart); - tft.setFreeFont(&Roboto_Thin_24); tft.setTextDatum(BL_DATUM); tft.drawString(tmpStr3, posx, posy, GFXFF); @@ -605,12 +754,12 @@ bool drawSceneSpeed(bool force) { sprintf(tmpStr3, " +%01.01f", params.cumulativeEnergyChargedKWh - params.cumulativeEnergyChargedKWhStart); tft.setTextDatum(BR_DATUM); tft.drawString(tmpStr3, posx, posy, GFXFF); - + posx = 320 / 2; sprintf(tmpStr3, " %01.01fkw ", params.batPowerKw); tft.setTextDatum(BC_DATUM); tft.drawString(tmpStr3, posx, posy, GFXFF); - + // Battery "cold gate" detection - red < 15C (43KW limit), <25 (blue - 55kW limit), green all ok sprintf(tmpStr3, "%01.00f", params.batTempC); tft.fillCircle(290, 30, 25, (params.batTempC >= 15) ? ((params.batTempC >= 25) ? TFT_DARKGREEN2 : TFT_BLUE) : TFT_RED); @@ -623,7 +772,7 @@ bool drawSceneSpeed(bool force) { } /** - Battery cells (Screen 2) + Battery cells (Screen 3) */ bool drawSceneBatteryCells(bool force) { @@ -633,12 +782,6 @@ bool drawSceneBatteryCells(bool force) { drawSmallRect(0, 0, 1, 1, tmpStr1, "HEATER", TFT_TEMP, TFT_CYAN); sprintf(tmpStr1, "%01.00f C", params.batInletC); drawSmallRect(1, 0, 1, 1, tmpStr1, "BAT.INLET", TFT_TEMP, TFT_CYAN); - /*Not needed yet - sprintf(tmpStr1, "%01.00fC", params.batMinC); - drawSmallRect(2, 0, 1, 1, tmpStr1, "BAT.MIN", TFT_TEMP, TFT_CYAN); - sprintf(tmpStr1, "%01.00fC", params.batMaxC); - drawSmallRect(3, 0, 1, 1, tmpStr1, "BAT.MAX", TFT_TEMP, TFT_CYAN);*/ - sprintf(tmpStr1, "%01.00f C", params.batModule01TempC); drawSmallRect(0, 1, 1, 1, tmpStr1, "MO1", TFT_TEMP, (params.batModule01TempC >= 15) ? ((params.batModule01TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED); sprintf(tmpStr1, "%01.00f C", params.batModule02TempC); @@ -677,7 +820,7 @@ bool drawSceneBatteryCells(bool force) { } /** - Charging graph (Screen 3) + Charging graph (Screen 4) */ bool drawSceneChargingGraph(bool force) { @@ -703,45 +846,30 @@ bool drawSceneChargingGraph(bool force) { drawSmallRect(1, 1, 1, 1, tmpStr1, "BAT.INLET", TFT_TEMP, TFT_CYAN); sprintf(tmpStr1, "%01.00f C", params.batMinC); drawSmallRect(2, 1, 1, 1, tmpStr1, "BAT.MIN", (params.batMinC >= 15) ? ((params.batMinC >= 25) ? TFT_DARKGREEN2 : TFT_BLUE) : TFT_RED, TFT_CYAN); - sprintf(tmpStr1, "%01.00f C", params.batMaxC); - drawSmallRect(3, 1, 1, 1, tmpStr1, "BAT.MAX", TFT_TEMP, TFT_CYAN); + sprintf(tmpStr1, "%01.01f C", params.outdoorTemperature); + drawSmallRect(3, 1, 1, 1, tmpStr1, "OUT.TEMP.", TFT_TEMP, TFT_CYAN); - tft.setTextSize(1); // Size for small 5x7 font - tft.setTextDatum(TR_DATUM); - sprintf(tmpStr1, "%01.00fC", params.batModule01TempC); - tft.setTextColor((params.batModule01TempC >= 15) ? ((params.batModule01TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP); - tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + 00, 2); - sprintf(tmpStr1, "%01.00fC", params.batModule02TempC); - tft.setTextColor((params.batModule02TempC >= 15) ? ((params.batModule02TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP); - tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + 20, 2); - sprintf(tmpStr1, "%01.00fC", params.batModule03TempC); - tft.setTextColor((params.batModule03TempC >= 15) ? ((params.batModule03TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP); - tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + 40, 2); - sprintf(tmpStr1, "%01.00fC", params.batModule04TempC); - tft.setTextColor((params.batModule04TempC >= 15) ? ((params.batModule04TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP); - tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + 60, 2); tft.setTextColor(TFT_SILVER, TFT_TEMP); - - + for (int i = 0; i <= 10; i++) { color = TFT_DARKRED; if (i == 0 || i == 5 || i == 10) color = TFT_NAVY; tft.drawFastVLine(zeroX + (i * 10 * mulX), zeroY - (maxKw * mulY), maxKw * mulY, color); if (i != 0 && i != 10) { - sprintf(tmpStr1, "%d%%", i*10); + sprintf(tmpStr1, "%d%%", i * 10); tft.setTextDatum(BC_DATUM); tft.drawString(tmpStr1, zeroX + (i * 10 * mulX), zeroY - (maxKw * mulY), 2); } if (i <= (maxKw / 10)) { tft.drawFastHLine(zeroX, zeroY - (i * 10 * mulY), 100 * mulX, color); if (i > 0) { - sprintf(tmpStr1, "%d", i*10); + sprintf(tmpStr1, "%d", i * 10); tft.setTextDatum(ML_DATUM); - tft.drawString(tmpStr1, zeroX + (100 * mulX)+ 3, zeroY - (i * 10 * mulY), 2); + tft.drawString(tmpStr1, zeroX + (100 * mulX) + 3, zeroY - (i * 10 * mulY), 2); } } - } + } for (int i = 0; i <= 100; i++) { if (params.chargingGraphKw[i] > 0) @@ -752,6 +880,264 @@ bool drawSceneChargingGraph(bool force) { tft.drawFastHLine(zeroX + (i * mulX) - (mulX / 2), zeroY - (params.chargingGraphMaxTempC[i]*mulY), mulX, TFT_BLUE); } + tft.setTextSize(1); // Size for small 5x7 font + tft.setTextDatum(TR_DATUM); + sprintf(tmpStr1, "1:%01.00fC", params.batModule01TempC); + tft.setTextColor((params.batModule01TempC >= 15) ? ((params.batModule01TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP); + tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (0 * 15), 2); + sprintf(tmpStr1, "2:%01.00fC", params.batModule02TempC); + tft.setTextColor((params.batModule02TempC >= 15) ? ((params.batModule02TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP); + tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (1 * 15), 2); + sprintf(tmpStr1, "3:%01.00fC", params.batModule03TempC); + tft.setTextColor((params.batModule03TempC >= 15) ? ((params.batModule03TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP); + tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (2 * 15), 2); + sprintf(tmpStr1, "4:%01.00fC", params.batModule04TempC); + tft.setTextColor((params.batModule04TempC >= 15) ? ((params.batModule04TempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP); + tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (3 * 15), 2); + sprintf(tmpStr1, "W:%01.00fC", params.coolingWaterTempC); + tft.setTextColor(TFT_CYAN, TFT_TEMP); + tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (4 * 15), 2); + sprintf(tmpStr1, "C1:%01.00fC", params.coolantTemp1C); + tft.setTextColor(TFT_CYAN, TFT_TEMP); + tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (5 * 15), 2); + sprintf(tmpStr1, "C2:%01.00fC", params.coolantTemp2C); + tft.setTextColor(TFT_CYAN, TFT_TEMP); + tft.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (6 * 15), 2); + + return true; +} + +/** + SOC 10% table (screen 5) +*/ +bool drawSceneSoc10Table(bool force) { + + int zeroY = 2; + float diffCec, diffCed, diffOdo = -1; + float firstCed = -1, lastCed = -1, diffCed0to5 = 0; + float firstCec = -1, lastCec = -1, diffCec0to5 = 0; + float firstOdo = -1, lastOdo = -1, diffOdo0to5 = 0; + float diffTime; + tft.setTextSize(1); // Size for small 5x7 font + tft.setTextColor(TFT_SILVER, TFT_TEMP); + tft.setTextDatum(TL_DATUM); + tft.drawString("CONSUMPTION | DISCH.100%->4% SOC", 2, zeroY, 2); + + tft.setTextDatum(TR_DATUM); + + tft.drawString("dis./char.kWh", 128, zeroY + (1 * 15), 2); + tft.drawString("km", 160, zeroY + (1 * 15), 2); + tft.drawString("kWh100", 224, zeroY + (1 * 15), 2); + tft.drawString("avg.speed", 310, zeroY + (1 * 15), 2); + + for (int i = 0; i <= 10; i++) { + sprintf(tmpStr1, "%d%%", (i == 0) ? 5 : i * 10); + tft.drawString(tmpStr1, 32, zeroY + ((12 - i) * 15), 2); + + firstCed = (params.soc10ced[i] != -1) ? params.soc10ced[i] : firstCed; + lastCed = (lastCed == -1 && params.soc10ced[i] != -1) ? params.soc10ced[i] : lastCed; + firstCec = (params.soc10cec[i] != -1) ? params.soc10cec[i] : firstCec; + lastCec = (lastCec == -1 && params.soc10cec[i] != -1) ? params.soc10cec[i] : lastCec; + firstOdo = (params.soc10odo[i] != -1) ? params.soc10odo[i] : firstOdo; + lastOdo = (lastOdo == -1 && params.soc10odo[i] != -1) ? params.soc10odo[i] : lastOdo; + + if (i != 10) { + diffCec = (params.soc10cec[i + 1] != -1 && params.soc10cec[i] != -1) ? (params.soc10cec[i] - params.soc10cec[i + 1]) : 0; + diffCed = (params.soc10ced[i + 1] != -1 && params.soc10ced[i] != -1) ? (params.soc10ced[i + 1] - params.soc10ced[i]) : 0; + diffOdo = (params.soc10odo[i + 1] != -1 && params.soc10odo[i] != -1) ? (params.soc10odo[i] - params.soc10odo[i + 1]) : -1; + diffTime = (params.soc10time[i + 1] != -1 && params.soc10time[i] != -1) ? (params.soc10time[i] - params.soc10time[i + 1]) : -1; + if (diffCec != 0) { + sprintf(tmpStr1, "+%01.01f", diffCec); + tft.drawString(tmpStr1, 128, zeroY + ((12 - i) * 15), 2); + diffCec0to5 = (i == 0) ? diffCec : diffCec0to5; + } + if (diffCed != 0) { + sprintf(tmpStr1, "%01.01f", diffCed); + tft.drawString(tmpStr1, 80, zeroY + ((12 - i) * 15), 2); + diffCed0to5 = (i == 0) ? diffCed : diffCed0to5; + } + if (diffOdo != -1) { + sprintf(tmpStr1, "%01.00f", diffOdo); + tft.drawString(tmpStr1, 160, zeroY + ((12 - i) * 15), 2); + diffOdo0to5 = (i == 0) ? diffOdo : diffOdo0to5; + if (diffTime > 0) { + sprintf(tmpStr1, "%01.01f", diffOdo / (diffTime / 3600)); + tft.drawString(tmpStr1, 310, zeroY + ((12 - i) * 15), 2); + } + } + if (diffOdo > 0 && diffCed != 0) { + sprintf(tmpStr1, "%01.1f", (-diffCed * 100.0 / diffOdo)); + tft.drawString(tmpStr1, 224, zeroY + ((12 - i) * 15), 2); + } + } + + if (diffOdo == -1 && params.soc10odo[i] != -1) { + sprintf(tmpStr1, "%01.00f", params.soc10odo[i]); + tft.drawString(tmpStr1, 160, zeroY + ((12 - i) * 15), 2); + } + } + + tft.drawString("0%", 32, zeroY + (13 * 15), 2); + tft.drawString("0-5% is calculated (same) as 5-10%", 310, zeroY + (13 * 15), 2); + + tft.drawString("TOT.", 32, zeroY + (14 * 15), 2); + diffCed = (lastCed != -1 && firstCed != -1) ? firstCed - lastCed + diffCed0to5 : 0; + sprintf(tmpStr1, "%01.01f", diffCed); + tft.drawString(tmpStr1, 80, zeroY + (14 * 15), 2); + diffCec = (lastCec != -1 && firstCec != -1) ? lastCec - firstCec + diffCec0to5 : 0; + sprintf(tmpStr1, "+%01.01f", diffCec); + tft.drawString(tmpStr1, 128, zeroY + (14 * 15), 2); + diffOdo = (lastOdo != -1 && firstOdo != -1) ? lastOdo - firstOdo + diffOdo0to5 : 0; + sprintf(tmpStr1, "%01.00f", diffOdo); + tft.drawString(tmpStr1, 160, zeroY + (14 * 15), 2); + sprintf(tmpStr1, "AVAIL.CAP: %01.01f kWh", -diffCed - diffCec); + tft.drawString(tmpStr1, 310, zeroY + (14 * 15), 2); + + return true; +} + +/** + Modify caption +*/ +String menuItemCaption(int16_t menuItemId, String title) { + + String prefix = "", suffix = ""; + + if (menuItemId == 401) // distance + suffix = (settings.distanceUnit == 'k') ? "[km]" : "[mi]"; + if (menuItemId == 402) // temperature + suffix = (settings.temperatureUnit == 'c') ? "[C]" : "[F]"; + if (menuItemId == 403) // pressure + suffix = (settings.pressureUnit == 'b') ? "[bar]" : "[psi]"; + + title = ((prefix == "") ? "" : prefix + " ") + title + ((suffix == "") ? "" : " " + suffix); + return title; +} + +/** + Display menu +*/ +bool showMenu() { + + uint16_t posY = 0, tmpCurrMenuItem = 0; + + menuVisible = true; + tft.fillScreen(TFT_BLACK); + tft.setTextDatum(TL_DATUM); + tft.setFreeFont(&Roboto_Thin_24); + + for (uint16_t i = 0; i < menuItemsCount; ++i) { + if (menuCurrent == menuItems[i].parentId) { + tft.fillRect(0, posY, 320, tft.fontHeight() + 2, (menuItemSelected == tmpCurrMenuItem) ? TFT_DARKGREEN2 : TFT_BLACK); + tft.setTextColor((menuItemSelected == tmpCurrMenuItem) ? TFT_WHITE : TFT_WHITE, (menuItemSelected == tmpCurrMenuItem) ? TFT_DARKGREEN2 : TFT_BLACK); + tft.drawString(menuItemCaption(menuItems[i].id, menuItems[i].title), 0, posY + 2, GFXFF); + posY += tft.fontHeight(); + tmpCurrMenuItem++; + } + } + + return true; +} + +/** + Hide menu +*/ +bool hideMenu() { + + menuVisible = false; + menuCurrent = 0; + menuItemSelected = 0; + redrawScreen(true); + + return false; +} + +/** + Move in menu with left/right button +*/ +bool menuMove(bool forward) { + + if (forward) { + uint16_t tmpCount = 0; + for (uint16_t i = 0; i < menuItemsCount; ++i) { + if (menuCurrent == menuItems[i].parentId) { + tmpCount++; + } + } + menuItemSelected = (menuItemSelected > tmpCount) ? tmpCount : menuItemSelected + 1; + } else { + menuItemSelected = (menuItemSelected <= 0) ? 0 : menuItemSelected - 1; + } + showMenu(); + + return true; +} + +/** + Enter menu item +*/ +bool menuItemClick() { + + // Locate menu item for meta data + MENU_ITEM tmpMenuItem; + uint16_t tmpCurrMenuItem = 0; + for (uint16_t i = 0; i < menuItemsCount; ++i) { + if (menuCurrent == menuItems[i].parentId) { + if (menuItemSelected == tmpCurrMenuItem) { + tmpMenuItem = menuItems[i]; + break; + } + tmpCurrMenuItem++; + } + } + + // Exit menu, parent level menu, open item + if (menuItemSelected == 0) { + // Exit menu + if (tmpMenuItem.parentId == 0 && tmpMenuItem.id == 0) { + menuVisible = false; + redrawScreen(true); + } else { + // Parent menu + menuCurrent = tmpMenuItem.targetParentId; + showMenu(); + } + return true; + } else { + Serial.println(tmpMenuItem.id); + // Device list + if (tmpMenuItem.id >= 10000 && tmpMenuItem.id < 10100) { + strlcpy((char*)settings.obdMacAddress, (char*)tmpMenuItem.obdMacAddress, 20); + Serial.print("Selected adapter MAC address "); + Serial.println(settings.obdMacAddress); + saveSettings(); + ESP.restart(); + } + // Other menus + switch (tmpMenuItem.id) { + // Set vehicle type + case 101: settings.carType = 1; break; + // Screen orientation + case 3011: settings.displayRotation = 1; tft.setRotation(settings.displayRotation); break; + case 3012: settings.displayRotation = 3; tft.setRotation(settings.displayRotation); break; + // Pair ble device + case 2: startBleScan(); return false; + // Reset settings + case 8: resetSettings(); hideMenu(); return false; + // Save settings + case 9: saveSettings(); break; + default: + // Submenu + menuCurrent = tmpMenuItem.id; + menuItemSelected = 0; + showMenu(); + return true; + } + + // close menu + hideMenu(); + } + return true; } @@ -760,6 +1146,10 @@ bool drawSceneChargingGraph(bool force) { */ bool redrawScreen(bool force) { + if (menuVisible) { + return false; + } + // Clear screen if needed if (force) { tft.fillScreen(TFT_BLACK); @@ -777,11 +1167,24 @@ bool redrawScreen(bool force) { if (displayScreen == 3) { drawSceneBatteryCells(force); } - // Charging graph + // 4. Charging graph if (displayScreen == 4) { drawSceneChargingGraph(force); } + // 5. SOC10% table (CEC-CED) + if (displayScreen == 5) { + drawSceneSoc10Table(force); + } + // BLE not connected + if (!bleConnected && bleConnect) { + // Print message + tft.setTextSize(1); + tft.setTextColor(TFT_WHITE, TFT_BLACK); + tft.setTextDatum(TL_DATUM); + tft.drawString(" BLE4 OBDII not connected... ", 0, 240 / 2, 2); + tft.drawString(" Press middle button to menu. ", 0, (240 / 2) + tft.fontHeight(), 2); + } return true; } @@ -840,13 +1243,25 @@ bool parseRowMerged() { params.speedKmh = hexToDec(responseRowMerged.substring(32, 36).c_str(), 2, false) * 0.0155; // / 100.0 *1.609 = real to gps is 1.750 } if (commandRequest.equals("2102")) { - params.auxPerc = hexToDec(responseRowMerged.substring(50, 52).c_str(), 1, false); // === OK Valid + params.auxPerc = hexToDec(responseRowMerged.substring(50, 52).c_str(), 1, false); params.auxCurrentAmp = - hexToDec(responseRowMerged.substring(46, 50).c_str(), 2, true) / 1000.0; } - if (commandRequest.equals("220100")) { - params.indoorTemperature = (hexToDec(responseRowMerged.substring(16, 18).c_str(), 1, false) / 2) - 40; // === OK Valid - params.outdoorTemperature = (hexToDec(responseRowMerged.substring(18, 20).c_str(), 1, false) / 2) - 40; // === OK Valid + // Cluster module 7c6 + if (commandRequest.equals("22B002")) { + params.odoKm = float(strtol(responseRowMerged.substring(18, 24).c_str(), 0, 16)); } + + // Aircon 7b3 + if (commandRequest.equals("220100")) { + params.indoorTemperature = (hexToDec(responseRowMerged.substring(16, 18).c_str(), 1, false) / 2) - 40; + params.outdoorTemperature = (hexToDec(responseRowMerged.substring(18, 20).c_str(), 1, false) / 2) - 40; + } + // Aircon 7b3 + if (commandRequest.equals("220102") && responseRowMerged.substring(12, 14) == "00") { + params.coolantTemp1C = (hexToDec(responseRowMerged.substring(14, 16).c_str(), 1, false) / 2) - 40; + params.coolantTemp2C = (hexToDec(responseRowMerged.substring(16, 18).c_str(), 1, false) / 2) - 40; + } + // BMS 7e4 if (commandRequest.equals("220101")) { params.cumulativeEnergyChargedKWh = float(strtol(responseRowMerged.substring(82, 90).c_str(), 0, 16)) / 10.0; if (params.cumulativeEnergyChargedKWhStart == -1) @@ -856,11 +1271,11 @@ bool parseRowMerged() { params.cumulativeEnergyDischargedKWhStart = params.cumulativeEnergyDischargedKWh; params.auxVoltage = hexToDec(responseRowMerged.substring(64, 66).c_str(), 2, true) / 10.0; params.batPowerAmp = - hexToDec(responseRowMerged.substring(26, 30).c_str(), 2, true) / 10.0; - params.batVoltage = hexToDec(responseRowMerged.substring(30, 34).c_str(), 2, false) / 10.0; // === OK Valid + params.batVoltage = hexToDec(responseRowMerged.substring(30, 34).c_str(), 2, false) / 10.0; params.batPowerKw = (params.batPowerAmp * params.batVoltage) / 1000.0; params.batPowerKwh100 = params.batPowerKw / params.speedKmh * 100; - params.batCellMaxV = hexToDec(responseRowMerged.substring(52, 54).c_str(), 1, false) / 50.0; // === OK Valid - params.batCellMinV = hexToDec(responseRowMerged.substring(56, 58).c_str(), 1, false) / 50.0; // === OK Valid + params.batCellMaxV = hexToDec(responseRowMerged.substring(52, 54).c_str(), 1, false) / 50.0; + params.batCellMinV = hexToDec(responseRowMerged.substring(56, 58).c_str(), 1, false) / 50.0; params.batModule01TempC = hexToDec(responseRowMerged.substring(38, 40).c_str(), 1, true); params.batModule02TempC = hexToDec(responseRowMerged.substring(40, 42).c_str(), 1, true); params.batModule03TempC = hexToDec(responseRowMerged.substring(42, 44).c_str(), 1, true); @@ -868,7 +1283,7 @@ bool parseRowMerged() { //params.batTempC = hexToDec(responseRowMerged.substring(36, 38).c_str(), 1, true); //params.batMaxC = hexToDec(responseRowMerged.substring(34, 36).c_str(), 1, true); //params.batMinC = hexToDec(responseRowMerged.substring(36, 38).c_str(), 1, true); - + // This is more accurate than min/max from BMS. It's required to detect kona/eniro cold gates (min 15C is needed > 43kW charging, min 25C is needed > 58kW charging) params.batMinC = params.batMaxC = params.batModule01TempC; params.batMinC = (params.batModule02TempC < params.batMinC) ? params.batModule02TempC : params.batMinC ; @@ -878,7 +1293,7 @@ bool parseRowMerged() { params.batMaxC = (params.batModule03TempC > params.batMaxC) ? params.batModule03TempC : params.batMaxC ; params.batMaxC = (params.batModule04TempC > params.batMaxC) ? params.batModule04TempC : params.batMaxC ; params.batTempC = params.batMinC; - + params.batInletC = hexToDec(responseRowMerged.substring(50, 52).c_str(), 1, true); if (params.speedKmh < 15 && params.batPowerKw >= 1 && params.socPerc > 0 && params.socPerc <= 100) { params.chargingGraphKw[int(params.socPerc)] = params.batPowerKw; @@ -886,39 +1301,62 @@ bool parseRowMerged() { params.chargingGraphMaxTempC[int(params.socPerc)] = params.batMaxC; } } - if (commandRequest.equals("220102")) { + // BMS 7e4 + if (commandRequest.equals("220102") && responseRowMerged.substring(12, 14) == "FF") { for (int i = 0; i < 32; i++) { params.cellVoltage[i] = hexToDec(responseRowMerged.substring(14 + (i * 2), 14 + (i * 2) + 2).c_str(), 1, false) / 50; } } + // BMS 7e4 if (commandRequest.equals("220103")) { for (int i = 0; i < 32; i++) { params.cellVoltage[32 + i] = hexToDec(responseRowMerged.substring(14 + (i * 2), 14 + (i * 2) + 2).c_str(), 1, false) / 50; } } + // BMS 7e4 if (commandRequest.equals("220104")) { for (int i = 0; i < 32; i++) { params.cellVoltage[64 + i] = hexToDec(responseRowMerged.substring(14 + (i * 2), 14 + (i * 2) + 2).c_str(), 1, false) / 50; } } + // BMS 7e4 if (commandRequest.equals("220105")) { params.sohPerc = hexToDec(responseRowMerged.substring(56, 60).c_str(), 2, false) / 10.0; params.socPerc = hexToDec(responseRowMerged.substring(68, 70).c_str(), 1, false) / 2.0; + + // Soc10ced table, record x0% CEC/CED table (ex. 90%->89%, 80%->79%) + if (oldParams.socPerc - params.socPerc > 0) { + byte index = (int(params.socPerc) == 4) ? 0 : (int)(params.socPerc / 10) + 1; + if ((int(params.socPerc) % 10 == 9 || int(params.socPerc) == 4) && params.soc10ced[index] == -1) { + struct tm now; + getLocalTime(&now, 0); + time_t time_now_epoch = mktime(&now); + params.soc10ced[index] = params.cumulativeEnergyDischargedKWh; + params.soc10cec[index] = params.cumulativeEnergyChargedKWh; + params.soc10odo[index] = params.odoKm; + params.soc10time[index] = time_now_epoch; + } + } params.batHeaterC = hexToDec(responseRowMerged.substring(52, 54).c_str(), 1, true); // for (int i = 30; i < 32; i++) { // ai/aj position params.cellVoltage[96 - 30 + i] = hexToDec(responseRowMerged.substring(14 + (i * 2), 14 + (i * 2) + 2).c_str(), 1, false) / 50; } } + // BMS 7e4 + if (commandRequest.equals("220106")) { + params.coolingWaterTempC = hexToDec(responseRowMerged.substring(14, 16).c_str(), 1, false); + } + // TPMS 7a0 if (commandRequest.equals("22c00b")) { params.tireFrontLeftPressureBar = hexToDec(responseRowMerged.substring(14, 16).c_str(), 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722 params.tireFrontRightPressureBar = hexToDec(responseRowMerged.substring(22, 24).c_str(), 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722 - params.tireRearLeftPressureBar = hexToDec(responseRowMerged.substring(30, 32).c_str(), 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722 - params.tireRearRightPressureBar = hexToDec(responseRowMerged.substring(38, 40).c_str(), 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722 - params.tireRearLeftTempC = hexToDec(responseRowMerged.substring(16, 18).c_str(), 2, false) - 50; // === OK Valid - params.tireRearRightTempC = hexToDec(responseRowMerged.substring(24, 26).c_str(), 2, false) - 50; // === OK Valid - params.tireFrontLeftTempC = hexToDec(responseRowMerged.substring(32, 34).c_str(), 2, false) - 50; // === OK Valid - params.tireFrontRightTempC = hexToDec(responseRowMerged.substring(40, 42).c_str(), 2, false) - 50; // === OK Valid + params.tireRearRightPressureBar = hexToDec(responseRowMerged.substring(30, 32).c_str(), 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722 + params.tireRearLeftPressureBar = hexToDec(responseRowMerged.substring(38, 40).c_str(), 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722 + params.tireFrontLeftTempC = hexToDec(responseRowMerged.substring(16, 18).c_str(), 2, false) - 50; // === OK Valid + params.tireFrontRightTempC = hexToDec(responseRowMerged.substring(24, 26).c_str(), 2, false) - 50; // === OK Valid + params.tireRearRightTempC = hexToDec(responseRowMerged.substring(32, 34).c_str(), 2, false) - 50; // === OK Valid + params.tireRearLeftTempC = hexToDec(responseRowMerged.substring(40, 42).c_str(), 2, false) - 50; // === OK Valid } return true; @@ -995,23 +1433,74 @@ bool testData() { responseRowMerged = "62C00BFFFF0000B93D0100B43E0100B43D0100BB3C0100AAAAAAAA"; parseRowMerged(); - params.batModule01TempC = 28; - params.batModule02TempC = 29; - params.batModule03TempC = 28; - params.batModule04TempC = 30; - //params.batTempC = hexToDec(responseRowMerged.substring(36, 38).c_str(), 1, true); - //params.batMaxC = hexToDec(responseRowMerged.substring(34, 36).c_str(), 1, true); - //params.batMinC = hexToDec(responseRowMerged.substring(36, 38).c_str(), 1, true); - - // This is more accurate than min/max from BMS. It's required to detect kona/eniro cold gates (min 15C is needed > 43kW charging, min 25C is needed > 58kW charging) - params.batMinC = params.batMaxC = params.batModule01TempC; - params.batMinC = (params.batModule02TempC < params.batMinC) ? params.batModule02TempC : params.batMinC ; - params.batMinC = (params.batModule03TempC < params.batMinC) ? params.batModule03TempC : params.batMinC ; - params.batMinC = (params.batModule04TempC < params.batMinC) ? params.batModule04TempC : params.batMinC ; - params.batMaxC = (params.batModule02TempC > params.batMaxC) ? params.batModule02TempC : params.batMaxC ; - params.batMaxC = (params.batModule03TempC > params.batMaxC) ? params.batModule03TempC : params.batMaxC ; - params.batMaxC = (params.batModule04TempC > params.batMaxC) ? params.batModule04TempC : params.batMaxC ; - params.batTempC = params.batMinC; + // 22b002 + commandRequest = "22b002"; + responseRowMerged = "62B002E0000000FFB400330B0000000000000000"; + parseRowMerged(); + + params.batModule01TempC = 28; + params.batModule02TempC = 29; + params.batModule03TempC = 28; + params.batModule04TempC = 30; + //params.batTempC = hexToDec(responseRowMerged.substring(36, 38).c_str(), 1, true); + //params.batMaxC = hexToDec(responseRowMerged.substring(34, 36).c_str(), 1, true); + //params.batMinC = hexToDec(responseRowMerged.substring(36, 38).c_str(), 1, true); + + // This is more accurate than min/max from BMS. It's required to detect kona/eniro cold gates (min 15C is needed > 43kW charging, min 25C is needed > 58kW charging) + params.batMinC = params.batMaxC = params.batModule01TempC; + params.batMinC = (params.batModule02TempC < params.batMinC) ? params.batModule02TempC : params.batMinC ; + params.batMinC = (params.batModule03TempC < params.batMinC) ? params.batModule03TempC : params.batMinC ; + params.batMinC = (params.batModule04TempC < params.batMinC) ? params.batModule04TempC : params.batMinC ; + params.batMaxC = (params.batModule02TempC > params.batMaxC) ? params.batModule02TempC : params.batMaxC ; + params.batMaxC = (params.batModule03TempC > params.batMaxC) ? params.batModule03TempC : params.batMaxC ; + params.batMaxC = (params.batModule04TempC > params.batMaxC) ? params.batModule04TempC : params.batMaxC ; + params.batTempC = params.batMinC; + + // + params.soc10ced[10] = 2200; + params.soc10cec[10] = 2500; + params.soc10odo[10] = 13000; + params.soc10time[10] = 13000; + params.soc10ced[9] = params.soc10ced[10] + 6.4; + params.soc10cec[9] = params.soc10cec[10] + 0; + params.soc10odo[9] = params.soc10odo[10] + 30; + params.soc10time[9] = params.soc10time[10] + 900; + params.soc10ced[8] = params.soc10ced[9] + 6.8; + params.soc10cec[8] = params.soc10cec[9] + 0; + params.soc10odo[8] = params.soc10odo[9] + 30; + params.soc10time[8] = params.soc10time[9] + 900; + params.soc10ced[7] = params.soc10ced[8] + 7.2; + params.soc10cec[7] = params.soc10cec[8] + 0.6; + params.soc10odo[7] = params.soc10odo[8] + 30; + params.soc10time[7] = params.soc10time[8] + 900; + params.soc10ced[6] = params.soc10ced[7] + 6.7; + params.soc10cec[6] = params.soc10cec[7] + 0; + params.soc10odo[6] = params.soc10odo[7] + 30; + params.soc10time[6] = params.soc10time[7] + 900; + params.soc10ced[5] = params.soc10ced[6] + 6.7; + params.soc10cec[5] = params.soc10cec[6] + 0; + params.soc10odo[5] = params.soc10odo[6] + 30; + params.soc10time[5] = params.soc10time[6] + 900; + params.soc10ced[4] = params.soc10ced[5] + 6.4; + params.soc10cec[4] = params.soc10cec[5] + 0.3; + params.soc10odo[4] = params.soc10odo[5] + 30; + params.soc10time[4] = params.soc10time[5] + 900; + params.soc10ced[3] = params.soc10ced[4] + 6.4; + params.soc10cec[3] = params.soc10cec[4] + 0; + params.soc10odo[3] = params.soc10odo[4] + 30; + params.soc10time[3] = params.soc10time[4] + 900; + params.soc10ced[2] = params.soc10ced[3] + 5.4; + params.soc10cec[2] = params.soc10cec[3] + 0.1; + params.soc10odo[2] = params.soc10odo[3] + 30; + params.soc10time[2] = params.soc10time[3] + 900; + params.soc10ced[1] = params.soc10ced[2] + 6.2; + params.soc10cec[1] = params.soc10cec[2] + 0.1; + params.soc10odo[1] = params.soc10odo[2] + 30; + params.soc10time[1] = params.soc10time[2] + 900; + params.soc10ced[0] = params.soc10ced[1] + 2.9; + params.soc10cec[0] = params.soc10cec[1] + 0.5; + params.soc10odo[0] = params.soc10odo[1] + 15; + params.soc10time[0] = params.soc10time[1] + 900; redrawScreen(false); return true; @@ -1035,6 +1524,7 @@ class MyClientCallback : public BLEClientCallbacks { void onDisconnect(BLEClient* pclient) { //connected = false; Serial.println("onDisconnect"); + displayMessage("BLE disconnected", ""); } }; @@ -1047,10 +1537,38 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { Called for each advertising BLE server. */ void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE advertised device found: "); Serial.println(advertisedDevice.toString().c_str()); + Serial.println(advertisedDevice.getAddress().toString().c_str()); - if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(settings.serviceUUID))) { + // Add to device list (max. 9 devices allowed yet) + String tmpStr; + if (scanningDeviceIndex < 10/* && advertisedDevice.haveServiceUUID()*/) { + for (uint16_t i = 0; i < menuItemsCount; ++i) { + if (menuItems[i].id == 10000 + scanningDeviceIndex) { + tmpStr = advertisedDevice.toString().c_str(); + tmpStr.replace("Name: ", ""); + tmpStr.replace("Address: ", ""); + tmpStr.toCharArray(menuItems[i].title, 48); + tmpStr = advertisedDevice.getAddress().toString().c_str(); + tmpStr.toCharArray(menuItems[i].obdMacAddress, 18); + } + } + scanningDeviceIndex++; + } + /* + if (advertisedDevice.getServiceDataUUID().toString() != "") { + Serial.print("ServiceDataUUID: "); + Serial.println(advertisedDevice.getServiceDataUUID().toString().c_str()); + if (advertisedDevice.getServiceUUID().toString() != "") { + Serial.print("ServiceUUID: "); + Serial.println(advertisedDevice.getServiceUUID().toString().c_str()); + } + }*/ + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(settings.serviceUUID)) && + (strcmp(advertisedDevice.getAddress().toString().c_str(), settings.obdMacAddress) == 0)) { Serial.println("Stop scanning. Found my BLE device."); BLEDevice::getScan()->stop(); foundMyBleDevice = new BLEAdvertisedDevice(advertisedDevice); @@ -1125,8 +1643,11 @@ static void notifyCallback (BLERemoteCharacteristic* pBLERemoteCharacteristic, u */ bool connectToServer(BLEAddress pAddress) { + displayMessage(" > Connecting device", ""); + Serial.print("bleConnect "); Serial.println(pAddress.toString().c_str()); + displayMessage(" > Connecting device - init", pAddress.toString().c_str()); BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); BLEDevice::setSecurityCallbacks(new MySecurity()); @@ -1136,39 +1657,48 @@ bool connectToServer(BLEAddress pAddress) { pSecurity->setCapability(ESP_IO_CAP_KBDISP); pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + displayMessage(" > Connecting device", pAddress.toString().c_str()); + pClient = BLEDevice::createClient(); pClient->setClientCallbacks(new MyClientCallback()); if ( pClient->connect(pAddress, BLE_ADDR_TYPE_RANDOM) ) Serial.println("bleConnected"); Serial.println(" - bleConnected to server"); + displayMessage(" > Connecting device", "Connecting service..."); // Remote service BLERemoteService* pRemoteService = pClient->getService(BLEUUID(settings.serviceUUID)); if (pRemoteService == nullptr) { Serial.print("Failed to find our service UUID: "); Serial.println(settings.serviceUUID); + displayMessage(" > Connecting device", "Unable to find service"); return false; } Serial.println(" - Found our service"); // Get characteristics + displayMessage(" > Connecting device", "Connecting TxUUID..."); pRemoteCharacteristic = pRemoteService->getCharacteristic(BLEUUID(settings.charTxUUID)); if (pRemoteCharacteristic == nullptr) { Serial.print("Failed to find our characteristic UUID: "); Serial.println(settings.charTxUUID);//.toString().c_str()); + displayMessage(" > Connecting device", "Unable to find TxUUID"); return false; } Serial.println(" - Found our characteristic"); // Get characteristics + displayMessage(" > Connecting device", "Connecting RxUUID..."); pRemoteCharacteristicWrite = pRemoteService->getCharacteristic(BLEUUID(settings.charRxUUID)); if (pRemoteCharacteristicWrite == nullptr) { Serial.print("Failed to find our characteristic UUID: "); Serial.println(settings.charRxUUID);//.toString().c_str()); + displayMessage(" > Connecting device", "Unable to find RxUUID"); return false; } Serial.println(" - Found our characteristic write"); + displayMessage(" > Connecting device", "Register callbacks..."); // Read the value of the characteristic. if (pRemoteCharacteristic->canNotify()) { Serial.println(" - canNotify"); @@ -1184,6 +1714,7 @@ bool connectToServer(BLEAddress pAddress) { } } + displayMessage(" > Connecting device", "Done..."); if (pRemoteCharacteristicWrite->canWrite()) { Serial.println(" - canWrite"); } @@ -1197,26 +1728,38 @@ bool connectToServer(BLEAddress pAddress) { bool startBleScan() { foundMyBleDevice = NULL; - int32_t posx, posy; + scanningDeviceIndex = 0; - // Print message - tft.fillScreen(TFT_BLACK); - tft.setTextDatum(ML_DATUM); - tft.setTextColor(TFT_WHITE, TFT_BLACK); - tft.setFreeFont(&Roboto_Thin_24); - tft.setTextDatum(BL_DATUM); - tft.drawString(" Searching BLE device... ", 0, 240 / 2, GFXFF); + displayMessage(" > Scanning BLE4 devices", "5 seconds"); // Start scanning Serial.println("Scanning BLE devices..."); - BLEScanResults foundDevices = pBLEScan->start(5, false); + Serial.print("Looking for "); + Serial.println(settings.obdMacAddress); + BLEScanResults foundDevices = pBLEScan->start(10, false); Serial.print("Devices found: "); Serial.println(foundDevices.getCount()); Serial.println("Scan done!"); pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory - // Redraw screen - redrawScreen(true); + sprintf(tmpStr1, "Found %d devices", foundDevices.getCount()); + displayMessage(" > Scanning BLE4 devices", tmpStr1); + + // Scan devices from menu, show list of devices + if (menuItemSelected == 2) { + Serial.println("Display menu with devices"); + menuVisible = true; + menuCurrent = 9999; + menuItemSelected = 0; + showMenu(); + } else { + // Redraw screen + if (foundMyBleDevice == NULL) { + displayMessage("Device not found", "Middle button - menu"); + } else { + redrawScreen(true); + } + } return true; } @@ -1230,8 +1773,9 @@ void setup(void) { Serial.begin(115200); Serial.println(""); Serial.println("Booting device..."); + initStructure(); - initSettings(); + loadSettings(); // Set button pins for input pinMode(BUTTON_MIDDLE, INPUT); @@ -1241,10 +1785,15 @@ void setup(void) { // Init display Serial.println("Init TFT display"); tft.begin(); - tft.setRotation(3 ); + tft.setRotation(settings.displayRotation); tft.fillScreen(TFT_BLACK); redrawScreen(true); + // Init time library + struct timeval tv; + tv.tv_sec = 1589011873; + settimeofday(&tv, NULL); + // Show test data on right button during boot device if (digitalRead(BUTTON_RIGHT) == LOW) { displayScreen = 1; @@ -1264,7 +1813,11 @@ void setup(void) { pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); - startBleScan(); + + // Skip BLE scan if middle button pressed + if (digitalRead(BUTTON_MIDDLE) == HIGH) { + startBleScan(); + } // End Serial.println("Device setup completed"); @@ -1278,14 +1831,17 @@ void loop() { //Serial.println("Loop"); // Connect BLE device - if (bleConnect == true && foundMyBleDevice) { + if (bleConnect == true && foundMyBleDevice != NULL) { pServerAddress = new BLEAddress(settings.obdMacAddress); if (connectToServer(*pServerAddress)) { bleConnected = true; bleConnect = false; + Serial.println("We are now connected to the BLE device."); - redrawScreen(true); + + // Print message + displayMessage(" > Processing init AT cmds", ""); // Serve first command (ATZ) doNextAtCommand(); @@ -1320,28 +1876,42 @@ void loop() { } else { if (!btnMiddlePressed) { btnMiddlePressed = true; - // doAction + if (menuVisible) { + menuItemClick(); + } else { + showMenu(); + } } } - if (digitalRead(BUTTON_LEFT) == HIGH) { + if (digitalRead((settings.displayRotation == 1) ? BUTTON_RIGHT : BUTTON_LEFT) == HIGH) { btnLeftPressed = false; } else { if (!btnLeftPressed) { btnLeftPressed = true; - displayScreen++; - if (displayScreen > displayScreenCount) - displayScreen = 0; // rotate screens - // Turn off display on screen 0 - digitalWrite(TFT_BL, (displayScreen == 0) ? LOW : HIGH); - redrawScreen(true); + // Menu handling + if (menuVisible) { + menuMove(false); + } else { + displayScreen++; + if (displayScreen > displayScreenCount) + displayScreen = 0; // rotate screens + // Turn off display on screen 0 + digitalWrite(TFT_BL, (displayScreen == 0) ? LOW : HIGH); + redrawScreen(true); + } } } - if (digitalRead(BUTTON_RIGHT) == HIGH) { + if (digitalRead((settings.displayRotation == 1) ? BUTTON_LEFT : BUTTON_RIGHT) == HIGH) { btnRightPressed = false; } else { if (!btnRightPressed) { btnRightPressed = true; - // doAction + // Menu handling + if (menuVisible) { + menuMove(true); + } else { + // doAction + } } }