Files
evDash/Board320_240.cpp
2020-12-20 12:24:56 +01:00

1442 lines
66 KiB
C++

#ifndef BOARD320_240_CPP
#define BOARD320_240_CPP
#include <SD.h>
#include <FS.h>
#include <analogWrite.h>
#include <TFT_eSPI.h>
//#include <WiFi.h>
//#include <WiFiClient.h>
#include <sys/time.h>
#include "config.h"
#include "BoardInterface.h"
#include "Board320_240.h"
/**
Init board
*/
void Board320_240::initBoard() {
// Set button pins for input
pinMode(pinButtonMiddle, INPUT);
pinMode(pinButtonLeft, INPUT);
pinMode(pinButtonRight, INPUT);
// Init display
Serial.println("Init tft display");
tft.begin();
tft.invertDisplay(invertDisplay);
tft.setRotation(liveData->settings.displayRotation);
setBrightness((liveData->settings.lcdBrightness == 0) ? 100 : liveData->settings.lcdBrightness);
tft.fillScreen(TFT_BLACK);
bool psramUsed = false; // 320x240 16bpp sprites requires psram
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if (psramFound())
psramUsed = true;
#endif
spr.setColorDepth((psramUsed) ? 16 : 8);
spr.createSprite(320, 240);
}
/**
After setup device
*/
void Board320_240::afterSetup() {
// Show test data on right button during boot device
displayScreen = liveData->settings.defaultScreen;
if (digitalRead(pinButtonRight) == LOW) {
loadTestData();
}
// Wifi
// Starting Wifi after BLE prevents reboot loop
if (liveData->settings.wifiEnabled == 1) {
/*Serial.print("memReport(): MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM bytes free. ");
Serial.println(heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM));
Serial.println("WiFi init...");
WiFi.enableSTA(true);
WiFi.mode(WIFI_STA);
WiFi.begin(liveData->settings.wifiSsid, liveData->settings.wifiPassword);
Serial.println("WiFi init completed...");*/
}
// Init GPS
if (liveData->settings.gpsHwSerialPort <= 2) {
gpsHwUart = new HardwareSerial(liveData->settings.gpsHwSerialPort);
gpsHwUart->begin(9600);
}
// SD card
if (liveData->settings.sdcardEnabled == 1) {
if (sdcardMount() && liveData->settings.sdcardAutstartLog == 1) {
sdcardToggleRecording();
}
}
BoardInterface::afterSetup();
}
/**
Set brightness level
*/
void Board320_240::setBrightness(byte lcdBrightnessPerc) {
analogWrite(TFT_BL, lcdBrightnessPerc);
}
/**
Clear screen a display two lines message
*/
void Board320_240::displayMessage(const char* row1, const char* row2) {
// Must draw directly, withou sprite (due to psramFound check)
tft.fillScreen(TFT_BLACK);
tft.setTextDatum(ML_DATUM);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setFreeFont(&Roboto_Thin_24);
tft.setTextDatum(BL_DATUM);
tft.drawString(row1, 0, 240 / 2, GFXFF);
tft.drawString(row2, 0, (240 / 2) + 30, GFXFF);
}
/**
Draw cell on dashboard
*/
void Board320_240::drawBigCell(int32_t x, int32_t y, int32_t w, int32_t h, const char* text, const char* desc, uint16_t bgColor, uint16_t fgColor) {
int32_t posx, posy;
posx = (x * 80) + 4;
posy = (y * 60) + 1;
spr.fillRect(x * 80, y * 60, ((w) * 80) - 1, ((h) * 60) - 1, bgColor);
spr.drawFastVLine(((x + w) * 80) - 1, ((y) * 60) - 1, h * 60, TFT_BLACK);
spr.drawFastHLine(((x) * 80) - 1, ((y + h) * 60) - 1, w * 80, TFT_BLACK);
spr.setTextDatum(TL_DATUM); // Topleft
spr.setTextColor(TFT_SILVER, bgColor); // Bk, fg color
spr.setTextSize(1); // Size for small 5x7 font
spr.drawString(desc, posx, posy, 2);
// Big 2x2 cell in the middle of screen
if (w == 2 && h == 2) {
// Bottom 2 numbers with charged/discharged kWh from start
posx = (x * 80) + 5;
posy = ((y + h) * 60) - 32;
sprintf(tmpStr3, "-%01.01f", liveData->params.cumulativeEnergyDischargedKWh - liveData->params.cumulativeEnergyDischargedKWhStart);
spr.setFreeFont(&Roboto_Thin_24);
spr.setTextDatum(TL_DATUM);
spr.drawString(tmpStr3, posx, posy, GFXFF);
posx = ((x + w) * 80) - 8;
sprintf(tmpStr3, "+%01.01f", liveData->params.cumulativeEnergyChargedKWh - liveData->params.cumulativeEnergyChargedKWhStart);
spr.setTextDatum(TR_DATUM);
spr.drawString(tmpStr3, posx, posy, GFXFF);
// Main number - kwh on roads, amps on charges
posy = (y * 60) + 24;
spr.setTextColor(fgColor, bgColor);
spr.setFreeFont(&Orbitron_Light_32);
spr.drawString(text, posx, posy, 7);
} else {
// All others 1x1 cells
spr.setTextDatum(MC_DATUM);
spr.setTextColor(fgColor, bgColor);
spr.setFreeFont(&Orbitron_Light_24);
posx = (x * 80) + (w * 80 / 2) - 3;
posy = (y * 60) + (h * 60 / 2) + 4;
spr.drawString(text, posx, posy, (w == 2 ? 7 : GFXFF));
}
}
/**
Draw small rect 80x30
*/
void Board320_240::drawSmallCell(int32_t x, int32_t y, int32_t w, int32_t h, const char* text, const char* desc, int16_t bgColor, int16_t fgColor) {
int32_t posx, posy;
posx = (x * 80) + 4;
posy = (y * 32) + 1;
spr.fillRect(x * 80, y * 32, ((w) * 80), ((h) * 32), bgColor);
spr.drawFastVLine(((x + w) * 80) - 1, ((y) * 32) - 1, h * 32, TFT_BLACK);
spr.drawFastHLine(((x) * 80) - 1, ((y + h) * 32) - 1, w * 80, TFT_BLACK);
spr.setTextDatum(TL_DATUM); // Topleft
spr.setTextColor(TFT_SILVER, bgColor); // Bk, fg bgColor
spr.setTextSize(1); // Size for small 5x7 font
spr.drawString(desc, posx, posy, 2);
spr.setTextDatum(TC_DATUM);
spr.setTextColor(fgColor, bgColor);
posx = (x * 80) + (w * 80 / 2) - 3;
spr.drawString(text, posx, posy + 14, 2);
}
/**
Show tire pressures / temperatures
Custom field
*/
void Board320_240::showTires(int32_t x, int32_t y, int32_t w, int32_t h, const char* topleft, const char* topright, const char* bottomleft, const char* bottomright, uint16_t color) {
int32_t posx, posy;
spr.fillRect(x * 80, y * 60, ((w) * 80) - 1, ((h) * 60) - 1, color);
spr.drawFastVLine(((x + w) * 80) - 1, ((y) * 60) - 1, h * 60, TFT_BLACK);
spr.drawFastHLine(((x) * 80) - 1, ((y + h) * 60) - 1, w * 80, TFT_BLACK);
spr.setTextDatum(TL_DATUM);
spr.setTextColor(TFT_SILVER, color);
spr.setTextSize(1);
posx = (x * 80) + 4;
posy = (y * 60) + 0;
spr.drawString(topleft, posx, posy, 2);
posy = (y * 60) + 14;
spr.drawString(bottomleft, posx, posy, 2);
spr.setTextDatum(TR_DATUM);
posx = ((x + w) * 80) - 4;
posy = (y * 60) + 0;
spr.drawString(topright, posx, posy, 2);
posy = (y * 60) + 14;
spr.drawString(bottomright, posx, posy, 2);
}
/**
Main screen (Screen 1)
*/
void Board320_240::drawSceneMain() {
// Tire pressure
char pressureStr[4] = "bar";
char temperatureStr[2] = "C";
if (liveData->settings.pressureUnit != 'b')
strcpy(pressureStr, "psi");
if (liveData->settings.temperatureUnit != 'c')
strcpy(temperatureStr, "F");
sprintf(tmpStr1, "%01.01f%s %02.00f%s", liveData->bar2pressure(liveData->params.tireFrontLeftPressureBar), pressureStr, liveData->celsius2temperature(liveData->params.tireFrontLeftTempC), temperatureStr);
sprintf(tmpStr2, "%02.00f%s %01.01f%s", liveData->celsius2temperature(liveData->params.tireFrontRightTempC), temperatureStr, liveData->bar2pressure(liveData->params.tireFrontRightPressureBar), pressureStr);
sprintf(tmpStr3, "%01.01f%s %02.00f%s", liveData->bar2pressure(liveData->params.tireRearLeftPressureBar), pressureStr, liveData->celsius2temperature(liveData->params.tireRearLeftTempC), temperatureStr);
sprintf(tmpStr4, "%02.00f%s %01.01f%s", liveData->celsius2temperature(liveData->params.tireRearRightTempC), temperatureStr, liveData->bar2pressure(liveData->params.tireRearRightPressureBar), pressureStr);
showTires(1, 0, 2, 1, tmpStr1, tmpStr2, tmpStr3, tmpStr4, TFT_BLACK);
// Added later - kwh total in tires box
// TODO: refactoring
spr.setTextDatum(TL_DATUM);
spr.setTextColor(TFT_GREEN, TFT_BLACK);
sprintf(tmpStr1, "C: %01.01f +%01.01fkWh", liveData->params.cumulativeEnergyChargedKWh, liveData->params.cumulativeEnergyChargedKWh - liveData->params.cumulativeEnergyChargedKWhStart);
spr.drawString(tmpStr1, (1 * 80) + 4, (0 * 60) + 30, 2);
spr.setTextColor(TFT_YELLOW, TFT_BLACK);
sprintf(tmpStr1, "D: %01.01f -%01.01fkWh", liveData->params.cumulativeEnergyDischargedKWh, liveData->params.cumulativeEnergyDischargedKWh - liveData->params.cumulativeEnergyDischargedKWhStart);
spr.drawString(tmpStr1, (1 * 80) + 4, (0 * 60) + 44, 2);
// batPowerKwh100 on roads, else batPowerAmp
if (liveData->params.speedKmh > 20) {
sprintf(tmpStr1, "%01.01f", liveData->km2distance(liveData->params.batPowerKwh100));
drawBigCell(1, 1, 2, 2, tmpStr1, ((liveData->settings.distanceUnit == 'k') ? "POWER KWH/100KM" : "POWER KWH/100MI"), (liveData->params.batPowerKwh100 >= 0 ? TFT_DARKGREEN2 : (liveData->params.batPowerKwh100 < -30.0 ? TFT_RED : TFT_DARKRED)), TFT_WHITE);
} else {
// batPowerAmp on chargers (under 10kmh)
sprintf(tmpStr1, "%01.01f", liveData->params.batPowerKw);
drawBigCell(1, 1, 2, 2, tmpStr1, "POWER KW", (liveData->params.batPowerKw >= 0 ? TFT_DARKGREEN2 : (liveData->params.batPowerKw <= -30 ? TFT_RED : TFT_DARKRED)), TFT_WHITE);
}
// socPerc
sprintf(tmpStr1, "%01.00f%%", liveData->params.socPerc);
sprintf(tmpStr2, (liveData->params.sohPerc == 100.0 ? "SOC/H%01.00f%%" : "SOC/H%01.01f%%"), liveData->params.sohPerc);
drawBigCell(0, 0, 1, 1, tmpStr1, tmpStr2, (liveData->params.socPerc < 10 || liveData->params.sohPerc < 100 ? TFT_RED : (liveData->params.socPerc > 80 ? TFT_DARKGREEN2 : TFT_DEFAULT_BK)), TFT_WHITE);
// batPowerAmp
sprintf(tmpStr1, (abs(liveData->params.batPowerAmp) > 9.9 ? "%01.00f" : "%01.01f"), liveData->params.batPowerAmp);
drawBigCell(0, 1, 1, 1, tmpStr1, "CURRENT A", (liveData->params.batPowerAmp >= 0 ? TFT_DARKGREEN2 : TFT_DARKRED), TFT_WHITE);
// batVoltage
sprintf(tmpStr1, "%03.00f", liveData->params.batVoltage);
drawBigCell(0, 2, 1, 1, tmpStr1, "VOLTAGE", TFT_DEFAULT_BK, TFT_WHITE);
// batCellMinV
sprintf(tmpStr1, "%01.02f", liveData->params.batCellMaxV - liveData->params.batCellMinV);
sprintf(tmpStr2, "CELLS %01.02f", liveData->params.batCellMinV);
drawBigCell(0, 3, 1, 1, ( liveData->params.batCellMaxV - liveData->params.batCellMinV == 0.00 ? "OK" : tmpStr1), tmpStr2, TFT_DEFAULT_BK, TFT_WHITE);
// batTempC
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f" : "%01.01f"), liveData->celsius2temperature(liveData->params.batMinC));
sprintf(tmpStr2, ((liveData->settings.temperatureUnit == 'c') ? "BATT. %01.00fC" : "BATT. %01.01fF"), liveData->celsius2temperature(liveData->params.batMaxC));
drawBigCell(1, 3, 1, 1, tmpStr1, tmpStr2, TFT_TEMP, (liveData->params.batTempC >= 15) ? ((liveData->params.batTempC >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED);
// 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);
// Aux perc
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
sprintf(tmpStr1, (abs(liveData->params.auxCurrentAmp) > 9.9 ? "%01.00f" : "%01.01f"), liveData->params.auxCurrentAmp);
drawBigCell(3, 1, 1, 1, tmpStr1, "AUX AMPS", (liveData->params.auxCurrentAmp >= 0 ? TFT_DARKGREEN2 : TFT_DARKRED), TFT_WHITE);
// auxVoltage
sprintf(tmpStr1, "%01.01f", liveData->params.auxVoltage);
drawBigCell(3, 2, 1, 1, tmpStr1, "AUX VOLTS", (liveData->params.auxVoltage < 12.1 ? TFT_RED : (liveData->params.auxVoltage < 12.6 ? TFT_ORANGE : TFT_DEFAULT_BK)), TFT_WHITE);
// indoorTemperature
sprintf(tmpStr1, "%01.01f", liveData->celsius2temperature(liveData->params.indoorTemperature));
sprintf(tmpStr2, "IN/OUT%01.01f", liveData->celsius2temperature(liveData->params.outdoorTemperature));
drawBigCell(3, 3, 1, 1, tmpStr1, tmpStr2, TFT_TEMP, TFT_WHITE);
}
/**
Speed + kwh/100km (Screen 2)
*/
void Board320_240::drawSceneSpeed() {
int32_t posx, posy;
// HUD
if (displayScreenSpeedHud) {
// Change rotation to vertical & mirror
if (tft.getRotation() != 6) {
tft.setRotation(6);
}
tft.fillScreen(TFT_BLACK);
tft.setTextDatum(TR_DATUM); // top-right alignment
tft.setTextColor(TFT_WHITE, TFT_BLACK); // foreground, background text color
// Draw speed
tft.setTextSize((liveData->params.speedKmh > 99) ? 1 : 2);
sprintf(tmpStr3, "0");
if (liveData->params.speedKmh > 10)
sprintf(tmpStr3, "%01.00f", liveData->km2distance(liveData->params.speedKmh));
tft.drawString(tmpStr3, 240, 0, 8);
// Draw power kWh/100km (>25kmh) else kW
tft.setTextSize(1);
if (liveData->params.speedKmh > 25 && liveData->params.batPowerKw < 0)
sprintf(tmpStr3, "%01.01f", liveData->km2distance(liveData->params.batPowerKwh100));
else
sprintf(tmpStr3, "%01.01f", liveData->params.batPowerKw);
tft.drawString(tmpStr3, 240, 150, 8);
// Draw soc%
sprintf(tmpStr3, "%01.00f", liveData->params.socPerc);
tft.drawString(tmpStr3, 240 , 230, 8);
// Cold gate cirlce
tft.fillCircle(30, 280, 25, (liveData->params.batTempC >= 15) ? ((liveData->params.batTempC >= 25) ? TFT_DARKGREEN2 : TFT_BLUE) : TFT_RED);
// Brake lights
tft.fillRect(0, 310, 240, 10, (liveData->params.brakeLights) ? TFT_RED : TFT_BLACK);
return;
}
//
spr.fillRect(0, 36, 200, 160, TFT_DARKRED);
posx = 320 / 2;
posy = 40;
spr.setTextDatum(TR_DATUM);
spr.setTextColor(TFT_WHITE, TFT_DARKRED);
spr.setTextSize(2); // Size for small 5cix7 font
sprintf(tmpStr3, "0");
if (liveData->params.speedKmh > 10)
sprintf(tmpStr3, "%01.00f", liveData->km2distance(liveData->params.speedKmh));
spr.drawString(tmpStr3, 200, posy, 7);
posy = 145;
spr.setTextDatum(TR_DATUM); // Top center
spr.setTextSize(1);
if (liveData->params.speedKmh > 25 && liveData->params.batPowerKw < 0) {
sprintf(tmpStr3, "%01.01f", liveData->km2distance(liveData->params.batPowerKwh100));
} else {
sprintf(tmpStr3, "%01.01f", liveData->params.batPowerKw);
}
spr.drawString(tmpStr3, 200, posy, 7);
// Bottom 2 numbers with charged/discharged kWh from start
spr.setFreeFont(&Roboto_Thin_24);
spr.setTextColor(TFT_WHITE, TFT_BLACK);
posx = 5;
posy = 5;
spr.setTextDatum(TL_DATUM);
sprintf(tmpStr3, ((liveData->settings.distanceUnit == 'k') ? "%01.00fkm " : "%01.00fmi "), liveData->km2distance(liveData->params.odoKm));
spr.drawString(tmpStr3, posx, posy, GFXFF);
if (liveData->params.motorRpm > -1) {
spr.setTextDatum(TR_DATUM);
sprintf(tmpStr3, " %01.00frpm" , liveData->params.motorRpm);
spr.drawString(tmpStr3, 320 - posx, posy, GFXFF);
}
// Bottom info
// Cummulative regen/power
posy = 240 - 5;
sprintf(tmpStr3, "-%01.01f ", liveData->params.cumulativeEnergyDischargedKWh - liveData->params.cumulativeEnergyDischargedKWhStart);
spr.setTextDatum(BL_DATUM);
spr.drawString(tmpStr3, posx, posy, GFXFF);
posx = 320 - 5;
sprintf(tmpStr3, " +%01.01f", liveData->params.cumulativeEnergyChargedKWh - liveData->params.cumulativeEnergyChargedKWhStart);
spr.setTextDatum(BR_DATUM);
spr.drawString(tmpStr3, posx, posy, GFXFF);
// Bat.power
posx = 320 / 2;
sprintf(tmpStr3, " %01.01fkw ", liveData->params.batPowerKw);
spr.setTextDatum(BC_DATUM);
spr.drawString(tmpStr3, posx, posy, GFXFF);
// RIGHT INFO
// Battery "cold gate" detection - red < 15C (43KW limit), <25 (blue - 55kW limit), green all ok
spr.fillCircle(290, 60, 25, (liveData->params.batTempC >= 15) ? ((liveData->params.batTempC >= 25) ? TFT_DARKGREEN2 : TFT_BLUE) : TFT_RED);
spr.setTextColor(TFT_WHITE, (liveData->params.batTempC >= 15) ? ((liveData->params.batTempC >= 25) ? TFT_DARKGREEN2 : TFT_BLUE) : TFT_RED);
spr.setFreeFont(&Roboto_Thin_24);
spr.setTextDatum(MC_DATUM);
sprintf(tmpStr3, "%01.00f", liveData->celsius2temperature(liveData->params.batTempC));
spr.drawString(tmpStr3, 290, 60, GFXFF);
// Brake lights
spr.fillRect(210, 40, 40, 40, (liveData->params.brakeLights) ? TFT_RED : TFT_BLACK);
// Soc%, bat.kWh
spr.setFreeFont(&Orbitron_Light_32);
spr.setTextColor(TFT_WHITE, TFT_BLACK);
spr.setTextDatum(TR_DATUM);
sprintf(tmpStr3, " %01.00f%%", liveData->params.socPerc);
spr.drawString(tmpStr3, 320, 94, GFXFF);
if (liveData->params.socPerc > 0) {
float capacity = liveData->params.batteryTotalAvailableKWh * (liveData->params.socPerc / 100);
// calibration for Niro/Kona, real available capacity is ~66.5kWh, 0-10% ~6.2kWh, 90-100% ~7.2kWh
if (liveData->settings.carType == CAR_KIA_ENIRO_2020_64 || liveData->settings.carType == CAR_HYUNDAI_KONA_2020_64) {
capacity = (liveData->params.socPerc * 0.615) * (1 + (liveData->params.socPerc * 0.0008));
}
sprintf(tmpStr3, " %01.01f", capacity);
spr.drawString(tmpStr3, 320, 129, GFXFF);
spr.drawString("kWh", 320, 164, GFXFF);
}
}
/**
Battery cells (Screen 3)
*/
void Board320_240::drawSceneBatteryCells() {
int32_t posx, posy;
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batHeaterC));
drawSmallCell(0, 0, 1, 1, tmpStr1, "HEATER", TFT_TEMP, TFT_CYAN);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batInletC));
drawSmallCell(1, 0, 1, 1, tmpStr1, "BAT.INLET", TFT_TEMP, TFT_CYAN);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batModuleTempC[0]));
drawSmallCell(0, 1, 1, 1, tmpStr1, "MO1", TFT_TEMP, (liveData->params.batModuleTempC[0] >= 15) ? ((liveData->params.batModuleTempC[0] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batModuleTempC[1]));
drawSmallCell(1, 1, 1, 1, tmpStr1, "MO2", TFT_TEMP, (liveData->params.batModuleTempC[1] >= 15) ? ((liveData->params.batModuleTempC[1] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batModuleTempC[2]));
drawSmallCell(2, 1, 1, 1, tmpStr1, "MO3", TFT_TEMP, (liveData->params.batModuleTempC[2] >= 15) ? ((liveData->params.batModuleTempC[2] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batModuleTempC[3]));
drawSmallCell(3, 1, 1, 1, tmpStr1, "MO4", TFT_TEMP, (liveData->params.batModuleTempC[3] >= 15) ? ((liveData->params.batModuleTempC[3] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED);
// Ioniq (up to 12 cells)
for (uint16_t i = 4; i < liveData->params.batModuleTempCount; i++) {
if (liveData->params.batModuleTempC[i] == 0)
continue;
posx = (((i - 4) % 8) * 40);
posy = ((floor((i - 4) / 8)) * 13) + 64;
//spr.fillRect(x * 80, y * 32, ((w) * 80), ((h) * 32), bgColor);
spr.setTextSize(1); // Size for small 5x7 font
spr.setTextDatum(TL_DATUM);
spr.setTextColor(((liveData->params.batModuleTempC[i] >= 15) ? ((liveData->params.batModuleTempC[i] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED), TFT_BLACK);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00fC" : "%01.01fF"), liveData->celsius2temperature(liveData->params.batModuleTempC[i]));
spr.drawString(tmpStr1, posx + 4, posy, 2);
}
spr.setTextDatum(TL_DATUM); // Topleft
spr.setTextSize(1); // Size for small 5x7 font
// Find min and max val
float minVal = -1, maxVal = -1;
for (int i = 0; i < 98; i++) {
if ((liveData->params.cellVoltage[i] < minVal || minVal == -1) && liveData->params.cellVoltage[i] != -1)
minVal = liveData->params.cellVoltage[i];
if ((liveData->params.cellVoltage[i] > maxVal || maxVal == -1) && liveData->params.cellVoltage[i] != -1)
maxVal = liveData->params.cellVoltage[i];
if (liveData->params.cellVoltage[i] > 0 && i > liveData->params.cellCount + 1)
liveData->params.cellCount = i + 1;
}
// Draw cell matrix
for (int i = 0; i < 98; i++) {
if (liveData->params.cellVoltage[i] == -1)
continue;
posx = ((i % 8) * 40) + 4;
posy = ((floor(i / 8) + (liveData->params.cellCount > 96 ? 0 : 1)) * 13) + 68;
sprintf(tmpStr3, "%01.02f", liveData->params.cellVoltage[i]);
spr.setTextColor(TFT_NAVY, TFT_BLACK);
if (liveData->params.cellVoltage[i] == minVal && minVal != maxVal)
spr.setTextColor(TFT_RED, TFT_BLACK);
if (liveData->params.cellVoltage[i] == maxVal && minVal != maxVal)
spr.setTextColor(TFT_GREEN, TFT_BLACK);
spr.drawString(tmpStr3, posx, posy, 2);
}
}
/**
drawPreDrawnChargingGraphs
P = U.I
*/
void Board320_240::drawPreDrawnChargingGraphs(int zeroX, int zeroY, int mulX, int mulY) {
// Rapid gate
spr.drawLine(zeroX + (/* SOC FROM */ 1 * mulX), zeroY - (/*I*/ 180 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 57 * mulX), zeroY - (/*I*/ 180 * /*U SOC*/ (57 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_RAPIDGATE35);
// Coldgate <5C
spr.drawLine(zeroX + (/* SOC FROM */ 1 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 65 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (65 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE0_5);
// Coldgate 5-14C
spr.drawLine(zeroX + (/* SOC FROM */ 1 * mulX), zeroY - (/*I*/ 110 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 57 * mulX), zeroY - (/*I*/ 110 * /*U SOC*/ (57 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE5_14);
spr.drawLine(zeroX + (/* SOC FROM */ 57 * mulX), zeroY - (/*I*/ 110 * /*U SOC*/ (57 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 58 * mulX), zeroY - (/*I*/ 75 * /*U SOC*/ (58 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE5_14);
spr.drawLine(zeroX + (/* SOC FROM */ 58 * mulX), zeroY - (/*I*/ 75 * /*U SOC*/ (58 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 64 * mulX), zeroY - (/*I*/ 75 * /*U SOC*/ (64 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE5_14);
spr.drawLine(zeroX + (/* SOC FROM */ 64 * mulX), zeroY - (/*I*/ 75 * /*U SOC*/ (64 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 65 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (65 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE5_14);
spr.drawLine(zeroX + (/* SOC FROM */ 65 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (65 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 82 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (82 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE5_14);
spr.drawLine(zeroX + (/* SOC FROM */ 82 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (82 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 83 * mulX), zeroY - (/*I*/ 40 * /*U SOC*/ (83 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE5_14);
// Coldgate 15-24C
spr.drawLine(zeroX + (/* SOC FROM */ 1 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 57 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (57 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE15_24);
spr.drawLine(zeroX + (/* SOC FROM */ 57 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (57 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC FROM */ 58 * mulX), zeroY - (/*I*/ 110 * /*U SOC*/ (58 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE15_24);
spr.drawLine(zeroX + (/* SOC TO */ 58 * mulX), zeroY - (/*I*/ 110 * /*U SOC*/ (58 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 78 * mulX), zeroY - (/*I*/ 110 * /*U SOC*/ (78 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_COLDGATE15_24);
// Optimal
spr.drawLine(zeroX + (/* SOC FROM */ 1 * mulX), zeroY - (/*I*/ 200 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 57 * mulX), zeroY - (/*I*/ 200 * /*U SOC*/ (57 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 51 * mulX), zeroY - (/*I*/ 200 * /*U SOC*/ (51 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 51 * mulX), zeroY - (/*I*/ 195 * /*U SOC*/ (51 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 53 * mulX), zeroY - (/*I*/ 200 * /*U SOC*/ (53 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 53 * mulX), zeroY - (/*I*/ 195 * /*U SOC*/ (53 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 55 * mulX), zeroY - (/*I*/ 200 * /*U SOC*/ (55 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 55 * mulX), zeroY - (/*I*/ 195 * /*U SOC*/ (55 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 57 * mulX), zeroY - (/*I*/ 200 * /*U SOC*/ (57 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 58 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (58 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 58 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (58 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 77 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (77 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 71 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (71 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 71 * mulX), zeroY - (/*I*/ 145 * /*U SOC*/ (71 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 73 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (73 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 73 * mulX), zeroY - (/*I*/ 145 * /*U SOC*/ (73 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 75 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (75 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 75 * mulX), zeroY - (/*I*/ 145 * /*U SOC*/ (75 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 77 * mulX), zeroY - (/*I*/ 150 * /*U SOC*/ (77 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 78 * mulX), zeroY - (/*I*/ 90 * /*U SOC*/ (78 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 78 * mulX), zeroY - (/*I*/ 90 * /*U SOC*/ (78 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 82 * mulX), zeroY - (/*I*/ 90 * /*U SOC*/ (82 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 82 * mulX), zeroY - (/*I*/ 90 * /*U SOC*/ (82 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 83 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (83 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 83 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (83 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 92 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (92 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 92 * mulX), zeroY - (/*I*/ 60 * /*U SOC*/ (92 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 95 * mulX), zeroY - (/*I*/ 35 * /*U SOC*/ (95 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 95 * mulX), zeroY - (/*I*/ 35 * /*U SOC*/ (95 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 98 * mulX), zeroY - (/*I*/ 35 * /*U SOC*/ (98 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
spr.drawLine(zeroX + (/* SOC FROM */ 98 * mulX), zeroY - (/*I*/ 35 * /*U SOC*/ (98 * 55 / 100 + 352) /**/ / 1000 * mulY),
zeroX + (/* SOC TO */ 100 * mulX), zeroY - (/*I*/ 15 * /*U SOC*/ (100 * 55 / 100 + 352) /**/ / 1000 * mulY), TFT_GRAPH_OPTIMAL25);
// Triangles
int x = zeroX;
int y;
if (liveData->params.batMaxC >= 35) {
y = zeroY - (/*I*/ 180 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY);
} else if (liveData->params.batMinC >= 25) {
y = zeroY - (/*I*/ 200 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY);
} else if (liveData->params.batMinC >= 15) {
y = zeroY - (/*I*/ 150 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY);
} else if (liveData->params.batMinC >= 5) {
y = zeroY - (/*I*/ 110 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY);
} else {
y = zeroY - (/*I*/ 60 * /*U SOC*/ (1 * 55 / 100 + 352) /**/ / 1000 * mulY);
}
spr.fillTriangle(x + 5, y, x , y - 5, x, y + 5, TFT_ORANGE);
}
/**
Charging graph (Screen 4)
*/
void Board320_240::drawSceneChargingGraph() {
int zeroX = 0;
int zeroY = 238;
int mulX = 3; // 100% = 300px;
int mulY = 2; // 100kW = 200px
int maxKw = 80;
int posy = 0;
uint16_t color;
spr.fillSprite(TFT_BLACK);
sprintf(tmpStr1, "%01.00f", liveData->params.socPerc);
drawSmallCell(0, 0, 1, 1, tmpStr1, "SOC", TFT_TEMP, TFT_CYAN);
sprintf(tmpStr1, "%01.01f", liveData->params.batPowerKw);
drawSmallCell(1, 0, 1, 1, tmpStr1, "POWER kW", TFT_TEMP, TFT_CYAN);
sprintf(tmpStr1, "%01.01f", liveData->params.batPowerAmp);
drawSmallCell(2, 0, 1, 1, tmpStr1, "CURRENT A", TFT_TEMP, TFT_CYAN);
sprintf(tmpStr1, "%03.00f", liveData->params.batVoltage);
drawSmallCell(3, 0, 1, 1, tmpStr1, "VOLTAGE", TFT_TEMP, TFT_CYAN);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batHeaterC));
drawSmallCell(0, 1, 1, 1, tmpStr1, "HEATER", TFT_TEMP, TFT_RED);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batInletC));
drawSmallCell(1, 1, 1, 1, tmpStr1, "BAT.INLET", TFT_TEMP, TFT_CYAN);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.batMinC));
drawSmallCell(2, 1, 1, 1, tmpStr1, "BAT.MIN", (liveData->params.batMinC >= 15) ? ((liveData->params.batMinC >= 25) ? TFT_DARKGREEN2 : TFT_BLUE) : TFT_RED, TFT_CYAN);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "%01.00f C" : "%01.01f F"), liveData->celsius2temperature(liveData->params.outdoorTemperature));
drawSmallCell(3, 1, 1, 1, tmpStr1, "OUT.TEMP.", TFT_TEMP, TFT_CYAN);
spr.setTextColor(TFT_SILVER, TFT_TEMP);
for (int i = 0; i <= 10; i++) {
color = TFT_DARKRED2;
if (i == 0 || i == 5 || i == 10)
color = TFT_DARKRED;
spr.drawFastVLine(zeroX + (i * 10 * mulX), zeroY - (maxKw * mulY), maxKw * mulY, color);
/*if (i != 0 && i != 10) {
sprintf(tmpStr1, "%d%%", i * 10);
spr.setTextDatum(BC_DATUM);
spr.drawString(tmpStr1, zeroX + (i * 10 * mulX), zeroY - (maxKw * mulY), 2);
}*/
if (i <= (maxKw / 10)) {
spr.drawFastHLine(zeroX, zeroY - (i * 10 * mulY), 100 * mulX, color);
if (i > 0) {
sprintf(tmpStr1, "%d", i * 10);
spr.setTextDatum(ML_DATUM);
spr.drawString(tmpStr1, zeroX + (100 * mulX) + 3, zeroY - (i * 10 * mulY), 2);
}
}
}
// Draw suggested curves
if (liveData->settings.predrawnChargingGraphs == 1) {
drawPreDrawnChargingGraphs(zeroX, zeroY, mulX, mulY);
}
// Draw realtime values
for (int i = 0; i <= 100; i++) {
if (liveData->params.chargingGraphBatMinTempC[i] > -10)
spr.drawFastHLine(zeroX + (i * mulX) - (mulX / 2), zeroY - (liveData->params.chargingGraphBatMinTempC[i]*mulY), mulX, TFT_BLUE);
if (liveData->params.chargingGraphBatMaxTempC[i] > -10)
spr.drawFastHLine(zeroX + (i * mulX) - (mulX / 2), zeroY - (liveData->params.chargingGraphBatMaxTempC[i]*mulY), mulX, TFT_BLUE);
if (liveData->params.chargingGraphWaterCoolantTempC[i] > -10)
spr.drawFastHLine(zeroX + (i * mulX) - (mulX / 2), zeroY - (liveData->params.chargingGraphWaterCoolantTempC[i]*mulY), mulX, TFT_PURPLE);
if (liveData->params.chargingGraphHeaterTempC[i] > -10)
spr.drawFastHLine(zeroX + (i * mulX) - (mulX / 2), zeroY - (liveData->params.chargingGraphHeaterTempC[i]*mulY), mulX, TFT_RED);
if (liveData->params.chargingGraphMinKw[i] > 0)
spr.drawFastHLine(zeroX + (i * mulX) - (mulX / 2), zeroY - (liveData->params.chargingGraphMinKw[i]*mulY), mulX, TFT_GREENYELLOW);
if (liveData->params.chargingGraphMaxKw[i] > 0)
spr.drawFastHLine(zeroX + (i * mulX) - (mulX / 2), zeroY - (liveData->params.chargingGraphMaxKw[i]*mulY), mulX, TFT_YELLOW);
}
// Bat.module temperatures
spr.setTextSize(1); // Size for small 5x7 font
spr.setTextDatum(BL_DATUM);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "1=%01.00fC " : "1=%01.00fF "), liveData->celsius2temperature(liveData->params.batModuleTempC[0]));
spr.setTextColor((liveData->params.batModuleTempC[0] >= 15) ? ((liveData->params.batModuleTempC[0] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP);
spr.drawString(tmpStr1, 0, zeroY - (maxKw * mulY), 2);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "2=%01.00fC " : "2=%01.00fF "), liveData->celsius2temperature(liveData->params.batModuleTempC[1]));
spr.setTextColor((liveData->params.batModuleTempC[1] >= 15) ? ((liveData->params.batModuleTempC[1] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP);
spr.drawString(tmpStr1, 48, zeroY - (maxKw * mulY), 2);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "3=%01.00fC " : "3=%01.00fF "), liveData->celsius2temperature(liveData->params.batModuleTempC[2]));
spr.setTextColor((liveData->params.batModuleTempC[2] >= 15) ? ((liveData->params.batModuleTempC[2] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP);
spr.drawString(tmpStr1, 96, zeroY - (maxKw * mulY), 2);
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "4=%01.00fC " : "4=%01.00fF "), liveData->celsius2temperature(liveData->params.batModuleTempC[3]));
spr.setTextColor((liveData->params.batModuleTempC[3] >= 15) ? ((liveData->params.batModuleTempC[3] >= 25) ? TFT_GREEN : TFT_BLUE) : TFT_RED, TFT_TEMP);
spr.drawString(tmpStr1, 144, zeroY - (maxKw * mulY), 2);
sprintf(tmpStr1, "ir %01.00fkOhm", liveData->params.isolationResistanceKOhm );
// Bms max.regen/power available
spr.setTextColor(TFT_WHITE, TFT_BLACK);
sprintf(tmpStr1, "xC=%01.00fkW ", liveData->params.availableChargePower);
spr.drawString(tmpStr1, 192, zeroY - (maxKw * mulY), 2);
spr.setTextColor(TFT_WHITE, TFT_BLACK);
sprintf(tmpStr1, "xD=%01.00fkW", liveData->params.availableDischargePower);
spr.drawString(tmpStr1, 256, zeroY - (maxKw * mulY), 2);
//
spr.setTextDatum(TR_DATUM);
if (liveData->params.coolingWaterTempC != -1) {
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "W=%01.00fC" : "W=%01.00fF"), liveData->celsius2temperature(liveData->params.coolingWaterTempC));
spr.setTextColor(TFT_PURPLE, TFT_TEMP);
spr.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (posy * 15), 2);
posy++;
}
spr.setTextColor(TFT_WHITE, TFT_TEMP);
if (liveData->params.batFanFeedbackHz > 0) {
sprintf(tmpStr1, "FF=%03.00fHz", liveData->params.batFanFeedbackHz);
spr.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (posy * 15), 2);
posy++;
}
if (liveData->params.batFanStatus > 0) {
sprintf(tmpStr1, "FS=%03.00f", liveData->params.batFanStatus);
spr.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (posy * 15), 2);
posy++;
}
if (liveData->params.coolantTemp1C != -1 && liveData->params.coolantTemp2C != -1) {
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "C1/2:%01.00f/%01.00fC" : "C1/2:%01.00f/%01.00fF"), liveData->celsius2temperature(liveData->params.coolantTemp1C), liveData->celsius2temperature(liveData->params.coolantTemp2C));
spr.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (posy * 15), 2);
posy++;
}
if (liveData->params.bmsUnknownTempA != -1) {
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "A=%01.00fC" : "W=%01.00fF"), liveData->celsius2temperature(liveData->params.bmsUnknownTempA));
spr.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (posy * 15), 2);
posy++;
}
if (liveData->params.bmsUnknownTempB != -1) {
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "B=%01.00fC" : "W=%01.00fF"), liveData->celsius2temperature(liveData->params.bmsUnknownTempB));
spr.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (posy * 15), 2);
posy++;
}
if (liveData->params.bmsUnknownTempC != -1) {
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "C=%01.00fC" : "W=%01.00fF"), liveData->celsius2temperature(liveData->params.bmsUnknownTempC));
spr.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (posy * 15), 2);
posy++;
}
if (liveData->params.bmsUnknownTempD != -1) {
sprintf(tmpStr1, ((liveData->settings.temperatureUnit == 'c') ? "D=%01.00fC" : "W=%01.00fF"), liveData->celsius2temperature(liveData->params.bmsUnknownTempD));
spr.drawString(tmpStr1, zeroX + (10 * 10 * mulX), zeroY - (maxKw * mulY) + (posy * 15), 2);
posy++;
}
// Print charging time
time_t diffTime = liveData->params.currentTime - liveData->params.chargingStartTime;
if ((diffTime / 60) > 99)
sprintf(tmpStr1, "%02d:%02d:%02d", (diffTime / 3600) % 24, (diffTime / 60) % 60, diffTime % 60);
else
sprintf(tmpStr1, "%02d:%02d", (diffTime / 60), diffTime % 60);
spr.setTextDatum(TL_DATUM);
spr.setTextColor(TFT_SILVER, TFT_BLACK);
spr.drawString(tmpStr1, 0, zeroY - (maxKw * mulY), 2);
}
/**
SOC 10% table (screen 5)
*/
void Board320_240::drawSceneSoc10Table() {
int zeroY = 2;
float diffCec, diffCed, diffOdo = -1;
float firstCed = -1, lastCed = -1, diffCed0to5 = 0;
float firstCec = -1, lastCec = -1, diffCec0to5 = 0;
float firstOdo = -1, lastOdo = -1, diffOdo0to5 = 0;
float diffTime;
spr.setTextSize(1); // Size for small 5x7 font
spr.setTextColor(TFT_SILVER, TFT_TEMP);
spr.setTextDatum(TL_DATUM);
spr.drawString("CONSUMPTION | DISCH.100%->4% SOC", 2, zeroY, 2);
spr.setTextDatum(TR_DATUM);
spr.drawString("dis./char.kWh", 128, zeroY + (1 * 15), 2);
spr.drawString(((liveData->settings.distanceUnit == 'k') ? "km" : "mi"), 160, zeroY + (1 * 15), 2);
spr.drawString("kWh100", 224, zeroY + (1 * 15), 2);
spr.drawString("avg.speed", 310, zeroY + (1 * 15), 2);
for (int i = 0; i <= 10; i++) {
sprintf(tmpStr1, "%d%%", (i == 0) ? 5 : i * 10);
spr.drawString(tmpStr1, 32, zeroY + ((12 - i) * 15), 2);
firstCed = (liveData->params.soc10ced[i] != -1) ? liveData->params.soc10ced[i] : firstCed;
lastCed = (lastCed == -1 && liveData->params.soc10ced[i] != -1) ? liveData->params.soc10ced[i] : lastCed;
firstCec = (liveData->params.soc10cec[i] != -1) ? liveData->params.soc10cec[i] : firstCec;
lastCec = (lastCec == -1 && liveData->params.soc10cec[i] != -1) ? liveData->params.soc10cec[i] : lastCec;
firstOdo = (liveData->params.soc10odo[i] != -1) ? liveData->params.soc10odo[i] : firstOdo;
lastOdo = (lastOdo == -1 && liveData->params.soc10odo[i] != -1) ? liveData->params.soc10odo[i] : lastOdo;
if (i != 10) {
diffCec = (liveData->params.soc10cec[i + 1] != -1 && liveData->params.soc10cec[i] != -1) ? (liveData->params.soc10cec[i] - liveData->params.soc10cec[i + 1]) : 0;
diffCed = (liveData->params.soc10ced[i + 1] != -1 && liveData->params.soc10ced[i] != -1) ? (liveData->params.soc10ced[i + 1] - liveData->params.soc10ced[i]) : 0;
diffOdo = (liveData->params.soc10odo[i + 1] != -1 && liveData->params.soc10odo[i] != -1) ? (liveData->params.soc10odo[i] - liveData->params.soc10odo[i + 1]) : -1;
diffTime = (liveData->params.soc10time[i + 1] != -1 && liveData->params.soc10time[i] != -1) ? (liveData->params.soc10time[i] - liveData->params.soc10time[i + 1]) : -1;
if (diffCec != 0) {
sprintf(tmpStr1, "+%01.01f", diffCec);
spr.drawString(tmpStr1, 128, zeroY + ((12 - i) * 15), 2);
diffCec0to5 = (i == 0) ? diffCec : diffCec0to5;
}
if (diffCed != 0) {
sprintf(tmpStr1, "%01.01f", diffCed);
spr.drawString(tmpStr1, 80, zeroY + ((12 - i) * 15), 2);
diffCed0to5 = (i == 0) ? diffCed : diffCed0to5;
}
if (diffOdo != -1) {
sprintf(tmpStr1, "%01.00f", liveData->km2distance(diffOdo));
spr.drawString(tmpStr1, 160, zeroY + ((12 - i) * 15), 2);
diffOdo0to5 = (i == 0) ? diffOdo : diffOdo0to5;
if (diffTime > 0) {
sprintf(tmpStr1, "%01.01f", liveData->km2distance(diffOdo) / (diffTime / 3600));
spr.drawString(tmpStr1, 310, zeroY + ((12 - i) * 15), 2);
}
}
if (diffOdo > 0 && diffCed != 0) {
sprintf(tmpStr1, "%01.1f", (-diffCed * 100.0 / liveData->km2distance(diffOdo)));
spr.drawString(tmpStr1, 224, zeroY + ((12 - i) * 15), 2);
}
}
if (diffOdo == -1 && liveData->params.soc10odo[i] != -1) {
sprintf(tmpStr1, "%01.00f", liveData->km2distance(liveData->params.soc10odo[i]));
spr.drawString(tmpStr1, 160, zeroY + ((12 - i) * 15), 2);
}
}
spr.drawString("0%", 32, zeroY + (13 * 15), 2);
spr.drawString("0-5% is calculated (same) as 5-10%", 310, zeroY + (13 * 15), 2);
spr.drawString("TOT.", 32, zeroY + (14 * 15), 2);
diffCed = (lastCed != -1 && firstCed != -1) ? firstCed - lastCed + diffCed0to5 : 0;
sprintf(tmpStr1, "%01.01f", diffCed);
spr.drawString(tmpStr1, 80, zeroY + (14 * 15), 2);
diffCec = (lastCec != -1 && firstCec != -1) ? lastCec - firstCec + diffCec0to5 : 0;
sprintf(tmpStr1, "+%01.01f", diffCec);
spr.drawString(tmpStr1, 128, zeroY + (14 * 15), 2);
diffOdo = (lastOdo != -1 && firstOdo != -1) ? lastOdo - firstOdo + diffOdo0to5 : 0;
sprintf(tmpStr1, "%01.00f", liveData->km2distance(diffOdo));
spr.drawString(tmpStr1, 160, zeroY + (14 * 15), 2);
sprintf(tmpStr1, "AVAIL.CAP: %01.01f kWh", -diffCed - diffCec);
spr.drawString(tmpStr1, 310, zeroY + (14 * 15), 2);
}
/**
Modify caption
*/
String Board320_240::menuItemCaption(int16_t menuItemId, String title) {
String prefix = "", suffix = "";
switch (menuItemId) {
// Set vehicle type
case MENU_VEHICLE_TYPE: sprintf(tmpStr1, "[%d]", liveData->settings.carType); suffix = tmpStr1; break;
case MENU_SAVE_SETTINGS: sprintf(tmpStr1, "[v%d]", liveData->settings.settingsVersion); suffix = tmpStr1; break;
case MENU_APP_VERSION: sprintf(tmpStr1, "[%s]", APP_VERSION); suffix = tmpStr1; break;
//
case 101: prefix = (liveData->settings.carType == CAR_KIA_ENIRO_2020_64) ? ">" : ""; break;
case 102: prefix = (liveData->settings.carType == CAR_HYUNDAI_KONA_2020_64) ? ">" : ""; break;
case 103: prefix = (liveData->settings.carType == CAR_HYUNDAI_IONIQ_2018) ? ">" : ""; break;
case 104: prefix = (liveData->settings.carType == CAR_KIA_ENIRO_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 107: prefix = (liveData->settings.carType == CAR_KIA_NIRO_PHEV) ? ">" : ""; 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:
suffix = "n/a";
switch (WiFi.status()) {
WL_CONNECTED: suffix = "CONNECTED"; break;
WL_NO_SHIELD: suffix = "NO_SHIELD"; break;
WL_IDLE_STATUS: suffix = "IDLE_STATUS"; break;
WL_SCAN_COMPLETED: suffix = "SCAN_COMPLETED"; break;
WL_CONNECT_FAILED: suffix = "CONNECT_FAILED"; break;
WL_CONNECTION_LOST: suffix = "CONNECTION_LOST"; break;
WL_DISCONNECTED: suffix = "DISCONNECTED"; break;
}
break;*/
case MENU_GPRS: sprintf(tmpStr1, "[%s] %s", (liveData->settings.gprsEnabled == 1) ? "on" : "off", liveData->settings.gprsApn); suffix = tmpStr1; break;
case MENU_SDCARD: sprintf(tmpStr1, "[%d] %lluMB", SD.cardType(), SD.cardSize() / (1024 * 1024)); suffix = tmpStr1; 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_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_HEADLIGHTS_REMINDER: suffix = (liveData->settings.headlightsReminder == 1) ? "[on]" : "[off]"; break;
case MENU_DEBUG_SCREEN: suffix = (liveData->settings.debugScreen == 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_SDCARD_ENABLED: sprintf(tmpStr1, "[%s]", (liveData->settings.sdcardEnabled == 1) ? "on" : "off"); suffix = tmpStr1; break;
case MENU_SDCARD_AUTOSTARTLOG: sprintf(tmpStr1, "[%s]", (liveData->settings.sdcardEnabled == 0) ? "n/a" : (liveData->settings.sdcardAutstartLog == 1) ? "on" : "off"); suffix = tmpStr1; break;
case MENU_SDCARD_MOUNT_STATUS: sprintf(tmpStr1, "[%s]", (liveData->settings.sdcardEnabled == 0) ? "n/a" :
(strlen(liveData->params.sdcardFilename) != 0) ? liveData->params.sdcardFilename :
(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_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_PASSWORD: sprintf(tmpStr1, "%s", liveData->settings.wifiPassword); suffix = tmpStr1; break;
//
case MENU_DISTANCE_UNIT: suffix = (liveData->settings.distanceUnit == 'k') ? "[km]" : "[mi]"; break;
case MENU_TEMPERATURE_UNIT: suffix = (liveData->settings.temperatureUnit == 'c') ? "[C]" : "[F]"; break;
case MENU_PRESSURE_UNIT: suffix = (liveData->settings.pressureUnit == 'b') ? "[bar]" : "[psi]"; break;
}
title = ((prefix == "") ? "" : prefix + " ") + title + ((suffix == "") ? "" : " " + suffix);
return title;
}
/**
Display menu
*/
void Board320_240::showMenu() {
uint16_t posY = 0, tmpCurrMenuItem = 0;
liveData->menuVisible = true;
spr.fillSprite(TFT_BLACK);
spr.setTextDatum(TL_DATUM);
spr.setFreeFont(&Roboto_Thin_24);
// Page scroll
uint8_t visibleCount = (int)(tft.height() / spr.fontHeight());
if (liveData->menuItemSelected >= liveData->menuItemOffset + visibleCount)
liveData->menuItemOffset = liveData->menuItemSelected - visibleCount + 1;
if (liveData->menuItemSelected < liveData->menuItemOffset)
liveData->menuItemOffset = liveData->menuItemSelected;
// Print items
for (uint16_t i = 0; i < liveData->menuItemsCount; ++i) {
if (liveData->menuCurrent == liveData->menuItems[i].parentId) {
if (tmpCurrMenuItem >= liveData->menuItemOffset) {
bool isMenuItemSelected = liveData->menuItemSelected == tmpCurrMenuItem;
spr.fillRect(0, posY, 320, spr.fontHeight() + 2, isMenuItemSelected ? TFT_DARKGREEN2 : TFT_BLACK);
spr.setTextColor(isMenuItemSelected ? TFT_WHITE : TFT_WHITE, isMenuItemSelected ? TFT_DARKGREEN2 : TFT_BLACK);
spr.drawString(menuItemCaption(liveData->menuItems[i].id, liveData->menuItems[i].title), 0, posY + 2, GFXFF);
posY += spr.fontHeight();
}
tmpCurrMenuItem++;
}
}
spr.pushSprite(0, 0);
}
/**
Hide menu
*/
void Board320_240::hideMenu() {
liveData->menuVisible = false;
liveData->menuCurrent = 0;
liveData->menuItemSelected = 0;
redrawScreen();
}
/**
Move in menu with left/right button
*/
void Board320_240::menuMove(bool forward) {
uint16_t tmpCount = 0;
for (uint16_t i = 0; i < liveData->menuItemsCount; ++i) {
if (liveData->menuCurrent == liveData->menuItems[i].parentId && strlen(liveData->menuItems[i].title) != 0)
tmpCount++;
}
if (forward) {
liveData->menuItemSelected = (liveData->menuItemSelected >= tmpCount - 1 ) ? 0 : liveData->menuItemSelected + 1;
} else {
liveData->menuItemSelected = (liveData->menuItemSelected <= 0) ? tmpCount - 1 : liveData->menuItemSelected - 1;
}
showMenu();
}
/**
Enter menu item
*/
void Board320_240::menuItemClick() {
// Locate menu item for meta data
MENU_ITEM* tmpMenuItem;
uint16_t tmpCurrMenuItem = 0;
int16_t parentMenu = -1;
uint16_t i;
for (i = 0; i < liveData->menuItemsCount; ++i) {
if (liveData->menuCurrent == liveData->menuItems[i].parentId) {
if (parentMenu == -1)
parentMenu = liveData->menuItems[i].targetParentId;
if (liveData->menuItemSelected == tmpCurrMenuItem) {
tmpMenuItem = &liveData->menuItems[i];
break;
}
tmpCurrMenuItem++;
}
}
// Exit menu, parent level menu, open item
bool showParentMenu = false;
if (liveData->menuItemSelected > 0) {
Serial.println(tmpMenuItem->id);
// Device list
if (tmpMenuItem->id > 10000 && tmpMenuItem->id < 10100) {
strlcpy((char*)liveData->settings.obdMacAddress, (char*)tmpMenuItem->obdMacAddress, 20);
Serial.print("Selected adapter MAC address ");
Serial.println(liveData->settings.obdMacAddress);
saveSettings();
ESP.restart();
}
// Other menus
switch (tmpMenuItem->id) {
// Set vehicle type
case 101: liveData->settings.carType = CAR_KIA_ENIRO_2020_64; showMenu(); return; break;
case 102: liveData->settings.carType = CAR_HYUNDAI_KONA_2020_64; showMenu(); return; break;
case 103: liveData->settings.carType = CAR_HYUNDAI_IONIQ_2018; showMenu(); return; break;
case 104: liveData->settings.carType = CAR_KIA_ENIRO_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 107: liveData->settings.carType = CAR_KIA_NIRO_PHEV; 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
case MENU_SCREEN_ROTATION: liveData->settings.displayRotation = (liveData->settings.displayRotation == 1) ? 3 : 1; tft.setRotation(liveData->settings.displayRotation); showMenu(); return; break;
// Default screen
case 3061: liveData->settings.defaultScreen = 1; showParentMenu = true; break;
case 3062: liveData->settings.defaultScreen = 2; showParentMenu = true; break;
case 3063: liveData->settings.defaultScreen = 3; showParentMenu = true; break;
case 3064: liveData->settings.defaultScreen = 4; showParentMenu = true; break;
case 3065: liveData->settings.defaultScreen = 5; showParentMenu = true; break;
// Debug screen off/on
case MENU_DEBUG_SCREEN: liveData->settings.debugScreen = (liveData->settings.debugScreen == 1) ? 0 : 1; showMenu(); return; break;
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;
// Pre-drawn charg.graphs off/on
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_GPS: liveData->settings.gpsHwSerialPort = (liveData->settings.gpsHwSerialPort == 2) ? 255 : liveData->settings.gpsHwSerialPort + 1; showMenu(); return; break;
// Wifi menu
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_PASSWORD: return; break;
// Sdcard
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_MOUNT_STATUS: sdcardMount(); break;
case MENU_SDCARD_REC: sdcardToggleRecording(); showMenu(); return; break;
// Distance
case 4011: liveData->settings.distanceUnit = 'k'; showParentMenu = true; break;
case 4012: liveData->settings.distanceUnit = 'm'; showParentMenu = true; break;
// Temperature
case 4021: liveData->settings.temperatureUnit = 'c'; showParentMenu = true; break;
case 4022: liveData->settings.temperatureUnit = 'f'; showParentMenu = true; break;
// Pressure
case 4031: liveData->settings.pressureUnit = 'b'; showParentMenu = true; break;
case 4032: liveData->settings.pressureUnit = 'p'; showParentMenu = true; break;
// Pair ble device
case 2: scanDevices = true; /*startBleScan(); */return;
// Reset settings
case 8: resetSettings(); hideMenu(); return;
// Save settings
case 9: saveSettings(); break;
// Version
case 10: hideMenu(); return;
// Shutdown
case 11: shutdownDevice(); return;
default:
// Submenu
liveData->menuCurrent = tmpMenuItem->id;
liveData->menuItemSelected = 0;
showMenu();
return;
}
}
// Exit menu
if (liveData->menuItemSelected == 0 || (showParentMenu && parentMenu != -1)) {
if (tmpMenuItem->parentId == 0 && tmpMenuItem->id == 0) {
liveData->menuVisible = false;
redrawScreen();
} else {
// Parent menu
liveData->menuItemSelected = 0;
for (i = 0; i < liveData->menuItemsCount; ++i) {
if (parentMenu == liveData->menuItems[i].parentId) {
if (liveData->menuItems[i].id == liveData->menuCurrent)
break;
liveData->menuItemSelected++;
}
}
liveData->menuCurrent = parentMenu;
Serial.println(liveData->menuCurrent);
showMenu();
}
return;
}
// Close menu
hideMenu();
}
/**
Redraw screen
*/
void Board320_240::redrawScreen() {
if (liveData->menuVisible) {
return;
}
// Lights not enabled
if (!testDataMode && liveData->settings.headlightsReminder == 1 && liveData->params.forwardDriveMode && !liveData->params.headLights && !liveData->params.dayLights) {
spr.fillSprite(TFT_RED);
spr.setFreeFont(&Orbitron_Light_32);
spr.setTextColor(TFT_WHITE, TFT_RED);
spr.setTextDatum(MC_DATUM);
spr.drawString("! LIGHTS OFF !", 160, 120, GFXFF);
spr.pushSprite(0, 0);
return;
}
spr.fillSprite(TFT_BLACK);
// 1. Auto mode = >5kpm Screen 3 - speed, other wise basic Screen2 - Main screen, if charging then Screen 5 Graph
if (displayScreen == SCREEN_AUTO) {
if (liveData->params.speedKmh > 5) {
if (displayScreenAutoMode != SCREEN_SPEED) {
displayScreenAutoMode = SCREEN_SPEED;
}
drawSceneSpeed();
} else if (liveData->params.batPowerKw > 1) {
if (displayScreenAutoMode != SCREEN_CHARGING) {
displayScreenAutoMode = SCREEN_CHARGING;
}
drawSceneChargingGraph();
} else {
if (displayScreenAutoMode != SCREEN_DASH) {
displayScreenAutoMode = SCREEN_DASH;
}
drawSceneMain();
}
} else {
displayScreenAutoMode = SCREEN_DASH;
}
// 2. Main screen
if (displayScreen == SCREEN_DASH) {
drawSceneMain();
}
// 3. Big speed + kwh/100km
if (displayScreen == SCREEN_SPEED) {
drawSceneSpeed();
}
// 4. Battery cells
if (displayScreen == SCREEN_CELLS) {
drawSceneBatteryCells();
}
// 5. Charging graph
if (displayScreen == SCREEN_CHARGING) {
drawSceneChargingGraph();
}
// 6. SOC10% table (CEC-CED)
if (displayScreen == SCREEN_SOC10) {
drawSceneSoc10Table();
}
if (!displayScreenSpeedHud) {
// SDCARD recording
/*liveData->params.sdcardRecording*/
if (liveData->settings.sdcardEnabled == 1) {
spr.fillCircle((displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED) ? 160 : 310, 10, 4, TFT_BLACK);
spr.fillCircle((displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED) ? 160 : 310, 10, 3,
(liveData->params.sdcardInit == 1) ?
(liveData->params.sdcardRecording) ?
(strlen(liveData->params.sdcardFilename) != 0) ?
TFT_GREEN /* assigned filename (opsec from bms or gsm/gps timestamp */ :
TFT_BLUE /* recording started but waiting for data */ :
TFT_ORANGE /* sdcard init ready but recording not started*/ :
TFT_YELLOW /* failed to initialize sdcard */
);
}
if (gpsHwUart != NULL && (displayScreen == SCREEN_SPEED || displayScreenAutoMode == SCREEN_SPEED)) {
spr.drawCircle(180, 10, 5, (gps.location.isValid()) ? TFT_GREEN : TFT_RED);
spr.setTextSize(1);
spr.setTextColor((gps.location.isValid()) ? TFT_GREEN : TFT_WHITE, TFT_BLACK);
spr.setTextDatum(TL_DATUM);
sprintf(tmpStr1, "%d", liveData->params.gpsSat);
spr.drawString(tmpStr1, 194, 2, 2);
}
// BLE not connected
if (!liveData->bleConnected && liveData->bleConnect) {
// Print message
spr.setTextSize(1);
spr.setTextColor(TFT_WHITE, TFT_BLACK);
spr.setTextDatum(TL_DATUM);
spr.drawString("BLE4 OBDII not connected...", 0, 180, 2);
spr.drawString("Press middle button to menu.", 0, 200, 2);
spr.drawString(APP_VERSION, 0, 220, 2);
}
spr.pushSprite(0, 0);
}
}
/**
Parse test data
*/
void Board320_240::loadTestData() {
testDataMode = true; // skip lights off message
carInterface->loadTestData();
redrawScreen();
}
/**
Board looop
*/
void Board320_240::mainLoop() {
///////////////////////////////////////////////////////////////////////
// Handle buttons
// MIDDLE - menu select
if (digitalRead(pinButtonMiddle) == HIGH) {
btnMiddlePressed = false;
} else {
if (!btnMiddlePressed) {
btnMiddlePressed = true;
tft.setRotation(liveData->settings.displayRotation);
if (liveData->menuVisible) {
menuItemClick();
} else {
showMenu();
}
}
}
// LEFT - screen rotate, menu
if (digitalRead((liveData->settings.displayRotation == 1) ? pinButtonRight : pinButtonLeft) == HIGH) {
btnLeftPressed = false;
} else {
if (!btnLeftPressed) {
btnLeftPressed = true;
tft.setRotation(liveData->settings.displayRotation);
// Menu handling
if (liveData->menuVisible) {
menuMove(false);
} else {
displayScreen++;
if (displayScreen > displayScreenCount - (liveData->settings.debugScreen == 0) ? 1 : 0)
displayScreen = 0; // rotate screens
// Turn off display on screen 0
setBrightness((displayScreen == SCREEN_BLANK) ? 0 : (liveData->settings.lcdBrightness == 0) ? 100 : liveData->settings.lcdBrightness);
redrawScreen();
}
}
}
// RIGHT - menu, debug screen rotation
if (digitalRead((liveData->settings.displayRotation == 1) ? pinButtonLeft : pinButtonRight) == HIGH) {
btnRightPressed = false;
} else {
if (!btnRightPressed) {
btnRightPressed = true;
tft.setRotation(liveData->settings.displayRotation);
// Menu handling
if (liveData->menuVisible) {
menuMove(true);
} else {
// doAction
if (displayScreen == SCREEN_SPEED) {
displayScreenSpeedHud = !displayScreenSpeedHud;
redrawScreen();
}
}
}
}
// Both left&right button (hide menu)
if (digitalRead(pinButtonLeft) == LOW && digitalRead(pinButtonRight) == LOW) {
hideMenu();
}
// GPS process
if (gpsHwUart != NULL) {
unsigned long start = millis();
do {
while (gpsHwUart->available())
gps.encode(gpsHwUart->read());
} while (millis() - start < 20);
//
syncGPS();
}
// currentTime
struct tm now;
getLocalTime(&now, 0);
liveData->params.currentTime = mktime(&now);
// SD card recording
if (liveData->params.sdcardInit && liveData->params.sdcardRecording && liveData->params.sdcardCanNotify &&
(liveData->params.odoKm != -1 && liveData->params.socPerc != -1)) {
//Serial.println(&now, "%y%m%d%H%M");
// create filename
if (liveData->params.operationTimeSec > 0 && strlen(liveData->params.sdcardFilename) == 0) {
sprintf(liveData->params.sdcardFilename, "/%llu.json", uint64_t(liveData->params.operationTimeSec / 60));
Serial.print("Log filename by opTimeSec: ");
Serial.println(liveData->params.sdcardFilename);
}
if (liveData->params.currTimeSyncWithGps && strlen(liveData->params.sdcardFilename) < 15) {
strftime(liveData->params.sdcardFilename, sizeof(liveData->params.sdcardFilename), "/%y%m%d%H%M.json", &now);
Serial.print("Log filename by GPS: ");
Serial.println(liveData->params.sdcardFilename);
}
// append buffer, clear buffer & notify state
if (strlen(liveData->params.sdcardFilename) != 0) {
liveData->params.sdcardCanNotify = false;
File file = SD.open(liveData->params.sdcardFilename, FILE_APPEND);
if (!file) {
Serial.println("Failed to open file for appending");
File file = SD.open(liveData->params.sdcardFilename, FILE_WRITE);
}
if (!file) {
Serial.println("Failed to create file");
}
if (file) {
Serial.println("Save buffer to SD card");
serializeParamsToJson(file);
file.print(",\n");
file.close();
}
}
}
// Shutdown when car is off
if (liveData->params.automaticShutdownTimer != 0 && liveData->params.currentTime - liveData->params.automaticShutdownTimer > 5)
shutdownDevice();
}
/**
skipAdapterScan
*/
bool Board320_240::skipAdapterScan() {
return digitalRead(pinButtonMiddle) == LOW || digitalRead(pinButtonLeft) == LOW || digitalRead(pinButtonRight) == LOW;
}
/**
Mount sdcard
*/
bool Board320_240::sdcardMount() {
if (liveData->params.sdcardInit) {
Serial.print("SD card already mounted...");
return true;
}
int8_t countdown = 3;
bool SdState = false;
while (1) {
Serial.print("Initializing SD card...");
#ifdef BOARD_TTGO_T4
SPIClass * hspi = new SPIClass(HSPI);
spiSD.begin(pinSdcardSclk, pinSdcardMiso, pinSdcardMosi, pinSdcardCs); //SCK,MISO,MOSI,ss
SdState = SD.begin(pinSdcardCs, *hspi, SPI_FREQUENCY);
#endif // BOARD_TTGO_T4
#ifdef BOARD_M5STACK_CORE
SdState = SD.begin(pinSdcardCs);
#endif // BOARD_M5STACK_CORE
if (SdState) {
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return false;
}
Serial.println("SD card found.");
liveData->params.sdcardInit = true;
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
return true;
}
Serial.println("Initialization failed!");
countdown--;
if (countdown <= 0) {
break;
}
delay(500);
}
return false;
}
/**
Toggle sdcard recording
*/
void Board320_240::sdcardToggleRecording() {
if (!liveData->params.sdcardInit)
return;
Serial.println("Toggle SD card recording...");
liveData->params.sdcardRecording = !liveData->params.sdcardRecording;
if (liveData->params.sdcardRecording) {
liveData->params.sdcardCanNotify = true;
} else {
String tmpStr = "";
tmpStr.toCharArray(liveData->params.sdcardFilename, tmpStr.length() + 1);
}
}
/**
Sync gps data
*/
void Board320_240::syncGPS() {
if (gps.location.isValid()) {
liveData->params.gpsLat = gps.location.lat();
liveData->params.gpsLon = gps.location.lng();
if (gps.altitude.isValid())
liveData->params.gpsAlt = gps.altitude.meters();
}
if (gps.satellites.isValid()) {
liveData->params.gpsSat = gps.satellites.value();
//Serial.print("GPS satellites: ");
//Serial.println(liveData->params.gpsSat);
}
if (!liveData->params.currTimeSyncWithGps && gps.date.isValid() && gps.time.isValid()) {
liveData->params.currTimeSyncWithGps = true;
struct tm tm;
tm.tm_year = gps.date.year() - 1900;
tm.tm_mon = gps.date.month() - 1;
tm.tm_mday = gps.date.day();
tm.tm_hour = gps.time.hour();
tm.tm_min = gps.time.minute();
tm.tm_sec = gps.time.second();
time_t t = mktime(&tm);
printf("%02d%02d%02d%02d%02d%02d\n", gps.date.year() - 2000, gps.date.month() - 1, gps.date.day(), gps.time.hour(), gps.time.minute(), gps.time.second());
struct timeval now = { .tv_sec = t };
settimeofday(&now, NULL);
}
}
#endif // BOARD320_240_CPP