Merge pull request #15 from nickn17/release-staging

Release staging
This commit is contained in:
NiCK.n17
2020-12-29 15:36:13 +01:00
committed by GitHub
43 changed files with 2652 additions and 1274 deletions

View File

@@ -1,7 +1,4 @@
#ifndef BOARD320_240_CPP //#include <SD.h>
#define BOARD320_240_CPP
#include <SD.h>
#include <FS.h> #include <FS.h>
#include <analogWrite.h> #include <analogWrite.h>
#include <TFT_eSPI.h> #include <TFT_eSPI.h>
@@ -11,6 +8,10 @@
#include "config.h" #include "config.h"
#include "BoardInterface.h" #include "BoardInterface.h"
#include "Board320_240.h" #include "Board320_240.h"
#include <ArduinoJson.h>
#include "SIM800L.h"
RTC_DATA_ATTR unsigned int bootCount = 0;
/** /**
Init board Init board
@@ -22,8 +23,42 @@ void Board320_240::initBoard() {
pinMode(pinButtonLeft, INPUT); pinMode(pinButtonLeft, INPUT);
pinMode(pinButtonRight, INPUT); pinMode(pinButtonRight, INPUT);
// Init time library
struct timeval tv;
tv.tv_sec = 1589011873;
settimeofday(&tv, NULL);
struct tm now;
getLocalTime(&now, 0);
liveData->params.chargingStartTime = liveData->params.currentTime = mktime(&now);
++bootCount;
syslog->print("Boot count: ");
syslog->println(bootCount);
}
/**
After setup device
*/
void Board320_240::afterSetup() {
if (digitalRead(pinButtonRight) == LOW) {
loadTestData();
}
bool afterSetup = false;
// Check if bard was sleeping
if (bootCount > 1) {
// Init comm device
afterSetup = true;
BoardInterface::afterSetup();
// Wake or continue with sleeping
afterSleep();
}
// Init display // Init display
Serial.println("Init tft display"); syslog->println("Init tft display");
tft.begin(); tft.begin();
tft.invertDisplay(invertDisplay); tft.invertDisplay(invertDisplay);
tft.setRotation(liveData->settings.displayRotation); tft.setRotation(liveData->settings.displayRotation);
@@ -37,12 +72,6 @@ void Board320_240::initBoard() {
#endif #endif
spr.setColorDepth((psramUsed) ? 16 : 8); spr.setColorDepth((psramUsed) ? 16 : 8);
spr.createSprite(320, 240); spr.createSprite(320, 240);
}
/**
After setup device
*/
void Board320_240::afterSetup() {
// Show test data on right button during boot device // Show test data on right button during boot device
displayScreen = liveData->settings.defaultScreen; displayScreen = liveData->settings.defaultScreen;
@@ -54,18 +83,24 @@ void Board320_240::afterSetup() {
// Starting Wifi after BLE prevents reboot loop // Starting Wifi after BLE prevents reboot loop
if (liveData->settings.wifiEnabled == 1) { if (liveData->settings.wifiEnabled == 1) {
/*Serial.print("memReport(): MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM bytes free. "); /*syslog->print("memReport(): MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM bytes free. ");
Serial.println(heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM)); syslog->println(heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM));
syslog->println("WiFi init...");
Serial.println("WiFi init...");
WiFi.enableSTA(true); WiFi.enableSTA(true);
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.begin(liveData->settings.wifiSsid, liveData->settings.wifiPassword); WiFi.begin(liveData->settings.wifiSsid, liveData->settings.wifiPassword);
Serial.println("WiFi init completed...");*/ syslog->println("WiFi init completed...");*/
} }
// Init GPS // Init GPS
if (liveData->settings.gpsHwSerialPort <= 2) { if (liveData->settings.gpsHwSerialPort <= 2) {
syslog->print("GPS initialization on hwUart: ");
syslog->println(liveData->settings.gpsHwSerialPort);
if (liveData->settings.gpsHwSerialPort == 0) {
syslog->println("hwUart0 collision with serial console! Disabling serial console");
syslog->flush();
syslog->end();
}
gpsHwUart = new HardwareSerial(liveData->settings.gpsHwSerialPort); gpsHwUart = new HardwareSerial(liveData->settings.gpsHwSerialPort);
gpsHwUart->begin(9600); gpsHwUart->begin(9600);
} }
@@ -73,11 +108,101 @@ void Board320_240::afterSetup() {
// SD card // SD card
if (liveData->settings.sdcardEnabled == 1) { if (liveData->settings.sdcardEnabled == 1) {
if (sdcardMount() && liveData->settings.sdcardAutstartLog == 1) { if (sdcardMount() && liveData->settings.sdcardAutstartLog == 1) {
syslog->println("Toggle recording on SD card");
sdcardToggleRecording(); sdcardToggleRecording();
} }
} }
BoardInterface::afterSetup(); // Init SIM800L
if (liveData->settings.gprsHwSerialPort <= 2) {
sim800lSetup();
}
// Init comm device
if (!afterSetup) {
BoardInterface::afterSetup();
}
}
/**
Go to Sleep for TIME_TO_SLEEP seconds
*/
void Board320_240::goToSleep() {
//Sleep MCP2515
commInterface->disconnectDevice();
//Sleep SIM800L
if (liveData->params.sim800l_enabled) {
if (sim800l->isConnectedGPRS()) {
bool disconnected = sim800l->disconnectGPRS();
for (uint8_t i = 0; i < 5 && !disconnected; i++) {
delay(1000);
disconnected = sim800l->disconnectGPRS();
}
}
if (sim800l->getPowerMode() == NORMAL) {
sim800l->setPowerMode(SLEEP);
delay(1000);
}
sim800l->enterSleepMode();
}
syslog->println("Going to sleep for " + String(TIME_TO_SLEEP) + " seconds!");
syslog->flush();
delay(1000);
//Sleep ESP32
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * 1000000ULL);
esp_deep_sleep_start();
}
/*
Wake up board from sleep
Iterate thru commands and determine if car is charging or ignition is on
*/
void Board320_240::afterSleep() {
syslog->println("Waking up from sleep mode!");
// Wakeup reason
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch (wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0 : syslog->println("Wakeup caused by external signal using RTC_IO"); break;
case ESP_SLEEP_WAKEUP_EXT1 : syslog->println("Wakeup caused by external signal using RTC_CNTL"); break;
case ESP_SLEEP_WAKEUP_TIMER : syslog->println("Wakeup caused by timer"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD : syslog->println("Wakeup caused by touchpad"); break;
case ESP_SLEEP_WAKEUP_ULP : syslog->println("Wakeup caused by ULP program"); break;
default: syslog->printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break;
}
//
bool firstRun = true;
while (liveData->commandQueueIndex - 1 > liveData->commandQueueLoopFrom || firstRun) {
if (liveData->commandQueueIndex - 1 == liveData->commandQueueLoopFrom) {
firstRun = false;
}
if (millis() > 5000) {
syslog->println("Time's up (5s timeout)...");
goToSleep();
}
commInterface->mainLoop();
}
if (liveData->params.auxVoltage > 5 && liveData->params.auxVoltage < 12) {
syslog->println("AuxBATT too low!");
goToSleep();
} else if (!liveData->params.ignitionOn && !liveData->params.chargingOn) {
syslog->println("Not started & Not charging.");
goToSleep();
} else {
syslog->println("Wake up conditions satisfied... Good morning!");
}
} }
/** /**
@@ -273,9 +398,14 @@ void Board320_240::drawSceneMain() {
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f" : "%01.01f"), liveData->celsius2temperature(liveData->params.batHeaterC)); sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f" : "%01.01f"), liveData->celsius2temperature(liveData->params.batHeaterC));
drawBigCell(2, 3, 1, 1, tmpStr1, "BAT.HEAT", TFT_TEMP, TFT_WHITE); drawBigCell(2, 3, 1, 1, tmpStr1, "BAT.HEAT", TFT_TEMP, TFT_WHITE);
// Aux perc // Aux perc / temp
sprintf(tmpStr1, "%01.00f%%", liveData->params.auxPerc); if (liveData->settings.carType == CAR_BMW_I3_2014) { // TODO: use invalid auxPerc value as decision point here?
drawBigCell(3, 0, 1, 1, tmpStr1, "AUX BAT.", (liveData->params.auxPerc < 60 ? TFT_RED : TFT_DEFAULT_BK), TFT_WHITE); sprintf(tmpStr1, "%01.00f", liveData->params.auxTemperature);
drawBigCell(3, 0, 1, 1, tmpStr1, "AUX TEMP.", (liveData->params.auxTemperature < 5 ? TFT_RED : TFT_DEFAULT_BK), TFT_WHITE);
} else {
sprintf(tmpStr1, "%01.00f%%", liveData->params.auxPerc);
drawBigCell(3, 0, 1, 1, tmpStr1, "AUX BAT.", (liveData->params.auxPerc < 60 ? TFT_RED : TFT_DEFAULT_BK), TFT_WHITE);
}
// Aux amp // Aux amp
sprintf(tmpStr1, (abs(liveData->params.auxCurrentAmp) > 9.9 ? "%01.00f" : "%01.01f"), liveData->params.auxCurrentAmp); sprintf(tmpStr1, (abs(liveData->params.auxCurrentAmp) > 9.9 ? "%01.00f" : "%01.01f"), liveData->params.auxCurrentAmp);
@@ -814,41 +944,6 @@ void Board320_240::drawSceneSoc10Table() {
spr.drawString(tmpStr1, 310, zeroY + (14 * 15), 2); spr.drawString(tmpStr1, 310, zeroY + (14 * 15), 2);
} }
/**
DEBUG screen
*/
void Board320_240::drawSceneDebug() {
int32_t posx, posy;
String chHex, chHex2;
uint8_t chByte;
spr.setTextSize(1); // Size for small 5x7 font
spr.setTextColor(TFT_SILVER, TFT_TEMP);
spr.setTextDatum(TL_DATUM);
spr.drawString(debugAtshRequest, 0, 0, 2);
spr.drawString(debugCommandRequest, 128, 0, 2);
spr.drawString(liveData->commandRequest, 256, 0, 2);
spr.setTextDatum(TR_DATUM);
for (int i = 0; i < debugLastString.length() / 2; i++) {
chHex = debugLastString.substring(i * 2, (i * 2) + 2);
chHex2 = debugPreviousString.substring(i * 2, (i * 2) + 2);
spr.setTextColor(((chHex.equals(chHex2)) ? TFT_SILVER : TFT_GREEN), TFT_TEMP);
chByte = liveData->hexToDec(chHex.c_str(), 1, false);
posx = (((i) % 10) * 32) + 24;
posy = ((floor((i) / 10)) * 32) + 24;
sprintf(tmpStr1, "%03d", chByte);
spr.drawString(tmpStr1, posx + 4, posy, 2);
spr.setTextColor(TFT_YELLOW, TFT_TEMP);
sprintf(tmpStr1, "%c", (char)chByte);
spr.drawString(tmpStr1, posx + 4, posy + 13, 2);
}
debugPreviousString = debugLastString;
}
/** /**
Modify caption Modify caption
*/ */
@@ -869,8 +964,12 @@ String Board320_240::menuItemCaption(int16_t menuItemId, String title) {
case 105: prefix = (liveData->settings.carType == CAR_HYUNDAI_KONA_2020_39) ? ">" : ""; break; case 105: prefix = (liveData->settings.carType == CAR_HYUNDAI_KONA_2020_39) ? ">" : ""; break;
case 106: prefix = (liveData->settings.carType == CAR_RENAULT_ZOE) ? ">" : ""; break; case 106: prefix = (liveData->settings.carType == CAR_RENAULT_ZOE) ? ">" : ""; break;
case 107: prefix = (liveData->settings.carType == CAR_KIA_NIRO_PHEV) ? ">" : ""; break; case 107: prefix = (liveData->settings.carType == CAR_KIA_NIRO_PHEV) ? ">" : ""; break;
case 108: prefix = (liveData->settings.carType == CAR_BMW_I3_2014) ? ">" : ""; break;
case 120: prefix = (liveData->settings.carType == CAR_DEBUG_OBD2_KIA) ? ">" : ""; break; case 120: prefix = (liveData->settings.carType == CAR_DEBUG_OBD2_KIA) ? ">" : ""; break;
// //
case MENU_ADAPTER_BLE4: prefix = (liveData->settings.commType == COMM_TYPE_OBD2BLE4) ? ">" : ""; break;
case MENU_ADAPTER_CAN: prefix = (liveData->settings.commType == COMM_TYPE_OBD2CAN) ? ">" : ""; break;
case MENU_ADAPTER_BT3: prefix = (liveData->settings.commType == COMM_TYPE_OBD2BT3) ? ">" : ""; break;
/*case MENU_WIFI: /*case MENU_WIFI:
suffix = "n/a"; suffix = "n/a";
switch (WiFi.status()) { switch (WiFi.status()) {
@@ -883,14 +982,23 @@ String Board320_240::menuItemCaption(int16_t menuItemId, String title) {
WL_DISCONNECTED: suffix = "DISCONNECTED"; break; WL_DISCONNECTED: suffix = "DISCONNECTED"; break;
} }
break;*/ break;*/
case MENU_GPRS: sprintf(tmpStr1, "[%s] %s", (liveData->settings.gprsEnabled == 1) ? "on" : "off", liveData->settings.gprsApn); suffix = tmpStr1; break; case MENU_GPRS: sprintf(tmpStr1, "[HW UART=%d]", liveData->settings.gprsHwSerialPort); suffix = (liveData->settings.gprsHwSerialPort == 255) ? "[off]" : tmpStr1; break;
case MENU_SDCARD: sprintf(tmpStr1, "[%d] %lluMB", SD.cardType(), SD.cardSize() / (1024 * 1024)); suffix = tmpStr1; break; case MENU_SDCARD: sprintf(tmpStr1, "[%d] %lluMB", SD.cardType(), SD.cardSize() / (1024 * 1024)); suffix = tmpStr1; break;
case MENU_SERIAL_CONSOLE: suffix = (liveData->settings.serialConsolePort == 255) ? "[off]" : "[on]"; break;
case MENU_DEBUG_LEVEL: switch (liveData->settings.debugLevel) {
case 0: suffix = "[all]" ; break;
case 1: suffix = "[comm]" ; break;
case 2: suffix = "[gsm]" ; break;
case 3: suffix = "[sdcard]" ; break;
default: suffix = "[unknown]";
}
break;
case MENU_SCREEN_ROTATION: suffix = (liveData->settings.displayRotation == 1) ? "[vertical]" : "[normal]"; break; case MENU_SCREEN_ROTATION: suffix = (liveData->settings.displayRotation == 1) ? "[vertical]" : "[normal]"; break;
case MENU_DEFAULT_SCREEN: sprintf(tmpStr1, "[%d]", liveData->settings.defaultScreen); suffix = tmpStr1; break; case MENU_DEFAULT_SCREEN: sprintf(tmpStr1, "[%d]", liveData->settings.defaultScreen); suffix = tmpStr1; break;
case MENU_SCREEN_BRIGHTNESS: sprintf(tmpStr1, "[%d%%]", liveData->settings.lcdBrightness); suffix = (liveData->settings.lcdBrightness == 0) ? "[auto]" : tmpStr1; break; case MENU_SCREEN_BRIGHTNESS: sprintf(tmpStr1, "[%d%%]", liveData->settings.lcdBrightness); suffix = (liveData->settings.lcdBrightness == 0) ? "[auto]" : tmpStr1; break;
case MENU_PREDRAWN_GRAPHS: suffix = (liveData->settings.predrawnChargingGraphs == 1) ? "[on]" : "[off]"; break; case MENU_PREDRAWN_GRAPHS: suffix = (liveData->settings.predrawnChargingGraphs == 1) ? "[on]" : "[off]"; break;
case MENU_HEADLIGHTS_REMINDER: suffix = (liveData->settings.headlightsReminder == 1) ? "[on]" : "[off]"; break; case MENU_HEADLIGHTS_REMINDER: suffix = (liveData->settings.headlightsReminder == 1) ? "[on]" : "[off]"; break;
case MENU_DEBUG_SCREEN: suffix = (liveData->settings.debugScreen == 1) ? "[on]" : "[off]"; break; case MENU_SLEEP_MODE: suffix = (liveData->settings.sleepModeEnabled == 1) ? "[on]" : "[off]"; break;
case MENU_GPS: sprintf(tmpStr1, "[HW UART=%d]", liveData->settings.gpsHwSerialPort); suffix = (liveData->settings.gpsHwSerialPort == 255) ? "[off]" : tmpStr1; break; case MENU_GPS: sprintf(tmpStr1, "[HW UART=%d]", liveData->settings.gpsHwSerialPort); suffix = (liveData->settings.gpsHwSerialPort == 255) ? "[off]" : tmpStr1; break;
// //
case MENU_SDCARD_ENABLED: sprintf(tmpStr1, "[%s]", (liveData->settings.sdcardEnabled == 1) ? "on" : "off"); suffix = tmpStr1; break; case MENU_SDCARD_ENABLED: sprintf(tmpStr1, "[%s]", (liveData->settings.sdcardEnabled == 1) ? "on" : "off"); suffix = tmpStr1; break;
@@ -899,6 +1007,7 @@ String Board320_240::menuItemCaption(int16_t menuItemId, String title) {
(strlen(liveData->params.sdcardFilename) != 0) ? liveData->params.sdcardFilename : (strlen(liveData->params.sdcardFilename) != 0) ? liveData->params.sdcardFilename :
(liveData->params.sdcardInit) ? "READY" : "MOUNT"); suffix = tmpStr1; break; (liveData->params.sdcardInit) ? "READY" : "MOUNT"); suffix = tmpStr1; break;
case MENU_SDCARD_REC: sprintf(tmpStr1, "[%s]", (liveData->settings.sdcardEnabled == 0) ? "n/a" : (liveData->params.sdcardRecording) ? "STOP" : "START"); suffix = tmpStr1; break; case MENU_SDCARD_REC: sprintf(tmpStr1, "[%s]", (liveData->settings.sdcardEnabled == 0) ? "n/a" : (liveData->params.sdcardRecording) ? "STOP" : "START"); suffix = tmpStr1; break;
case MENU_SDCARD_INTERVAL: sprintf(tmpStr1, "[%d]", liveData->settings.sdcardLogIntervalSec); suffix = tmpStr1; break;
// //
case MENU_WIFI_ENABLED: suffix = (liveData->settings.wifiEnabled == 1) ? "[on]" : "[off]"; break; case MENU_WIFI_ENABLED: suffix = (liveData->settings.wifiEnabled == 1) ? "[on]" : "[off]"; break;
case MENU_WIFI_SSID: sprintf(tmpStr1, "%s", liveData->settings.wifiSsid); suffix = tmpStr1; break; case MENU_WIFI_SSID: sprintf(tmpStr1, "%s", liveData->settings.wifiSsid); suffix = tmpStr1; break;
@@ -1005,12 +1114,12 @@ void Board320_240::menuItemClick() {
// Exit menu, parent level menu, open item // Exit menu, parent level menu, open item
bool showParentMenu = false; bool showParentMenu = false;
if (liveData->menuItemSelected > 0) { if (liveData->menuItemSelected > 0) {
Serial.println(tmpMenuItem->id); syslog->println(tmpMenuItem->id);
// Device list // Device list
if (tmpMenuItem->id > 10000 && tmpMenuItem->id < 10100) { if (tmpMenuItem->id > 10000 && tmpMenuItem->id < 10100) {
strlcpy((char*)liveData->settings.obdMacAddress, (char*)tmpMenuItem->obdMacAddress, 20); strlcpy((char*)liveData->settings.obdMacAddress, (char*)tmpMenuItem->obdMacAddress, 20);
Serial.print("Selected adapter MAC address "); syslog->print("Selected adapter MAC address ");
Serial.println(liveData->settings.obdMacAddress); syslog->println(liveData->settings.obdMacAddress);
saveSettings(); saveSettings();
ESP.restart(); ESP.restart();
} }
@@ -1024,7 +1133,12 @@ void Board320_240::menuItemClick() {
case 105: liveData->settings.carType = CAR_HYUNDAI_KONA_2020_39; showMenu(); return; break; case 105: liveData->settings.carType = CAR_HYUNDAI_KONA_2020_39; showMenu(); return; break;
case 106: liveData->settings.carType = CAR_RENAULT_ZOE; showMenu(); return; break; case 106: liveData->settings.carType = CAR_RENAULT_ZOE; showMenu(); return; break;
case 107: liveData->settings.carType = CAR_KIA_NIRO_PHEV; showMenu(); return; break; case 107: liveData->settings.carType = CAR_KIA_NIRO_PHEV; showMenu(); return; break;
case 108: liveData->settings.carType = CAR_BMW_I3_2014; showMenu(); return; break;
case 120: liveData->settings.carType = CAR_DEBUG_OBD2_KIA; showMenu(); return; break; case 120: liveData->settings.carType = CAR_DEBUG_OBD2_KIA; showMenu(); return; break;
// Comm type
case MENU_ADAPTER_BLE4: liveData->settings.commType = COMM_TYPE_OBD2BLE4; showMenu(); return; break;
case MENU_ADAPTER_CAN: liveData->settings.commType = COMM_TYPE_OBD2CAN; showMenu(); return; break;
case MENU_ADAPTER_BT3: liveData->settings.commType = COMM_TYPE_OBD2BT3; showMenu(); return; break;
// Screen orientation // Screen orientation
case MENU_SCREEN_ROTATION: liveData->settings.displayRotation = (liveData->settings.displayRotation == 1) ? 3 : 1; tft.setRotation(liveData->settings.displayRotation); showMenu(); return; break; case MENU_SCREEN_ROTATION: liveData->settings.displayRotation = (liveData->settings.displayRotation == 1) ? 3 : 1; tft.setRotation(liveData->settings.displayRotation); showMenu(); return; break;
// Default screen // Default screen
@@ -1033,23 +1147,26 @@ void Board320_240::menuItemClick() {
case 3063: liveData->settings.defaultScreen = 3; showParentMenu = true; break; case 3063: liveData->settings.defaultScreen = 3; showParentMenu = true; break;
case 3064: liveData->settings.defaultScreen = 4; showParentMenu = true; break; case 3064: liveData->settings.defaultScreen = 4; showParentMenu = true; break;
case 3065: liveData->settings.defaultScreen = 5; showParentMenu = true; break; case 3065: liveData->settings.defaultScreen = 5; showParentMenu = true; break;
// Debug screen off/on // SleepMode off/on
case MENU_DEBUG_SCREEN: liveData->settings.debugScreen = (liveData->settings.debugScreen == 1) ? 0 : 1; showMenu(); return; break; case MENU_SLEEP_MODE: liveData->settings.sleepModeEnabled = (liveData->settings.sleepModeEnabled == 1) ? 0 : 1; showMenu(); return; break;
case MENU_SCREEN_BRIGHTNESS: liveData->settings.lcdBrightness += 20; if (liveData->settings.lcdBrightness > 100) liveData->settings.lcdBrightness = 0; case MENU_SCREEN_BRIGHTNESS: liveData->settings.lcdBrightness += 20; if (liveData->settings.lcdBrightness > 100) liveData->settings.lcdBrightness = 0;
setBrightness((liveData->settings.lcdBrightness == 0) ? 100 : liveData->settings.lcdBrightness); showMenu(); return; break; setBrightness((liveData->settings.lcdBrightness == 0) ? 100 : liveData->settings.lcdBrightness); showMenu(); return; break;
// Pre-drawn charg.graphs off/on // Pre-drawn charg.graphs off/on
case MENU_PREDRAWN_GRAPHS: liveData->settings.predrawnChargingGraphs = (liveData->settings.predrawnChargingGraphs == 1) ? 0 : 1; showMenu(); return; break; case MENU_PREDRAWN_GRAPHS: liveData->settings.predrawnChargingGraphs = (liveData->settings.predrawnChargingGraphs == 1) ? 0 : 1; showMenu(); return; break;
case MENU_HEADLIGHTS_REMINDER: liveData->settings.headlightsReminder = (liveData->settings.headlightsReminder == 1) ? 0 : 1; showMenu(); return; break; case MENU_HEADLIGHTS_REMINDER: liveData->settings.headlightsReminder = (liveData->settings.headlightsReminder == 1) ? 0 : 1; showMenu(); return; break;
case MENU_GPS: liveData->settings.gpsHwSerialPort = (liveData->settings.gpsHwSerialPort == 2) ? 255 : liveData->settings.gpsHwSerialPort + 1; showMenu(); return; break; case MENU_GPRS: liveData->settings.gprsHwSerialPort = (liveData->settings.gprsHwSerialPort == 2) ? 255 : liveData->settings.gprsHwSerialPort + 1; showMenu(); return; break;
case MENU_GPS: liveData->settings.gpsHwSerialPort = (liveData->settings.gpsHwSerialPort == 2) ? 255 : liveData->settings.gpsHwSerialPort + 1; showMenu(); return; break;
case MENU_SERIAL_CONSOLE: liveData->settings.serialConsolePort = (liveData->settings.serialConsolePort == 0) ? 255 : liveData->settings.serialConsolePort + 1; showMenu(); return; break;
case MENU_DEBUG_LEVEL: liveData->settings.debugLevel = (liveData->settings.debugLevel == 3) ? 0 : liveData->settings.debugLevel + 1; syslog->setDebugLevel(liveData->settings.debugLevel); showMenu(); return; break;
// Wifi menu // Wifi menu
case MENU_WIFI_ENABLED: liveData->settings.wifiEnabled = (liveData->settings.wifiEnabled == 1) ? 0 : 1; showMenu(); return; break; case MENU_WIFI_ENABLED: liveData->settings.wifiEnabled = (liveData->settings.wifiEnabled == 1) ? 0 : 1; showMenu(); return; break;
case MENU_WIFI_SSID: return; break; case MENU_WIFI_SSID: return; break;
case MENU_WIFI_PASSWORD: return; break; case MENU_WIFI_PASSWORD: return; break;
// Sdcard // Sdcard
case MENU_SDCARD_ENABLED: liveData->settings.sdcardEnabled = (liveData->settings.sdcardEnabled == 1) ? 0 : 1; showMenu(); return; break; case MENU_SDCARD_ENABLED: liveData->settings.sdcardEnabled = (liveData->settings.sdcardEnabled == 1) ? 0 : 1; showMenu(); return; break;
case MENU_SDCARD_AUTOSTARTLOG: liveData->settings.sdcardAutstartLog = (liveData->settings.sdcardAutstartLog == 1) ? 0 : 1; showMenu(); return; break; case MENU_SDCARD_AUTOSTARTLOG: liveData->settings.sdcardAutstartLog = (liveData->settings.sdcardAutstartLog == 1) ? 0 : 1; showMenu(); return; break;
case MENU_SDCARD_MOUNT_STATUS: sdcardMount(); break; case MENU_SDCARD_MOUNT_STATUS: sdcardMount(); break;
case MENU_SDCARD_REC: sdcardToggleRecording(); showMenu(); return; break; case MENU_SDCARD_REC: sdcardToggleRecording(); showMenu(); return; break;
// Distance // Distance
case 4011: liveData->settings.distanceUnit = 'k'; showParentMenu = true; break; case 4011: liveData->settings.distanceUnit = 'k'; showParentMenu = true; break;
case 4012: liveData->settings.distanceUnit = 'm'; showParentMenu = true; break; case 4012: liveData->settings.distanceUnit = 'm'; showParentMenu = true; break;
@@ -1060,13 +1177,40 @@ void Board320_240::menuItemClick() {
case 4031: liveData->settings.pressureUnit = 'b'; showParentMenu = true; break; case 4031: liveData->settings.pressureUnit = 'b'; showParentMenu = true; break;
case 4032: liveData->settings.pressureUnit = 'p'; showParentMenu = true; break; case 4032: liveData->settings.pressureUnit = 'p'; showParentMenu = true; break;
// Pair ble device // Pair ble device
case 2: scanDevices = true; /*startBleScan(); */return; case 2:
if (liveData->settings.commType == COMM_TYPE_OBD2CAN) {
displayMessage("Not supported", "in CAN mode");
delay(3000);
hideMenu();
return;
}
scanDevices = true;
liveData->menuCurrent = 9999;
commInterface->scanDevices();
return;
// Reset settings // Reset settings
case 8: resetSettings(); hideMenu(); return; case 8: resetSettings(); hideMenu(); return;
// Save settings // Save settings
case 9: saveSettings(); break; case 9: saveSettings(); break;
// Version // Version
case 10: hideMenu(); return; case 10:
/* commInterface->executeCommand("ATSH770");
delay(50);
commInterface->executeCommand("3E");
delay(50);
commInterface->executeCommand("1003");
delay(50);
commInterface->executeCommand("2FBC1003");
delay(5000);
commInterface->executeCommand("ATSH770");
delay(50);
commInterface->executeCommand("3E");
delay(50);
commInterface->executeCommand("1003");
delay(50);
commInterface->executeCommand("2FBC1103");
delay(5000);*/
hideMenu(); return;
// Shutdown // Shutdown
case 11: shutdownDevice(); return; case 11: shutdownDevice(); return;
default: default:
@@ -1094,7 +1238,7 @@ void Board320_240::menuItemClick() {
} }
} }
liveData->menuCurrent = parentMenu; liveData->menuCurrent = parentMenu;
Serial.println(liveData->menuCurrent); syslog->println(liveData->menuCurrent);
showMenu(); showMenu();
} }
return; return;
@@ -1146,7 +1290,7 @@ void Board320_240::redrawScreen() {
drawSceneMain(); drawSceneMain();
} }
} else { } else {
displayScreenAutoMode = SCREEN_DASH; displayScreenAutoMode = SCREEN_DASH;
} }
// 2. Main screen // 2. Main screen
if (displayScreen == SCREEN_DASH) { if (displayScreen == SCREEN_DASH) {
@@ -1168,18 +1312,14 @@ void Board320_240::redrawScreen() {
if (displayScreen == SCREEN_SOC10) { if (displayScreen == SCREEN_SOC10) {
drawSceneSoc10Table(); drawSceneSoc10Table();
} }
// 7. DEBUG SCREEN
if (displayScreen == SCREEN_DEBUG) {
drawSceneDebug();
}
if (!displayScreenSpeedHud) { if (!displayScreenSpeedHud) {
// SDCARD recording // SDCARD recording
/*liveData->params.sdcardRecording*/ /*liveData->params.sdcardRecording*/
if (liveData->settings.sdcardEnabled == 1) { if (liveData->settings.sdcardEnabled == 1 && (liveData->params.mainLoopCounter & 1) == 1) {
spr.fillCircle((displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED) ? 160 : 310, 10, 4, TFT_BLACK); spr.fillCircle((displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED) ? 140 : 310, 10, 4, TFT_BLACK);
spr.fillCircle((displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED) ? 160 : 310, 10, 3, spr.fillCircle((displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED) ? 140 : 310, 10, 3,
(liveData->params.sdcardInit == 1) ? (liveData->params.sdcardInit == 1) ?
(liveData->params.sdcardRecording) ? (liveData->params.sdcardRecording) ?
(strlen(liveData->params.sdcardFilename) != 0) ? (strlen(liveData->params.sdcardFilename) != 0) ?
@@ -1189,18 +1329,32 @@ void Board320_240::redrawScreen() {
TFT_YELLOW /* failed to initialize sdcard */ TFT_YELLOW /* failed to initialize sdcard */
); );
} }
// GPS state
if (gpsHwUart != NULL && (displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED)) { if (gpsHwUart != NULL && (displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED)) {
spr.drawCircle(180, 10, 5, (gps.location.isValid()) ? TFT_GREEN : TFT_RED); spr.drawCircle(160, 10, 5, (gps.location.isValid()) ? TFT_GREEN : TFT_RED);
spr.setTextSize(1); spr.setTextSize(1);
spr.setTextColor((gps.location.isValid()) ? TFT_GREEN : TFT_WHITE, TFT_BLACK); spr.setTextColor((gps.location.isValid()) ? TFT_GREEN : TFT_WHITE, TFT_BLACK);
spr.setTextDatum(TL_DATUM); spr.setTextDatum(TL_DATUM);
sprintf(tmpStr1, "%d", liveData->params.gpsSat); sprintf(tmpStr1, "%d", liveData->params.gpsSat);
spr.drawString(tmpStr1, 194, 2, 2); spr.drawString(tmpStr1, 174, 2, 2);
} }
// Door status
if (liveData->params.trunkDoorOpen)
spr.fillRect(20, 0, 320 - 40, 20, TFT_YELLOW);
if (liveData->params.leftFrontDoorOpen)
spr.fillRect(0, 20, 20, 98, TFT_YELLOW);
if (liveData->params.rightFrontDoorOpen)
spr.fillRect(0, 122, 20, 98, TFT_YELLOW);
if (liveData->params.leftRearDoorOpen)
spr.fillRect(320 - 20, 20, 20, 98, TFT_YELLOW);
if (liveData->params.rightRearDoorOpen)
spr.fillRect(320 - 20, 122, 20, 98, TFT_YELLOW);
if (liveData->params.hoodDoorOpen)
spr.fillRect(20, 240 - 20, 320 - 40, 20, TFT_YELLOW);
// BLE not connected // BLE not connected
if (!liveData->bleConnected && liveData->bleConnect) { if (!liveData->commConnected && liveData->bleConnect && liveData->tmpSettings.commType == COMM_TYPE_OBD2BLE4) {
// Print message // Print message
spr.setTextSize(1); spr.setTextSize(1);
spr.setTextColor(TFT_WHITE, TFT_BLACK); spr.setTextColor(TFT_WHITE, TFT_BLACK);
@@ -1209,7 +1363,7 @@ void Board320_240::redrawScreen() {
spr.drawString("Press middle button to menu.", 0, 200, 2); spr.drawString("Press middle button to menu.", 0, 200, 2);
spr.drawString(APP_VERSION, 0, 220, 2); spr.drawString(APP_VERSION, 0, 220, 2);
} }
spr.pushSprite(0, 0); spr.pushSprite(0, 0);
} }
} }
@@ -1219,6 +1373,8 @@ void Board320_240::redrawScreen() {
*/ */
void Board320_240::loadTestData() { void Board320_240::loadTestData() {
syslog->println("Loading test data");
testDataMode = true; // skip lights off message testDataMode = true; // skip lights off message
carInterface->loadTestData(); carInterface->loadTestData();
redrawScreen(); redrawScreen();
@@ -1229,6 +1385,8 @@ void Board320_240::loadTestData() {
*/ */
void Board320_240::mainLoop() { void Board320_240::mainLoop() {
liveData->params.mainLoopCounter++;
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
// Handle buttons // Handle buttons
// MIDDLE - menu select // MIDDLE - menu select
@@ -1257,7 +1415,7 @@ void Board320_240::mainLoop() {
menuMove(false); menuMove(false);
} else { } else {
displayScreen++; displayScreen++;
if (displayScreen > displayScreenCount - (liveData->settings.debugScreen == 0) ? 1 : 0) if (displayScreen > displayScreenCount - 1)
displayScreen = 0; // rotate screens displayScreen = 0; // rotate screens
// Turn off display on screen 0 // Turn off display on screen 0
setBrightness((displayScreen == SCREEN_BLANK) ? 0 : (liveData->settings.lcdBrightness == 0) ? 100 : liveData->settings.lcdBrightness); setBrightness((displayScreen == SCREEN_BLANK) ? 0 : (liveData->settings.lcdBrightness == 0) ? 100 : liveData->settings.lcdBrightness);
@@ -1281,11 +1439,6 @@ void Board320_240::mainLoop() {
displayScreenSpeedHud = !displayScreenSpeedHud; displayScreenSpeedHud = !displayScreenSpeedHud;
redrawScreen(); redrawScreen();
} }
if (liveData->settings.debugScreen == 1 && displayScreen == SCREEN_DEBUG) {
debugCommandIndex = (debugCommandIndex >= liveData->commandQueueCount) ? liveData->commandQueueLoopFrom : debugCommandIndex + 1;
redrawScreen();
}
} }
} }
} }
@@ -1305,6 +1458,12 @@ void Board320_240::mainLoop() {
syncGPS(); syncGPS();
} }
// SIM800L
if (liveData->params.lastDataSent + SIM800L_TIMER < liveData->params.currentTime && liveData->params.sim800l_enabled) {
sendDataViaGPRS();
liveData->params.lastDataSent = liveData->params.currentTime;
}
// currentTime // currentTime
struct tm now; struct tm now;
getLocalTime(&now, 0); getLocalTime(&now, 0);
@@ -1312,20 +1471,20 @@ void Board320_240::mainLoop() {
// SD card recording // SD card recording
if (liveData->params.sdcardInit && liveData->params.sdcardRecording && liveData->params.sdcardCanNotify && if (liveData->params.sdcardInit && liveData->params.sdcardRecording && liveData->params.sdcardCanNotify &&
(liveData->params.odoKm != -1 && liveData->params.socPerc != -1)) { (liveData->params.odoKm != -1 && liveData->params.socPerc != -1)) {
//syslog->println(&now, "%y%m%d%H%M");
//Serial.println(&now, "%y%m%d%H%M");
// create filename // create filename
if (liveData->params.operationTimeSec > 0 && strlen(liveData->params.sdcardFilename) == 0) { if (liveData->params.operationTimeSec > 0 && strlen(liveData->params.sdcardFilename) == 0) {
sprintf(liveData->params.sdcardFilename, "/%llu.json", uint64_t(liveData->params.operationTimeSec / 60)); sprintf(liveData->params.sdcardFilename, "/%llu.json", uint64_t(liveData->params.operationTimeSec / 60));
Serial.print("Log filename by opTimeSec: "); syslog->print("Log filename by opTimeSec: ");
Serial.println(liveData->params.sdcardFilename); syslog->println(liveData->params.sdcardFilename);
} }
if (liveData->params.currTimeSyncWithGps && strlen(liveData->params.sdcardFilename) < 15) { if (liveData->params.currTimeSyncWithGps && strlen(liveData->params.sdcardFilename) < 15) {
strftime(liveData->params.sdcardFilename, sizeof(liveData->params.sdcardFilename), "/%y%m%d%H%M.json", &now); strftime(liveData->params.sdcardFilename, sizeof(liveData->params.sdcardFilename), "/%y%m%d%H%M.json", &now);
Serial.print("Log filename by GPS: "); syslog->print("Log filename by GPS: ");
Serial.println(liveData->params.sdcardFilename); syslog->println(liveData->params.sdcardFilename);
} }
// append buffer, clear buffer & notify state // append buffer, clear buffer & notify state
@@ -1333,14 +1492,14 @@ void Board320_240::mainLoop() {
liveData->params.sdcardCanNotify = false; liveData->params.sdcardCanNotify = false;
File file = SD.open(liveData->params.sdcardFilename, FILE_APPEND); File file = SD.open(liveData->params.sdcardFilename, FILE_APPEND);
if (!file) { if (!file) {
Serial.println("Failed to open file for appending"); syslog->println("Failed to open file for appending");
File file = SD.open(liveData->params.sdcardFilename, FILE_WRITE); File file = SD.open(liveData->params.sdcardFilename, FILE_WRITE);
} }
if (!file) { if (!file) {
Serial.println("Failed to create file"); syslog->println("Failed to create file");
} }
if (file) { if (file) {
Serial.println("Save buffer to SD card"); syslog->println("Save buffer to SD card");
serializeParamsToJson(file); serializeParamsToJson(file);
file.print(",\n"); file.print(",\n");
file.close(); file.close();
@@ -1348,9 +1507,24 @@ void Board320_240::mainLoop() {
} }
} }
// Shutdown when car is off // Turn off display if Ignition is off for more than 10s, less than month (prevent sleep when gps time is synchronized)
if (liveData->params.automaticShutdownTimer != 0 && liveData->params.currentTime - liveData->params.automaticShutdownTimer > 5) if (liveData->params.currentTime - liveData->params.lastIgnitionOnTime > 10 && liveData->params.currentTime - liveData->params.lastIgnitionOnTime < MONTH_SEC
shutdownDevice(); && liveData->params.lastIgnitionOnTime != 0
&& liveData->settings.sleepModeEnabled) {
setBrightness(0);
} else {
setBrightness((liveData->settings.lcdBrightness == 0) ? 100 : liveData->settings.lcdBrightness);
}
// Go to sleep when car is off for more than 30s and not charging (AC charger is disabled for few seconds when ignition is turned off)
if (liveData->params.currentTime - liveData->params.lastIgnitionOnTime > 30 && liveData->params.currentTime - liveData->params.lastIgnitionOnTime < MONTH_SEC
&& !liveData->params.chargingOn
&& liveData->params.lastIgnitionOnTime != 0
&& liveData->settings.sleepModeEnabled)
goToSleep();
// Read data from BLE/CAN
commInterface->mainLoop();
} }
/** /**
@@ -1366,43 +1540,53 @@ bool Board320_240::skipAdapterScan() {
bool Board320_240::sdcardMount() { bool Board320_240::sdcardMount() {
if (liveData->params.sdcardInit) { if (liveData->params.sdcardInit) {
Serial.print("SD card already mounted..."); syslog->print("SD card already mounted...");
return true; return true;
} }
int8_t countdown = 3; int8_t countdown = 3;
while (1) { bool SdState = false;
Serial.print("Initializing SD card...");
if (SD.begin(pinSdcardCs)) { while (1) {
syslog->print("Initializing SD card...");
/* syslog->print(" TTGO-T4 ");
SPIClass * hspi = new SPIClass(HSPI);
spiSD.begin(pinSdcardSclk, pinSdcardMiso, pinSdcardMosi, pinSdcardCs); //SCK,MISO,MOSI,ss
SdState = SD.begin(pinSdcardCs, *hspi, SPI_FREQUENCY);*/
syslog->print(" M5STACK ");
SdState = SD.begin(pinSdcardCs);
if (SdState) {
uint8_t cardType = SD.cardType(); uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) { if (cardType == CARD_NONE) {
Serial.println("No SD card attached"); syslog->println("No SD card attached");
return false; return false;
} }
Serial.println("SD card found."); syslog->println("SD card found.");
liveData->params.sdcardInit = true; liveData->params.sdcardInit = true;
Serial.print("SD Card Type: "); syslog->print("SD Card Type: ");
if (cardType == CARD_MMC) { if (cardType == CARD_MMC) {
Serial.println("MMC"); syslog->println("MMC");
} else if (cardType == CARD_SD) { } else if (cardType == CARD_SD) {
Serial.println("SDSC"); syslog->println("SDSC");
} else if (cardType == CARD_SDHC) { } else if (cardType == CARD_SDHC) {
Serial.println("SDHC"); syslog->println("SDHC");
} else { } else {
Serial.println("UNKNOWN"); syslog->println("UNKNOWN");
} }
uint64_t cardSize = SD.cardSize() / (1024 * 1024); uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize); syslog->printf("SD Card Size: %lluMB\n", cardSize);
return true; return true;
} }
Serial.println("Initialization failed!"); syslog->println("Initialization failed!");
countdown--; countdown--;
if (countdown <= 0) { if (countdown <= 0) {
break; break;
@@ -1421,7 +1605,7 @@ void Board320_240::sdcardToggleRecording() {
if (!liveData->params.sdcardInit) if (!liveData->params.sdcardInit)
return; return;
Serial.println("Toggle SD card recording..."); syslog->println("Toggle SD card recording...");
liveData->params.sdcardRecording = !liveData->params.sdcardRecording; liveData->params.sdcardRecording = !liveData->params.sdcardRecording;
if (liveData->params.sdcardRecording) { if (liveData->params.sdcardRecording) {
liveData->params.sdcardCanNotify = true; liveData->params.sdcardCanNotify = true;
@@ -1444,8 +1628,8 @@ void Board320_240::syncGPS() {
} }
if (gps.satellites.isValid()) { if (gps.satellites.isValid()) {
liveData->params.gpsSat = gps.satellites.value(); liveData->params.gpsSat = gps.satellites.value();
//Serial.print("GPS satellites: "); //syslog->print("GPS satellites: ");
//Serial.println(liveData->params.gpsSat); //syslog->println(liveData->params.gpsSat);
} }
if (!liveData->params.currTimeSyncWithGps && gps.date.isValid() && gps.time.isValid()) { if (!liveData->params.currTimeSyncWithGps && gps.date.isValid() && gps.time.isValid()) {
liveData->params.currTimeSyncWithGps = true; liveData->params.currTimeSyncWithGps = true;
@@ -1464,4 +1648,139 @@ void Board320_240::syncGPS() {
} }
} }
#endif // BOARD320_240_CPP
/**
SIM800L
*/
bool Board320_240::sim800lSetup() {
syslog->print("Setting SIM800L module. HW port: ");
syslog->println(liveData->settings.gprsHwSerialPort);
gprsHwUart = new HardwareSerial(liveData->settings.gprsHwSerialPort);
gprsHwUart->begin(9600);
sim800l = new SIM800L((Stream *)gprsHwUart, SIM800L_RST, 768 , 128);
// SIM800L DebugMode:
//sim800l = new SIM800L((Stream *)gprsHwUart, SIM800L_RST, 768 , 128, (Stream *)&Serial);
bool sim800l_ready = sim800l->isReady();
for (uint8_t i = 0; i < 5 && !sim800l_ready; i++) {
syslog->println("Problem to initialize SIM800L module, retry in 1 sec");
delay(1000);
sim800l_ready = sim800l->isReady();
}
if (!sim800l_ready) {
syslog->println("Problem to initialize SIM800L module");
} else {
syslog->println("SIM800L module initialized");
sim800l->exitSleepMode();
if (sim800l->getPowerMode() != NORMAL) {
syslog->println("SIM800L module in sleep mode - Waking up");
if (sim800l->setPowerMode(NORMAL)) {
syslog->println("SIM800L in normal power mode");
} else {
syslog->println("Failed to switch SIM800L to normal power mode");
}
}
syslog->print("Setting GPRS APN to: ");
syslog->println(liveData->settings.gprsApn);
bool sim800l_gprs = sim800l->setupGPRS(liveData->settings.gprsApn);
for (uint8_t i = 0; i < 5 && !sim800l_gprs; i++) {
syslog->println("Problem to set GPRS APN, retry in 1 sec");
delay(1000);
sim800l_gprs = sim800l->setupGPRS(liveData->settings.gprsApn);
}
if (sim800l_gprs) {
liveData->params.sim800l_enabled = true;
syslog->println("GPRS APN set OK");
} else {
syslog->println("Problem to set GPRS APN");
}
}
return true;
}
bool Board320_240::sendDataViaGPRS() {
syslog->println("Sending data via GPRS");
if (liveData->params.socPerc < 0) {
syslog->println("No valid data, skipping data send");
return false;
}
NetworkRegistration network = sim800l->getRegistrationStatus();
if (network != REGISTERED_HOME && network != REGISTERED_ROAMING) {
syslog->println("SIM800L module not connected to network, skipping data send");
return false;
}
if (!sim800l->isConnectedGPRS()) {
syslog->println("GPRS not connected... Connecting");
bool connected = sim800l->connectGPRS();
for (uint8_t i = 0; i < 5 && !connected; i++) {
syslog->println("Problem to connect GPRS, retry in 1 sec");
delay(1000);
connected = sim800l->connectGPRS();
}
if (connected) {
syslog->println("GPRS connected!");
} else {
syslog->println("GPRS not connected! Reseting SIM800L module!");
sim800l->reset();
sim800lSetup();
return false;
}
}
syslog->println("Start HTTP POST...");
StaticJsonDocument<768> jsonData;
jsonData["apikey"] = liveData->settings.remoteApiKey;
jsonData["carType"] = liveData->settings.carType;
jsonData["ignitionOn"] = liveData->params.ignitionOn;
jsonData["chargingOn"] = liveData->params.chargingOn;
jsonData["socPerc"] = liveData->params.socPerc;
jsonData["sohPerc"] = liveData->params.sohPerc;
jsonData["batPowerKw"] = liveData->params.batPowerKw;
jsonData["batPowerAmp"] = liveData->params.batPowerAmp;
jsonData["batVoltage"] = liveData->params.batVoltage;
jsonData["auxVoltage"] = liveData->params.auxVoltage;
jsonData["auxAmp"] = liveData->params.auxCurrentAmp;
jsonData["batMinC"] = liveData->params.batMinC;
jsonData["batMaxC"] = liveData->params.batMaxC;
jsonData["batInletC"] = liveData->params.batInletC;
jsonData["batFanStatus"] = liveData->params.batFanStatus;
jsonData["speedKmh"] = liveData->params.speedKmh;
jsonData["odoKm"] = liveData->params.odoKm;
jsonData["cumulativeEnergyChargedKWh"] = liveData->params.cumulativeEnergyChargedKWh;
jsonData["cumulativeEnergyDischargedKWh"] = liveData->params.cumulativeEnergyDischargedKWh;
char payload[768];
serializeJson(jsonData, payload);
syslog->print("Sending payload: ");
syslog->println(payload);
syslog->print("Remote API server: ");
syslog->println(liveData->settings.remoteApiUrl);
uint16_t rc = sim800l->doPost(liveData->settings.remoteApiUrl, "application/json", payload, 10000, 10000);
if (rc == 200) {
syslog->println("HTTP POST successful");
} else {
// Failed...
syslog->print("HTTP POST error: ");
syslog->println(rc);
}
return true;
}

View File

@@ -1,5 +1,4 @@
#ifndef BOARD320_240_H #pragma once
#define BOARD320_240_H
// TFT COMMON // TFT COMMON
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH #define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
@@ -12,10 +11,16 @@
#define SMOOTH_FONT #define SMOOTH_FONT
#define GFXFF 1 // TFT FOnts #define GFXFF 1 // TFT FOnts
// DEEP SLEEP
#define TIME_TO_SLEEP 60 // Sleep time in secs
// //
#include <TFT_eSPI.h> #include <TFT_eSPI.h>
#include <TinyGPS++.h> #include <TinyGPS++.h>
#include "BoardInterface.h" #include "BoardInterface.h"
#include <SD.h>
#include <SPI.h>
#include "SIM800L.h"
class Board320_240 : public BoardInterface { class Board320_240 : public BoardInterface {
@@ -23,8 +28,9 @@ class Board320_240 : public BoardInterface {
// TFT, SD SPI // TFT, SD SPI
TFT_eSPI tft = TFT_eSPI(); TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft); TFT_eSprite spr = TFT_eSprite(&tft);
//SPIClass spiSD(HSPI);
HardwareSerial* gpsHwUart = NULL; HardwareSerial* gpsHwUart = NULL;
HardwareSerial* gprsHwUart = NULL;
SIM800L* sim800l;
TinyGPSPlus gps; TinyGPSPlus gps;
char tmpStr1[20]; char tmpStr1[20];
char tmpStr2[20]; char tmpStr2[20];
@@ -46,11 +52,16 @@ class Board320_240 : public BoardInterface {
void afterSetup() override; void afterSetup() override;
void mainLoop() override; void mainLoop() override;
bool skipAdapterScan() override; bool skipAdapterScan() override;
void goToSleep();
void afterSleep();
// SD card // SD card
bool sdcardMount() override; bool sdcardMount() override;
void sdcardToggleRecording() override; void sdcardToggleRecording() override;
// GPS // GPS
void syncGPS(); void syncGPS();
// SIM800L
bool sim800lSetup();
bool sendDataViaGPRS();
// Basic GUI // Basic GUI
void setBrightness(byte lcdBrightnessPerc) override; void setBrightness(byte lcdBrightnessPerc) override;
void displayMessage(const char* row1, const char* row2) override; void displayMessage(const char* row1, const char* row2) override;
@@ -76,5 +87,3 @@ class Board320_240 : public BoardInterface {
void loadTestData(); void loadTestData();
// //
}; };
#endif // BOARD320_240_H

View File

@@ -1,6 +1,3 @@
#ifndef BOARDINTERFACE_CPP
#define BOARDINTERFACE_CPP
#define ARDUINOJSON_USE_LONG_LONG 1 #define ARDUINOJSON_USE_LONG_LONG 1
#include <ArduinoJson.h> #include <ArduinoJson.h>
@@ -31,7 +28,7 @@ void BoardInterface::attachCar(CarInterface* pCarInterface) {
*/ */
void BoardInterface::shutdownDevice() { void BoardInterface::shutdownDevice() {
Serial.println("Shutdown."); syslog->println("Shutdown.");
char msg[20]; char msg[20];
for (int i = 3; i >= 1; i--) { for (int i = 3; i >= 1; i--) {
@@ -41,7 +38,7 @@ void BoardInterface::shutdownDevice() {
} }
#ifdef SIM800L_ENABLED #ifdef SIM800L_ENABLED
if(sim800l->isConnectedGPRS()) { if (sim800l->isConnectedGPRS()) {
sim800l->disconnectGPRS(); sim800l->disconnectGPRS();
} }
sim800l->setPowerMode(MINIMUM); sim800l->setPowerMode(MINIMUM);
@@ -51,7 +48,8 @@ void BoardInterface::shutdownDevice() {
setBrightness(0); setBrightness(0);
//WiFi.disconnect(true); //WiFi.disconnect(true);
//WiFi.mode(WIFI_OFF); //WiFi.mode(WIFI_OFF);
btStop();
commInterface->disconnectDevice();
//adc_power_off(); //adc_power_off();
//esp_wifi_stop(); //esp_wifi_stop();
esp_bt_controller_disable(); esp_bt_controller_disable();
@@ -67,7 +65,7 @@ void BoardInterface::shutdownDevice() {
void BoardInterface::saveSettings() { void BoardInterface::saveSettings() {
// Flash to memory // Flash to memory
Serial.println("Settings saved to eeprom."); syslog->println("Settings saved to eeprom.");
EEPROM.put(0, liveData->settings); EEPROM.put(0, liveData->settings);
EEPROM.commit(); EEPROM.commit();
} }
@@ -78,7 +76,7 @@ void BoardInterface::saveSettings() {
void BoardInterface::resetSettings() { void BoardInterface::resetSettings() {
// Flash to memory // Flash to memory
Serial.println("Factory reset."); syslog->println("Factory reset.");
liveData->settings.initFlag = 1; liveData->settings.initFlag = 1;
EEPROM.put(0, liveData->settings); EEPROM.put(0, liveData->settings);
EEPROM.commit(); EEPROM.commit();
@@ -98,7 +96,7 @@ void BoardInterface::loadSettings() {
// Default settings // Default settings
liveData->settings.initFlag = 183; liveData->settings.initFlag = 183;
liveData->settings.settingsVersion = 5; liveData->settings.settingsVersion = 6;
liveData->settings.carType = CAR_KIA_ENIRO_2020_64; liveData->settings.carType = CAR_KIA_ENIRO_2020_64;
tmpStr = "00:00:00:00:00:00"; // Pair via menu (middle button) tmpStr = "00:00:00:00:00:00"; // Pair via menu (middle button)
tmpStr.toCharArray(liveData->settings.obdMacAddress, tmpStr.length() + 1); tmpStr.toCharArray(liveData->settings.obdMacAddress, tmpStr.length() + 1);
@@ -114,9 +112,9 @@ void BoardInterface::loadSettings() {
liveData->settings.pressureUnit = 'b'; liveData->settings.pressureUnit = 'b';
liveData->settings.defaultScreen = 1; liveData->settings.defaultScreen = 1;
liveData->settings.lcdBrightness = 0; liveData->settings.lcdBrightness = 0;
liveData->settings.debugScreen = 0; liveData->settings.sleepModeEnabled = 0;
liveData->settings.predrawnChargingGraphs = 1; liveData->settings.predrawnChargingGraphs = 1;
liveData->settings.commType = 0; // BLE4 liveData->settings.commType = COMM_TYPE_OBD2BLE4; // BLE4
liveData->settings.wifiEnabled = 0; liveData->settings.wifiEnabled = 0;
tmpStr = "empty"; tmpStr = "empty";
tmpStr.toCharArray(liveData->settings.wifiSsid, tmpStr.length() + 1); tmpStr.toCharArray(liveData->settings.wifiSsid, tmpStr.length() + 1);
@@ -138,19 +136,24 @@ void BoardInterface::loadSettings() {
tmpStr.toCharArray(liveData->settings.remoteApiKey, tmpStr.length() + 1); tmpStr.toCharArray(liveData->settings.remoteApiKey, tmpStr.length() + 1);
liveData->settings.headlightsReminder = 0; liveData->settings.headlightsReminder = 0;
liveData->settings.gpsHwSerialPort = 255; // off liveData->settings.gpsHwSerialPort = 255; // off
liveData->settings.gprsHwSerialPort = 255; // off
liveData->settings.serialConsolePort = 0; // hwuart0
liveData->settings.debugLevel = 1; // 0 - info only, 1 - debug communication (BLE/CAN), 2 - debug GSM, 3 - debug SDcard
liveData->settings.sdcardLogIntervalSec = 2;
liveData->settings.gprsLogIntervalSec = 60;
// Load settings and replace default values // Load settings and replace default values
Serial.println("Reading settings from eeprom."); syslog->println("Reading settings from eeprom.");
EEPROM.begin(sizeof(SETTINGS_STRUC)); EEPROM.begin(sizeof(SETTINGS_STRUC));
EEPROM.get(0, liveData->tmpSettings); EEPROM.get(0, liveData->tmpSettings);
// Init flash with default settings // Init flash with default settings
if (liveData->tmpSettings.initFlag != 183) { if (liveData->tmpSettings.initFlag != 183) {
Serial.println("Settings not found. Initialization."); syslog->println("Settings not found. Initialization.");
saveSettings(); saveSettings();
} else { } else {
Serial.print("Loaded settings ver.: "); syslog->print("Loaded settings ver.: ");
Serial.println(liveData->tmpSettings.settingsVersion); syslog->println(liveData->tmpSettings.settingsVersion);
// Upgrade structure // Upgrade structure
if (liveData->settings.settingsVersion != liveData->tmpSettings.settingsVersion) { if (liveData->settings.settingsVersion != liveData->tmpSettings.settingsVersion) {
@@ -158,7 +161,7 @@ void BoardInterface::loadSettings() {
liveData->tmpSettings.settingsVersion = 2; liveData->tmpSettings.settingsVersion = 2;
liveData->tmpSettings.defaultScreen = liveData->settings.defaultScreen; liveData->tmpSettings.defaultScreen = liveData->settings.defaultScreen;
liveData->tmpSettings.lcdBrightness = liveData->settings.lcdBrightness; liveData->tmpSettings.lcdBrightness = liveData->settings.lcdBrightness;
liveData->tmpSettings.debugScreen = liveData->settings.debugScreen; liveData->tmpSettings.sleepModeEnabled = liveData->settings.sleepModeEnabled;
} }
if (liveData->tmpSettings.settingsVersion == 2) { if (liveData->tmpSettings.settingsVersion == 2) {
liveData->tmpSettings.settingsVersion = 3; liveData->tmpSettings.settingsVersion = 3;
@@ -166,7 +169,7 @@ void BoardInterface::loadSettings() {
} }
if (liveData->tmpSettings.settingsVersion == 3) { if (liveData->tmpSettings.settingsVersion == 3) {
liveData->tmpSettings.settingsVersion = 4; liveData->tmpSettings.settingsVersion = 4;
liveData->tmpSettings.commType = 0; // BLE4 liveData->tmpSettings.commType = COMM_TYPE_OBD2BLE4; // BLE4
liveData->tmpSettings.wifiEnabled = 0; liveData->tmpSettings.wifiEnabled = 0;
tmpStr = "empty"; tmpStr = "empty";
tmpStr.toCharArray(liveData->tmpSettings.wifiSsid, tmpStr.length() + 1); tmpStr.toCharArray(liveData->tmpSettings.wifiSsid, tmpStr.length() + 1);
@@ -192,6 +195,13 @@ void BoardInterface::loadSettings() {
liveData->tmpSettings.settingsVersion = 5; liveData->tmpSettings.settingsVersion = 5;
liveData->tmpSettings.gpsHwSerialPort = 255; // off liveData->tmpSettings.gpsHwSerialPort = 255; // off
} }
if (liveData->tmpSettings.settingsVersion == 5) {
liveData->tmpSettings.settingsVersion = 6;
liveData->tmpSettings.serialConsolePort = 0; // hwuart0
liveData->tmpSettings.debugLevel = 0; // show all
liveData->tmpSettings.sdcardLogIntervalSec = 2;
liveData->tmpSettings.gprsLogIntervalSec = 60;
}
// Save upgraded structure // Save upgraded structure
liveData->settings = liveData->tmpSettings; liveData->settings = liveData->tmpSettings;
@@ -201,6 +211,8 @@ void BoardInterface::loadSettings() {
// Apply settings from flash if needed // Apply settings from flash if needed
liveData->settings = liveData->tmpSettings; liveData->settings = liveData->tmpSettings;
} }
syslog->setDebugLevel(liveData->settings.debugLevel);
} }
/** /**
@@ -208,14 +220,22 @@ void BoardInterface::loadSettings() {
*/ */
void BoardInterface::afterSetup() { void BoardInterface::afterSetup() {
syslog->println("BoardInterface::afterSetup");
// Init Comm iterface // Init Comm iterface
syslog->print("Init communication device: ");
syslog->println(liveData->settings.commType);
if (liveData->settings.commType == COMM_TYPE_OBD2BLE4) { if (liveData->settings.commType == COMM_TYPE_OBD2BLE4) {
commInterface = new CommObd2Ble4(); commInterface = new CommObd2Ble4();
} else if (liveData->settings.commType == COMM_TYPE_OBD2CAN) { } else if (liveData->settings.commType == COMM_TYPE_OBD2CAN) {
commInterface = new CommObd2Ble4(); commInterface = new CommObd2Can();
//commInterface = new CommObd2Can(); } else if (liveData->settings.commType == COMM_TYPE_OBD2BT3) {
//commInterface = new CommObd2Bt3();
syslog->println("BT3 not implemented");
} }
//commInterface->initComm(liveData, NULL);
commInterface->initComm(liveData, this);
commInterface->connectDevice(); commInterface->connectDevice();
} }
@@ -241,6 +261,15 @@ void BoardInterface::customConsoleCommand(String cmd) {
if (key == "remoteApiKey") value.toCharArray(liveData->settings.remoteApiKey, value.length() + 1); if (key == "remoteApiKey") value.toCharArray(liveData->settings.remoteApiKey, value.length() + 1);
} }
/**
Parser response from obd2/can
*/
void BoardInterface::parseRowMerged() {
carInterface->parseRowMerged();
}
/** /**
Serialize parameters Serialize parameters
*/ */
@@ -313,5 +342,3 @@ bool BoardInterface::serializeParamsToJson(File file, bool inclApiKey) {
serializeJson(jsonData, Serial); serializeJson(jsonData, Serial);
serializeJson(jsonData, file); serializeJson(jsonData, file);
} }
#endif // BOARDINTERFACE_CPP

View File

@@ -1,5 +1,4 @@
#ifndef BOARDINTERFACE_H #pragma once
#define BOARDINTERFACE_H
#include <FS.h> #include <FS.h>
#include "LiveData.h" #include "LiveData.h"
@@ -24,12 +23,6 @@ class BoardInterface {
bool testDataMode = false; bool testDataMode = false;
bool scanDevices = false; bool scanDevices = false;
String sdcardRecordBuffer = ""; String sdcardRecordBuffer = "";
// Debug screen - next command with right button
uint16_t debugCommandIndex = 0;
String debugAtshRequest = "ATSH7E4";
String debugCommandRequest = "220101";
String debugLastString = "620101FFF7E7FF99000000000300B10EFE120F11100F12000018C438C30B00008400003864000035850000153A00001374000647010D017F0BDA0BDA03E8";
String debugPreviousString = "620101FFF7E7FFB3000000000300120F9B111011101011000014CC38CB3B00009100003A510000367C000015FB000013D3000690250D018E0000000003E8";
// //
void setLiveData(LiveData* pLiveData); void setLiveData(LiveData* pLiveData);
void attachCar(CarInterface* pCarInterface); void attachCar(CarInterface* pCarInterface);
@@ -41,6 +34,7 @@ class BoardInterface {
virtual void displayMessage(const char* row1, const char* row2)=0; virtual void displayMessage(const char* row1, const char* row2)=0;
virtual void setBrightness(byte lcdBrightnessPerc)=0; virtual void setBrightness(byte lcdBrightnessPerc)=0;
virtual void redrawScreen()=0; virtual void redrawScreen()=0;
void parseRowMerged();
// Menu // Menu
virtual void showMenu()=0; virtual void showMenu()=0;
virtual void hideMenu()=0; virtual void hideMenu()=0;
@@ -55,5 +49,3 @@ class BoardInterface {
virtual void sdcardToggleRecording()=0; virtual void sdcardToggleRecording()=0;
bool serializeParamsToJson(File file, bool inclApiKey = false); bool serializeParamsToJson(File file, bool inclApiKey = false);
}; };
#endif // BOARDINTERFACE_H

View File

@@ -1,6 +1,3 @@
#ifndef BOARDM5STACKCORE_CPP
#define BOARDM5STACKCORE_CPP
#include "BoardInterface.h" #include "BoardInterface.h"
#include "Board320_240.h" #include "Board320_240.h"
#include "BoardM5stackCore.h" #include "BoardM5stackCore.h"
@@ -24,7 +21,7 @@ void BoardM5stackCore::initBoard() {
// Mute speaker // Mute speaker
//ledcWriteTone(TONE_PIN_CHANNEL, 0); //ledcWriteTone(TONE_PIN_CHANNEL, 0);
digitalWrite(SPEAKER_PIN, 0); dacWrite(SPEAKER_PIN, 0);
// //
Board320_240::initBoard(); Board320_240::initBoard();
@@ -34,5 +31,3 @@ void BoardM5stackCore::mainLoop() {
Board320_240::mainLoop(); Board320_240::mainLoop();
} }
#endif // BOARDM5STACKCORE_CPP

View File

@@ -1,5 +1,4 @@
#ifndef BOARDM5STACKCORE_H #pragma once
#define BOARDM5STACKCORE_H
// Setup for m5stack core // Setup for m5stack core
#define USER_SETUP_LOADED 1 #define USER_SETUP_LOADED 1
@@ -42,5 +41,3 @@ class BoardM5stackCore : public Board320_240 {
void initBoard() override; void initBoard() override;
void mainLoop() override; void mainLoop() override;
}; };
#endif // BOARDM5STACKCORE_H

View File

@@ -1,6 +1,3 @@
#ifndef BOARDTTGOT4V13_CPP
#define BOARDTTGOT4V13_CPP
#include "BoardInterface.h" #include "BoardInterface.h"
#include "Board320_240.h" #include "Board320_240.h"
#include "BoardTtgoT4v13.h" #include "BoardTtgoT4v13.h"
@@ -22,5 +19,3 @@ void BoardTtgoT4v13::initBoard() {
Board320_240::initBoard(); Board320_240::initBoard();
} }
#endif // BOARDTTGOT4V13_CPP

412
CarBmwI3.cpp Normal file
View File

@@ -0,0 +1,412 @@
#include "CarBmwI3.h"
#include <vector>
#include <algorithm>
/**
activateliveData->commandQueue
*/
void CarBmwI3::activateCommandQueue() {
const uint16_t commandQueueLoopFrom = 18;
// const std::vector<String> commandQueue = {
const std::vector<LiveData::Command_t> commandQueue = {
{0, "ATZ"}, // Reset all
{0, "ATD"}, // All to defaults
{0, "ATI"}, // Print the version ID
{0, "ATE0"}, // Echo off
{0, "ATPP2COFF"}, // Disable prog parameter 2C
//{0, "ATSH6F1"}, // Set header to 6F1
{0, "ATCF600"}, // Set the ID filter to 600
{0, "ATCM700"}, // Set the ID mask to 700
{0, "ATPBC001"}, // Protocol B options and baudrate (div 1 = 500k)
{0, "ATSPB"}, // Set protocol to B and save it (USER1 11bit, 125kbaud)
{0, "ATAT0"}, // Adaptive timing off
{0, "ATSTFF"}, // Set timeout to ff x 4ms
{0, "ATAL"}, // Allow long messages ( > 7 Bytes)
{0, "ATH1"}, // Additional headers on
{0, "ATS0"}, // Printing of spaces off
{0, "ATL0"}, // Linefeeds off
{0, "ATCSM0"}, // Silent monitoring off
{0, "ATCTM5"}, // Set timer multiplier to 5
{0, "ATJE"}, // Use J1939 SAE data format
// Loop from (BMW i3)
// BMS
{0, "ATSH6F1"},
{0x12, "22402B"}, // STATUS_MESSWERTE_IBS - 12V Bat
//////{0x12, "22F101"}, // STATUS_A_T_ELUE ???
{0x60, "22D107"}, // Speed km/h (ELM CAN send: '600322D107000000')
{0x60, "22D10D"}, // Total km without offset (ELM CAN send: '600322D10D000000')
{0x60, "22D114"}, // Total km - offset (ELM CAN send: '600322D114000000')
{0x78, "22D85C"}, // Calculated indoor temperature (ELM CAN send: '780322D85C000000')
{0x78, "22D96B"}, // Outdoor temperature
//{0, "22DC61"}, // BREMSLICHT_SCHALTER
{0x07, "22DD7B"}, // ALTERUNG_KAPAZITAET Aging of kapacity
{0x07, "22DD7C"}, // GW_INFO - should contain kWh but in some strange form
{0x07, "22DDBF"}, // Min and Max cell voltage
{0x07, "22DDC0"}, // TEMPERATUREN
{0x07, "22DD69"}, // HV_STORM
{0x07, "22DD6C"}, // KUEHLKREISLAUF_TEMP
{0x07, "22DDB4"}, // HV_SPANNUNG
{0x07, "22DDBC"} // SOC
};
// 60Ah / 22kWh version
liveData->params.batteryTotalAvailableKWh = 18.8;
liveData->params.batModuleTempCount = 4; //?
// init params which are currently not filled from parsed data
liveData->params.tireFrontLeftPressureBar = 0;
liveData->params.tireFrontLeftTempC = 0;
liveData->params.tireRearLeftPressureBar = 0;
liveData->params.tireRearLeftTempC = 0;
liveData->params.tireFrontRightPressureBar = 0;
liveData->params.tireFrontRightTempC = 0;
liveData->params.tireRearRightPressureBar = 0;
liveData->params.tireRearRightTempC = 0;
// Empty and fill command queue
liveData->commandQueue.clear(); // probably not needed before assign
liveData->commandQueue.assign(commandQueue.begin(), commandQueue.end());
liveData->commandQueueLoopFrom = commandQueueLoopFrom;
liveData->commandQueueCount = commandQueue.size();
liveData->bAdditionalStartingChar = true; // there is one additional byte in received packets compared to other cars
liveData->expectedMinimalPacketLength = 6; // to filter occasional 5-bytes long packets
liveData->rxTimeoutMs = 500; // timeout for receiving of CAN response
liveData->delayBetweenCommandsMs = 100; // delay between commands, set to 0 if no delay is needed
}
/**
parseRowMerged
*/
void CarBmwI3::parseRowMerged()
{
syslog->print("--mergedVectorLength: "); syslog->println(liveData->vResponseRowMerged.size());
struct Header_t
{
uint8_t startChar;
uint8_t pid[2];
uint8_t pData[];
uint16_t getPid() { return 256 * pid[0] + pid[1]; };
};
Header_t* pHeader = (Header_t*)liveData->vResponseRowMerged.data();
const uint16_t payloadLength = liveData->vResponseRowMerged.size() - sizeof(Header_t);
// create reversed payload to get little endian order of data
std::vector<uint8_t> payloadReversed(pHeader->pData, pHeader->pData + payloadLength);
std::reverse(payloadReversed.begin(), payloadReversed.end());
//syslog->print("--extracted PID: "); syslog->println(pHeader->getPid());
//syslog->print("--payload length: "); syslog->println(payloadLength);
#pragma pack(push, 1)
// BMS
if (liveData->currentAtshRequest.equals("ATSH6F1")) {
switch (pHeader->getPid()) {
case 0x402B:
{
struct s402B_t {
int16_t unknown[13];
uint16_t auxRawCurrent;
uint16_t auxRawVoltage;
int16_t auxTemp;
};
if (payloadLength == sizeof(s402B_t)) {
s402B_t* ptr = (s402B_t*)payloadReversed.data();
liveData->params.auxTemperature = ptr->auxTemp / 10.0;
liveData->params.auxVoltage = ptr->auxRawVoltage / 4000.0 + 6;
liveData->params.auxCurrentAmp = - (ptr->auxRawCurrent / 12.5 - 200);
}
}
break;
case 0xD107: // Speed km/h
{
struct D107_t {
uint16_t speedKmh;
};
if (payloadLength == sizeof(D107_t)) {
D107_t* ptr = (D107_t*)payloadReversed.data();
liveData->params.speedKmh = ptr->speedKmh / 10.0;
syslog->print("----speed km/h: "); syslog->println(liveData->params.speedKmh);
}
}
break;
case 0xD10D: // Total km wihtout offset
{
struct D10D_t {
uint32_t totalKm1; // one of those two is RAM and other is EEPROM value
uint32_t totalKm2;
};
if (payloadLength == sizeof(D10D_t)) {
D10D_t* ptr = (D10D_t*)payloadReversed.data();
liveData->params.odoKm = ptr->totalKm1 - totalDistanceKmOffset;
syslog->print("----total km1: "); syslog->println(ptr->totalKm1);
syslog->print("----total km2: "); syslog->println(ptr->totalKm2);
syslog->print("----total km: "); syslog->println(liveData->params.odoKm);
}
}
break;
case 0xD114: // Offset of total km
{
struct D114_t {
uint8_t offsetKm;
};
if (payloadLength == sizeof(D114_t)) {
D114_t* ptr = (D114_t*)payloadReversed.data();
totalDistanceKmOffset = ptr->offsetKm;
syslog->print("----total off: "); syslog->println(totalDistanceKmOffset);
}
}
break;
case 0xD85C:
{
struct D85C_t {
int8_t indoorTemp;
};
if (payloadLength == sizeof(D85C_t)) {
D85C_t* ptr = (D85C_t*)payloadReversed.data();
liveData->params.indoorTemperature = ptr->indoorTemp;
}
}
break;
case 0xD96B:
{
struct D96B_t {
uint16_t outdoorTempRaw;
};
if (payloadLength == sizeof(D96B_t)) {
D96B_t* ptr = (D96B_t*)payloadReversed.data();
liveData->params.outdoorTemperature = (ptr->outdoorTempRaw / 2.0) - 40.0;
}
}
break;
case 0xDD69:
{
struct DD69_t {
uint8_t unknown[4];
int32_t batAmp;
};
if (payloadLength == sizeof(DD69_t)) {
DD69_t* ptr = (DD69_t*)payloadReversed.data();
liveData->params.batPowerAmp = ptr->batAmp / 100.0; //liveData->hexToDecFromResponse(6, 14, 4, true) / 100.0;
liveData->params.batPowerKw = (liveData->params.batPowerAmp * liveData->params.batVoltage) / 1000.0;
if (liveData->params.batPowerKw < 0) // Reset charging start time
liveData->params.chargingStartTime = liveData->params.currentTime;
// calculate kWh/100
liveData->params.batPowerKwh100 = liveData->params.batPowerKw / liveData->params.speedKmh * 100;
// update charging graph data if car is charging
if (liveData->params.speedKmh < 10 && liveData->params.batPowerKw >= 1 && liveData->params.socPerc > 0 && liveData->params.socPerc <= 100) {
if ( liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)] < 0 || liveData->params.batPowerKw < liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)])
liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)] = liveData->params.batPowerKw;
if ( liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)] < 0 || liveData->params.batPowerKw > liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)])
liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)] = liveData->params.batPowerKw;
}
}
}
break;
case 0xDD6C:
{
struct DD6C_t {
int16_t tempCoolant;
};
if (payloadLength == sizeof(DD6C_t)) {
DD6C_t* ptr = (DD6C_t*)payloadReversed.data();
liveData->params.coolingWaterTempC = ptr->tempCoolant / 10.0;
liveData->params.coolantTemp1C = ptr->tempCoolant / 10.0;
liveData->params.coolantTemp2C = ptr->tempCoolant / 10.0;
// update charging graph data if car is charging
if (liveData->params.speedKmh < 10 && liveData->params.batPowerKw >= 1 && liveData->params.socPerc > 0 && liveData->params.socPerc <= 100) {
liveData->params.chargingGraphWaterCoolantTempC[int(liveData->params.socPerc)] = liveData->params.coolingWaterTempC;
}
}
}
break;
case 0xDD7B:
{
struct DD7B_t {
uint8_t agingOfCapacity;
};
if (payloadLength == sizeof(DD7B_t)) {
DD7B_t* ptr = (DD7B_t*)payloadReversed.data();
liveData->params.sohPerc = ptr->agingOfCapacity;
}
}
break;
case 0xDD7C:
{
struct DD7C_t {
//uint8_t unused1;
uint32_t discharged;
uint32_t charged;
uint8_t unknown[];
};
Serial.print("DD7C received, struct sizeof is "); Serial.println(sizeof(DD7C_t));
if (payloadLength >= sizeof(DD7C_t)) {
DD7C_t* ptr = (DD7C_t*)(payloadReversed.data() + 1); // skip one charcter on beginning (TODO: fix when pragma push/pack is done)
liveData->params.cumulativeEnergyDischargedKWh = ptr->discharged / 100000.0;
if (liveData->params.cumulativeEnergyDischargedKWhStart == -1)
liveData->params.cumulativeEnergyDischargedKWhStart = liveData->params.cumulativeEnergyDischargedKWh;
liveData->params.cumulativeEnergyChargedKWh = ptr->charged / 100000.0;
if (liveData->params.cumulativeEnergyChargedKWhStart == -1)
liveData->params.cumulativeEnergyChargedKWhStart = liveData->params.cumulativeEnergyChargedKWh;
}
}
break;
case 0xDDB4:
{
struct DDB4_t {
uint16_t batVoltage;
};
if (payloadLength == sizeof(DDB4_t)) { // HV_SPANNUNG_BATTERIE
DDB4_t* ptr = (DDB4_t*)payloadReversed.data();
liveData->params.batVoltage = ptr->batVoltage / 100.0;
liveData->params.batPowerKw = (liveData->params.batPowerAmp * liveData->params.batVoltage) / 1000.0;
if (liveData->params.batPowerKw < 0) // Reset charging start time
liveData->params.chargingStartTime = liveData->params.currentTime;
}
}
break;
case 0xDDBF:
{
struct DDBF_t {
uint16_t unused[2];
uint16_t ucellMax;
uint16_t ucellMin;
};
if (payloadLength == sizeof(DDBF_t)) { // HV_SPANNUNG_BATTERIE
DDBF_t* ptr = (DDBF_t*)payloadReversed.data();
liveData->params.batCellMaxV = ptr->ucellMax / 1000.0;
liveData->params.batCellMinV = ptr->ucellMin / 1000.0;
}
}
break;
case 0xDDC0:
{
struct DDC0_t {
uint8_t unknown[2];
int16_t tempAvg;
int16_t tempMax;
int16_t tempMin;
};
if (payloadLength == sizeof(DDC0_t)) {
DDC0_t* ptr = (DDC0_t*)payloadReversed.data();
liveData->params.batMinC = ptr->tempMin / 100.0;
liveData->params.batTempC = ptr->tempAvg / 100.0;
liveData->params.batMaxC = ptr->tempMax / 100.0;
liveData->params.batModuleTempC[0] = liveData->params.batTempC;
liveData->params.batModuleTempC[1] = liveData->params.batTempC;
liveData->params.batModuleTempC[2] = liveData->params.batTempC;
liveData->params.batModuleTempC[3] = liveData->params.batTempC;
// update charging graph data if car is charging
if (liveData->params.speedKmh < 10 && liveData->params.batPowerKw >= 1 && liveData->params.socPerc > 0 && liveData->params.socPerc <= 100) {
liveData->params.chargingGraphBatMinTempC[int(liveData->params.socPerc)] = liveData->params.batMinC;
liveData->params.chargingGraphBatMaxTempC[int(liveData->params.socPerc)] = liveData->params.batMaxC;
//liveData->params.chargingGraphHeaterTempC[int(liveData->params.socPerc)] = liveData->params.batHeaterC;
}
}
}
break;
case 0xDDBC:
{
struct DDBC_t {
uint8_t unknown[2];
uint16_t socMin;
uint16_t socMax;
uint16_t soc;
};
if (payloadLength == sizeof(DDBC_t)) {
DDBC_t* ptr = (DDBC_t*)payloadReversed.data();
liveData->params.socPercPrevious = liveData->params.socPerc;
liveData->params.socPerc = ptr->soc / 10.0;
// Soc10ced table, record x0% CEC/CED table (ex. 90%->89%, 80%->79%)
if(liveData->params.socPercPrevious - liveData->params.socPerc > 0) {
byte index = (int(liveData->params.socPerc) == 4) ? 0 : (int)(liveData->params.socPerc / 10) + 1;
if ((int(liveData->params.socPerc) % 10 == 9 || int(liveData->params.socPerc) == 4) && liveData->params.soc10ced[index] == -1) {
liveData->params.soc10ced[index] = liveData->params.cumulativeEnergyDischargedKWh;
liveData->params.soc10cec[index] = liveData->params.cumulativeEnergyChargedKWh;
liveData->params.soc10odo[index] = liveData->params.odoKm;
liveData->params.soc10time[index] = liveData->params.currentTime;
}
}
}
}
break;
} // switch
} // ATSH6F1
#pragma pack(pop)
}
/**
loadTestData
*/
void CarBmwI3::loadTestData()
{
}

16
CarBmwI3.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "CarInterface.h"
class CarBmwI3 : public CarInterface {
protected:
public:
void activateCommandQueue() override;
void parseRowMerged() override;
void loadTestData() override;
private:
uint8_t totalDistanceKmOffset = 0;
};

View File

@@ -1,9 +1,6 @@
#ifndef CARHYUNDAIIONIQ_CPP
#define CARHYUNDAIIONIQ_CPP
#include "CarHyundaiIoniq.h" #include "CarHyundaiIoniq.h"
#include <vector>
#define commandQueueCountHyundaiIoniq 25
#define commandQueueLoopFromHyundaiIoniq 8 #define commandQueueLoopFromHyundaiIoniq 8
/** /**
@@ -11,7 +8,7 @@
*/ */
void CarHyundaiIoniq::activateCommandQueue() { void CarHyundaiIoniq::activateCommandQueue() {
String commandQueueHyundaiIoniq[commandQueueCountHyundaiIoniq] = { std::vector<String> commandQueueHyundaiIoniq = {
"AT Z", // Reset all "AT Z", // Reset all
"AT I", // Print the version ID "AT I", // Print the version ID
"AT E0", // Echo off "AT E0", // Echo off
@@ -34,13 +31,17 @@ void CarHyundaiIoniq::activateCommandQueue() {
"2103", // cell voltages, screen 3 only "2103", // cell voltages, screen 3 only
"2104", // cell voltages, screen 3 only "2104", // cell voltages, screen 3 only
"2105", // soh, soc, .. "2105", // soh, soc, ..
"2106", // cooling water temp
// VMCU // VMCU
"ATSH7E2", "ATSH7E2",
"2101", // speed, ... "2101", // speed, ...
"2102", // aux, ... "2102", // aux, ...
// IGPM
"ATSH770",
"22BC03", // low beam
"22BC06", // brake light
//"ATSH7Df", //"ATSH7Df",
//"2106", //"2106",
//"220106", //"220106",
@@ -67,15 +68,13 @@ void CarHyundaiIoniq::activateCommandQueue() {
liveData->params.batModuleTempCount = 12; liveData->params.batModuleTempCount = 12;
// Empty and fill command queue // Empty and fill command queue
for (int i = 0; i < 300; i++) { liveData->commandQueue.clear();
liveData->commandQueue[i] = ""; for (auto cmd : commandQueueHyundaiIoniq) {
} liveData->commandQueue.push_back({ 0, cmd });
for (int i = 0; i < commandQueueCountHyundaiIoniq; i++) {
liveData->commandQueue[i] = commandQueueHyundaiIoniq[i];
} }
liveData->commandQueueLoopFrom = commandQueueLoopFromHyundaiIoniq; liveData->commandQueueLoopFrom = commandQueueLoopFromHyundaiIoniq;
liveData->commandQueueCount = commandQueueCountHyundaiIoniq; liveData->commandQueueCount = commandQueueHyundaiIoniq.size();
} }
/** /**
@@ -83,6 +82,10 @@ void CarHyundaiIoniq::activateCommandQueue() {
*/ */
void CarHyundaiIoniq::parseRowMerged() { void CarHyundaiIoniq::parseRowMerged() {
uint8_t tempByte;
float tempFloat;
String tmpStr;
// VMCU 7E2 // VMCU 7E2
if (liveData->currentAtshRequest.equals("ATSH7E2")) { if (liveData->currentAtshRequest.equals("ATSH7E2")) {
if (liveData->commandRequest.equals("2101")) { if (liveData->commandRequest.equals("2101")) {
@@ -91,11 +94,29 @@ void CarHyundaiIoniq::parseRowMerged() {
liveData->params.speedKmh = 0; liveData->params.speedKmh = 0;
} }
if (liveData->commandRequest.equals("2102")) { if (liveData->commandRequest.equals("2102")) {
liveData->params.auxPerc = liveData->hexToDecFromResponse(50, 52, 1, false);
liveData->params.auxCurrentAmp = - liveData->hexToDecFromResponse(46, 50, 2, true) / 1000.0; liveData->params.auxCurrentAmp = - liveData->hexToDecFromResponse(46, 50, 2, true) / 1000.0;
} }
} }
// IGPM
if (liveData->currentAtshRequest.equals("ATSH770")) {
if (liveData->commandRequest.equals("22BC03")) {
tempByte = liveData->hexToDecFromResponse(16, 18, 1, false);
liveData->params.ignitionOn = (bitRead(tempByte, 5) == 1);
if (liveData->params.ignitionOn) {
liveData->params.lastIgnitionOnTime = liveData->params.currentTime;
}
tempByte = liveData->hexToDecFromResponse(18, 20, 1, false);
liveData->params.headLights = (bitRead(tempByte, 5) == 1);
liveData->params.dayLights = (bitRead(tempByte, 3) == 1);
}
if (liveData->commandRequest.equals("22BC06")) {
tempByte = liveData->hexToDecFromResponse(14, 16, 1, false);
liveData->params.brakeLights = (bitRead(tempByte, 5) == 1);
}
}
// Cluster module 7c6 // Cluster module 7c6
if (liveData->currentAtshRequest.equals("ATSH7C6")) { if (liveData->currentAtshRequest.equals("ATSH7C6")) {
if (liveData->commandRequest.equals("22B002")) { if (liveData->commandRequest.equals("22B002")) {
@@ -126,10 +147,25 @@ void CarHyundaiIoniq::parseRowMerged() {
liveData->params.cumulativeEnergyDischargedKWhStart = liveData->params.cumulativeEnergyDischargedKWh; liveData->params.cumulativeEnergyDischargedKWhStart = liveData->params.cumulativeEnergyDischargedKWh;
liveData->params.availableChargePower = liveData->decFromResponse(16, 20) / 100.0; liveData->params.availableChargePower = liveData->decFromResponse(16, 20) / 100.0;
liveData->params.availableDischargePower = liveData->decFromResponse(20, 24) / 100.0; liveData->params.availableDischargePower = liveData->decFromResponse(20, 24) / 100.0;
liveData->params.isolationResistanceKOhm = liveData->hexToDecFromResponse(118, 122, 2, true); liveData->params.isolationResistanceKOhm = liveData->hexToDecFromResponse(118, 122, 2, false);
liveData->params.batFanStatus = liveData->hexToDecFromResponse(58, 60, 2, true); liveData->params.batFanStatus = liveData->hexToDecFromResponse(58, 60, 1, false);
liveData->params.batFanFeedbackHz = liveData->hexToDecFromResponse(60, 62, 2, true); liveData->params.batFanFeedbackHz = liveData->hexToDecFromResponse(60, 62, 1, false);
liveData->params.auxVoltage = liveData->hexToDecFromResponse(62, 64, 2, true) / 10.0; liveData->params.auxVoltage = liveData->hexToDecFromResponse(62, 64, 1, false) / 10.0;
float tmpAuxPerc;
if(liveData->params.ignitionOn) {
tmpAuxPerc = (float)(liveData->params.auxVoltage - 12.8) * 100 / (float)(14.8 - 12.8); //min: 12.8V; max: 14.8V
} else {
tmpAuxPerc = (float)(liveData->params.auxVoltage - 11.6) * 100 / (float)(12.8 - 11.6); //min 11.6V; max: 12.8V
}
if(tmpAuxPerc > 100) {
liveData->params.auxPerc = 100;
} else if(tmpAuxPerc < 0) {
liveData->params.auxPerc = 0;
} else {
liveData->params.auxPerc = tmpAuxPerc;
}
liveData->params.batPowerAmp = - liveData->hexToDecFromResponse(24, 28, 2, true) / 10.0; liveData->params.batPowerAmp = - liveData->hexToDecFromResponse(24, 28, 2, true) / 10.0;
liveData->params.batVoltage = liveData->hexToDecFromResponse(28, 32, 2, false) / 10.0; liveData->params.batVoltage = liveData->hexToDecFromResponse(28, 32, 2, false) / 10.0;
liveData->params.batPowerKw = (liveData->params.batPowerAmp * liveData->params.batVoltage) / 1000.0; liveData->params.batPowerKw = (liveData->params.batPowerAmp * liveData->params.batVoltage) / 1000.0;
@@ -147,6 +183,12 @@ void CarHyundaiIoniq::parseRowMerged() {
//liveData->params.batMaxC = liveData->hexToDecFromResponse(32, 34, 1, true); //liveData->params.batMaxC = liveData->hexToDecFromResponse(32, 34, 1, true);
//liveData->params.batMinC = liveData->hexToDecFromResponse(34, 36, 1, true); //liveData->params.batMinC = liveData->hexToDecFromResponse(34, 36, 1, true);
tempByte = liveData->hexToDecFromResponse(22, 24, 1, false);
liveData->params.chargingOn = (bitRead(tempByte, 5) == 1 || bitRead(tempByte, 6) == 1); // bit 5 = AC; bit 6 = DC
if(liveData->params.chargingOn) {
liveData->params.lastChargingOnTime = liveData->params.currentTime;
}
// 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) // 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)
liveData->params.batInletC = liveData->hexToDecFromResponse(48, 50, 1, true); liveData->params.batInletC = liveData->hexToDecFromResponse(48, 50, 1, true);
if (liveData->params.speedKmh < 10 && liveData->params.batPowerKw >= 1 && liveData->params.socPerc > 0 && liveData->params.socPerc <= 100) { if (liveData->params.speedKmh < 10 && liveData->params.batPowerKw >= 1 && liveData->params.socPerc > 0 && liveData->params.socPerc <= 100) {
@@ -249,11 +291,11 @@ void CarHyundaiIoniq::loadTestData() {
liveData->currentAtshRequest = "ATSH7E2"; liveData->currentAtshRequest = "ATSH7E2";
// 2101 // 2101
liveData->commandRequest = "2101"; liveData->commandRequest = "2101";
liveData->responseRowMerged = "6101FFE0000009211222062F03000000001D7734"; liveData->responseRowMerged = "6101FFE0000009215A09061803000000000E773404200000000000";
parseRowMerged(); parseRowMerged();
// 2102 // 2102
liveData->commandRequest = "2102"; liveData->commandRequest = "2102";
liveData->responseRowMerged = "6102FF80000001010000009315B2888D390B08618B683900000000"; liveData->responseRowMerged = "6102FF80000001010000009522C570273A0F0D9199953900000000";
parseRowMerged(); parseRowMerged();
// "ATSH7DF", // "ATSH7DF",
@@ -263,52 +305,57 @@ void CarHyundaiIoniq::loadTestData() {
liveData->currentAtshRequest = "ATSH7B3"; liveData->currentAtshRequest = "ATSH7B3";
// 220100 // 220100
liveData->commandRequest = "220100"; liveData->commandRequest = "220100";
liveData->responseRowMerged = "6201007E5007C8FF8A876A011010FFFF10FF10FFFFFFFFFFFFFFFFFF2EEF767D00FFFF00FFFF000000"; liveData->responseRowMerged = "6201007E5007C8FF7A665D00A981FFFF81FF10FFFFFFFFFFFFFFFFFF44CAA7AD00FFFF01FFFF000000";
parseRowMerged(); parseRowMerged();
// 220102 // 220102
liveData->commandRequest = "220102"; liveData->commandRequest = "220102";
liveData->responseRowMerged = "620102FF800000A3950000000000002600000000"; liveData->responseRowMerged = "620102FF800000CA5E0101000101005100000000";
parseRowMerged(); parseRowMerged();
// BMS ATSH7E4 // BMS ATSH7E4
liveData->currentAtshRequest = "ATSH7E4"; liveData->currentAtshRequest = "ATSH7E4";
// 220101 // 220101
liveData->commandRequest = "2101"; liveData->commandRequest = "2101";
liveData->responseRowMerged = "6101FFFFFFFF5026482648A3FFC30D9E181717171718170019B50FB501000090000142230001425F0000771B00007486007815D809015C0000000003E800"; liveData->responseRowMerged = "6101FFFFFFFFBD136826480300220F600B0B0B0B0B0B0B000CCD05CC0A00009100012C4A00012A1800006F37000069F700346CC30D01890000000003E800";
parseRowMerged(); parseRowMerged();
// 220102 // 220102
liveData->commandRequest = "2102"; liveData->commandRequest = "2102";
liveData->responseRowMerged = "6102FFFFFFFFB5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5000000"; liveData->responseRowMerged = "6102FFFFFFFFCDCDCDCDCDCDCDCDCDCCCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCCCDCDCD000000";
parseRowMerged(); parseRowMerged();
// 220103 // 220103
liveData->commandRequest = "2103"; liveData->commandRequest = "2103";
liveData->responseRowMerged = "6103FFFFFFFFB5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5000000"; liveData->responseRowMerged = "6103FFFFFFFFCDCDCDCDCDCDCCCDCDCDCDCDCDCDCDCDCCCDCDCCCDCDCDCDCDCDCDCCCDCDCDCC000000";
parseRowMerged(); parseRowMerged();
// 220104 // 220104
liveData->commandRequest = "2104"; liveData->commandRequest = "2104";
liveData->responseRowMerged = "6104FFFFFFFFB5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5000000"; liveData->responseRowMerged = "6104FFFFFFFFCDCDCDCDCDCDCDCDCDCDCCCCCDCDCCCDCDCDCDCDCDCDCDCDCDCDCDCCCCCCCDCD000000";
parseRowMerged(); parseRowMerged();
// 220105 // 220105
liveData->commandRequest = "2105"; liveData->commandRequest = "2105";
liveData->responseRowMerged = "6105FFFFFFFF00000000001717171817171726482648000150181703E81A03E801520029000000000000000000000000"; liveData->responseRowMerged = "6105FFFFFFFF00000000000B0B0B0B0B0B0B136826480001500B0B03E80203E831C60031000000000000000000000000";
parseRowMerged();
// 220106
liveData->commandRequest = "2106";
liveData->responseRowMerged = "7F2112"; // n/a on ioniq
parseRowMerged(); parseRowMerged();
// BCM / TPMS ATSH7A0 // BCM / TPMS ATSH7A0
liveData->currentAtshRequest = "ATSH7A0"; liveData->currentAtshRequest = "ATSH7A0";
// 22c00b // 22c00b
liveData->commandRequest = "22c00b"; liveData->commandRequest = "22c00b";
liveData->responseRowMerged = "62C00BFFFF0000B9510100B9510100B84F0100B54F0100AAAAAAAA"; liveData->responseRowMerged = "62C00BFFFF0000B73D0100B63D0100B43D0100B53C0100AAAAAAAA";
parseRowMerged(); parseRowMerged();
// ATSH7C6 // ATSH7C6
liveData->currentAtshRequest = "ATSH7C6"; liveData->currentAtshRequest = "ATSH7C6";
// 22b002 // 22b002
liveData->commandRequest = "22b002"; liveData->commandRequest = "22b002";
liveData->responseRowMerged = "62B002E000000000AD003D2D0000000000000000"; liveData->responseRowMerged = "62B002E000000000AA003B0B0000000000000000";
parseRowMerged();
//ATSH770
liveData->currentAtshRequest = "ATSH770";
liveData->commandRequest = "22BC03";
liveData->responseRowMerged = "62BC03FDEE3C7300600000AAAA";
parseRowMerged();
liveData->commandRequest = "22BC06";
liveData->responseRowMerged = "62BC06B480000000000000AAAA";
parseRowMerged(); parseRowMerged();
/* liveData->params.batModule01TempC = 28; /* liveData->params.batModule01TempC = 28;
@@ -377,5 +424,3 @@ void CarHyundaiIoniq::loadTestData() {
*/ */
} }
#endif //CARHYUNDAIIONIQ_CPP

View File

@@ -1,5 +1,4 @@
#ifndef CARHYUNDAIIONIQ_H #pragma once
#define CARHYUNDAIIONIQ_H
#include "CarInterface.h" #include "CarInterface.h"
@@ -12,5 +11,3 @@ class CarHyundaiIoniq : public CarInterface {
void parseRowMerged() override; void parseRowMerged() override;
void loadTestData() override; void loadTestData() override;
}; };
#endif

View File

@@ -1,6 +1,3 @@
#ifndef CARINTERFACE_CPP
#define CARINTERFACE_CPP
#include "CarInterface.h" #include "CarInterface.h"
#include "LiveData.h" #include "LiveData.h"
@@ -19,5 +16,3 @@ void CarInterface::parseRowMerged() {
void CarInterface::loadTestData() { void CarInterface::loadTestData() {
} }
#endif // CARINTERFACE_CPP

View File

@@ -1,9 +1,7 @@
#ifndef CARKIADEBUGOBD2_CPP
#define CARKIADEBUGOBD2_CPP
#include "CarKiaDebugObd2.h" #include "CarKiaDebugObd2.h"
#include <vector>
#define commandQueueCountDebugObd2Kia 256 //#define commandQueueCountDebugObd2Kia 256
#define commandQueueLoopFromDebugObd2Kia 8 #define commandQueueLoopFromDebugObd2Kia 8
/** /**
@@ -11,7 +9,7 @@
*/ */
void CarKiaDebugObd2::activateCommandQueue() { void CarKiaDebugObd2::activateCommandQueue() {
String commandQueueDebugObd2Kia[commandQueueCountDebugObd2Kia] = { std::vector<String> commandQueueDebugObd2Kia = {
"AT Z", // Reset all "AT Z", // Reset all
"AT I", // Print the version ID "AT I", // Print the version ID
"AT E0", // Echo off "AT E0", // Echo off
@@ -222,15 +220,13 @@ void CarKiaDebugObd2::activateCommandQueue() {
liveData->params.batteryTotalAvailableKWh = 64; liveData->params.batteryTotalAvailableKWh = 64;
// Empty and fill command queue // Empty and fill command queue
for (uint16_t i = 0; i < 300; i++) { liveData->commandQueue.clear();
liveData->commandQueue[i] = ""; for (auto cmd : commandQueueDebugObd2Kia) {
} liveData->commandQueue.push_back({ 0, cmd });
for (uint16_t i = 0; i < commandQueueCountDebugObd2Kia; i++) {
liveData->commandQueue[i] = commandQueueDebugObd2Kia[i];
} }
liveData->commandQueueLoopFrom = commandQueueLoopFromDebugObd2Kia; liveData->commandQueueLoopFrom = commandQueueLoopFromDebugObd2Kia;
liveData->commandQueueCount = commandQueueCountDebugObd2Kia; liveData->commandQueueCount = commandQueueDebugObd2Kia.size();
} }
/** /**
@@ -516,5 +512,3 @@ void CarKiaDebugObd2::loadTestData() {
liveData->params.soc10time[0] = liveData->params.soc10time[1] + 900; liveData->params.soc10time[0] = liveData->params.soc10time[1] + 900;
} }
#endif // CARKIADEBUGOBD2_CPP

View File

@@ -1,5 +1,4 @@
#ifndef CARKIADEBUGOBD2_H #pragma once
#define CARKIADEBUGOBD2_H
#include "CarInterface.h" #include "CarInterface.h"
@@ -12,5 +11,3 @@ class CarKiaDebugObd2 : public CarInterface {
void parseRowMerged() override; void parseRowMerged() override;
void loadTestData() override; void loadTestData() override;
}; };
#endif // CARKIADEBUGOBD2_H

View File

@@ -1,15 +1,12 @@
#ifndef CARKIAENIRO_CPP /*
#define CARKIAENIRO_CPP eNiro/Kona chargings limits depending on battery temperature (min.value of 01-04 battery module)
/*
* eNiro/Kona chargings limits depending on battery temperature (min.value of 01-04 battery module)
>= 35°C BMS allows max 180A >= 35°C BMS allows max 180A
>= 25°C without limit (200A) >= 25°C without limit (200A)
>= 15°C BMS allows max 120A >= 15°C BMS allows max 120A
>= 5°C BMS allows max 90A >= 5°C BMS allows max 90A
>= 1°C BMS allows max 60A >= 1°C BMS allows max 60A
<= 0°C BMS allows max 40A <= 0°C BMS allows max 40A
*/ */
#include <Arduino.h> #include <Arduino.h>
#include <stdint.h> #include <stdint.h>
@@ -18,16 +15,16 @@
#include <sys/time.h> #include <sys/time.h>
#include "LiveData.h" #include "LiveData.h"
#include "CarKiaEniro.h" #include "CarKiaEniro.h"
#include <vector>
#define commandQueueCountKiaENiro 30 #define commandQueueLoopFromKiaENiro 8
#define commandQueueLoopFromKiaENiro 10
/** /**
* activateCommandQueue activateCommandQueue
*/ */
void CarKiaEniro::activateCommandQueue() { void CarKiaEniro::activateCommandQueue() {
String commandQueueKiaENiro[commandQueueCountKiaENiro] = { std::vector<String> commandQueueKiaENiro = {
"AT Z", // Reset all "AT Z", // Reset all
"AT I", // Print the version ID "AT I", // Print the version ID
"AT S0", // Printing of spaces on "AT S0", // Printing of spaces on
@@ -45,18 +42,31 @@ void CarKiaEniro::activateCommandQueue() {
// Loop from (KIA ENIRO) // Loop from (KIA ENIRO)
// ABS / ESP + AHB
"ATSH7D1",
"22C101", // brake, park/drive mode
// IGPM // IGPM
"ATSH770", "ATSH770",
"22BC03", // low beam "22BC03", // low beam
"22BC06", // brake light "22BC06", // brake light
// ABS / ESP + AHB
"ATSH7D1",
"22C101", // brake, park/drive mode
// BCM / TPMS
"ATSH7A0",
"22c00b", // tire pressure/temp
// Aircondition
"ATSH7B3",
"220100", // in/out temp
"220102", // coolant temp1, 2
// CLUSTER MODULE
"ATSH7C6",
"22B002", // odo
// VMCU // VMCU
"ATSH7E2", "ATSH7E2",
"2101", // speed, ... // "2101", // speed, ...
"2102", // aux, ... "2102", // aux, ...
// BMS // BMS
@@ -68,102 +78,94 @@ void CarKiaEniro::activateCommandQueue() {
"220105", // soh, soc, .. "220105", // soh, soc, ..
"220106", // cooling water temp "220106", // cooling water temp
// Aircondition
"ATSH7B3",
"220100", // in/out temp
"220102", // coolant temp1, 2
// BCM / TPMS
"ATSH7A0",
"22c00b", // tire pressure/temp
// CLUSTER MODULE
"ATSH7C6",
"22B002", // odo
}; };
// 39 or 64 kWh model? // 39 or 64 kWh model?
liveData->params.batModuleTempCount = 4; liveData->params.batModuleTempCount = 4;
liveData->params.batteryTotalAvailableKWh = 64; liveData->params.batteryTotalAvailableKWh = 64;
// =(I18*0,615)*(1+(I18*0,0008)) soc to kwh niro ev 2020 // =(I18*0,615)*(1+(I18*0,0008)) soc to kwh niro ev 2020
// Calculates based on nick.n17 dashboard data
if (liveData->settings.carType == CAR_KIA_ENIRO_2020_39 || liveData->settings.carType == CAR_HYUNDAI_KONA_2020_39) { if (liveData->settings.carType == CAR_KIA_ENIRO_2020_39 || liveData->settings.carType == CAR_HYUNDAI_KONA_2020_39) {
liveData->params.batteryTotalAvailableKWh = 39.2; liveData->params.batteryTotalAvailableKWh = 39.2;
} }
// Empty and fill command queue // Empty and fill command queue
for (int i = 0; i < 300; i++) { liveData->commandQueue.clear();
liveData->commandQueue[i] = ""; //for (int i = 0; i < commandQueueCountKiaENiro; i++) {
} for (auto cmd : commandQueueKiaENiro) {
for (int i = 0; i < commandQueueCountKiaENiro; i++) { liveData->commandQueue.push_back({ 0, cmd }); // stxChar not used, keep it 0
liveData->commandQueue[i] = commandQueueKiaENiro[i];
} }
liveData->commandQueueLoopFrom = commandQueueLoopFromKiaENiro; liveData->commandQueueLoopFrom = commandQueueLoopFromKiaENiro;
liveData->commandQueueCount = commandQueueCountKiaENiro; liveData->commandQueueCount = commandQueueKiaENiro.size();
} }
/** /**
* parseRowMerged parseRowMerged
*/ */
void CarKiaEniro::parseRowMerged() { void CarKiaEniro::parseRowMerged() {
bool tempByte; uint8_t tempByte;
float tempFloat; float tempFloat;
String tmpStr; String tmpStr;
// IGPM
// RESPONDING WHEN CAR IS OFF
if (liveData->currentAtshRequest.equals("ATSH770")) {
if (liveData->commandRequest.equals("22BC03")) {
//
tempByte = liveData->hexToDecFromResponse(14, 16, 1, false);
liveData->params.hoodDoorOpen = (bitRead(tempByte, 7) == 1);
liveData->params.leftFrontDoorOpen = (bitRead(tempByte, 5) == 1);
liveData->params.rightFrontDoorOpen = (bitRead(tempByte, 0) == 1);
liveData->params.leftRearDoorOpen = (bitRead(tempByte, 4) == 1);
liveData->params.rightRearDoorOpen = (bitRead(tempByte, 2) == 1);
//
tempByte = liveData->hexToDecFromResponse(16, 18, 1, false);
liveData->params.ignitionOn = (bitRead(tempByte, 5) == 1);
liveData->params.trunkDoorOpen = (bitRead(tempByte, 0) == 1);
if (liveData->params.ignitionOn) {
liveData->params.lastIgnitionOnTime = liveData->params.currentTime;
}
tempByte = liveData->hexToDecFromResponse(18, 20, 1, false);
liveData->params.headLights = (bitRead(tempByte, 5) == 1);
liveData->params.dayLights = (bitRead(tempByte, 3) == 1);
}
if (liveData->commandRequest.equals("22BC06")) {
tempByte = liveData->hexToDecFromResponse(14, 16, 1, false);
liveData->params.brakeLights = (bitRead(tempByte, 5) == 1);
}
}
// ABS / ESP + AHB 7D1 // ABS / ESP + AHB 7D1
// RESPONDING WHEN CAR IS OFF
if (liveData->currentAtshRequest.equals("ATSH7D1")) { if (liveData->currentAtshRequest.equals("ATSH7D1")) {
if (liveData->commandRequest.equals("22C101")) { if (liveData->commandRequest.equals("22C101")) {
uint8_t driveMode = liveData->hexToDecFromResponse(22, 24, 1, false); uint8_t driveMode = liveData->hexToDecFromResponse(22, 24, 1, false);
liveData->params.forwardDriveMode = (driveMode == 4); liveData->params.forwardDriveMode = (driveMode == 4);
liveData->params.reverseDriveMode = (driveMode == 2); liveData->params.reverseDriveMode = (driveMode == 2);
liveData->params.parkModeOrNeutral = (driveMode == 1); liveData->params.parkModeOrNeutral = (driveMode == 1);
// Speed
liveData->params.speedKmh = liveData->hexToDecFromResponse(18, 20, 2, false);
} }
} }
// IGPM // TPMS 7A0
if (liveData->currentAtshRequest.equals("ATSH770")) { if (liveData->currentAtshRequest.equals("ATSH7A0")) {
if (liveData->commandRequest.equals("22BC03")) { if (liveData->commandRequest.equals("22c00b")) {
tempByte = liveData->hexToDecFromResponse(16, 18, 1, false); liveData->params.tireFrontLeftPressureBar = liveData->hexToDecFromResponse(14, 16, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.ignitionOnPrevious = liveData->params.ignitionOn; liveData->params.tireFrontRightPressureBar = liveData->hexToDecFromResponse(22, 24, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.ignitionOn = (bitRead(tempByte, 5) == 1); liveData->params.tireRearRightPressureBar = liveData->hexToDecFromResponse(30, 32, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
if (liveData->params.ignitionOnPrevious && !liveData->params.ignitionOn) liveData->params.tireRearLeftPressureBar = liveData->hexToDecFromResponse(38, 40, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.automaticShutdownTimer = liveData->params.currentTime; liveData->params.tireFrontLeftTempC = liveData->hexToDecFromResponse(16, 18, 2, false) - 50; // === OK Valid
liveData->params.tireFrontRightTempC = liveData->hexToDecFromResponse(24, 26, 2, false) - 50; // === OK Valid
liveData->params.lightInfo = liveData->hexToDecFromResponse(18, 20, 1, false); liveData->params.tireRearRightTempC = liveData->hexToDecFromResponse(32, 34, 2, false) - 50; // === OK Valid
liveData->params.headLights = (bitRead(liveData->params.lightInfo, 5) == 1); liveData->params.tireRearLeftTempC = liveData->hexToDecFromResponse(40, 42, 2, false) - 50; // === OK Valid
liveData->params.dayLights = (bitRead(liveData->params.lightInfo, 3) == 1);
}
if (liveData->commandRequest.equals("22BC06")) {
liveData->params.brakeLightInfo = liveData->hexToDecFromResponse(14, 16, 1, false);
liveData->params.brakeLights = (bitRead(liveData->params.brakeLightInfo, 5) == 1);
} }
} }
// VMCU 7E2 // Aircon 7B3
if (liveData->currentAtshRequest.equals("ATSH7E2")) {
if (liveData->commandRequest.equals("2101")) {
liveData->params.speedKmh = liveData->hexToDecFromResponse(32, 36, 2, false) * 0.0155; // / 100.0 *1.609 = real to gps is 1.750
if (liveData->params.speedKmh < -99 || liveData->params.speedKmh > 200)
liveData->params.speedKmh = 0;
}
if (liveData->commandRequest.equals("2102")) {
liveData->params.auxPerc = liveData->hexToDecFromResponse(50, 52, 1, false);
liveData->params.auxCurrentAmp = - liveData->hexToDecFromResponse(46, 50, 2, true) / 1000.0;
}
}
// Cluster module 7c6
if (liveData->currentAtshRequest.equals("ATSH7C6")) {
if (liveData->commandRequest.equals("22B002")) {
tempFloat = liveData->params.odoKm;
liveData->params.odoKm = liveData->decFromResponse(18, 24);
//if (tempFloat != liveData->params.odoKm) liveData->params.sdcardCanNotify = true;
}
}
// Aircon 7b3
if (liveData->currentAtshRequest.equals("ATSH7B3")) { if (liveData->currentAtshRequest.equals("ATSH7B3")) {
if (liveData->commandRequest.equals("220100")) { if (liveData->commandRequest.equals("220100")) {
liveData->params.indoorTemperature = (liveData->hexToDecFromResponse(16, 18, 1, false) / 2) - 40; liveData->params.indoorTemperature = (liveData->hexToDecFromResponse(16, 18, 1, false) / 2) - 40;
@@ -175,6 +177,28 @@ void CarKiaEniro::parseRowMerged() {
} }
} }
// Cluster module 7C6
if (liveData->currentAtshRequest.equals("ATSH7C6")) {
if (liveData->commandRequest.equals("22B002")) {
tempFloat = liveData->params.odoKm;
liveData->params.odoKm = liveData->decFromResponse(18, 24);
//if (tempFloat != liveData->params.odoKm) liveData->params.sdcardCanNotify = true;
}
}
// VMCU 7E2
if (liveData->currentAtshRequest.equals("ATSH7E2")) {
/*if (liveData->commandRequest.equals("2101")) {
liveData->params.speedKmh = liveData->hexToDecFromResponse(32, 36, 2, false) * 0.0155; // / 100.0 *1.609 = real to gps is 1.750
if (liveData->params.speedKmh < -99 || liveData->params.speedKmh > 200)
liveData->params.speedKmh = 0;
}*/
if (liveData->commandRequest.equals("2102")) {
liveData->params.auxCurrentAmp = - liveData->hexToDecFromResponse(46, 50, 2, true) / 1000.0;
liveData->params.auxPerc = liveData->hexToDecFromResponse(50, 52, 1, false);
}
}
// BMS 7e4 // BMS 7e4
if (liveData->currentAtshRequest.equals("ATSH7E4")) { if (liveData->currentAtshRequest.equals("ATSH7E4")) {
if (liveData->commandRequest.equals("220101")) { if (liveData->commandRequest.equals("220101")) {
@@ -188,15 +212,15 @@ void CarKiaEniro::parseRowMerged() {
liveData->params.availableChargePower = liveData->decFromResponse(16, 20) / 100.0; liveData->params.availableChargePower = liveData->decFromResponse(16, 20) / 100.0;
liveData->params.availableDischargePower = liveData->decFromResponse(20, 24) / 100.0; liveData->params.availableDischargePower = liveData->decFromResponse(20, 24) / 100.0;
//liveData->params.isolationResistanceKOhm = liveData->hexToDecFromResponse(118, 122, 2, true); //liveData->params.isolationResistanceKOhm = liveData->hexToDecFromResponse(118, 122, 2, true);
liveData->params.batFanStatus = liveData->hexToDecFromResponse(60, 62, 2, true); liveData->params.batFanStatus = liveData->hexToDecFromResponse(60, 62, 1, false);
liveData->params.batFanFeedbackHz = liveData->hexToDecFromResponse(62, 64, 2, true); liveData->params.batFanFeedbackHz = liveData->hexToDecFromResponse(62, 64, 1, false);
liveData->params.auxVoltage = liveData->hexToDecFromResponse(64, 66, 2, true) / 10.0;
liveData->params.batPowerAmp = - liveData->hexToDecFromResponse(26, 30, 2, true) / 10.0; liveData->params.batPowerAmp = - liveData->hexToDecFromResponse(26, 30, 2, true) / 10.0;
liveData->params.batVoltage = liveData->hexToDecFromResponse(30, 34, 2, false) / 10.0; liveData->params.batVoltage = liveData->hexToDecFromResponse(30, 34, 2, false) / 10.0;
liveData->params.batPowerKw = (liveData->params.batPowerAmp * liveData->params.batVoltage) / 1000.0; liveData->params.batPowerKw = (liveData->params.batPowerAmp * liveData->params.batVoltage) / 1000.0;
if (liveData->params.batPowerKw < 0) // Reset charging start time if (liveData->params.batPowerKw < 0) // Reset charging start time
liveData->params.chargingStartTime = liveData->params.currentTime; liveData->params.chargingStartTime = liveData->params.currentTime;
liveData->params.batPowerKwh100 = liveData->params.batPowerKw / liveData->params.speedKmh * 100; liveData->params.batPowerKwh100 = liveData->params.batPowerKw / liveData->params.speedKmh * 100;
liveData->params.auxVoltage = liveData->hexToDecFromResponse(64, 66, 1, false) / 10.0;
liveData->params.batCellMaxV = liveData->hexToDecFromResponse(52, 54, 1, false) / 50.0; liveData->params.batCellMaxV = liveData->hexToDecFromResponse(52, 54, 1, false) / 50.0;
liveData->params.batCellMinV = liveData->hexToDecFromResponse(56, 58, 1, false) / 50.0; liveData->params.batCellMinV = liveData->hexToDecFromResponse(56, 58, 1, false) / 50.0;
liveData->params.batModuleTempC[0] = liveData->hexToDecFromResponse(38, 40, 1, true); liveData->params.batModuleTempC[0] = liveData->hexToDecFromResponse(38, 40, 1, true);
@@ -208,6 +232,15 @@ void CarKiaEniro::parseRowMerged() {
//liveData->params.batMaxC = liveData->hexToDecFromResponse(34, 36, 1, true); //liveData->params.batMaxC = liveData->hexToDecFromResponse(34, 36, 1, true);
//liveData->params.batMinC = liveData->hexToDecFromResponse(36, 38, 1, true); //liveData->params.batMinC = liveData->hexToDecFromResponse(36, 38, 1, true);
// Ignition Off/on
// tempByte = liveData->hexToDecFromResponse(106, 108, 1, false);
// liveData->params.chargingOn = (bitRead(tempByte, 2) == 1);
tempByte = liveData->hexToDecFromResponse(24, 26, 1, false);
liveData->params.chargingOn = (bitRead(tempByte, 5) == 1 || bitRead(tempByte, 6) == 1); // bit 5 = AC; bit 6 = DC
if(liveData->params.chargingOn) {
liveData->params.lastChargingOnTime = liveData->params.currentTime;
}
// 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) // 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)
liveData->params.batMinC = liveData->params.batMaxC = liveData->params.batModuleTempC[0]; liveData->params.batMinC = liveData->params.batMaxC = liveData->params.batModuleTempC[0];
for (uint16_t i = 1; i < liveData->params.batModuleTempCount; i++) { for (uint16_t i = 1; i < liveData->params.batModuleTempCount; i++) {
@@ -278,7 +311,7 @@ void CarKiaEniro::parseRowMerged() {
} }
// BMS 7e4 // BMS 7e4
if (liveData->commandRequest.equals("220106")) { if (liveData->commandRequest.equals("220106")) {
liveData->params.coolingWaterTempC = liveData->hexToDecFromResponse(14, 16, 1, false); liveData->params.coolingWaterTempC = liveData->hexToDecFromResponse(14, 16, 1, true);
liveData->params.bmsUnknownTempC = liveData->hexToDecFromResponse(18, 20, 1, true); liveData->params.bmsUnknownTempC = liveData->hexToDecFromResponse(18, 20, 1, true);
liveData->params.bmsUnknownTempD = liveData->hexToDecFromResponse(46, 48, 1, true); liveData->params.bmsUnknownTempD = liveData->hexToDecFromResponse(46, 48, 1, true);
// log 220106 to sdcard // log 220106 to sdcard
@@ -286,25 +319,11 @@ void CarKiaEniro::parseRowMerged() {
tmpStr.toCharArray(liveData->params.debugData2, tmpStr.length() + 1); tmpStr.toCharArray(liveData->params.debugData2, tmpStr.length() + 1);
} }
} }
// TPMS 7a0
if (liveData->currentAtshRequest.equals("ATSH7A0")) {
if (liveData->commandRequest.equals("22c00b")) {
liveData->params.tireFrontLeftPressureBar = liveData->hexToDecFromResponse(14, 16, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireFrontRightPressureBar = liveData->hexToDecFromResponse(22, 24, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireRearRightPressureBar = liveData->hexToDecFromResponse(30, 32, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireRearLeftPressureBar = liveData->hexToDecFromResponse(38, 40, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireFrontLeftTempC = liveData->hexToDecFromResponse(16, 18, 2, false) - 50; // === OK Valid
liveData->params.tireFrontRightTempC = liveData->hexToDecFromResponse(24, 26, 2, false) - 50; // === OK Valid
liveData->params.tireRearRightTempC = liveData->hexToDecFromResponse(32, 34, 2, false) - 50; // === OK Valid
liveData->params.tireRearLeftTempC = liveData->hexToDecFromResponse(40, 42, 2, false) - 50; // === OK Valid
}
}
} }
/** /**
* loadTestData loadTestData
*/ */
void CarKiaEniro::loadTestData() { void CarKiaEniro::loadTestData() {
// IGPM // IGPM
@@ -454,5 +473,3 @@ void CarKiaEniro::loadTestData() {
liveData->params.soc10time[0] = liveData->params.soc10time[1] + 900; liveData->params.soc10time[0] = liveData->params.soc10time[1] + 900;
} }
#endif // CARKIAENIRO_CPP

View File

@@ -1,5 +1,4 @@
#ifndef CARKIAENIRO_H #pragma once
#define CARKIAENIRO_H
#include "CarInterface.h" #include "CarInterface.h"
@@ -12,5 +11,3 @@ class CarKiaEniro : public CarInterface {
void parseRowMerged() override; void parseRowMerged() override;
void loadTestData() override; void loadTestData() override;
}; };
#endif // CARKIAENIRO_H

View File

@@ -1,9 +1,6 @@
#ifndef CARKIANIROPHEV_CPP
#define CARKIANIROPHEV_CPP
#include "CarKiaNiroPhev.h" #include "CarKiaNiroPhev.h"
#include <vector>
#define commandQueueCountKiaNiroPhev 25
#define commandQueueLoopFromKiaNiroPhev 8 #define commandQueueLoopFromKiaNiroPhev 8
/** /**
@@ -11,7 +8,7 @@
*/ */
void CarKiaNiroPhev::activateCommandQueue() { void CarKiaNiroPhev::activateCommandQueue() {
String commandQueueKiaNiroPhev[commandQueueCountKiaNiroPhev] = { std::vector<String> commandQueueKiaNiroPhev = {
"AT Z", // Reset all "AT Z", // Reset all
"AT I", // Print the version ID "AT I", // Print the version ID
"AT E0", // Echo off "AT E0", // Echo off
@@ -67,15 +64,13 @@ void CarKiaNiroPhev::activateCommandQueue() {
liveData->params.batModuleTempCount = 5; liveData->params.batModuleTempCount = 5;
// Empty and fill command queue // Empty and fill command queue
for (int i = 0; i < 300; i++) { liveData->commandQueue.clear();
liveData->commandQueue[i] = ""; for (auto cmd : commandQueueKiaNiroPhev) {
} liveData->commandQueue.push_back({ 0, cmd });
for (int i = 0; i < commandQueueCountKiaNiroPhev; i++) {
liveData->commandQueue[i] = commandQueueKiaNiroPhev[i];
} }
liveData->commandQueueLoopFrom = commandQueueLoopFromKiaNiroPhev; liveData->commandQueueLoopFrom = commandQueueLoopFromKiaNiroPhev;
liveData->commandQueueCount = commandQueueCountKiaNiroPhev; liveData->commandQueueCount = commandQueueKiaNiroPhev.size();
} }
/** /**
@@ -376,5 +371,3 @@ void CarKiaNiroPhev::loadTestData() {
*/ */
} }
#endif //CARKIANIROPHEV_CPP

View File

@@ -1,5 +1,4 @@
#ifndef CARKIANIROPHEV_H #pragma once
#define CARKIANIROPHEV_H
#include "CarInterface.h" #include "CarInterface.h"
@@ -12,5 +11,3 @@ class CarKiaNiroPhev: public CarInterface {
void parseRowMerged() override; void parseRowMerged() override;
void loadTestData() override; void loadTestData() override;
}; };
#endif

View File

@@ -1,6 +1,3 @@
#ifndef CARRENAULTZOE_CPP
#define CARRENAULTZOE_CPP
#include <Arduino.h> #include <Arduino.h>
#include <stdint.h> #include <stdint.h>
#include <WString.h> #include <WString.h>
@@ -8,16 +5,16 @@
#include <sys/time.h> #include <sys/time.h>
#include "LiveData.h" #include "LiveData.h"
#include "CarRenaultZoe.h" #include "CarRenaultZoe.h"
#include <vector>
#define commandQueueCountRenaultZoe 18 #define commandQueueLoopFromRenaultZoe 8
#define commandQueueLoopFromRenaultZoe 11
/** /**
activateCommandQueue activateCommandQueue
*/ */
void CarRenaultZoe::activateCommandQueue() { void CarRenaultZoe::activateCommandQueue() {
String commandQueueRenaultZoe[commandQueueCountRenaultZoe] = { std::vector<String> commandQueueRenaultZoe = {
"AT Z", // Reset all "AT Z", // Reset all
"AT I", // Print the version ID "AT I", // Print the version ID
"AT S0", // Printing of spaces on "AT S0", // Printing of spaces on
@@ -32,36 +29,97 @@ void CarRenaultZoe::activateCommandQueue() {
////"AT AT0", // disabled adaptive timing ////"AT AT0", // disabled adaptive timing
"AT DP", "AT DP",
"AT ST16", // reduced timeout to 1, orig.16 "AT ST16", // reduced timeout to 1, orig.16
"atfcsd300010",
"atfcsm1", // Allow long messages
// Loop from (RENAULT ZOE) // Loop from (RENAULT ZOE)
// LBC Lithium battery controller // LBC Lithium battery controller
"ATSH79B", "ATSH79B",
"ATFCSH79B", "ATFCSH79B",
"2101", "atfcsd300010",
"2103", "atfcsm1",
"2104", "221415",
"2141", "2101", // 034 61011383138600000000000000000000000009970D620FC920D0000005420000000000000008D80500000B202927100000000000000000
"2142", "2103", // 01D 6103018516A717240000000001850185000000FFFF07D00516E60000030000000000
"2161", "2104", // 04D 6104099A37098D37098F3709903709AC3609BB3609A136098B37099737098A37098437099437FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF363637000000000000
"2141", // 07e 61410F360F380F380F360F380F380F380F380F390F390F3A0F390F3A0F390F390F380F380F390F380F380F380F390F390F380F380F3A0F390F380F380F380F390F390F350F380F360F380F380F380F380F360F380F360F350F380F360F380F360F360F350F360F360F380F380F380F3A0F380F3A0F3A0F390F390F380F36000000000000
"2142", // 04a 61420F3A0F390F390F390F380F380F390F390F390F390F360F360F380F380F360F380F360F380F390F390F3A0F390F390F3A0F3A0F3A0F380F390F390F3A0F390F390F380F38922091F20000
"2161", // 014 6161000AA820C8C8C8C2C20001545800004696FF
// CLUSTER Instrument panel
"ATSH743",
"ATFCSH743",
"atfcsd300010",
"atfcsm1",
// "220201", // 62020175300168
// "220202", // 62020274710123
// "220203"- "220205", // 7F2212
"220206", // 62020600015459
//"222204", // temp ext.
// BCB 793 Battery Connection Box
// "ATSH792",
// "ATFCSH792",
// "atfcsd300010",
// "atfcsm1",
// "223101", to "223114", // all with negative 7F2212*/
// CLIM 764 CLIMATE CONTROL
"ATSH744",
"ATFCSH744",
"atfcsd300010",
"atfcsm1",
"2143",
// "2180", // NO DATA
// "2181", // NO DATA
//"2182", // 618038303139520430343239353031520602051523080201008815
// "2125", // 6125000000000000000000000000000000000000
// "2126", // NO DATA
// "2128", // NO DATA
// EVC 7ec El vehicle controler
"ATSH7E4",
"ATFCSH7E4",
"atfcsd300010",
"atfcsm1",
// "222001", // 62200136
// "222002", // 6220020B3D
"222003", // 6220030000
// "222004", // 62200402ED
// "222005", // 6220050532
// "222006", // 622006015459
// PEB 77e Power Electronics Bloc
// "ATSH75A",
// "ATFCSH75A",
// "atfcsd300010",
// "atfcsm1",
// "223009", // 6230093640
// UBP 7bc Uncoupled Braking Pedal
// "ATSH79C",
// "ATFCSH79C",
// "atfcsd300010",
// "atfcsm1",
// "21F0", // 61F0303235315204303337333733325215160C0400000101008800
// "21F1", // 61F10000000000F000000000F0000000000012061400005C91F600
// "21FE", // 61FE333731325204303337333733325215160C0400010201008800
}; };
// //
liveData->params.batModuleTempCount = 12; // 24, 12 is display limit liveData->params.batModuleTempCount = 12; // 24, 12 is display limit
liveData->params.batteryTotalAvailableKWh = 28; liveData->params.batteryTotalAvailableKWh = 22;
// usable 22, total 26
// Empty and fill command queue // Empty and fill command queue
for (int i = 0; i < 300; i++) { liveData->commandQueue.clear();
liveData->commandQueue[i] = ""; for (auto cmd : commandQueueRenaultZoe) {
} liveData->commandQueue.push_back({ 0, cmd });
for (int i = 0; i < commandQueueCountRenaultZoe; i++) {
liveData->commandQueue[i] = commandQueueRenaultZoe[i];
} }
liveData->commandQueueLoopFrom = commandQueueLoopFromRenaultZoe; liveData->commandQueueLoopFrom = commandQueueLoopFromRenaultZoe;
liveData->commandQueueCount = commandQueueCountRenaultZoe; liveData->commandQueueCount = commandQueueRenaultZoe.size();
} }
/** /**
@@ -69,13 +127,19 @@ void CarRenaultZoe::activateCommandQueue() {
*/ */
void CarRenaultZoe::parseRowMerged() { void CarRenaultZoe::parseRowMerged() {
bool tempByte; uint8_t tempByte;
// LBC 79B // LBC 79B
if (liveData->currentAtshRequest.equals("ATSH79B")) { if (liveData->currentAtshRequest.equals("ATSH79B")) {
if (liveData->commandRequest.equals("221415")) {
liveData->params.batVoltage = liveData->hexToDecFromResponse(6, 8, 2, false);
}
if (liveData->commandRequest.equals("2101")) { if (liveData->commandRequest.equals("2101")) {
liveData->params.batPowerAmp = liveData->hexToDecFromResponse(4, 8, 2, false) - 5000; liveData->params.batPowerAmp = liveData->hexToDecFromResponse(4, 8, 2, false) - 5000;
liveData->params.batPowerKw = (liveData->params.batPowerAmp * liveData->params.batVoltage) / 1000.0; liveData->params.batPowerKw = (liveData->params.batPowerAmp * liveData->params.batVoltage) / 1000.0;
if (liveData->params.batPowerKw < 0) // Reset charging start time
liveData->params.chargingStartTime = liveData->params.currentTime;
liveData->params.batPowerKwh100 = liveData->params.batPowerKw / liveData->params.speedKmh * 100;
liveData->params.auxVoltage = liveData->hexToDecFromResponse(56, 60, 2, false) / 100.0; liveData->params.auxVoltage = liveData->hexToDecFromResponse(56, 60, 2, false) / 100.0;
liveData->params.availableChargePower = liveData->hexToDecFromResponse(84, 88, 2, false) / 100.0; liveData->params.availableChargePower = liveData->hexToDecFromResponse(84, 88, 2, false) / 100.0;
liveData->params.batCellMinV = liveData->hexToDecFromResponse(24, 28, 2, false) / 100.0; liveData->params.batCellMinV = liveData->hexToDecFromResponse(24, 28, 2, false) / 100.0;
@@ -92,6 +156,14 @@ void CarRenaultZoe::parseRowMerged() {
for (uint16_t i = 12; i < 24; i++) { for (uint16_t i = 12; i < 24; i++) {
liveData->params.batModuleTempC[i] = liveData->hexToDecFromResponse(80 + ((i - 12) * 6), 82 + ((i - 12) * 6), 1, false) - 40; liveData->params.batModuleTempC[i] = liveData->hexToDecFromResponse(80 + ((i - 12) * 6), 82 + ((i - 12) * 6), 1, false) - 40;
} }
liveData->params.batMinC = liveData->params.batMaxC = liveData->params.batModuleTempC[0];
for (uint16_t i = 1; i < 24; i++) {
if (liveData->params.batModuleTempC[i] < liveData->params.batMinC)
liveData->params.batMinC = liveData->params.batModuleTempC[i];
if (liveData->params.batModuleTempC[i] > liveData->params.batMaxC)
liveData->params.batMaxC = liveData->params.batModuleTempC[i];
}
liveData->params.batTempC = liveData->params.batMinC;
} }
if (liveData->commandRequest.equals("2141")) { if (liveData->commandRequest.equals("2141")) {
for (int i = 0; i < 62; i++) { for (int i = 0; i < 62; i++) {
@@ -108,178 +180,99 @@ void CarRenaultZoe::parseRowMerged() {
} }
} }
// CLUSTER 743
/* niro if (liveData->currentAtshRequest.equals("ATSH743")) {
// ABS / ESP + AHB 7D1 if (liveData->commandRequest.equals("220206")) {
if (liveData->currentAtshRequest.equals("ATSH7D1")) { liveData->params.odoKm = liveData->hexToDecFromResponse(6, 14, 4, false);
if (liveData->commandRequest.equals("22C101")) {
uint8_t driveMode = liveData->hexToDecFromResponse(22, 24, 1, false);
liveData->params.forwardDriveMode = (driveMode == 4);
liveData->params.reverseDriveMode = (driveMode == 2);
liveData->params.parkModeOrNeutral = (driveMode == 1);
}
} }
}
// IGPM // CLUSTER ATSH7E4
if (liveData->currentAtshRequest.equals("ATSH770")) { if (liveData->currentAtshRequest.equals("ATSH7E4")) {
if (liveData->commandRequest.equals("22BC03")) { if (liveData->commandRequest.equals("222003")) {
tempByte = liveData->hexToDecFromResponse(16, 18, 1, false); liveData->params.speedKmh = liveData->hexToDecFromResponse(6, 8, 2, false) / 100;
liveData->params.ignitionOnPrevious = liveData->params.ignitionOn; if (liveData->params.speedKmh < -99 || liveData->params.speedKmh > 200)
liveData->params.ignitionOn = (bitRead(tempByte, 5) == 1); liveData->params.speedKmh = 0;
if (liveData->params.ignitionOnPrevious && !liveData->params.ignitionOn)
liveData->params.automaticShutdownTimer = liveData->params.currentTime;
liveData->params.lightInfo = liveData->hexToDecFromResponse(18, 20, 1, false);
liveData->params.headLights = (bitRead(liveData->params.lightInfo, 5) == 1);
liveData->params.dayLights = (bitRead(liveData->params.lightInfo, 3) == 1);
}
if (liveData->commandRequest.equals("22BC06")) {
liveData->params.brakeLightInfo = liveData->hexToDecFromResponse(14, 16, 1, false);
liveData->params.brakeLights = (bitRead(liveData->params.brakeLightInfo, 5) == 1);
}
} }
}
// VMCU 7E2 // CLIM 744 CLIMATE CONTROL
if (liveData->currentAtshRequest.equals("ATSH7E2")) { if (liveData->currentAtshRequest.equals("ATSH744")) {
if (liveData->commandRequest.equals("2101")) { if (liveData->commandRequest.equals("2143")) {
liveData->params.speedKmh = liveData->hexToDecFromResponse(32, 36, 2, false) * 0.0155; // / 100.0 *1.609 = real to gps is 1.750 liveData->params.outdoorTemperature = (liveData->hexToDecFromResponse(26, 28, 1, false)) - 40;
if (liveData->params.speedKmh < -99 || liveData->params.speedKmh > 200) //liveData->params.indoorTemperature = (liveData->hexToDecFromResponse(16, 18, 1, false) / 2) - 40;
liveData->params.speedKmh = 0; //liveData->params.coolantTemp1C = (liveData->hexToDecFromResponse(14, 16, 1, false) / 2) - 40;
} //liveData->params.coolantTemp2C = (liveData->hexToDecFromResponse(16, 18, 1, false) / 2) - 40;
if (liveData->commandRequest.equals("2102")) {
liveData->params.auxPerc = liveData->hexToDecFromResponse(50, 52, 1, false);
liveData->params.auxCurrentAmp = - liveData->hexToDecFromResponse(46, 50, 2, true) / 1000.0;
}
} }
}
// Cluster module 7c6 /*uint8_t driveMode = liveData->hexToDecFromResponse(22, 24, 1, false);
if (liveData->currentAtshRequest.equals("ATSH7C6")) { liveData->params.forwardDriveMode = (driveMode == 4);
if (liveData->commandRequest.equals("22B002")) { liveData->params.reverseDriveMode = (driveMode == 2);
liveData->params.odoKm = liveData->decFromResponse(18, 24); liveData->params.parkModeOrNeutral = (driveMode == 1);
}
}
// Aircon 7b3 // IGPM
if (liveData->currentAtshRequest.equals("ATSH7B3")) { tempByte = liveData->hexToDecFromResponse(16, 18, 1, false);
if (liveData->commandRequest.equals("220100")) { liveData->params.ignitionOnPrevious = liveData->params.ignitionOn;
liveData->params.indoorTemperature = (liveData->hexToDecFromResponse(16, 18, 1, false) / 2) - 40; liveData->params.ignitionOn = (bitRead(tempByte, 5) == 1);
liveData->params.outdoorTemperature = (liveData->hexToDecFromResponse(18, 20, 1, false) / 2) - 40; if (liveData->params.ignitionOnPrevious && !liveData->params.ignitionOn)
} liveData->params.automaticShutdownTimer = liveData->params.currentTime;
if (liveData->commandRequest.equals("220102") && liveData->responseRowMerged.substring(12, 14) == "00") { liveData->params.lightInfo = liveData->hexToDecFromResponse(18, 20, 1, false);
liveData->params.coolantTemp1C = (liveData->hexToDecFromResponse(14, 16, 1, false) / 2) - 40; liveData->params.headLights = (bitRead(liveData->params.lightInfo, 5) == 1);
liveData->params.coolantTemp2C = (liveData->hexToDecFromResponse(16, 18, 1, false) / 2) - 40; liveData->params.dayLights = (bitRead(liveData->params.lightInfo, 3) == 1);
} liveData->params.brakeLightInfo = liveData->hexToDecFromResponse(14, 16, 1, false);
} liveData->params.brakeLights = (bitRead(liveData->params.brakeLightInfo, 5) == 1);
liveData->params.auxPerc = liveData->hexToDecFromResponse(50, 52, 1, false);
liveData->params.auxCurrentAmp = - liveData->hexToDecFromResponse(46, 50, 2, true) / 1000.0;
liveData->params.cumulativeEnergyChargedKWh = liveData->decFromResponse(82, 90) / 10.0;
if (liveData->params.cumulativeEnergyChargedKWhStart == -1)
liveData->params.cumulativeEnergyChargedKWhStart = liveData->params.cumulativeEnergyChargedKWh;
liveData->params.cumulativeEnergyDischargedKWh = liveData->decFromResponse(90, 98) / 10.0;
if (liveData->params.cumulativeEnergyDischargedKWhStart == -1)
liveData->params.cumulativeEnergyDischargedKWhStart = liveData->params.cumulativeEnergyDischargedKWh;
liveData->params.availableDischargePower = liveData->decFromResponse(20, 24) / 100.0;
//liveData->params.isolationResistanceKOhm = liveData->hexToDecFromResponse(118, 122, 2, true);
liveData->params.batFanStatus = liveData->hexToDecFromResponse(60, 62, 2, true);
liveData->params.batFanFeedbackHz = liveData->hexToDecFromResponse(62, 64, 2, true);
liveData->params.motorRpm = liveData->hexToDecFromResponse(112, 116, 2, false);
// 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)
liveData->params.batInletC = liveData->hexToDecFromResponse(50, 52, 1, true);
liveData->params.bmsUnknownTempA = liveData->hexToDecFromResponse(30, 32, 1, true);
liveData->params.batHeaterC = liveData->hexToDecFromResponse(52, 54, 1, true);
liveData->params.bmsUnknownTempB = liveData->hexToDecFromResponse(82, 84, 1, true);
liveData->params.coolingWaterTempC = liveData->hexToDecFromResponse(14, 16, 1, false);
liveData->params.bmsUnknownTempC = liveData->hexToDecFromResponse(18, 20, 1, true);
liveData->params.bmsUnknownTempD = liveData->hexToDecFromResponse(46, 48, 1, true);
liveData->params.tireFrontLeftPressureBar = liveData->hexToDecFromResponse(14, 16, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireFrontRightPressureBar = liveData->hexToDecFromResponse(22, 24, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireRearRightPressureBar = liveData->hexToDecFromResponse(30, 32, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireRearLeftPressureBar = liveData->hexToDecFromResponse(38, 40, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireFrontLeftTempC = liveData->hexToDecFromResponse(16, 18, 2, false) - 50; // === OK Valid
liveData->params.tireFrontRightTempC = liveData->hexToDecFromResponse(24, 26, 2, false) - 50; // === OK Valid
liveData->params.tireRearRightTempC = liveData->hexToDecFromResponse(32, 34, 2, false) - 50; // === OK Valid
liveData->params.tireRearLeftTempC = liveData->hexToDecFromResponse(40, 42, 2, false) - 50; // === OK Valid
// BMS 7e4 if (liveData->params.speedKmh < 10 && liveData->params.batPowerKw >= 1 && liveData->params.socPerc > 0 && liveData->params.socPerc <= 100) {
if (liveData->currentAtshRequest.equals("ATSH7E4")) { if ( liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)] < 0 || liveData->params.batPowerKw < liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)])
if (liveData->commandRequest.equals("220101")) { liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)] = liveData->params.batPowerKw;
liveData->params.cumulativeEnergyChargedKWh = liveData->decFromResponse(82, 90) / 10.0; if ( liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)] < 0 || liveData->params.batPowerKw > liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)])
if (liveData->params.cumulativeEnergyChargedKWhStart == -1) liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)] = liveData->params.batPowerKw;
liveData->params.cumulativeEnergyChargedKWhStart = liveData->params.cumulativeEnergyChargedKWh; liveData->params.chargingGraphBatMinTempC[int(liveData->params.socPerc)] = liveData->params.batMinC;
liveData->params.cumulativeEnergyDischargedKWh = liveData->decFromResponse(90, 98) / 10.0; liveData->params.chargingGraphBatMaxTempC[int(liveData->params.socPerc)] = liveData->params.batMaxC;
if (liveData->params.cumulativeEnergyDischargedKWhStart == -1) liveData->params.chargingGraphHeaterTempC[int(liveData->params.socPerc)] = liveData->params.batHeaterC;
liveData->params.cumulativeEnergyDischargedKWhStart = liveData->params.cumulativeEnergyDischargedKWh; liveData->params.chargingGraphWaterCoolantTempC[int(liveData->params.socPerc)] = liveData->params.coolingWaterTempC;
liveData->params.availableDischargePower = liveData->decFromResponse(20, 24) / 100.0; }
//liveData->params.isolationResistanceKOhm = liveData->hexToDecFromResponse(118, 122, 2, true); }
liveData->params.batFanStatus = liveData->hexToDecFromResponse(60, 62, 2, true); // BMS 7e4
liveData->params.batFanFeedbackHz = liveData->hexToDecFromResponse(62, 64, 2, true); if (liveData->params.socPercPrevious - liveData->params.socPerc > 0) {
liveData->params.auxVoltage = liveData->hexToDecFromResponse(64, 66, 2, true) / 10.0; byte index = (int(liveData->params.socPerc) == 4) ? 0 : (int)(liveData->params.socPerc / 10) + 1;
liveData->params.batVoltage = liveData->hexToDecFromResponse(30, 34, 2, false) / 10.0; if ((int(liveData->params.socPerc) % 10 == 9 || int(liveData->params.socPerc) == 4) && liveData->params.soc10ced[index] == -1) {
if (liveData->params.batPowerKw < 0) // Reset charging start time liveData->params.soc10ced[index] = liveData->params.cumulativeEnergyDischargedKWh;
liveData->params.chargingStartTime = liveData->params.currentTime; liveData->params.soc10cec[index] = liveData->params.cumulativeEnergyChargedKWh;
liveData->params.batPowerKwh100 = liveData->params.batPowerKw / liveData->params.speedKmh * 100; liveData->params.soc10odo[index] = liveData->params.odoKm;
liveData->params.batModuleTempC[0] = liveData->hexToDecFromResponse(38, 40, 1, true); liveData->params.soc10time[index] = liveData->params.currentTime;
liveData->params.batModuleTempC[1] = liveData->hexToDecFromResponse(40, 42, 1, true); }
liveData->params.batModuleTempC[2] = liveData->hexToDecFromResponse(42, 44, 1, true); }
liveData->params.batModuleTempC[3] = liveData->hexToDecFromResponse(44, 46, 1, true);
liveData->params.motorRpm = liveData->hexToDecFromResponse(112, 116, 2, false);
// 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)
liveData->params.batMinC = liveData->params.batMaxC = liveData->params.batModuleTempC[0];
for (uint16_t i = 1; i < liveData->params.batModuleTempCount; i++) {
if (liveData->params.batModuleTempC[i] < liveData->params.batMinC)
liveData->params.batMinC = liveData->params.batModuleTempC[i];
if (liveData->params.batModuleTempC[i] > liveData->params.batMaxC)
liveData->params.batMaxC = liveData->params.batModuleTempC[i];
}
liveData->params.batTempC = liveData->params.batMinC;
liveData->params.batInletC = liveData->hexToDecFromResponse(50, 52, 1, true);
if (liveData->params.speedKmh < 10 && liveData->params.batPowerKw >= 1 && liveData->params.socPerc > 0 && liveData->params.socPerc <= 100) {
if ( liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)] < 0 || liveData->params.batPowerKw < liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)])
liveData->params.chargingGraphMinKw[int(liveData->params.socPerc)] = liveData->params.batPowerKw;
if ( liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)] < 0 || liveData->params.batPowerKw > liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)])
liveData->params.chargingGraphMaxKw[int(liveData->params.socPerc)] = liveData->params.batPowerKw;
liveData->params.chargingGraphBatMinTempC[int(liveData->params.socPerc)] = liveData->params.batMinC;
liveData->params.chargingGraphBatMaxTempC[int(liveData->params.socPerc)] = liveData->params.batMaxC;
liveData->params.chargingGraphHeaterTempC[int(liveData->params.socPerc)] = liveData->params.batHeaterC;
liveData->params.chargingGraphWaterCoolantTempC[int(liveData->params.socPerc)] = liveData->params.coolingWaterTempC;
}
}
// BMS 7e4
if (liveData->commandRequest.equals("220102") && liveData->responseRowMerged.substring(12, 14) == "FF") {
for (int i = 0; i < 32; i++) {
liveData->params.cellVoltage[i] = liveData->hexToDecFromResponse(14 + (i * 2), 14 + (i * 2) + 2, 1, false) / 50;
}
}
// BMS 7e4
if (liveData->commandRequest.equals("220103")) {
for (int i = 0; i < 32; i++) {
liveData->params.cellVoltage[32 + i] = liveData->hexToDecFromResponse(14 + (i * 2), 14 + (i * 2) + 2, 1, false) / 50;
}
}
// BMS 7e4
if (liveData->commandRequest.equals("220104")) {
for (int i = 0; i < 32; i++) {
liveData->params.cellVoltage[64 + i] = liveData->hexToDecFromResponse(14 + (i * 2), 14 + (i * 2) + 2, 1, false) / 50;
}
}
// BMS 7e4
if (liveData->commandRequest.equals("220105")) {
liveData->params.socPercPrevious = liveData->params.socPerc;
liveData->params.sohPerc = liveData->hexToDecFromResponse(56, 60, 2, false) / 10.0;
liveData->params.socPerc = liveData->hexToDecFromResponse(68, 70, 1, false) / 2.0;
// Soc10ced table, record x0% CEC/CED table (ex. 90%->89%, 80%->79%)
if (liveData->params.socPercPrevious - liveData->params.socPerc > 0) {
byte index = (int(liveData->params.socPerc) == 4) ? 0 : (int)(liveData->params.socPerc / 10) + 1;
if ((int(liveData->params.socPerc) % 10 == 9 || int(liveData->params.socPerc) == 4) && liveData->params.soc10ced[index] == -1) {
liveData->params.soc10ced[index] = liveData->params.cumulativeEnergyDischargedKWh;
liveData->params.soc10cec[index] = liveData->params.cumulativeEnergyChargedKWh;
liveData->params.soc10odo[index] = liveData->params.odoKm;
liveData->params.soc10time[index] = liveData->params.currentTime;
}
}
liveData->params.bmsUnknownTempA = liveData->hexToDecFromResponse(30, 32, 1, true);
liveData->params.batHeaterC = liveData->hexToDecFromResponse(52, 54, 1, true);
liveData->params.bmsUnknownTempB = liveData->hexToDecFromResponse(82, 84, 1, true);
//
for (int i = 30; i < 32; i++) { // ai/aj position
liveData->params.cellVoltage[96 - 30 + i] = liveData->hexToDecFromResponse(14 + (i * 2), 14 + (i * 2) + 2, 1, false) / 50;
}
}
// BMS 7e4
if (liveData->commandRequest.equals("220106")) {
liveData->params.coolingWaterTempC = liveData->hexToDecFromResponse(14, 16, 1, false);
liveData->params.bmsUnknownTempC = liveData->hexToDecFromResponse(18, 20, 1, true);
liveData->params.bmsUnknownTempD = liveData->hexToDecFromResponse(46, 48, 1, true);
}
}
// TPMS 7a0
if (liveData->currentAtshRequest.equals("ATSH7A0")) {
if (liveData->commandRequest.equals("22c00b")) {
liveData->params.tireFrontLeftPressureBar = liveData->hexToDecFromResponse(14, 16, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireFrontRightPressureBar = liveData->hexToDecFromResponse(22, 24, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireRearRightPressureBar = liveData->hexToDecFromResponse(30, 32, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireRearLeftPressureBar = liveData->hexToDecFromResponse(38, 40, 2, false) / 72.51886900361; // === OK Valid *0.2 / 14.503773800722
liveData->params.tireFrontLeftTempC = liveData->hexToDecFromResponse(16, 18, 2, false) - 50; // === OK Valid
liveData->params.tireFrontRightTempC = liveData->hexToDecFromResponse(24, 26, 2, false) - 50; // === OK Valid
liveData->params.tireRearRightTempC = liveData->hexToDecFromResponse(32, 34, 2, false) - 50; // === OK Valid
liveData->params.tireRearLeftTempC = liveData->hexToDecFromResponse(40, 42, 2, false) - 50; // === OK Valid
}
}
*/ */
} }
@@ -309,6 +302,11 @@ void CarRenaultZoe::loadTestData() {
liveData->responseRowMerged = "6161000AA820C8C8C8C2C2000153B400004669FF"; liveData->responseRowMerged = "6161000AA820C8C8C8C2C2000153B400004669FF";
parseRowMerged(); parseRowMerged();
// CLUSTER 743
liveData->currentAtshRequest = "ATSH743";
liveData->commandRequest = "220206";
liveData->responseRowMerged = "62020600015459";
parseRowMerged();
/* /*
niro niro
@@ -459,5 +457,3 @@ void CarRenaultZoe::loadTestData() {
liveData->params.soc10time[0] = liveData->params.soc10time[1] + 900; liveData->params.soc10time[0] = liveData->params.soc10time[1] + 900;
*/ */
} }
#endif // CARRENAULTZOE_CPP

View File

@@ -1,5 +1,4 @@
#ifndef CARRENAULTZOE_H #pragma once
#define CARRENAULTZOE_H
#include "CarInterface.h" #include "CarInterface.h"
@@ -12,5 +11,3 @@ class CarRenaultZoe : public CarInterface {
void parseRowMerged() override; void parseRowMerged() override;
void loadTestData() override; void loadTestData() override;
}; };
#endif // CARRENAULTZOE_H

View File

@@ -1,13 +1,88 @@
#ifndef COMMINTERFACE_CPP
#define COMMINTERFACE_CPP
#include "CommInterface.h" #include "CommInterface.h"
//#include "BoardInterface.h" #include "BoardInterface.h"
//#include "CarInterface.h"
#include "LiveData.h" #include "LiveData.h"
void CommInterface::initComm(LiveData* pLiveData/*, BoardInterface* pBoard*/) { /**
liveData = pLiveData; *
//board = pBoard; */
} void CommInterface::initComm(LiveData* pLiveData, BoardInterface* pBoard) {
#endif // COMMINTERFACE_CPP liveData = pLiveData;
board = pBoard;
response = "";
}
/**
* Main loop
*/
void CommInterface::mainLoop() {
// Send command from TTY to OBD2
if (syslog->available()) {
ch = syslog->read();
if (ch == '\r' || ch == '\n') {
board->customConsoleCommand(response);
response = response + ch;
syslog->info(DEBUG_COMM, response);
executeCommand(response);
response = "";
} else {
response = response + ch;
}
}
// Drop ChargingOn when status was not updated for more than 10 seconds
if(liveData->params.currentTime - liveData->params.lastChargingOnTime > 10 && liveData->params.chargingOn) {
liveData->params.chargingOn = false;
}
// Can send next command from queue to OBD
if (liveData->canSendNextAtCommand) {
liveData->canSendNextAtCommand = false;
doNextQueueCommand();
}
}
/**
Do next AT command from queue
*/
bool CommInterface::doNextQueueCommand() {
// Restart loop with AT commands
if (liveData->commandQueueIndex >= liveData->commandQueueCount) {
liveData->commandQueueIndex = liveData->commandQueueLoopFrom;
board->redrawScreen();
// log every queue loop (temp) TODO and seconds interval
liveData->params.sdcardCanNotify = true;
}
// Send AT command to obd
liveData->commandRequest = liveData->commandQueue[liveData->commandQueueIndex].request;
liveData->commandStartChar = liveData->commandQueue[liveData->commandQueueIndex].startChar; // TODO: add to struct?
if (liveData->commandRequest.startsWith("ATSH")) {
liveData->currentAtshRequest = liveData->commandRequest;
}
syslog->infoNolf(DEBUG_COMM, ">>> ");
syslog->info(DEBUG_COMM, liveData->commandRequest);
liveData->responseRowMerged = "";
executeCommand(liveData->commandRequest);
liveData->commandQueueIndex++;
}
/**
Parse result from OBD, merge frames to sigle response
*/
bool CommInterface::parseResponse() {
// 1 frame data
syslog->info(DEBUG_COMM, liveData->responseRow);
// Merge frames 0:xxxx 1:yyyy 2:zzzz to single response xxxxyyyyzzzz string
if (liveData->responseRow.length() >= 2 && liveData->responseRow.charAt(1) == ':') {
liveData->responseRowMerged += liveData->responseRow.substring(2);
}
}

View File

@@ -1,19 +1,26 @@
#ifndef COMMINTERFACE_H #pragma once
#define COMMINTERFACE_H
#include "LiveData.h" #include "LiveData.h"
//#include "BoardInterface.h" //#include "BoardInterface.h"
class BoardInterface; // Forward declaration
class CommInterface { class CommInterface {
protected: protected:
LiveData* liveData; LiveData* liveData;
//BoardInterface* board; BoardInterface* board;
char ch;
String response;
time_t lastDataSent;
public: public:
void initComm(LiveData* pLiveData/*, BoardInterface* pBoard**/); void initComm(LiveData* pLiveData, BoardInterface* pBoard);
virtual void connectDevice() = 0; virtual void connectDevice() = 0;
virtual void disconnectDevice() = 0; virtual void disconnectDevice() = 0;
virtual void scanDevices() = 0; virtual void scanDevices() = 0;
virtual void mainLoop();
virtual void executeCommand(String cmd) = 0;
//
bool doNextQueueCommand();
bool parseResponse();
}; };
#endif // COMMINTERFACE_H

View File

@@ -1,29 +1,360 @@
#ifndef COMMOBD2BLE4_CPP
#define COMMOBD2BLE4_CPP
#include <BLEDevice.h> #include <BLEDevice.h>
#include "CommObd2Ble4.h" #include "CommObd2Ble4.h"
#include "BoardInterface.h"
#include "LiveData.h" #include "LiveData.h"
CommObd2Ble4* commObj;
BoardInterface* boardObj;
LiveData* liveDataObj;
/** /**
* Connect ble4 adapter BLE callbacks
*/ */
class MyClientCallback : public BLEClientCallbacks {
// On BLE connect
void onConnect(BLEClient* pclient) {
syslog->println("onConnect");
}
// On BLE disconnect
void onDisconnect(BLEClient* pclient) {
liveDataObj->commConnected = false;
syslog->println("onDisconnect");
boardObj->displayMessage("BLE disconnected", "");
}
};
/**
Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
// Called for each advertising BLE server.
void onResult(BLEAdvertisedDevice advertisedDevice) {
syslog->print("BLE advertised device found: ");
syslog->println(advertisedDevice.toString().c_str());
syslog->println(advertisedDevice.getAddress().toString().c_str());
// Add to device list (max. 9 devices allowed yet)
String tmpStr;
if (liveDataObj->scanningDeviceIndex < 10) { // && advertisedDevice.haveServiceUUID()
for (uint16_t i = 0; i < liveDataObj->menuItemsCount; ++i) {
if (liveDataObj->menuItems[i].id == 10001 + liveDataObj->scanningDeviceIndex) {
tmpStr = advertisedDevice.toString().c_str();
tmpStr.replace("Name: ", "");
tmpStr.replace("Address: ", "");
tmpStr.toCharArray(liveDataObj->menuItems[i].title, 48);
tmpStr = advertisedDevice.getAddress().toString().c_str();
tmpStr.toCharArray(liveDataObj->menuItems[i].obdMacAddress, 18);
}
}
liveDataObj->scanningDeviceIndex++;
}
// if (advertisedDevice.getServiceDataUUID().toString() != "<NULL>") {
// syslog->print("ServiceDataUUID: ");
// syslog->println(advertisedDevice.getServiceDataUUID().toString().c_str());
// if (advertisedDevice.getServiceUUID().toString() != "<NULL>") {
// syslog->print("ServiceUUID: ");
// syslog->println(advertisedDevice.getServiceUUID().toString().c_str());
// }
// }
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(liveDataObj->settings.serviceUUID)) &&
(strcmp(advertisedDevice.getAddress().toString().c_str(), liveDataObj->settings.obdMacAddress) == 0)) {
syslog->println("Stop scanning. Found my BLE device.");
BLEDevice::getScan()->stop();
liveDataObj->foundMyBleDevice = new BLEAdvertisedDevice(advertisedDevice);
}
}
};
uint32_t PIN = 1234;
/**
BLE Security
*/
class MySecurity : public BLESecurityCallbacks {
uint32_t onPassKeyRequest() {
syslog->printf("Pairing password: %d \r\n", PIN);
return PIN;
}
void onPassKeyNotify(uint32_t pass_key) {
syslog->printf("onPassKeyNotify\r\n");
}
bool onConfirmPIN(uint32_t pass_key) {
syslog->printf("onConfirmPIN\r\n");
return true;
}
bool onSecurityRequest() {
syslog->printf("onSecurityRequest\r\n");
return true;
}
void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl) {
if (auth_cmpl.success) {
syslog->printf("onAuthenticationComplete\r\n");
} else {
syslog->println("Auth failure. Incorrect PIN?");
liveDataObj->bleConnect = false;
}
}
};
/**
Ble notification callback
*/
static void notifyCallback (BLERemoteCharacteristic * pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
char ch;
// Parse multiframes to single response
liveDataObj->responseRow = "";
for (int i = 0; i <= length; i++) {
ch = pData[i];
if (ch == '\r' || ch == '\n' || ch == '\0') {
if (liveDataObj->responseRow != "")
commObj->parseResponse();
liveDataObj->responseRow = "";
} else {
liveDataObj->responseRow += ch;
if (liveDataObj->responseRow == ">") {
if (liveDataObj->responseRowMerged != "") {
syslog->infoNolf(DEBUG_COMM, "merged:");
syslog->info(DEBUG_COMM, liveDataObj->responseRowMerged);
boardObj->parseRowMerged();
}
liveDataObj->responseRowMerged = "";
liveDataObj->canSendNextAtCommand = true;
}
}
}
}
/**
Connect ble4 adapter
*/
void CommObd2Ble4::connectDevice() { void CommObd2Ble4::connectDevice() {
Serial.println("COMM connectDevice");
}
/** commObj = this;
* Disconnect device liveDataObj = liveData;
*/ boardObj = board;
void CommObd2Ble4::disconnectDevice() {
Serial.println("COMM disconnectDevice"); syslog->println("BLE4 connectDevice");
// Start BLE connection
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
BLEDevice::init("");
// Retrieve a Scanner and set the callback we want to use to be informed when we have detected a new device.
// Specify that we want active scanning and start the scan to run for 10 seconds.
syslog->println("Setup BLE scan");
liveData->pBLEScan = BLEDevice::getScan();
liveData->pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
liveData->pBLEScan->setInterval(1349);
liveData->pBLEScan->setWindow(449);
liveData->pBLEScan->setActiveScan(true);
// Skip BLE scan if middle button pressed
if (strcmp(liveData->settings.obdMacAddress, "00:00:00:00:00:00") != 0 && !board->skipAdapterScan()) {
syslog->println(liveData->settings.obdMacAddress);
startBleScan();
}
} }
/** /**
* Scan device list Disconnect device
*/ */
void CommObd2Ble4::scanDevices() { void CommObd2Ble4::disconnectDevice() {
Serial.println("COMM scanDevices");
syslog->println("COMM disconnectDevice");
btStop();
} }
#endif // COMMOBD2BLE4_CPP /**
Scan device list, from menu
*/
void CommObd2Ble4::scanDevices() {
syslog->println("COMM scanDevices");
startBleScan();
}
///////////////////////////////////
/**
Start ble scan
*/
void CommObd2Ble4::startBleScan() {
liveData->foundMyBleDevice = NULL;
liveData->scanningDeviceIndex = 0;
board->displayMessage(" > Scanning BLE4 devices", "40sec.or hold middle&RST");
// Start scanning
syslog->println("Scanning BLE devices...");
syslog->print("Looking for ");
syslog->println(liveData->settings.obdMacAddress);
BLEScanResults foundDevices = liveData->pBLEScan->start(40, false);
syslog->print("Devices found: ");
syslog->println(foundDevices.getCount());
syslog->println("Scan done!");
liveData->pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
char tmpStr1[20];
sprintf(tmpStr1, "Found %d devices", foundDevices.getCount());
board->displayMessage(" > Scanning BLE4 devices", tmpStr1);
// Scan devices from menu, show list of devices
if (liveData->menuCurrent == 9999) {
syslog->println("Display menu with devices");
liveData->menuVisible = true;
//liveData->menuCurrent = 9999;
liveData->menuItemSelected = 0;
board->showMenu();
} else {
// Redraw screen
if (liveData->foundMyBleDevice == NULL) {
board->displayMessage("Device not found", "Middle button - menu");
} else {
board->redrawScreen();
}
}
}
/**
Do connect BLE with server (OBD device)
*/
bool CommObd2Ble4::connectToServer(BLEAddress pAddress) {
board->displayMessage(" > Connecting device", "");
syslog->print("liveData->bleConnect ");
syslog->println(pAddress.toString().c_str());
board->displayMessage(" > Connecting device - init", pAddress.toString().c_str());
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
BLEDevice::setSecurityCallbacks(new MySecurity());
BLESecurity *pSecurity = new BLESecurity();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); //
pSecurity->setCapability(ESP_IO_CAP_KBDISP);
pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
board->displayMessage(" > Connecting device", pAddress.toString().c_str());
liveData->pClient = BLEDevice::createClient();
liveData->pClient->setClientCallbacks(new MyClientCallback());
if (liveData->pClient->connect(pAddress, BLE_ADDR_TYPE_RANDOM) ) syslog->println("liveData->bleConnected");
syslog->println(" - liveData->bleConnected to server");
// Remote service
board->displayMessage(" > Connecting device", "Connecting service...");
BLERemoteService* pRemoteService = liveData->pClient->getService(BLEUUID(liveData->settings.serviceUUID));
if (pRemoteService == nullptr)
{
syslog->print("Failed to find our service UUID: ");
syslog->println(liveData->settings.serviceUUID);
board->displayMessage(" > Connecting device", "Unable to find service");
return false;
}
syslog->println(" - Found our service");
// Get characteristics
board->displayMessage(" > Connecting device", "Connecting TxUUID...");
liveData->pRemoteCharacteristic = pRemoteService->getCharacteristic(BLEUUID(liveData->settings.charTxUUID));
if (liveData->pRemoteCharacteristic == nullptr) {
syslog->print("Failed to find our characteristic UUID: ");
syslog->println(liveData->settings.charTxUUID);//.toString().c_str());
board->displayMessage(" > Connecting device", "Unable to find TxUUID");
return false;
}
syslog->println(" - Found our characteristic");
// Get characteristics
board->displayMessage(" > Connecting device", "Connecting RxUUID...");
liveData->pRemoteCharacteristicWrite = pRemoteService->getCharacteristic(BLEUUID(liveData->settings.charRxUUID));
if (liveData->pRemoteCharacteristicWrite == nullptr) {
syslog->print("Failed to find our characteristic UUID: ");
syslog->println(liveData->settings.charRxUUID);//.toString().c_str());
board->displayMessage(" > Connecting device", "Unable to find RxUUID");
return false;
}
syslog->println(" - Found our characteristic write");
board->displayMessage(" > Connecting device", "Register callbacks...");
// Read the value of the characteristic.
if (liveData->pRemoteCharacteristic->canNotify()) {
syslog->println(" - canNotify");
if (liveData->pRemoteCharacteristic->canIndicate()) {
syslog->println(" - canIndicate");
const uint8_t indicationOn[] = {0x2, 0x0};
//const uint8_t indicationOff[] = {0x0,0x0};
liveData->pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)indicationOn, 2, true);
//liveData->pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notifyOff,2,true);
liveData->pRemoteCharacteristic->registerForNotify(notifyCallback, false);
delay(200);
}
}
board->displayMessage(" > Connecting device", "Done...");
if (liveData->pRemoteCharacteristicWrite->canWrite()) {
syslog->println(" - canWrite");
}
return true;
}
/**
Main loop
*/
void CommObd2Ble4::mainLoop() {
// Connect BLE device
if (liveData->bleConnect == true && liveData->foundMyBleDevice != NULL) {
liveData->pServerAddress = new BLEAddress(liveData->settings.obdMacAddress);
if (connectToServer(*liveData->pServerAddress)) {
liveData->commConnected = true;
liveData->bleConnect = false;
syslog->println("We are now connected to the BLE device.");
// Print message
board->displayMessage(" > Processing init AT cmds", "");
// Serve first command (ATZ)
doNextQueueCommand();
} else {
syslog->println("We have failed to connect to the server; there is nothing more we will do.");
}
}
// Parent declaration
CommInterface::mainLoop();
if (board->scanDevices) {
board->scanDevices = false;
startBleScan();
}
}
/**
* Send command
*/
void CommObd2Ble4::executeCommand(String cmd) {
String tmpStr = cmd + "\r";
if (liveData->commConnected) {
liveData->pRemoteCharacteristicWrite->writeValue(tmpStr.c_str(), tmpStr.length());
}
}

View File

@@ -1,17 +1,21 @@
#ifndef COMMOBD2BLE4_H #pragma once
#define COMMOBD2BLE4_H
#include <BLEDevice.h>
#include "LiveData.h" #include "LiveData.h"
#include "CommInterface.h" #include "CommInterface.h"
class CommObd2Ble4 : public CommInterface { class CommObd2Ble4 : public CommInterface {
protected: protected:
uint32_t PIN = 1234; uint32_t PIN = 1234;
public: public:
void connectDevice() override; void connectDevice() override;
void disconnectDevice() override; void disconnectDevice() override;
void scanDevices() override; void scanDevices() override;
void mainLoop() override;
void executeCommand(String cmd) override;
//
void startBleScan();
bool connectToServer(BLEAddress pAddress);
//
}; };
#endif // COMMOBD2BLE4_H

View File

@@ -1,7 +1,539 @@
#ifndef COMMINTERFACE_CPP #include "CommObd2CAN.h"
#define COMMINTERFACE_CPP #include "BoardInterface.h"
#include "CommInterface.h"
#include "LiveData.h" #include "LiveData.h"
#include <mcp_CAN.h>
#endif // COMMINTERFACE_CPP //#include <string.h>
/**
Connect CAN adapter
*/
void CommObd2Can::connectDevice() {
syslog->println("CAN connectDevice");
//CAN = new MCP_CAN(pinCanCs); // todo: remove if smart pointer is ok
CAN.reset(new MCP_CAN(pinCanCs)); // smart pointer so it's automatically cleaned when out of context and also free to re-init
if (CAN == nullptr) {
syslog->println("Error: Not enough memory to instantiate CAN class");
syslog->println("init_can() failed");
return;
}
// Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.
if (CAN->begin(MCP_STDEXT, CAN_500KBPS, MCP_8MHZ) == CAN_OK) {
syslog->println("MCP2515 Initialized Successfully!");
board->displayMessage(" > CAN init OK", "");
} else {
syslog->println("Error Initializing MCP2515...");
board->displayMessage(" > CAN init failed", "");
return;
}
if (liveData->settings.carType == CAR_BMW_I3_2014) {
//initialise mask and filter to allow only receipt of 0x7xx CAN IDs
CAN->init_Mask(0, 0, 0x07000000); // Init first mask...
CAN->init_Mask(1, 0, 0x07000000); // Init second mask...
for (uint8_t i = 0; i < 6; ++i) {
CAN->init_Filt(i, 0, 0x06000000); //Init filters
}
}
if (MCP2515_OK != CAN->setMode(MCP_NORMAL)) { // Set operation mode to normal so the MCP2515 sends acks to received data.
syslog->println("Error: CAN->setMode(MCP_NORMAL) failed");
board->displayMessage(" > CAN init failed", "");
return;
}
pinMode(pinCanInt, INPUT); // Configuring pin for /INT input
// Serve first command (ATZ)
liveData->commConnected = true;
doNextQueueCommand();
syslog->println("init_can() done");
}
/**
Disconnect device
*/
void CommObd2Can::disconnectDevice() {
liveData->commConnected = false;
// CAN->setMode(MCP_SLEEP);
syslog->println("COMM disconnectDevice");
}
/**
Scan device list, from menu
*/
void CommObd2Can::scanDevices() {
syslog->println("COMM scanDevices");
}
/**
Main loop
*/
void CommObd2Can::mainLoop() {
CommInterface::mainLoop();
// if delay between commands is defined, check if this delay is not expired
if (liveData->delayBetweenCommandsMs != 0) {
if (bResponseProcessed && (unsigned long)(millis() - lastDataSent) > liveData->delayBetweenCommandsMs) {
bResponseProcessed = false;
liveData->canSendNextAtCommand = true;
return;
}
}
// Read data
const uint8_t firstByte = receivePID();
if ((firstByte & 0xf0) == 0x10) { // First frame, request another
sendFlowControlFrame();
delay(10);
for (uint16_t i = 0; i < 1000; i++) {
receivePID();
if (rxRemaining <= 2)
break;
delay(1);
// apply timeout for next frames loop too
if (lastDataSent != 0 && (unsigned long)(millis() - lastDataSent) > liveData->rxTimeoutMs) {
syslog->info(DEBUG_COMM, "CAN execution timeout (multiframe message).");
break;
}
}
// Process incomplette messages
if (liveData->responseRowMerged.length() > 7) {
processMergedResponse();
return;
}
}
if (lastDataSent != 0 && (unsigned long)(millis() - lastDataSent) > liveData->rxTimeoutMs) {
syslog->info(DEBUG_COMM, "CAN execution timeout. Continue with next command.");
liveData->canSendNextAtCommand = true;
return;
}
}
/**
Send command to CAN bus
*/
void CommObd2Can::executeCommand(String cmd) {
syslog->infoNolf(DEBUG_COMM, "executeCommand ");
syslog->info(DEBUG_COMM, cmd);
if (cmd.equals("") || cmd.startsWith("AT")) { // skip AT commands as not used by direct CAN connection
lastDataSent = 0;
liveData->canSendNextAtCommand = true;
return;
}
// Send command
liveData->responseRowMerged = "";
liveData->currentAtshRequest.replace(" ", ""); // remove possible spaces
String atsh = "0" + liveData->currentAtshRequest.substring(4); // remove ATSH
cmd.replace(" ", ""); // remove possible spaces
sendPID(liveData->hexToDec(atsh, 2, false), cmd);
delay(40);
}
/**
Send PID
remark: parameter cmd as const reference to aviod copying
*/
void CommObd2Can::sendPID(const uint16_t pid, const String& cmd) {
uint8_t txBuf[8] = { 0 }; // init with zeroes
String tmpStr;
if (liveData->bAdditionalStartingChar)
{
struct Packet_t
{
uint8_t startChar;
uint8_t length;
uint8_t payload[6];
};
Packet_t* pPacket = (Packet_t*)txBuf;
pPacket->startChar = liveData->commandStartChar; // todo: handle similar way as cmd input param?
pPacket->length = cmd.length() / 2;
for (uint8_t i = 0; i < sizeof(pPacket->payload); i++) {
tmpStr = cmd;
tmpStr = tmpStr.substring(i * 2, ((i + 1) * 2));
if (tmpStr != "") {
pPacket->payload[i] = liveData->hexToDec(tmpStr, 1, false);
}
}
}
else
{
struct Packet_t
{
uint8_t length;
uint8_t payload[7];
};
Packet_t* pPacket = (Packet_t*)txBuf;
pPacket->length = cmd.length() / 2;
for (uint8_t i = 0; i < sizeof(pPacket->payload); i++) {
tmpStr = cmd;
tmpStr = tmpStr.substring(i * 2, ((i + 1) * 2));
if (tmpStr != "") {
pPacket->payload[i] = liveData->hexToDec(tmpStr, 1, false);
}
}
}
lastPid = pid;
bResponseProcessed = false;
const uint8_t sndStat = CAN->sendMsgBuf(pid, 0, 8, txBuf); // 11 bit
// uint8_t sndStat = CAN->sendMsgBuf(0x7e4, 1, 8, tmp); // 29 bit extended frame
if (sndStat == CAN_OK) {
syslog->infoNolf(DEBUG_COMM, "SENT ");
lastDataSent = millis();
} else {
syslog->infoNolf(DEBUG_COMM, "Error sending PID ");
lastDataSent = millis();
}
syslog->infoNolf(DEBUG_COMM, pid);
for (uint8_t i = 0; i < 8; i++) {
sprintf(msgString, " 0x%.2X", txBuf[i]);
syslog->infoNolf(DEBUG_COMM, msgString);
}
syslog->info(DEBUG_COMM, "");
}
/**
sendFlowControlFrame
*/
void CommObd2Can::sendFlowControlFrame() {
uint8_t txBuf[8] = { 0x30, requestFramesCount /*request count*/, 20 /*ms between frames*/ , 0, 0, 0, 0, 0 };
// insert start char if needed
if (liveData->bAdditionalStartingChar) {
memmove(txBuf + 1, txBuf, 7);
txBuf[0] = liveData->commandStartChar;
}
const uint8_t sndStat = CAN->sendMsgBuf(lastPid, 0, 8, txBuf); // 11 bit
if (sndStat == CAN_OK) {
syslog->infoNolf(DEBUG_COMM, "Flow control frame sent ");
} else {
syslog->infoNolf(DEBUG_COMM, "Error sending flow control frame ");
}
syslog->infoNolf(DEBUG_COMM, lastPid);
for (auto txByte : txBuf) {
sprintf(msgString, " 0x%.2X", txByte);
syslog->infoNolf(DEBUG_COMM, msgString);
}
syslog->info(DEBUG_COMM, "");
}
/**
Receive PID
*/
uint8_t CommObd2Can::receivePID() {
if (!digitalRead(pinCanInt)) // If CAN0_INT pin is low, read receive buffer
{
lastDataSent = millis();
syslog->infoNolf(DEBUG_COMM, " CAN READ ");
CAN->readMsgBuf(&rxId, &rxLen, rxBuf); // Read data: len = data length, buf = data byte(s)
if ((rxId & 0x80000000) == 0x80000000) // Determine if ID is standard (11 bits) or extended (29 bits)
sprintf(msgString, "Extended ID: 0x%.8lX DLC: %1d Data:", (rxId & 0x1FFFFFFF), rxLen);
else
sprintf(msgString, "Standard ID: 0x%.3lX DLC: %1d Data:", rxId, rxLen);
syslog->infoNolf(DEBUG_COMM, msgString);
if ((rxId & 0x40000000) == 0x40000000) { // Determine if message is a remote request frame.
sprintf(msgString, " REMOTE REQUEST FRAME");
syslog->infoNolf(DEBUG_COMM, msgString);
} else {
for (uint8_t i = 0; i < rxLen; i++) {
sprintf(msgString, " 0x%.2X", rxBuf[i]);
syslog->infoNolf(DEBUG_COMM, msgString);
}
}
// Check if this packet shall be discarded due to its length.
// If liveData->expectedPacketLength is set to 0, accept any length.
if(liveData->expectedMinimalPacketLength != 0 && rxLen < liveData->expectedMinimalPacketLength) {
syslog->info(DEBUG_COMM, " [Ignored packet]");
return 0xff;
}
// Filter received messages (Ioniq only)
if(liveData->settings.carType == CAR_HYUNDAI_IONIQ_2018) {
long unsigned int atsh_response = liveData->hexToDec(liveData->currentAtshRequest.substring(4), 2, false) + 8;
if(rxId != atsh_response) {
syslog->info(DEBUG_COMM, " [Filtered packet]");
return 0xff;
}
}
syslog->info(DEBUG_COMM, "");
processFrameBytes();
//processFrame();
} else {
//syslog->println(" CAN NOT READ ");
return 0xff;
}
const uint8_t rxBuffOffset = liveData->bAdditionalStartingChar? 1 : 0;
return rxBuf[0 + rxBuffOffset]; // return byte containing frame type, which requires removing offset byte
}
static void printHexBuffer(uint8_t* pData, const uint16_t length, const bool bAddNewLine)
{
char str[8] = { 0 };
for (uint8_t i = 0; i < length; i++) {
sprintf(str, " 0x%.2X", pData[i]);
syslog->infoNolf(DEBUG_COMM, str);
}
if (bAddNewLine) {
syslog->info(DEBUG_COMM, "");
}
}
static void buffer2string(String& out_targetString, uint8_t* in_pBuffer, const uint16_t in_length)
{
char str[8] = { 0 };
for (uint16_t i = 0; i < in_length; i++) {
sprintf(str, "%.2X", in_pBuffer[i]);
out_targetString += str;
}
}
CommObd2Can::enFrame_t CommObd2Can::getFrameType(const uint8_t firstByte) {
const uint8_t frameType = (firstByte & 0xf0) >> 4; // frame type is in bits 7 to 4
switch(frameType) {
case 0:
return enFrame_t::single;
case 1:
return enFrame_t::first;
case 2:
return enFrame_t::consecutive;
default:
return enFrame_t::unknown;
}
}
/**
Process can frame on byte level
https://en.wikipedia.org/wiki/ISO_15765-2
*/
bool CommObd2Can::processFrameBytes() {
const uint8_t rxBuffOffset = liveData->bAdditionalStartingChar ? 1 : 0;
uint8_t* pDataStart = rxBuf + rxBuffOffset; // set pointer to data start based on specific offset of car
const auto frameType = getFrameType(*pDataStart);
const uint8_t frameLenght = rxLen - rxBuffOffset;
switch (frameType) {
case enFrame_t::single: // Single frame
{
struct SingleFrame_t
{
uint8_t size : 4;
uint8_t frameType : 4;
uint8_t pData[];
};
SingleFrame_t* pSingleFrame = (SingleFrame_t*)pDataStart;
mergedData.assign(pSingleFrame->pData, pSingleFrame->pData + pSingleFrame->size);
rxRemaining = 0;
//syslog->print("----Processing SingleFrame payload: "); printHexBuffer(pSingleFrame->pData, pSingleFrame->size, true);
// single frame - process directly
buffer2string(liveData->responseRowMerged, mergedData.data(), mergedData.size());
liveData->vResponseRowMerged.assign(mergedData.begin(), mergedData.end());
processMergedResponse();
return true;
}
break;
case enFrame_t::first: // First frame
{
struct FirstFrame_t
{
uint8_t sizeMSB : 4;
uint8_t frameType : 4;
uint8_t sizeLSB : 8;
uint8_t pData[];
uint16_t lengthOfFullPacket() { return (256 * sizeMSB) + sizeLSB; }
};
FirstFrame_t* pFirstFrame = (FirstFrame_t*)pDataStart;
rxRemaining = pFirstFrame->lengthOfFullPacket(); // length of complete data
mergedData.clear();
dataRows.clear();
const uint8_t framePayloadSize = frameLenght - sizeof(FirstFrame_t); // remove one byte of header
dataRows[0].assign(pFirstFrame->pData, pFirstFrame->pData + framePayloadSize);
rxRemaining -= framePayloadSize;
//syslog->print("----Processing FirstFrame payload: "); printHexBuffer(pFirstFrame->pData, framePayloadSize, true);
}
break;
case enFrame_t::consecutive: // Consecutive frame
{
struct ConsecutiveFrame_t
{
uint8_t index : 4;
uint8_t frameType : 4;
uint8_t pData[];
};
const uint8_t structSize = sizeof(ConsecutiveFrame_t);
//syslog->print("[debug] sizeof(ConsecutiveFrame_t) is expected to be 1 and it's "); syslog->println(structSize);
ConsecutiveFrame_t* pConseqFrame = (ConsecutiveFrame_t*)pDataStart;
const uint8_t framePayloadSize = frameLenght - sizeof(ConsecutiveFrame_t); // remove one byte of header
dataRows[pConseqFrame->index].assign(pConseqFrame->pData, pConseqFrame->pData + framePayloadSize);
rxRemaining -= framePayloadSize;
//syslog->print("----Processing ConsecFrame payload: "); printHexBuffer(pConseqFrame->pData, framePayloadSize, true);
}
break;
default:
syslog->infoNolf(DEBUG_COMM, "Unknown frame type within CommObd2Can::processFrameBytes(): ");
syslog->info(DEBUG_COMM, (uint8_t)frameType);
return false;
break;
} // \switch (frameType)
// Merge data if all data was received
if (rxRemaining <= 0) {
// multiple frames and no data remaining - merge everything to single packet
for (int i = 0; i < dataRows.size(); i++) {
//syslog->print("------merging packet index ");
//syslog->print(i);
//syslog->print(" with length ");
//syslog->println(dataRows[i].size());
mergedData.insert(mergedData.end(), dataRows[i].begin(), dataRows[i].end());
}
buffer2string(liveData->responseRowMerged, mergedData.data(), mergedData.size()); // output for string parsing
liveData->vResponseRowMerged.assign(mergedData.begin(), mergedData.end()); // output for binary parsing
processMergedResponse();
}
return true;
}
/**
Process can frame
https://en.wikipedia.org/wiki/ISO_15765-2
*/
bool CommObd2Can::processFrame() {
const uint8_t frameType = (rxBuf[0] & 0xf0) >> 4;
uint8_t start = 1; // Single and Consecutive starts with pos 1
uint8_t index = 0; // 0 - f
liveData->responseRow = "";
switch (frameType) {
// Single frame
case 0:
rxRemaining = (rxBuf[1] & 0x0f);
requestFramesCount = 0;
break;
// First frame
case 1:
rxRemaining = ((rxBuf[0] & 0x0f) << 8) + rxBuf[1];
requestFramesCount = ceil((rxRemaining - 6) / 7.0);
liveData->responseRowMerged = "";
for (uint16_t i = 0; i < rxRemaining - 1; i++)
liveData->responseRowMerged += "00";
liveData->responseRow = "0:";
start = 2;
break;
// Consecutive frames
case 2:
index = (rxBuf[0] & 0x0f);
sprintf(msgString, "%.1X:", index);
liveData->responseRow = msgString; // convert 0..15 to ascii 0..F);
break;
}
syslog->infoNolf(DEBUG_COMM, "> frametype:");
syslog->infoNolf(DEBUG_COMM, frameType);
syslog->infoNolf(DEBUG_COMM, ", r: ");
syslog->infoNolf(DEBUG_COMM, rxRemaining);
syslog->infoNolf(DEBUG_COMM, " ");
for (uint8_t i = start; i < rxLen; i++) {
sprintf(msgString, "%.2X", rxBuf[i]);
liveData->responseRow += msgString;
rxRemaining--;
}
syslog->infoNolf(DEBUG_COMM, ", r: ");
syslog->infoNolf(DEBUG_COMM, rxRemaining);
syslog->info(DEBUG_COMM, " ");
//parseResponse();
// We need to sort frames
// 1 frame data
syslog->info(DEBUG_COMM, liveData->responseRow);
// Merge frames 0:xxxx 1:yyyy 2:zzzz to single response xxxxyyyyzzzz string
if (liveData->responseRow.length() >= 2 && liveData->responseRow.charAt(1) == ':') {
//liveData->responseRowMerged += liveData->responseRow.substring(2);
uint8_t rowNo = liveData->hexToDec(liveData->responseRow.substring(0, 1), 1, false);
uint16_t startPos = (rowNo * 14) - ((rowNo > 0) ? 2 : 0);
uint16_t endPos = ((rowNo + 1) * 14) - ((rowNo > 0) ? 2 : 0);
liveData->responseRowMerged = liveData->responseRowMerged.substring(0, startPos) + liveData->responseRow.substring(2) + liveData->responseRowMerged.substring(endPos);
syslog->info(DEBUG_COMM, liveData->responseRowMerged);
}
// Send response to board module
if (rxRemaining <= 2) {
processMergedResponse();
return false;
}
return true;
}
/**
processMergedResponse
*/
void CommObd2Can::processMergedResponse() {
syslog->infoNolf(DEBUG_COMM, "merged:");
syslog->info(DEBUG_COMM, liveData->responseRowMerged);
board->parseRowMerged();
liveData->responseRowMerged = "";
liveData->vResponseRowMerged.clear();
bResponseProcessed = true; // to allow delay untill next message
if (liveData->delayBetweenCommandsMs == 0) {
liveData->canSendNextAtCommand = true; // allow next command immediately
}
}

View File

@@ -1,12 +1,54 @@
#ifndef COMMOBD2CAN_H #pragma once
#define COMMOBD2CAN_H
#include "LiveData.h" #include "LiveData.h"
#include "CommInterface.h"
#include <mcp_can.h>
#include <memory>
#include <vector>
#include <unordered_map>
class CommObd2Can : public CommInterface { class CommObd2Can : public CommInterface {
protected:
public:
};
#endif // COMMOBD2CAN_H protected:
const uint8_t pinCanInt = 15;
const uint8_t pinCanCs = 12;
std::unique_ptr <MCP_CAN> CAN;
long unsigned int rxId;
unsigned char rxLen = 0;
uint8_t rxBuf[32];
int16_t rxRemaining; // Remaining bytes to complete message, signed is ok
uint8_t requestFramesCount;
char msgString[128]; // Array to store serial string
uint16_t lastPid;
unsigned long lastDataSent = 0;
std::vector<uint8_t> mergedData;
std::unordered_map<uint16_t, std::vector<uint8_t>> dataRows;
bool bResponseProcessed = false;
enum class enFrame_t
{
single = 0,
first = 1,
consecutive = 2,
unknown = 9
};
public:
void connectDevice() override;
void disconnectDevice() override;
void scanDevices() override;
void mainLoop() override;
void executeCommand(String cmd) override;
private:
void sendPID(const uint16_t pid, const String& cmd);
void sendFlowControlFrame();
uint8_t receivePID();
enFrame_t getFrameType(const uint8_t firstByte);
bool processFrameBytes();
bool processFrame();
void processMergedResponse();
};

View File

@@ -6,7 +6,7 @@ M5STACK (Many thanks to DimZen)
https://docs.google.com/document/d/17vJmeveNfN0exQy9wKC-5igU8zzNjsuOn1DPuPV_yJA/edit?usp=sharing https://docs.google.com/document/d/17vJmeveNfN0exQy9wKC-5igU8zzNjsuOn1DPuPV_yJA/edit?usp=sharing
TTGO-T4 (older) TTGO-T4 (older guide)
https://docs.google.com/document/d/1nEezrtXY-8X6mQ1hiZVWDjBVse1sXQg1SlnizaRmJwU/edit?usp=sharing https://docs.google.com/document/d/1nEezrtXY-8X6mQ1hiZVWDjBVse1sXQg1SlnizaRmJwU/edit?usp=sharing

View File

@@ -1,16 +1,21 @@
#ifndef LIVEDATA_CPP
#define LIVEDATA_CPP
#include "LiveData.h" #include "LiveData.h"
#include "menu.h" #include "menu.h"
LogSerial* syslog;
/**
* Debug level
*/
void debug(String msg, uint8_t debugLevel) {
syslog->println(msg);
}
/** /**
Init params with default values Init params with default values
*/ */
void LiveData::initParams() { void LiveData::initParams() {
params.automaticShutdownTimer = 0; params.mainLoopCounter = 0;
// SIM // SIM
params.lastDataSent = 0; params.lastDataSent = 0;
params.sim800l_enabled = false; params.sim800l_enabled = false;
@@ -28,18 +33,22 @@ void LiveData::initParams() {
params.gpsAlt = -1; params.gpsAlt = -1;
// Car data // Car data
params.ignitionOn = false; params.ignitionOn = false;
params.ignitionOnPrevious = false; params.lastIgnitionOnTime = 0;
params.operationTimeSec = 0; params.operationTimeSec = 0;
params.chargingStartTime = params.currentTime = 0; params.chargingStartTime = params.currentTime = 0;
params.lightInfo = 0; params.chargingOn = false;
params.headLights = false; params.headLights = false;
params.dayLights = false; params.dayLights = false;
params.brakeLights = false; params.brakeLights = false;
params.brakeLightInfo = 0; params.trunkDoorOpen = false;
params.leftFrontDoorOpen = false;
params.rightFrontDoorOpen = false;
params.leftRearDoorOpen = false;
params.rightRearDoorOpen = false;
params.hoodDoorOpen = false;
params.forwardDriveMode = false; params.forwardDriveMode = false;
params.reverseDriveMode = false; params.reverseDriveMode = false;
params.parkModeOrNeutral = false; params.parkModeOrNeutral = false;
params.espState = 0;
params.speedKmh = -1; params.speedKmh = -1;
params.motorRpm = -1; params.motorRpm = -1;
params.odoKm = -1; params.odoKm = -1;
@@ -185,9 +194,3 @@ float LiveData::celsius2temperature(float inCelsius) {
float LiveData::bar2pressure(float inBar) { float LiveData::bar2pressure(float inBar) {
return (settings.pressureUnit == 'b') ? inBar : inBar * 14.503773800722; return (settings.pressureUnit == 'b') ? inBar : inBar * 14.503773800722;
} }
//
#endif // LIVEDATA_CPP

View File

@@ -1,6 +1,4 @@
#pragma once
#ifndef LIVEDATA_H
#define LIVEDATA_H
#include <Arduino.h> #include <Arduino.h>
#include <stdint.h> #include <stdint.h>
@@ -9,6 +7,8 @@
#include <sys/time.h> #include <sys/time.h>
#include <BLEDevice.h> #include <BLEDevice.h>
#include "config.h" #include "config.h"
#include "LogSerial.h"
#include <vector>
// SUPPORTED CARS // SUPPORTED CARS
#define CAR_KIA_ENIRO_2020_64 0 #define CAR_KIA_ENIRO_2020_64 0
@@ -18,11 +18,13 @@
#define CAR_HYUNDAI_KONA_2020_39 4 #define CAR_HYUNDAI_KONA_2020_39 4
#define CAR_RENAULT_ZOE 5 #define CAR_RENAULT_ZOE 5
#define CAR_KIA_NIRO_PHEV 6 #define CAR_KIA_NIRO_PHEV 6
#define CAR_BMW_I3_2014 7
#define CAR_DEBUG_OBD2_KIA 999 #define CAR_DEBUG_OBD2_KIA 999
// // COMM TYPE
#define COMM_TYPE_OBD2BLE4 0 #define COMM_TYPE_OBD2BLE4 0
#define COMM_TYPE_OBD2CAN 1 #define COMM_TYPE_OBD2CAN 1
#define COMM_TYPE_OBD2BT3 2
// SCREENS // SCREENS
#define SCREEN_BLANK 0 #define SCREEN_BLANK 0
@@ -32,14 +34,18 @@
#define SCREEN_CELLS 4 #define SCREEN_CELLS 4
#define SCREEN_CHARGING 5 #define SCREEN_CHARGING 5
#define SCREEN_SOC10 6 #define SCREEN_SOC10 6
#define SCREEN_DEBUG 7
//
#define MONTH_SEC 2678400
extern LogSerial* syslog;
// Structure with realtime values // Structure with realtime values
typedef struct { typedef struct {
// System // System
time_t currentTime; time_t currentTime;
time_t chargingStartTime; time_t chargingStartTime;
time_t automaticShutdownTimer; uint32_t mainLoopCounter;
// SIM // SIM
time_t lastDataSent; time_t lastDataSent;
bool sim800l_enabled; bool sim800l_enabled;
@@ -55,7 +61,9 @@ typedef struct {
char sdcardFilename[32]; char sdcardFilename[32];
// Car params // Car params
bool ignitionOn; bool ignitionOn;
bool ignitionOnPrevious; bool chargingOn;
time_t lastIgnitionOnTime;
time_t lastChargingOnTime;
uint64_t operationTimeSec; uint64_t operationTimeSec;
bool sdcardCanNotify; bool sdcardCanNotify;
bool forwardDriveMode; bool forwardDriveMode;
@@ -64,9 +72,15 @@ typedef struct {
bool headLights; bool headLights;
bool dayLights; bool dayLights;
bool brakeLights; bool brakeLights;
uint8_t lightInfo; bool trunkDoorOpen;
bool leftFrontDoorOpen;
bool rightFrontDoorOpen;
bool leftRearDoorOpen;
bool rightRearDoorOpen;
bool hoodDoorOpen;
/* uint8_t lightInfo;
uint8_t brakeLightInfo; uint8_t brakeLightInfo;
uint8_t espState; uint8_t espState;*/
float batteryTotalAvailableKWh; float batteryTotalAvailableKWh;
float speedKmh; float speedKmh;
float motorRpm; float motorRpm;
@@ -106,6 +120,7 @@ typedef struct {
float auxPerc; float auxPerc;
float auxCurrentAmp; float auxCurrentAmp;
float auxVoltage; float auxVoltage;
float auxTemperature;
float indoorTemperature; float indoorTemperature;
float outdoorTemperature; float outdoorTemperature;
float tireFrontLeftTempC; float tireFrontLeftTempC;
@@ -161,11 +176,11 @@ typedef struct {
// ================================= // =================================
byte defaultScreen; // 1 .. 6 byte defaultScreen; // 1 .. 6
byte lcdBrightness; // 0 - auto, 1 .. 100% byte lcdBrightness; // 0 - auto, 1 .. 100%
byte debugScreen; // 0 - off, 1 - on byte sleepModeEnabled; // 0 - off, 1 - on
byte predrawnChargingGraphs; // 0 - off, 1 - on byte predrawnChargingGraphs; // 0 - off, 1 - on
// === settings version 4 // === settings version 4
// ================================= // =================================
byte commType; // 0 - OBD2 BLE4 adapter, 1 - CAN byte commType; // 0 - OBD2 BLE4 adapter, 1 - CAN, 2 - BT3
// Wifi // Wifi
byte wifiEnabled; // 0/1 byte wifiEnabled; // 0/1
char wifiSsid[32]; char wifiSsid[32];
@@ -189,23 +204,36 @@ typedef struct {
// === settings version 5 // === settings version 5
// ================================= // =================================
byte gpsHwSerialPort; // 255-off, 0,1,2 - hw serial byte gpsHwSerialPort; // 255-off, 0,1,2 - hw serial
byte gprsHwSerialPort; // 255-off, 0,1,2 - hw serial
// === settings version 6
// =================================
byte serialConsolePort; // 255-off, 0 - hw serial (std)
uint8_t debugLevel; // 0 - info only, 1 - debug communication (BLE/CAN), 2 - debug GSM, 3 - debug SDcard
uint16_t sdcardLogIntervalSec; // every x seconds
uint16_t gprsLogIntervalSec; // every x seconds
// //
} SETTINGS_STRUC; } SETTINGS_STRUC;
// LiveData class
//
class LiveData { class LiveData {
protected: protected:
public: public:
// Command loop // Command loop
struct Command_t {
uint8_t startChar; // special starting character used by some cars
String request;
};
uint16_t commandQueueCount; uint16_t commandQueueCount;
uint16_t commandQueueLoopFrom; uint16_t commandQueueLoopFrom;
String commandQueue[300]; std::vector<Command_t> commandQueue;
String responseRow; String responseRow;
String responseRowMerged; String responseRowMerged;
std::vector<uint8_t> vResponseRowMerged;
uint16_t commandQueueIndex; uint16_t commandQueueIndex;
bool canSendNextAtCommand = false; bool canSendNextAtCommand = false;
String commandRequest = ""; uint8_t commandStartChar;
String commandRequest = ""; // TODO: us Command_t struct
String currentAtshRequest = ""; String currentAtshRequest = "";
// Menu // Menu
bool menuVisible = false; bool menuVisible = false;
@@ -216,15 +244,22 @@ class LiveData {
uint16_t scanningDeviceIndex = 0; uint16_t scanningDeviceIndex = 0;
MENU_ITEM* menuItems; MENU_ITEM* menuItems;
// Comm
boolean commConnected = true;
// Bluetooth4 // Bluetooth4
boolean bleConnect = true; boolean bleConnect = true;
boolean bleConnected = false;
BLEAddress *pServerAddress; BLEAddress *pServerAddress;
BLERemoteCharacteristic* pRemoteCharacteristic; BLERemoteCharacteristic* pRemoteCharacteristic;
BLERemoteCharacteristic* pRemoteCharacteristicWrite; BLERemoteCharacteristic* pRemoteCharacteristicWrite;
BLEAdvertisedDevice* foundMyBleDevice; BLEAdvertisedDevice* foundMyBleDevice;
BLEClient* pClient; BLEClient* pClient;
BLEScan* pBLEScan; BLEScan* pBLEScan;
// Canbus
bool bAdditionalStartingChar = false; // some cars uses additional starting character in beginning of tx and rx messages
uint8_t expectedMinimalPacketLength = 0; // what length of packet should be accepted. Set to 0 to accept any length
uint16_t rxTimeoutMs = 100; // timeout for receiving of CAN response
uint16_t delayBetweenCommandsMs = 0; // delay between commands, set to 0 if no delay is needed
// Params // Params
PARAMS_STRUC params; // Realtime sensor values PARAMS_STRUC params; // Realtime sensor values
@@ -240,7 +275,3 @@ class LiveData {
float celsius2temperature(float inCelsius); float celsius2temperature(float inCelsius);
float bar2pressure(float inBar); float bar2pressure(float inBar);
}; };
//
#endif // LIVEDATA_H

15
LogSerial.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include "LogSerial.h"
/**
* Constructor
*/
LogSerial::LogSerial() : HardwareSerial(0) {
HardwareSerial::begin(115200);
}
/**
* Set debug level
*/
void LogSerial::setDebugLevel(uint8_t aDebugLevel) {
debugLevel = aDebugLevel;
}

58
LogSerial.h Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include <HardwareSerial.h>
// DEBUG LEVEL
#define DEBUG_NONE 0
#define DEBUG_COMM 1 // filter comm
#define DEBUG_GSM 2 // filter gsm messages
#define DEBUG_SDCARD 3 // filter sdcard
//
class LogSerial: public HardwareSerial {
protected:
uint8_t debugLevel;
public:
LogSerial();
//
void setDebugLevel(uint8_t aDebugLevel);
// info
template <class T, typename... Args> void info(uint8_t aDebugLevel, T msg) {
if (debugLevel != DEBUG_NONE && aDebugLevel != DEBUG_NONE && aDebugLevel != debugLevel)
return;
println(msg);
}
template <class T, typename... Args> void infoNolf(uint8_t aDebugLevel, T msg) {
if (debugLevel != DEBUG_NONE && aDebugLevel != DEBUG_NONE && aDebugLevel != debugLevel)
return;
print(msg);
}
// warning
template <class T, typename... Args> void warn(uint8_t aDebugLevel, T msg) {
if (debugLevel != DEBUG_NONE && aDebugLevel != DEBUG_NONE && aDebugLevel != debugLevel)
return;
print("WARN ");
println(msg);
}
template <class T, typename... Args> void warnNolf(uint8_t aDebugLevel, T msg) {
if (debugLevel != DEBUG_NONE && aDebugLevel != DEBUG_NONE && aDebugLevel != debugLevel)
return;
print("WARN ");
print(msg);
}
// error
template <class T, typename... Args> void err(uint8_t aDebugLevel, T msg) {
if (debugLevel != DEBUG_NONE && aDebugLevel != DEBUG_NONE && aDebugLevel != debugLevel)
return;
print("ERR ");
println(msg);
}
template <class T, typename... Args> void errNolf(uint8_t aDebugLevel, T msg) {
if (debugLevel != DEBUG_NONE && aDebugLevel != DEBUG_NONE && aDebugLevel != debugLevel)
return;
print("ERR ");
print(msg);
}
};

View File

@@ -1,31 +1,30 @@
# evDash (old enirodashboard) # evDash (old enirodashboard)
Supported devices Supported devices
1. LILYGO TTGO T4 v1.3 1. M5STACK CORE1 IOT Development Kit (best option)
2. M5STACK CORE1 IOT Development Kit 2. LILYGO TTGO T4 v1.3 (!!! limited support, no SDcard/GSM/GPS/CAN module)
Working only with electric vehicles Working only with electric vehicles
Kia e-NIRO (EV), Hyundai Kona EV, Hyundai Ioniq EV, Kia Niro Phev 8.9kWh Kia e-NIRO (EV), Hyundai Kona EV, Hyundai Ioniq EV, Kia Niro Phev 8.9kWh
Vgate iCar Pro Bluetooth 4.0 (BLE4) OBD2 adapter is required. See Release notes, quick installation via flash tool bellow. Vgate iCar Pro Bluetooth 4.0 (BLE4) OBD2 adapter is required or CAN (m5 COMMU module).
See Release notes, quick installation via flash tool bellow.
Use it at your own risk! Use it at your own risk!
Author: nick.n17@gmail.com (Lubos Petrovic / Slovakia)
## Supporting me evDash Discord server: https://discord.gg/rfAvH7xzTr
- 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).
Many thanks to Blas, Jens, Калин, Aleš Dokupil and others for help. Thank you for supporting me.
## Required hardware ## Required hardware
Board Board
- M5STACK CORE1 IOT Development Kit(~EUR 35) - M5STACK CORE1 IOT Development Kit(~EUR 35)
https://rlx.sk/sk/m5stack/7285-esp32-basic-core-iot-development-kit-m5-k001-m5stack.html https://rlx.sk/sk/m5stack/7285-esp32-basic-core-iot-development-kit-m5-k001-m5stack.html
- optional M5 COMMU (CAN) module
- optional M5 GPS NEO-M8N (with external atenna)
- optional M5 SIM800L GPS module (dev)
or or
- 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 - older device LILYGO TTGO T4 v1.3 (~USD $30)
I RECOMMEND TO REMOVE LION BATTERY DUE TO HIGH SUMMER TEMPERATURES 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
I RECOMMEND TO REMOVE LION BATTERY DUE TO HIGH SUMMER TEMPERATURES
OBD2 adapter OBD2 adapter
- Supported is only this model... Vgate iCar Pro Bluetooth 4.0 (BLE4) OBD2 (~USD $30) - Supported is only this model... Vgate iCar Pro Bluetooth 4.0 (BLE4) OBD2 (~USD $30)
@@ -51,15 +50,19 @@ See INSTALLATION.md
Screen list Screen list
- no0. blank screen, lcd off - no0. blank screen, lcd off
- no1. auto mode (summary info / speed kmh / charging graph) - no1. automatic mode (summary info / speed kmh / charging graph)
- no2. summary info (default) - no2. summary info
- no3. speed kmh + kwh/100km (or kw for discharge) - no3. speed kmh + kwh/100km
- no4. battery cells + battery module temperatures - no4. battery cells + battery module temperatures
- no5. charging graph - no5. charging graph
- no6. consumption table. Can be used to measure available battery capacity! - no6. consumption table. Can be used to measure available battery capacity.
- no7. debug screen (default off in the menu)
![image](https://github.com/nickn17/evDash/blob/master/screenshots/v1.jpg) ![image](https://github.com/nickn17/evDash/blob/master/screenshots/v2.jpg)
![image](https://github.com/nickn17/evDash/blob/master/screenshots/v2_m5charging2.jpg)
[![Watch the video](https://github.com/nickn17/evDash/blob/master/screenshots/v0.9.jpg)](https://www.youtube.com/watch?v=Jg5VP2P58Yg&) ## Supporting me
- nick.n17@gmail.com (Lubos Petrovic / Slovakia)
- Buy Me a Beer via paypal https://www.paypal.me/nickn17
- Many thanks to all evDash contributors.

View File

@@ -2,6 +2,20 @@
### Next version ### Next version
### v2.2.0 2020-12-29
- Direct CAN support with m5 COMMU module (instead obd2 BLE4 adapter). RECOMMENDED
- EvDash deep sleep & wake up for Hyundai Ioniq/Kona & Kia e-Niro (kolaCZek).
- Send data via GPRS to own server (kolaCZek). Simple web api project https://github.com/kolaCZek/evDash_serverapi)
- Better support for Hyundai Ioniq (kolaCZek).
- Kia e-niro - added support for open doors/hood/trunk.
- Serial console off/on and improved logging & debug level setting
- Avoid GPS on UART0 collision with serial console.
- DEV initial support for Bmw i3 (Janulo)
- Command queue refactoring (Janulo)
- Sdcard is working only with m5stack
- Removed debug screen
- M5 mute speaker fix
### v2.1.1 2020-12-14 ### v2.1.1 2020-12-14
- tech refactoring: `hexToDecFromResponse`, `decFromResponse` - tech refactoring: `hexToDecFromResponse`, `decFromResponse`
- added support for GPS module on HW UART (user HWUART=2 for m5stack NEO-M8N) - added support for GPS module on HW UART (user HWUART=2 for m5stack NEO-M8N)

View File

@@ -45,6 +45,9 @@ const char AT_CMD_CFUN0[] PROGMEM = "AT+CFUN=0"; //
const char AT_CMD_CFUN1[] PROGMEM = "AT+CFUN=1"; // Switch normal power mode const char AT_CMD_CFUN1[] PROGMEM = "AT+CFUN=1"; // Switch normal power mode
const char AT_CMD_CFUN4[] PROGMEM = "AT+CFUN=4"; // Switch sleep power mode const char AT_CMD_CFUN4[] PROGMEM = "AT+CFUN=4"; // Switch sleep power mode
const char AC_CMD_CSCLK0[] PROGMEM = "AT+CSCLK=0"; // AT+CSCLK=0 command
const char AC_CMD_CSCLK2[] PROGMEM = "AT+CSCLK=2"; // AT+CSCLK=2 command
const char AT_CMD_CREG_TEST[] PROGMEM = "AT+CREG?"; // Check the network registration status const char AT_CMD_CREG_TEST[] PROGMEM = "AT+CREG?"; // Check the network registration status
const char AT_CMD_SAPBR_GPRS[] PROGMEM = "AT+SAPBR=3,1,\"Contype\",\"GPRS\""; // Configure the GPRS bearer const char AT_CMD_SAPBR_GPRS[] PROGMEM = "AT+SAPBR=3,1,\"Contype\",\"GPRS\""; // Configure the GPRS bearer
const char AT_CMD_SAPBR_APN[] PROGMEM = "AT+SAPBR=3,1,\"APN\","; // Configure the APN for the GPRS const char AT_CMD_SAPBR_APN[] PROGMEM = "AT+SAPBR=3,1,\"APN\","; // Configure the APN for the GPRS
@@ -721,6 +724,16 @@ bool SIM800L::setPowerMode(PowerMode powerMode) {
return currentPowerMode == powerMode; return currentPowerMode == powerMode;
} }
void SIM800L::exitSleepMode() {
sendCommand_P(AC_CMD_CSCLK0);
purgeSerial();
}
void SIM800L::enterSleepMode() {
sendCommand_P(AC_CMD_CSCLK2);
purgeSerial();
}
/** /**
* Status function: Check the strengh of the signal * Status function: Check the strengh of the signal
*/ */

View File

@@ -66,6 +66,8 @@ class SIM800L {
// Define the power mode (for parameter: see PowerMode enum) // Define the power mode (for parameter: see PowerMode enum)
bool setPowerMode(PowerMode powerMode); bool setPowerMode(PowerMode powerMode);
void enterSleepMode();
void exitSleepMode();
// Enable/disable GPRS // Enable/disable GPRS
bool setupGPRS(const char *apn); bool setupGPRS(const char *apn);

View File

@@ -1,10 +1,9 @@
#ifndef CONFIG_H #pragma once
#define CONFIG_H
#include <BLEDevice.h> #include <BLEDevice.h>
#define APP_VERSION "v2.1.1" #define APP_VERSION "v2.2.0"
#define APP_RELEASE_DATE "2020-12-14" #define APP_RELEASE_DATE "2020-12-29"
// TFT COLORS FOR TTGO // TFT COLORS FOR TTGO
#define TFT_BLACK 0x0000 /* 0, 0, 0 */ #define TFT_BLACK 0x0000 /* 0, 0, 0 */
@@ -48,13 +47,8 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// SIM800L // SIM800L
///////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////
#ifdef SIM800L_ENABLED
#define SIM800L_RX 16
#define SIM800L_TX 17
#define SIM800L_RST 5 #define SIM800L_RST 5
#define SIM800L_TIMER 60 #define SIM800L_TIMER 60
#endif //SIM800L_ENABLED
// MENU ITEM // MENU ITEM
typedef struct { typedef struct {
@@ -67,9 +61,14 @@ typedef struct {
} MENU_ITEM; } MENU_ITEM;
#define MENU_VEHICLE_TYPE 1 #define MENU_VEHICLE_TYPE 1
#define MENU_ADAPTER_TYPE 5
#define MENU_SAVE_SETTINGS 9 #define MENU_SAVE_SETTINGS 9
#define MENU_APP_VERSION 10 #define MENU_APP_VERSION 10
// //
#define MENU_ADAPTER_BLE4 501
#define MENU_ADAPTER_CAN 502
#define MENU_ADAPTER_BT3 503
//
#define MENU_WIFI 301 #define MENU_WIFI 301
#define MENU_GPRS 302 #define MENU_GPRS 302
#define MENU_NTP 303 #define MENU_NTP 303
@@ -80,8 +79,10 @@ typedef struct {
#define MENU_PREDRAWN_GRAPHS 308 #define MENU_PREDRAWN_GRAPHS 308
#define MENU_REMOTE_UPLOAD 309 #define MENU_REMOTE_UPLOAD 309
#define MENU_HEADLIGHTS_REMINDER 310 #define MENU_HEADLIGHTS_REMINDER 310
#define MENU_DEBUG_SCREEN 311 #define MENU_SLEEP_MODE 311
#define MENU_GPS 312 #define MENU_GPS 312
#define MENU_SERIAL_CONSOLE 313
#define MENU_DEBUG_LEVEL 314
// //
#define MENU_DISTANCE_UNIT 401 #define MENU_DISTANCE_UNIT 401
#define MENU_TEMPERATURE_UNIT 402 #define MENU_TEMPERATURE_UNIT 402
@@ -95,6 +96,5 @@ typedef struct {
#define MENU_SDCARD_AUTOSTARTLOG 3042 #define MENU_SDCARD_AUTOSTARTLOG 3042
#define MENU_SDCARD_MOUNT_STATUS 3043 #define MENU_SDCARD_MOUNT_STATUS 3043
#define MENU_SDCARD_REC 3044 #define MENU_SDCARD_REC 3044
#define MENU_SDCARD_INTERVAL 3045
// //
#endif // CONFIG_H

Binary file not shown.

Binary file not shown.

View File

@@ -1,20 +1,16 @@
/* /*
Project renamed from eNiroDashboard to evDash Project renamed from eNiroDashboard to evDash
!! 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
Serial console commands Serial console commands
serviceUUID=xxx serviceUUID=xxx
charTxUUID=xxx charTxUUID=xxx
charRxUUID=xxx charRxUUID=xxx
wifiSsid=xxx wifiSsid=xxx
wifiPassword=xxx wifiPassword=xxx
gprsApn=xxx gprsApn=xxx
remoteApiUrl=xxx remoteApiUrl=xxx
remoteApiKey=xxx remoteApiKey=xxx
Required libraries Required libraries
- esp32 board support - esp32 board support
@@ -31,10 +27,8 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Boards // Boards
#define BOARD_TTGO_T4 //#define BOARD_TTGO_T4
//#define BOARD_M5STACK_CORE #define BOARD_M5STACK_CORE
//#define SIM800L_ENABLED
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@@ -49,8 +43,6 @@
#include "BoardM5stackCore.h" #include "BoardM5stackCore.h"
#endif // BOARD_M5STACK_CORE #endif // BOARD_M5STACK_CORE
#include <BLEDevice.h>
#include <sys/time.h>
#include "config.h" #include "config.h"
#include "LiveData.h" #include "LiveData.h"
#include "CarInterface.h" #include "CarInterface.h"
@@ -59,485 +51,34 @@
#include "CarRenaultZoe.h" #include "CarRenaultZoe.h"
#include "CarKiaNiroPhev.h" #include "CarKiaNiroPhev.h"
#include "CarKiaDebugObd2.h" #include "CarKiaDebugObd2.h"
#include "CarBmwI3.h"
#ifdef SIM800L_ENABLED
#include <ArduinoJson.h>
#include "SIM800L.h"
SIM800L* sim800l;
HardwareSerial SerialGPRS(2);
#endif //SIM800L_ENABLED
// Temporary variables
char ch;
String line;
// Board, Car, Livedata (params, settings) // Board, Car, Livedata (params, settings)
BoardInterface* board; BoardInterface* board;
CarInterface* car; CarInterface* car;
LiveData* liveData; LiveData* liveData;
/**
Do next AT command from queue
*/
bool doNextAtCommand() {
// Restart loop with AT commands
if (liveData->commandQueueIndex >= liveData->commandQueueCount) {
liveData->commandQueueIndex = liveData->commandQueueLoopFrom;
board->redrawScreen();
// log every queue loop (temp)
liveData->params.sdcardCanNotify = true;
}
// Send AT command to obd
liveData->commandRequest = liveData->commandQueue[liveData->commandQueueIndex];
if (liveData->commandRequest.startsWith("ATSH")) {
liveData->currentAtshRequest = liveData->commandRequest;
}
Serial.print(">>> ");
Serial.println(liveData->commandRequest);
String tmpStr = liveData->commandRequest + "\r";
liveData->pRemoteCharacteristicWrite->writeValue(tmpStr.c_str(), tmpStr.length());
liveData->commandQueueIndex++;
return true;
}
/**
Parse result from OBD, create single line liveData->responseRowMerged
*/
bool parseRow() {
// Simple 1 line responses
Serial.print("");
Serial.println(liveData->responseRow);
// Merge 0:xxxx 1:yyyy 2:zzzz to single xxxxyyyyzzzz string
if (liveData->responseRow.length() >= 2 && liveData->responseRow.charAt(1) == ':') {
if (liveData->responseRow.charAt(0) == '0') {
liveData->responseRowMerged = "";
}
liveData->responseRowMerged += liveData->responseRow.substring(2);
}
return true;
}
/**
Parse merged row (after merge completed)
*/
bool parseRowMerged() {
Serial.print("merged:");
Serial.println(liveData->responseRowMerged);
// Catch output for debug screen
if (board->displayScreen == SCREEN_DEBUG) {
if (board->debugCommandIndex == liveData->commandQueueIndex) {
board->debugAtshRequest = liveData->currentAtshRequest;
board->debugCommandRequest = liveData->commandRequest;
board->debugLastString = liveData->responseRowMerged;
}
}
// Parse by selected car interface
car->parseRowMerged();
return true;
}
/**
BLE callbacks
*/
class MyClientCallback : public BLEClientCallbacks {
/**
On BLE connect
*/
void onConnect(BLEClient* pclient) {
Serial.println("onConnect");
}
/**
On BLE disconnect
*/
void onDisconnect(BLEClient* pclient) {
//connected = false;
Serial.println("onDisconnect");
board->displayMessage("BLE disconnected", "");
}
};
/**
Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
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());
// Add to device list (max. 9 devices allowed yet)
String tmpStr;
if (liveData->scanningDeviceIndex < 10/* && advertisedDevice.haveServiceUUID()*/) {
for (uint16_t i = 0; i < liveData->menuItemsCount; ++i) {
if (liveData->menuItems[i].id == 10001 + liveData->scanningDeviceIndex) {
tmpStr = advertisedDevice.toString().c_str();
tmpStr.replace("Name: ", "");
tmpStr.replace("Address: ", "");
tmpStr.toCharArray(liveData->menuItems[i].title, 48);
tmpStr = advertisedDevice.getAddress().toString().c_str();
tmpStr.toCharArray(liveData->menuItems[i].obdMacAddress, 18);
}
}
liveData->scanningDeviceIndex++;
}
/*
if (advertisedDevice.getServiceDataUUID().toString() != "<NULL>") {
Serial.print("ServiceDataUUID: ");
Serial.println(advertisedDevice.getServiceDataUUID().toString().c_str());
if (advertisedDevice.getServiceUUID().toString() != "<NULL>") {
Serial.print("ServiceUUID: ");
Serial.println(advertisedDevice.getServiceUUID().toString().c_str());
}
}*/
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(liveData->settings.serviceUUID)) &&
(strcmp(advertisedDevice.getAddress().toString().c_str(), liveData->settings.obdMacAddress) == 0)) {
Serial.println("Stop scanning. Found my BLE device.");
BLEDevice::getScan()->stop();
liveData->foundMyBleDevice = new BLEAdvertisedDevice(advertisedDevice);
}
}
};
uint32_t PIN = 1234;
/**
BLE Security
*/
class MySecurity : public BLESecurityCallbacks {
uint32_t onPassKeyRequest() {
Serial.printf("Pairing password: %d \r\n", PIN);
return PIN;
}
void onPassKeyNotify(uint32_t pass_key) {
Serial.printf("onPassKeyNotify\r\n");
}
bool onConfirmPIN(uint32_t pass_key) {
Serial.printf("onConfirmPIN\r\n");
return true;
}
bool onSecurityRequest() {
Serial.printf("onSecurityRequest\r\n");
return true;
}
void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl) {
if (auth_cmpl.success) {
Serial.printf("onAuthenticationComplete\r\n");
} else {
Serial.println("Auth failure. Incorrect PIN?");
liveData->bleConnect = false;
}
}
};
/**
Ble notification callback
*/
static void notifyCallback (BLERemoteCharacteristic * pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
char ch;
// Parse multi line response to single lines
liveData->responseRow = "";
for (int i = 0; i <= length; i++) {
ch = pData[i];
if (ch == '\r' || ch == '\n' || ch == '\0') {
if (liveData->responseRow != "")
parseRow();
liveData->responseRow = "";
} else {
liveData->responseRow += ch;
if (liveData->responseRow == ">") {
if (liveData->responseRowMerged != "") {
parseRowMerged();
}
liveData->responseRowMerged = "";
liveData->canSendNextAtCommand = true;
}
}
}
}
/**
Do connect BLE with server (OBD device)
*/
bool connectToServer(BLEAddress pAddress) {
board->displayMessage(" > Connecting device", "");
Serial.print("liveData->bleConnect ");
Serial.println(pAddress.toString().c_str());
board->displayMessage(" > Connecting device - init", pAddress.toString().c_str());
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
BLEDevice::setSecurityCallbacks(new MySecurity());
BLESecurity *pSecurity = new BLESecurity();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); //
pSecurity->setCapability(ESP_IO_CAP_KBDISP);
pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
board->displayMessage(" > Connecting device", pAddress.toString().c_str());
liveData->pClient = BLEDevice::createClient();
liveData->pClient->setClientCallbacks(new MyClientCallback());
if (liveData->pClient->connect(pAddress, BLE_ADDR_TYPE_RANDOM) ) Serial.println("liveData->bleConnected");
Serial.println(" - liveData->bleConnected to server");
// Remote service
board->displayMessage(" > Connecting device", "Connecting service...");
BLERemoteService* pRemoteService = liveData->pClient->getService(BLEUUID(liveData->settings.serviceUUID));
if (pRemoteService == nullptr)
{
Serial.print("Failed to find our service UUID: ");
Serial.println(liveData->settings.serviceUUID);
board->displayMessage(" > Connecting device", "Unable to find service");
return false;
}
Serial.println(" - Found our service");
// Get characteristics
board->displayMessage(" > Connecting device", "Connecting TxUUID...");
liveData->pRemoteCharacteristic = pRemoteService->getCharacteristic(BLEUUID(liveData->settings.charTxUUID));
if (liveData->pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(liveData->settings.charTxUUID);//.toString().c_str());
board->displayMessage(" > Connecting device", "Unable to find TxUUID");
return false;
}
Serial.println(" - Found our characteristic");
// Get characteristics
board->displayMessage(" > Connecting device", "Connecting RxUUID...");
liveData->pRemoteCharacteristicWrite = pRemoteService->getCharacteristic(BLEUUID(liveData->settings.charRxUUID));
if (liveData->pRemoteCharacteristicWrite == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(liveData->settings.charRxUUID);//.toString().c_str());
board->displayMessage(" > Connecting device", "Unable to find RxUUID");
return false;
}
Serial.println(" - Found our characteristic write");
board->displayMessage(" > Connecting device", "Register callbacks...");
// Read the value of the characteristic.
if (liveData->pRemoteCharacteristic->canNotify()) {
Serial.println(" - canNotify");
//liveData->pRemoteCharacteristic->registerForNotify(notifyCallback);
if (liveData->pRemoteCharacteristic->canIndicate()) {
Serial.println(" - canIndicate");
const uint8_t indicationOn[] = {0x2, 0x0};
//const uint8_t indicationOff[] = {0x0,0x0};
liveData->pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)indicationOn, 2, true);
//liveData->pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notifyOff,2,true);
liveData->pRemoteCharacteristic->registerForNotify(notifyCallback, false);
delay(200);
}
}
board->displayMessage(" > Connecting device", "Done...");
if (liveData->pRemoteCharacteristicWrite->canWrite()) {
Serial.println(" - canWrite");
}
return true;
}
/**
Start ble scan
*/
void startBleScan() {
liveData->foundMyBleDevice = NULL;
liveData->scanningDeviceIndex = 0;
board->displayMessage(" > Scanning BLE4 devices", "40sec.or hold middle&RST");
// Start scanning
Serial.println("Scanning BLE devices...");
Serial.print("Looking for ");
Serial.println(liveData->settings.obdMacAddress);
BLEScanResults foundDevices = liveData->pBLEScan->start(40, false);
Serial.print("Devices found: ");
Serial.println(foundDevices.getCount());
Serial.println("Scan done!");
liveData->pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
char tmpStr1[20];
sprintf(tmpStr1, "Found %d devices", foundDevices.getCount());
board->displayMessage(" > Scanning BLE4 devices", tmpStr1);
// Scan devices from menu, show list of devices
if (liveData->menuItemSelected == 2) {
Serial.println("Display menu with devices");
liveData->menuVisible = true;
liveData->menuCurrent = 9999;
liveData->menuItemSelected = 0;
board->showMenu();
} else {
// Redraw screen
if (liveData->foundMyBleDevice == NULL) {
board->displayMessage("Device not found", "Middle button - menu");
} else {
board->redrawScreen();
}
}
}
/**
SIM800L
*/
#ifdef SIM800L_ENABLED
bool sim800lSetup() {
Serial.println("Setting SIM800L module");
SerialGPRS.begin(9600);
sim800l = new SIM800L((Stream *)&SerialGPRS, SIM800L_RST, 512 , 512);
// SIM800L DebugMode:
//sim800l = new SIM800L((Stream *)&SerialGPRS, SIM800L_RST, 512 , 512, (Stream *)&Serial);
bool sim800l_ready = sim800l->isReady();
for (uint8_t i = 0; i < 5 && !sim800l_ready; i++) {
Serial.println("Problem to initialize SIM800L module, retry in 1 sec");
delay(1000);
sim800l_ready = sim800l->isReady();
}
if (!sim800l_ready) {
Serial.println("Problem to initialize SIM800L module");
} else {
Serial.println("SIM800L module initialized");
Serial.print("Setting GPRS APN to: ");
Serial.println(liveData->settings.gprsApn);
bool sim800l_gprs = sim800l->setupGPRS(liveData->settings.gprsApn);
for (uint8_t i = 0; i < 5 && !sim800l_gprs; i++) {
Serial.println("Problem to set GPRS APN, retry in 1 sec");
delay(1000);
sim800l_gprs = sim800l->setupGPRS(liveData->settings.gprsApn);
}
if (sim800l_gprs) {
liveData->params.sim800l_enabled = true;
Serial.println("GPRS APN set OK");
} else {
Serial.println("Problem to set GPRS APN");
}
}
return true;
}
bool sendDataViaGPRS() {
Serial.println("Sending data via GPRS");
NetworkRegistration network = sim800l->getRegistrationStatus();
if (network != REGISTERED_HOME && network != REGISTERED_ROAMING) {
Serial.println("SIM800L module not connected to network, skipping data send");
return false;
}
if(!sim800l->isConnectedGPRS()) {
Serial.println("GPRS not connected... Connecting");
bool connected = sim800l->connectGPRS();
for (uint8_t i = 0; i < 5 && !connected; i++) {
Serial.println("Problem to connect GPRS, retry in 1 sec");
delay(1000);
connected = sim800l->connectGPRS();
}
if(connected) {
Serial.println("GPRS connected!");
} else {
Serial.println("GPRS not connected! Reseting SIM800L module!");
sim800l->reset();
sim800lSetup();
return false;
}
}
Serial.println("Start HTTP POST...");
StaticJsonDocument<512> jsonData;
jsonData["apikey"] = liveData->settings.remoteApiKey;
jsonData["carType"] = liveData->settings.carType;
jsonData["socPerc"] = liveData->params.socPerc;
jsonData["sohPerc"] = liveData->params.sohPerc;
jsonData["batPowerKw"] = liveData->params.batPowerKw;
jsonData["batPowerAmp"] = liveData->params.batPowerAmp;
jsonData["batVoltage"] = liveData->params.batVoltage;
jsonData["auxVoltage"] = liveData->params.auxVoltage;
jsonData["auxAmp"] = liveData->params.auxCurrentAmp;
jsonData["batMinC"] = liveData->params.batMinC;
jsonData["batMaxC"] = liveData->params.batMaxC;
jsonData["batInletC"] = liveData->params.batInletC;
jsonData["batFanStatus"] = liveData->params.batFanStatus;
jsonData["speedKmh"] = liveData->params.speedKmh;
jsonData["cumulativeEnergyChargedKWh"] = liveData->params.cumulativeEnergyChargedKWh;
jsonData["cumulativeEnergyDischargedKWh"] = liveData->params.cumulativeEnergyDischargedKWh;
char payload[512];
serializeJson(jsonData, payload);
Serial.print("Sending payload: ");
Serial.println(payload);
Serial.print("Remote API server: ");
Serial.println(liveData->settings.remoteApiUrl);
uint16_t rc = sim800l->doPost(liveData->settings.remoteApiUrl, "application/json", payload, 10000, 10000);
if (rc == 200) {
Serial.println("HTTP POST successful");
} else {
// Failed...
Serial.print("HTTP POST error: ");
Serial.println(rc);
}
return true;
}
#endif //SIM800L_ENABLED
/** /**
Setup device Setup device
*/ */
void setup(void) { void setup(void) {
// Serial console, init structures // Serial console
Serial.begin(115200); syslog = new LogSerial();
Serial.println(""); syslog->println("\nBooting device...");
Serial.println("Booting device...");
// Init settings/params, board library // Init settings/params
line = "";
liveData = new LiveData(); liveData = new LiveData();
liveData->initParams(); liveData->initParams();
// Turn off serial console
if (liveData->settings.serialConsolePort == 255) {
syslog->println("Serial console disabled...");
syslog->flush();
syslog->end();
}
// Init board
#ifdef BOARD_TTGO_T4 #ifdef BOARD_TTGO_T4
board = new BoardTtgoT4v13(); board = new BoardTtgoT4v13();
#endif // BOARD_TTGO_T4 #endif // BOARD_TTGO_T4
@@ -548,124 +89,47 @@ void setup(void) {
board->loadSettings(); board->loadSettings();
board->initBoard(); board->initBoard();
// Car interface // Init selected car interface
if (liveData->settings.carType == CAR_KIA_ENIRO_2020_64 || liveData->settings.carType == CAR_HYUNDAI_KONA_2020_64 || switch (liveData->settings.carType) {
liveData->settings.carType == CAR_KIA_ENIRO_2020_39 || liveData->settings.carType == CAR_HYUNDAI_KONA_2020_39) { case CAR_KIA_ENIRO_2020_39:
car = new CarKiaEniro(); case CAR_KIA_ENIRO_2020_64:
} else if (liveData->settings.carType == CAR_HYUNDAI_IONIQ_2018) { case CAR_HYUNDAI_KONA_2020_39:
car = new CarHyundaiIoniq(); case CAR_HYUNDAI_KONA_2020_64:
} else if (liveData->settings.carType == CAR_KIA_NIRO_PHEV) { car = new CarKiaEniro();
car = new CarKiaNiroPhev(); break;
} else if (liveData->settings.carType == CAR_RENAULT_ZOE) { case CAR_HYUNDAI_IONIQ_2018:
car = new CarRenaultZoe(); car = new CarHyundaiIoniq();
} else { break;
// if (liveData->settings.carType == CAR_DEBUG_OBD2_KIA) case CAR_KIA_NIRO_PHEV:
car = new CarKiaDebugObd2(); car = new CarKiaNiroPhev();
break;
case CAR_RENAULT_ZOE:
car = new CarRenaultZoe();
break;
case CAR_BMW_I3_2014:
car = new CarBmwI3();
break;
default:
car = new CarKiaDebugObd2();
} }
car->setLiveData(liveData); car->setLiveData(liveData);
car->activateCommandQueue(); car->activateCommandQueue();
board->attachCar(car); board->attachCar(car);
board->debugCommandIndex = liveData->commandQueueLoopFrom;
// Finish board setup
board->afterSetup();
// Redraw screen // Redraw screen
board->redrawScreen(); board->redrawScreen();
// Init time library
struct timeval tv;
tv.tv_sec = 1589011873;
settimeofday(&tv, NULL);
struct tm now;
getLocalTime(&now, 0);
liveData->params.chargingStartTime = liveData->params.currentTime = mktime(&now);
// Start BLE connection
Serial.println("Start BLE with PIN auth");
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
BLEDevice::init("");
// Retrieve a Scanner and set the callback we want to use to be informed when we have detected a new device.
// Specify that we want active scanning and start the scan to run for 10 seconds.
Serial.println("Setup BLE scan");
liveData->pBLEScan = BLEDevice::getScan();
liveData->pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
liveData->pBLEScan->setInterval(1349);
liveData->pBLEScan->setWindow(449);
liveData->pBLEScan->setActiveScan(true);
// Skip BLE scan if middle button pressed
if (strcmp(liveData->settings.obdMacAddress, "00:00:00:00:00:00") != 0 && !board->skipAdapterScan()) {
Serial.println(liveData->settings.obdMacAddress);
startBleScan();
}
#ifdef SIM800L_ENABLED
sim800lSetup();
#endif //SIM800L_ENABLED
// Hold right button
board->afterSetup();
// End // End
Serial.println("Device setup completed"); syslog->println("Device setup completed");
} }
/** /**
Loop Main loop
*/ */
void loop() { void loop() {
// Connect BLE device
if (liveData->bleConnect == true && liveData->foundMyBleDevice != NULL) {
liveData->pServerAddress = new BLEAddress(liveData->settings.obdMacAddress);
if (connectToServer(*liveData->pServerAddress)) {
liveData->bleConnected = true;
liveData->bleConnect = false;
Serial.println("We are now connected to the BLE device.");
// Print message
board->displayMessage(" > Processing init AT cmds", "");
// Serve first command (ATZ)
doNextAtCommand();
} else {
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
}
}
// Send command from TTY to OBD2
if (Serial.available()) {
ch = Serial.read();
if (ch == '\r' || ch == '\n') {
board->customConsoleCommand(line);
line = line + ch;
Serial.println(line);
if (liveData->bleConnected) {
liveData->pRemoteCharacteristicWrite->writeValue(line.c_str(), line.length());
}
line = "";
} else {
line = line + ch;
}
}
// Can send next command from queue to OBD
if (liveData->canSendNextAtCommand) {
liveData->canSendNextAtCommand = false;
doNextAtCommand();
}
#ifdef SIM800L_ENABLED
if (liveData->params.lastDataSent + SIM800L_TIMER < liveData->params.currentTime && liveData->params.sim800l_enabled) {
sendDataViaGPRS();
liveData->params.lastDataSent = liveData->params.currentTime;
}
#endif // SIM800L_ENABLED
board->mainLoop(); board->mainLoop();
if (board->scanDevices) {
board->scanDevices = false;
startBleScan();
}
} }

43
menu.h
View File

@@ -1,11 +1,12 @@
#pragma once
#include "config.h"
#include "config.h";
MENU_ITEM menuItemsSource[100] = { MENU_ITEM menuItemsSource[100] = {
{0, 0, 0, "<- exit menu"}, {0, 0, 0, "<- exit menu"},
{MENU_VEHICLE_TYPE, 0, -1, "Vehicle type"}, {MENU_VEHICLE_TYPE, 0, -1, "Vehicle type"},
{MENU_ADAPTER_TYPE, 0, -1, "Adapter type"},
{2, 0, -1, "Select OBD2 BLE4 adapter"}, {2, 0, -1, "Select OBD2 BLE4 adapter"},
{3, 0, -1, "Others"}, {3, 0, -1, "Others"},
{4, 0, -1, "Units"}, {4, 0, -1, "Units"},
@@ -21,14 +22,22 @@ MENU_ITEM menuItemsSource[100] = {
{104, 1, -1, "Kia eNiro 2020 39kWh"}, {104, 1, -1, "Kia eNiro 2020 39kWh"},
{105, 1, -1, "Hyundai Kona 2020 39kWh"}, {105, 1, -1, "Hyundai Kona 2020 39kWh"},
{106, 1, -1, "Renault Zoe 22kWh (DEV)"}, {106, 1, -1, "Renault Zoe 22kWh (DEV)"},
{107, 1, -1, "Kia Niro PHEV 8.9kWh"}, {107, 1, -1, "Kia Niro PHEV 8.9kWh (DEV)"},
{108, 1, -1, "BMW i3 2014 22kWh (DEV)"},
{120, 1, -1, "Debug OBD2 Kia"}, {120, 1, -1, "Debug OBD2 Kia"},
{MENU_ADAPTER_BLE4-1, MENU_ADAPTER_TYPE, 0, "<- parent menu"},
{MENU_ADAPTER_BLE4, MENU_ADAPTER_TYPE, -1, "Bluetooth 4 (BLE4)"},
{MENU_ADAPTER_CAN, MENU_ADAPTER_TYPE, -1, "CAN bus (MCP2515-1/SO)"},
//{MENU_ADAPTER_BT3, MENU_ADAPTER_TYPE, -1, "Bluetooth 3 (dev)"},
{300, 3, 0, "<- parent menu"}, {300, 3, 0, "<- parent menu"},
// {MENU_WIFI, 3, -1, "[dev] WiFi network"}, // {MENU_WIFI, 3, -1, "[dev] WiFi network"},
{MENU_SDCARD, 3, -1, "SD card"}, {MENU_SDCARD, 3, -1, "SD card"},
{MENU_GPS, 3, -1, "GPS"}, {MENU_GPS, 3, -1, "GPS"},
{MENU_GPRS, 3, -1, "[dev] GSM/GPRS"}, {MENU_GPRS, 3, -1, "GSM/GPRS"},
{MENU_SERIAL_CONSOLE, 3, -1, "Serial console"},
{MENU_DEBUG_LEVEL, 3, -1, "Debug level"},
//{MENU_REMOTE_UPLOAD, 3, -1, "[dev] Remote upload"}, //{MENU_REMOTE_UPLOAD, 3, -1, "[dev] Remote upload"},
//{MENU_NTP, 3, -1, "[dev] NTP"}, //{MENU_NTP, 3, -1, "[dev] NTP"},
{MENU_SCREEN_ROTATION, 3, -1, "Screen rotation"}, {MENU_SCREEN_ROTATION, 3, -1, "Screen rotation"},
@@ -36,21 +45,8 @@ MENU_ITEM menuItemsSource[100] = {
{MENU_SCREEN_BRIGHTNESS, 3, -1, "LCD brightness"}, {MENU_SCREEN_BRIGHTNESS, 3, -1, "LCD brightness"},
{MENU_PREDRAWN_GRAPHS, 3, -1, "Pre-drawn ch.graphs"}, {MENU_PREDRAWN_GRAPHS, 3, -1, "Pre-drawn ch.graphs"},
{MENU_HEADLIGHTS_REMINDER, 3, -1, "Headlight reminder"}, {MENU_HEADLIGHTS_REMINDER, 3, -1, "Headlight reminder"},
{MENU_DEBUG_SCREEN, 3, -1, "Debug screen"}, {MENU_SLEEP_MODE, 3, -1, "SleepMode"},
/*
// NTP
byte ntpEnabled; // 0/1
byte ntpTimezone;
byte ntpDaySaveTime; // 0/1
// GPRS SIM800L
byte gprsEnabled; // 0/1
char gprsApn[64];
// Remote upload
byte remoteUploadEnabled; // 0/1
char remoteApiUrl[64];
char remoteApiKey[32];*/
{400, 4, 0, "<- parent menu"}, {400, 4, 0, "<- parent menu"},
{MENU_DISTANCE_UNIT, 4, -1, "Distance"}, {MENU_DISTANCE_UNIT, 4, -1, "Distance"},
{MENU_TEMPERATURE_UNIT, 4, -1, "Temperature"}, {MENU_TEMPERATURE_UNIT, 4, -1, "Temperature"},
@@ -61,11 +57,12 @@ MENU_ITEM menuItemsSource[100] = {
{MENU_WIFI_SSID, 301, -1, "SSID"}, {MENU_WIFI_SSID, 301, -1, "SSID"},
{MENU_WIFI_PASSWORD, 301, -1, "Password"}, {MENU_WIFI_PASSWORD, 301, -1, "Password"},
{3040, 304, 3, "<- parent menu"}, {MENU_SDCARD*10, MENU_SDCARD, 3, "<- parent menu"},
{MENU_SDCARD_ENABLED, 304, -1, "SD enabled"}, {MENU_SDCARD_ENABLED, MENU_SDCARD, -1, "SD enabled"},
{MENU_SDCARD_AUTOSTARTLOG, 304, -1, "Autostart log enabled"}, {MENU_SDCARD_AUTOSTARTLOG, MENU_SDCARD, -1, "Autostart log enabled"},
{MENU_SDCARD_MOUNT_STATUS, 304, -1, "Status"}, {MENU_SDCARD_MOUNT_STATUS, MENU_SDCARD, -1, "Status"},
{MENU_SDCARD_REC, 304, -1, "Record"}, {MENU_SDCARD_REC, MENU_SDCARD, -1, "Record"},
//{MENU_SDCARD_INTERVAL, MENU_SDCARD, -1, "Log interval sec."},
{3060, 306, 3, "<- parent menu"}, {3060, 306, 3, "<- parent menu"},
{3061, 306, -1, "Auto mode"}, {3061, 306, -1, "Auto mode"},

BIN
screenshots/v2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
screenshots/v2_m5speed.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB