From 28e4fc0643336b33df3c9ef1c060877663c6ecc7 Mon Sep 17 00:00:00 2001 From: free-x Date: Fri, 24 Jan 2025 12:22:26 +0100 Subject: [PATCH 01/15] extend CI for OBP boards --- lib/obp60task/platformio.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index da78f39..16f3269 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -2,7 +2,9 @@ #if you want a pio run to only build #your special environments you can set this here #by uncommenting the next line -default_envs = obp60_s3 +default_envs = + obp60_s3 + obp40_s3 [env:obp60_s3] platform = espressif32@6.8.1 From 7afcb864042343dd863439f07793d678309d1370 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Fri, 24 Jan 2025 15:23:02 +0100 Subject: [PATCH 02/15] OBP40 Battery voltage measuring and capacity calculation --- lib/obp60task/OBPSensorTask.cpp | 33 +++++++++++++++++++++++++++++++-- lib/obp60task/Pagedata.h | 2 ++ lib/obp60task/platformio.ini | 6 +++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 99071ab..cd488ab 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -88,8 +88,16 @@ void sensorTask(void *param){ double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat(); double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat(); if(String(powsensor1) == "off"){ - sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 + #ifdef VOLTAGE_SENSOR + sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + #else + sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + #endif sensors.batteryVoltage = sensors.batteryVoltage * vslope + voffset; // Calibration + #ifdef LIPO_ACCU_1200 + sensors.BatteryChargeStatus = 0; // Set to discharging + sensors.batteryLevelLiPo = 0; // Level 0...100% + #endif sensors.batteryCurrent = 0; sensors.batteryPower = 0; // Fill average arrays with start values @@ -459,8 +467,29 @@ void sensorTask(void *param){ // Send supply voltage value all 1s if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){ starttime5 = millis(); - sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 + #ifdef VOLTAGE_SENSOR + sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + #else + sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + #endif sensors.batteryVoltage = sensors.batteryVoltage * vslope + voffset; // Calibration + #ifdef LIPO_ACCU_1200 + if(sensors.batteryVoltage > 4.1){ + sensors.BatteryChargeStatus = 1; // Charging active + } + else{ + sensors.BatteryChargeStatus = 0; // Discharging + } + // Polynomfit for LiPo capacity calculation for 3,7V LiPo accus, 0...100% + sensors.batteryLevelLiPo = sensors.batteryVoltage * sensors.batteryVoltage * 174.9513 + sensors.batteryVoltage * 1147,7686 + 1868.5120; + // Limiter + if(sensors.batteryLevelLiPo > 100){ + sensors.batteryLevelLiPo = 100; + } + if(sensors.batteryLevelLiPo < 0){ + sensors.batteryLevelLiPo = 0; + } + #endif // Save new data in average array batV.reading(int(sensors.batteryVoltage * 100)); // Calculate the average values for different time lines from integer values diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index b385b5c..d511aee 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -31,6 +31,8 @@ typedef struct{ double batteryVoltage300 = 0; // Sliding average over 300 values double batteryCurrent300 = 0; double batteryPower300 = 0; + double batteryLevelLiPo = 0; // Battery level for OBP40 LiPo accu + int BatteryChargeStatus = 0; // LiPo charge status: 0 = discharge, 1 = loading activ double solarVoltage = 0; double solarCurrent = 0; double solarPower = 0; diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index da78f39..662032a 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -2,7 +2,9 @@ #if you want a pio run to only build #your special environments you can set this here #by uncommenting the next line -default_envs = obp60_s3 +default_envs = + obp60_s3 + obp40_s3 [env:obp60_s3] platform = espressif32@6.8.1 @@ -91,6 +93,8 @@ build_flags= -D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib -D BOARD_OBP40S3 #Board OBP40 V1.0 with ESP32S3 SKU:DIE07300S (CrowPanel 4.2) -D DISPLAY_GDEY042T81 #new E-Ink display from Waveshare, R10 2.2 ohm + -D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh + -D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors ${env.build_flags} upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27 From e9ee49a6ef923a6e888a40364acb109c1650c8bb Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Fri, 24 Jan 2025 17:33:34 +0100 Subject: [PATCH 03/15] Add undervoltage for LiPo accu for OBP40 --- lib/obp60task/obp60task.cpp | 39 ++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 3defb2b..b87c4bd 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -308,13 +308,42 @@ void registerAllPages(PageList &list){ // Undervoltage detection for shutdown display void underVoltageDetection(GwApi *api, CommonData &common){ + float actVoltage = 0; + float minVoltage = 0; // Read settings float vslope = uint(api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asFloat()); float voffset = uint(api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asFloat()); // Read supply voltage - float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // V = 1/20 * Vin + #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 + actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu + #else + actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + minVoltage = MIN_VOLTAGE; + #endif actVoltage = actVoltage * vslope + voffset; - if(actVoltage < MIN_VOLTAGE){ + if(actVoltage < minVoltage){ + #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 + // Switch off all power lines + setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off + setFlashLED(false); // Flash LED Off + buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms + // Shutdown EInk display + getdisplay().setFullWindow(); // Set full Refresh + //getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().fillScreen(common.bgcolor); // Clear screen + getdisplay().setTextColor(common.fgcolor); + getdisplay().setFont(&Ubuntu_Bold20pt7b); + getdisplay().setCursor(65, 150); + getdisplay().print("Undervoltage"); + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(65, 175); + getdisplay().print("To wake up press reset"); + getdisplay().nextPage(); // Partial update + getdisplay().powerOff(); // Display power off + setPortPin(OBP_POWER_EPD, false); // Power off ePaper display + setPortPin(OBP_POWER_SD, false); // Power off SD card + #else // Switch off all power lines setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off setFlashLED(false); // Flash LED Off @@ -327,8 +356,12 @@ void underVoltageDetection(GwApi *api, CommonData &common){ getdisplay().setFont(&Ubuntu_Bold20pt7b); getdisplay().setCursor(65, 150); getdisplay().print("Undervoltage"); + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(65, 175); + getdisplay().print("To wake up repower system"); getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off + #endif // Stop system while(true){ esp_deep_sleep_start(); // Deep Sleep without weakup. Weakup only after power cycle (restart). @@ -353,7 +386,7 @@ void deepSleep(CommonData &common){ getdisplay().print("Sleep Mode"); getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(65, 175); - getdisplay().print("For wakeup press wheel and wait 5s"); + getdisplay().print("To wake up press wheel and wait 5s"); getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_EPD, false); // Power off ePaper display From 0b7863cb86785766929a184da43481a85e8a7061 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Fri, 24 Jan 2025 18:41:48 +0100 Subject: [PATCH 04/15] Typo --- lib/obp60task/obp60task.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index b87c4bd..4c0ff64 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -370,7 +370,7 @@ void underVoltageDetection(GwApi *api, CommonData &common){ } #ifdef BOARD_OBP40S3 -// Deep sleep funktion +// Deep sleep function void deepSleep(CommonData &common){ // Switch off all power lines setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off From f116e41964308ffea52c9d0e6158b190167355b6 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Fri, 24 Jan 2025 22:18:38 +0100 Subject: [PATCH 05/15] Fix for config.json, depensencies to other ode --- lib/obp60task/OBP60Extensions.cpp | 4 ++-- lib/obp60task/obp60task.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 9f7df24..3407434 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -146,7 +146,7 @@ void deepSleep(CommonData &common){ getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_50, false); // Power off ePaper display // Stop system - esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin + esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin } #endif #ifdef BOARD_OBP40S3 @@ -166,7 +166,7 @@ void deepSleep(CommonData &common){ getdisplay().print("Sleep Mode"); getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(65, 175); - getdisplay().print("For wakeup press wheel and wait 5s"); + getdisplay().print("to wake up press wheel and wait 5s"); getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_EPD, false); // Power off ePaper display diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 7dfd29d..fea1fb6 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -78,7 +78,7 @@ void OBP60Init(GwApi *api){ } #ifdef BOARD_OBP40S3 - //String sdcard = config->getConfigItem(config->useSDCard, true)->asString(); +// String sdcard = config->getConfigItem(config->useSDCard, true)->asString(); String sdcard = "on"; if (sdcard == "on") { SPIClass SD_SPI = SPIClass(HSPI); @@ -415,7 +415,8 @@ void OBP60Task(GwApi *api){ String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString(); uint fullrefreshtime = uint(api->getConfig()->getConfigItem(api->getConfig()->fullRefreshTime,true)->asInt()); #ifdef BOARD_OBP40S3 - bool syspage_enabled = config->getBool(config->systemPage); +// bool syspage_enabled = config->getBool(config->systemPage); + bool syspage_enabled = false; #endif #ifdef DISPLAY_GDEY042T81 From ea9a2ff9c49eb8ff98f5f3e3e17d7baeb3e51385 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Fri, 24 Jan 2025 22:18:38 +0100 Subject: [PATCH 06/15] Fix for config.json, dependencies to other ode --- lib/obp60task/OBP60Extensions.cpp | 4 ++-- lib/obp60task/obp60task.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 9f7df24..3407434 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -146,7 +146,7 @@ void deepSleep(CommonData &common){ getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_50, false); // Power off ePaper display // Stop system - esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin + esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin } #endif #ifdef BOARD_OBP40S3 @@ -166,7 +166,7 @@ void deepSleep(CommonData &common){ getdisplay().print("Sleep Mode"); getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(65, 175); - getdisplay().print("For wakeup press wheel and wait 5s"); + getdisplay().print("to wake up press wheel and wait 5s"); getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_EPD, false); // Power off ePaper display diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 7dfd29d..fea1fb6 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -78,7 +78,7 @@ void OBP60Init(GwApi *api){ } #ifdef BOARD_OBP40S3 - //String sdcard = config->getConfigItem(config->useSDCard, true)->asString(); +// String sdcard = config->getConfigItem(config->useSDCard, true)->asString(); String sdcard = "on"; if (sdcard == "on") { SPIClass SD_SPI = SPIClass(HSPI); @@ -415,7 +415,8 @@ void OBP60Task(GwApi *api){ String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString(); uint fullrefreshtime = uint(api->getConfig()->getConfigItem(api->getConfig()->fullRefreshTime,true)->asInt()); #ifdef BOARD_OBP40S3 - bool syspage_enabled = config->getBool(config->systemPage); +// bool syspage_enabled = config->getBool(config->systemPage); + bool syspage_enabled = false; #endif #ifdef DISPLAY_GDEY042T81 From 26e551c6162f8ff61aae381637c29da0cd129371 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Fri, 24 Jan 2025 22:29:12 +0100 Subject: [PATCH 07/15] Typo --- lib/obp60task/OBP60Extensions.cpp | 4 ++-- lib/obp60task/obp60task.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 3407434..b74d76d 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -141,7 +141,7 @@ void deepSleep(CommonData &common){ getdisplay().print("Sleep Mode"); getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(65, 175); - getdisplay().print("For wakeup press key and wait 5s"); + getdisplay().print("To wake up press key and wait 5s"); getdisplay().nextPage(); // Update display contents getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_50, false); // Power off ePaper display @@ -166,7 +166,7 @@ void deepSleep(CommonData &common){ getdisplay().print("Sleep Mode"); getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(65, 175); - getdisplay().print("to wake up press wheel and wait 5s"); + getdisplay().print("To wake up press wheel and wait 5s"); getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_EPD, false); // Power off ePaper display diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index fea1fb6..51828e4 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -339,7 +339,7 @@ void underVoltageDetection(GwApi *api, CommonData &common){ getdisplay().print("Undervoltage"); getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(65, 175); - getdisplay().print("To wake up press reset"); + getdisplay().print("Charge battery and restart"); getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_EPD, false); // Power off ePaper display @@ -416,7 +416,7 @@ void OBP60Task(GwApi *api){ uint fullrefreshtime = uint(api->getConfig()->getConfigItem(api->getConfig()->fullRefreshTime,true)->asInt()); #ifdef BOARD_OBP40S3 // bool syspage_enabled = config->getBool(config->systemPage); - bool syspage_enabled = false; + bool syspage_enabled = true; #endif #ifdef DISPLAY_GDEY042T81 From bdfaf3c8864004e0e1ee827d855559d765a4baae Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Sat, 25 Jan 2025 09:39:12 +0100 Subject: [PATCH 08/15] Added battery symbols --- lib/obp60task/OBP60Extensions.h | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index b6f653f..845283f 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -160,6 +160,46 @@ static std::map iconmap = { {"AP", ap_bits} }; +// Battery +#define battery_width 24 +#define battery_height 16 + +static unsigned char battery_0_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0x03, 0x00, 0x78, 0x03, 0x00, 0xf8, 0x03, 0x00, 0xd8, + 0x03, 0x00, 0xd8, 0x03, 0x00, 0xd8, 0x03, 0x00, 0xf8, 0x03, 0x00, 0x78, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_25_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0x3b, 0x00, 0x78, 0x3b, 0x00, 0xf8, 0x3b, 0x00, 0xd8, + 0x3b, 0x00, 0xd8, 0x3b, 0x00, 0xd8, 0x3b, 0x00, 0xf8, 0x3b, 0x00, 0x78, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_50_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0xbb, 0x03, 0x78, 0xbb, 0x03, 0xf8, 0xbb, 0x03, 0xd8, + 0xbb, 0x03, 0xd8, 0xbb, 0x03, 0xd8, 0xbb, 0x03, 0xf8, 0xbb, 0x03, 0x78, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_75_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0xbb, 0x3b, 0x78, 0xbb, 0x3b, 0xf8, 0xbb, 0x3b, 0xd8, + 0xbb, 0x3b, 0xd8, 0xbb, 0x3b, 0xd8, 0xbb, 0x3b, 0xf8, 0xbb, 0x3b, 0x78, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_100_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0xbb, 0xbb, 0x7b, 0xbb, 0xbb, 0xfb, 0xbb, 0xbb, 0xdb, + 0xbb, 0xbb, 0xdb, 0xbb, 0xbb, 0xdb, 0xbb, 0xbb, 0xfb, 0xbb, 0xbb, 0x7b, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_loading_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xfe, 0xe4, 0x0f, 0xff, 0xec, 0x1f, + 0x03, 0x08, 0x18, 0x03, 0x18, 0x78, 0x03, 0x30, 0xf8, 0x83, 0x3f, 0xd8, + 0x03, 0x7f, 0xd8, 0x03, 0x03, 0xd8, 0x03, 0x06, 0xf8, 0x03, 0x04, 0x78, + 0x03, 0x0c, 0x18, 0xff, 0xcb, 0x1f, 0xfe, 0xd3, 0x0f, 0x00, 0x10, 0x00 }; + // Other symbols #define swipe_width 24 #define swipe_height 16 From 097111c27002b1f9bfe1032b3dd39ba107b87f9d Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Sat, 25 Jan 2025 10:21:34 +0100 Subject: [PATCH 09/15] Added code for new OBP40 battery symbols --- lib/obp60task/OBP60Extensions.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 9f7df24..95b3044 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -392,6 +392,25 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa getdisplay().drawXBitmap(166, 1, swipe_bits, swipe_width, swipe_height, commonData.fgcolor); } #endif +#ifdef LIPO_ACCU_1200 + if (commonData.data.BatteryChargeStatus == 1) { + getdisplay().drawXBitmap(170, 1, battery_loading_bits, battery_width, battery_height, commonData.fgcolor); + } else { +#ifdef VOLTAGE_SENSOR + if (commonData.data.batteryLevelLiPo < 10) { + getdisplay().drawXBitmap(170, 1, battery_0_bits, battery_width, battery_height, commonData.fgcolor); + } else if (commonData.data.batteryLevelLiPo < 25) { + getdisplay().drawXBitmap(170, 1, battery_25_bits, battery_width, battery_height, commonData.fgcolor); + } else if (commonData.data.batteryLevelLiPo < 50) { + getdisplay().drawXBitmap(170, 1, battery_50_bits, battery_width, battery_height, commonData.fgcolor); + } else if (commonData.data.batteryLevelLiPo < 75) { + getdisplay().drawXBitmap(170, 1, battery_75_bits, battery_width, battery_height, commonData.fgcolor); + } else { + getdisplay().drawXBitmap(170, 1, battery_100_bits, battery_width, battery_height, commonData.fgcolor); + } +#endif // VOLTAGE_SENSOR + } +#endif // LIPO_ACCU_1200 // Heartbeat as dot getdisplay().setTextColor(commonData.fgcolor); From df81e6e443abc2ce80fb909ff2574c2b43e979ca Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sat, 25 Jan 2025 16:19:10 +0100 Subject: [PATCH 10/15] Fix charge status, add to PageVoltage --- boards/obp40_s3_n8r8.json | 4 +- extra_script.py | 16 +- extra_script.py.old | 518 ++++++++++++++++++++++++++++++++ lib/obp60task/OBP60Extensions.h | 4 +- lib/obp60task/OBPSensorTask.cpp | 37 ++- lib/obp60task/PageVoltage.cpp | 17 ++ lib/obp60task/config_obp40.json | 4 +- lib/obp60task/obp60task.cpp | 18 +- 8 files changed, 586 insertions(+), 32 deletions(-) create mode 100644 extra_script.py.old diff --git a/boards/obp40_s3_n8r8.json b/boards/obp40_s3_n8r8.json index 260050c..2ac8c3e 100644 --- a/boards/obp40_s3_n8r8.json +++ b/boards/obp40_s3_n8r8.json @@ -19,8 +19,8 @@ "flash_mode": "qio", "hwids": [ [ - "0x303A", - "0x1001" + "0x1A86", + "0x7523" ] ], "mcu": "esp32s3", diff --git a/extra_script.py b/extra_script.py index ed62c64..b185a93 100644 --- a/extra_script.py +++ b/extra_script.py @@ -10,7 +10,7 @@ from datetime import datetime import re import pprint from platformio.project.config import ProjectConfig - +from platformio.project.exception import InvalidProjectConfError Import("env") #print(env.Dump()) @@ -104,7 +104,17 @@ def writeFileIfChanged(fileName,data): return True def mergeConfig(base,other): + try: + customconfig = env.GetProjectOption("custom_config") + except InvalidProjectConfError: + customconfig = None for bdir in other: + if customconfig and os.path.exists(os.path.join(bdir,customconfig)): + cname=os.path.join(bdir,customconfig) + print("merge custom config {}".format(cname)) + with open(cname,'rb') as ah: + base += json.load(ah) + continue cname=os.path.join(bdir,"config.json") if os.path.exists(cname): print("merge config %s"%cname) @@ -274,9 +284,9 @@ class Grove: def _ss(self,z=False): if z: return self.name - return self.name if self.name is not 'Z' else '' + return self.name if self.name != 'Z' else '' def _suffix(self): - return '_'+self.name if self.name is not 'Z' else '' + return '_'+self.name if self.name != 'Z' else '' def replace(self,line): if line is None: return line diff --git a/extra_script.py.old b/extra_script.py.old new file mode 100644 index 0000000..ed62c64 --- /dev/null +++ b/extra_script.py.old @@ -0,0 +1,518 @@ +print("running extra...") +import gzip +import shutil +import os +import sys +import inspect +import json +import glob +from datetime import datetime +import re +import pprint +from platformio.project.config import ProjectConfig + + +Import("env") +#print(env.Dump()) +OWN_FILE="extra_script.py" +GEN_DIR='lib/generated' +CFG_FILE='web/config.json' +XDR_FILE='web/xdrconfig.json' +INDEXJS="index.js" +INDEXCSS="index.css" +CFG_INCLUDE='GwConfigDefinitions.h' +CFG_INCLUDE_IMPL='GwConfigDefImpl.h' +XDR_INCLUDE='GwXdrTypeMappings.h' +TASK_INCLUDE='GwUserTasks.h' +GROVE_CONFIG="GwM5GroveGen.h" +GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in" +EMBEDDED_INCLUDE="GwEmbeddedFiles.h" + +def getEmbeddedFiles(env): + rt=[] + efiles=env.GetProjectOption("board_build.embed_files") + for f in efiles.split("\n"): + if f == '': + continue + rt.append(f) + return rt + +def basePath(): + #see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined + return os.path.dirname(inspect.getfile(lambda: None)) + +def outPath(): + return os.path.join(basePath(),GEN_DIR) +def checkDir(): + dn=outPath() + if not os.path.exists(dn): + os.makedirs(dn) + if not os.path.isdir(dn): + print("unable to create %s"%dn) + return False + return True + +def isCurrent(infile,outfile): + if os.path.exists(outfile): + otime=os.path.getmtime(outfile) + itime=os.path.getmtime(infile) + if (otime >= itime): + own=os.path.join(basePath(),OWN_FILE) + if os.path.exists(own): + owntime=os.path.getmtime(own) + if owntime > otime: + return False + print("%s is newer then %s, no need to recreate"%(outfile,infile)) + return True + return False +def compressFile(inFile,outfile): + if isCurrent(inFile,outfile): + return + print("compressing %s"%inFile) + with open(inFile, 'rb') as f_in: + with gzip.open(outfile, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + +def generateFile(infile,outfile,callback,inMode='rb',outMode='w'): + if isCurrent(infile,outfile): + return + print("creating %s"%outfile) + oh=None + with open(infile,inMode) as ch: + with open(outfile,outMode) as oh: + try: + callback(ch,oh,inFile=infile) + oh.close() + except Exception as e: + try: + oh.close() + except: + pass + os.unlink(outfile) + raise + +def writeFileIfChanged(fileName,data): + if os.path.exists(fileName): + with open(fileName,"r") as ih: + old=ih.read() + ih.close() + if old == data: + return False + print("#generating %s"%fileName) + with open(fileName,"w") as oh: + oh.write(data) + return True + +def mergeConfig(base,other): + for bdir in other: + cname=os.path.join(bdir,"config.json") + if os.path.exists(cname): + print("merge config %s"%cname) + with open(cname,'rb') as ah: + merge=json.load(ah) + base=base+merge + return base + +def replaceTexts(data,replacements): + if replacements is None: + return data + if isinstance(data,str): + for k,v in replacements.items(): + data=data.replace("$"+k,str(v)) + return data + if isinstance(data,list): + rt=[] + for e in data: + rt.append(replaceTexts(e,replacements)) + return rt + if isinstance(data,dict): + rt={} + for k,v in data.items(): + rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements) + return rt + return data +def expandConfig(config): + rt=[] + for item in config: + type=item.get('type') + if type != 'array': + rt.append(item) + continue + replacements=item.get('replace') + children=item.get('children') + name=item.get('name') + if name is None: + name="#unknown#" + if not isinstance(replacements,list): + raise Exception("missing replacements at array %s"%name) + for replace in replacements: + if children is not None: + for c in children: + rt.append(replaceTexts(c,replace)) + return rt + +def generateMergedConfig(inFile,outFile,addDirs=[]): + if not os.path.exists(inFile): + raise Exception("unable to read cfg file %s"%inFile) + data="" + with open(inFile,'rb') as ch: + config=json.load(ch) + config=mergeConfig(config,addDirs) + config=expandConfig(config) + data=json.dumps(config,indent=2) + writeFileIfChanged(outFile,data) + +def generateCfg(inFile,outFile,impl): + if not os.path.exists(inFile): + raise Exception("unable to read cfg file %s"%inFile) + data="" + with open(inFile,'rb') as ch: + config=json.load(ch) + data+="//generated from %s\n"%inFile + l=len(config) + idx=0 + if not impl: + data+='#include "GwConfigItem.h"\n' + data+='class GwConfigDefinitions{\n' + data+=' public:\n' + data+=' int getNumConfig() const{return %d;}\n'%(l) + for item in config: + n=item.get('name') + if n is None: + continue + if len(n) > 15: + raise Exception("%s: config names must be max 15 caracters"%n) + data+=' static constexpr const char* %s="%s";\n'%(n,n) + data+="};\n" + else: + data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n' + for item in config: + name=item.get('name') + if name is None: + continue + data+=' configs[%d]='%(idx) + idx+=1 + secret="false"; + if item.get('type') == 'password': + secret="true" + data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret) + data+='}\n' + writeFileIfChanged(outFile,data) + +def labelFilter(label): + return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label)) +def generateXdrMappings(fp,oh,inFile=''): + jdoc=json.load(fp) + oh.write("static GwXDRTypeMapping* typeMappings[]={\n") + first=True + for cat in jdoc: + item=jdoc[cat] + cid=item.get('id') + if cid is None: + continue + tc=item.get('type') + if tc is not None: + if first: + first=False + else: + oh.write(",\n") + oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat)) + fields=item.get('fields') + if fields is None: + continue + idx=0 + for fe in fields: + if not isinstance(fe,dict): + continue + tc=fe.get('t') + id=fe.get('v') + if id is None: + id=idx + idx+=1 + l=fe.get('l') or '' + if tc is None or id is None: + continue + if first: + first=False + else: + oh.write(",\n") + oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l)) + oh.write("\n") + oh.write("};\n") + for cat in jdoc: + item=jdoc[cat] + cid=item.get('id') + if cid is None: + continue + selectors=item.get('selector') + if selectors is not None: + for selector in selectors: + label=selector.get('l') + value=selector.get('v') + if label is not None and value is not None: + label=labelFilter(label) + define=("GWXDRSEL_%s_%s"%(cat,label)).upper() + oh.write(" #define %s %s\n"%(define,value)) + fields=item.get('fields') + if fields is not None: + idx=0 + for field in fields: + v=field.get('v') + if v is None: + v=idx + else: + v=int(v) + label=field.get('l') + if v is not None and label is not None: + define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper(); + oh.write(" #define %s %s\n"%(define,str(v))) + idx+=1 + +class Grove: + def __init__(self,name) -> None: + self.name=name + def _ss(self,z=False): + if z: + return self.name + return self.name if self.name is not 'Z' else '' + def _suffix(self): + return '_'+self.name if self.name is not 'Z' else '' + def replace(self,line): + if line is None: + return line + return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix()) +def generateGroveDefs(inh,outh,inFile=''): + GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')] + definition=[] + started=False + def writeConfig(): + for grove in GROVES: + for cl in definition: + outh.write(grove.replace(cl)) + + for line in inh: + if re.match(" *#GROVE",line): + started=True + if len(definition) > 0: + writeConfig() + definition=[] + continue + if started: + definition.append(line) + if len(definition) > 0: + writeConfig() + + + +userTaskDirs=[] + +def getUserTaskDirs(): + rt=[] + taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*')) + for task in taskdirs: + rt.append(task) + return rt + +def checkAndAdd(file,names,ilist): + if not file.endswith('.h'): + return + match=False + for cmp in names: + #print("##check %s<->%s"%(f.lower(),cmp)) + if file.lower() == cmp: + match=True + if not match: + return + ilist.append(file) +def genereateUserTasks(outfile): + includes=[] + for task in userTaskDirs: + #print("##taskdir=%s"%task) + base=os.path.basename(task) + includeNames=[base.lower()+".h",'gw'+base.lower()+'.h'] + for f in os.listdir(task): + checkAndAdd(f,includeNames,includes) + includeData="" + for i in includes: + print("#task include %s"%i) + includeData+="#include <%s>\n"%i + writeFileIfChanged(outfile,includeData) + +def generateEmbedded(elist,outFile): + content="" + for entry in elist: + content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry + writeFileIfChanged(outFile,content) + +def getContentType(fn): + if (fn.endswith('.gz')): + fn=fn[0:-3] + if (fn.endswith('html')): + return "text/html" + if (fn.endswith('json')): + return "application/json" + if (fn.endswith('js')): + return "text/javascript" + if (fn.endswith('css')): + return "text/css" + return "application/octet-stream" + + +def getLibs(): + base=os.path.join(basePath(),"lib") + rt=[] + for sd in os.listdir(base): + if sd == '..': + continue + if sd == '.': + continue + fn=os.path.join(base,sd) + if os.path.isdir(fn): + rt.append(sd) + EXTRAS=['generated'] + for e in EXTRAS: + if not e in rt: + rt.append(e) + return rt + + + +def joinFiles(target,pattern,dirlist): + flist=[] + for dir in dirlist: + fn=os.path.join(dir,pattern) + if os.path.exists(fn): + flist.append(fn) + current=False + if os.path.exists(target): + current=True + for f in flist: + if not isCurrent(f,target): + current=False + break + if current: + print("%s is up to date"%target) + return + print("creating %s"%target) + with gzip.open(target,"wb") as oh: + for fn in flist: + print("adding %s to %s"%(fn,target)) + with open(fn,"rb") as rh: + shutil.copyfileobj(rh,oh) + + +OWNLIBS=getLibs()+["FS","WiFi"] +GLOBAL_INCLUDES=[] + +def handleDeps(env): + #overwrite the GetProjectConfig + #to inject all our libs + oldGetProjectConfig=env.GetProjectConfig + def GetProjectConfigX(env): + rt=oldGetProjectConfig() + cenv="env:"+env['PIOENV'] + libs=[] + for section,options in rt.as_tuple(): + if section == cenv: + for key,values in options: + if key == 'lib_deps': + libs=values + + mustUpdate=False + for lib in OWNLIBS: + if not lib in libs: + libs.append(lib) + mustUpdate=True + if mustUpdate: + update=[(cenv,[('lib_deps',libs)])] + rt.update(update) + return rt + env.AddMethod(GetProjectConfigX,"GetProjectConfig") + #store the list of all includes after we resolved + #the dependencies for our main project + #we will use them for all compilations afterwards + oldLibBuilder=env.ConfigureProjectLibBuilder + def ConfigureProjectLibBuilderX(env): + global GLOBAL_INCLUDES + project=oldLibBuilder() + #print("##ConfigureProjectLibBuilderX") + #pprint.pprint(project) + if project.depbuilders: + #print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders))) + for db in project.depbuilders: + idirs=db.get_include_dirs() + for id in idirs: + if not id in GLOBAL_INCLUDES: + GLOBAL_INCLUDES.append(id) + return project + env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder") + def injectIncludes(env,node): + return env.Object( + node, + CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES + ) + env.AddBuildMiddleware(injectIncludes) + + +def prebuild(env): + global userTaskDirs + print("#prebuild running") + if not checkDir(): + sys.exit(1) + ldf_mode=env.GetProjectOption("lib_ldf_mode") + if ldf_mode == 'off': + print("##ldf off - own dependency handling") + handleDeps(env) + userTaskDirs=getUserTaskDirs() + mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE)) + generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs) + compressFile(mergedConfig,mergedConfig+".gz") + generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False) + generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True) + joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs) + joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs) + embedded=getEmbeddedFiles(env) + filedefs=[] + for ef in embedded: + print("#checking embedded file %s"%ef) + (dn,fn)=os.path.split(ef) + pureName=fn + if pureName.endswith('.gz'): + pureName=pureName[0:-3] + ct=getContentType(pureName) + usname=ef.replace('/','_').replace('.','_') + filedefs.append((pureName,usname,ct)) + inFile=os.path.join(basePath(),"web",pureName) + if os.path.exists(inFile): + compressFile(inFile,ef) + else: + print("#WARNING: infile %s for %s not found"%(inFile,ef)) + generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE)) + genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE)) + generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings) + generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r') + version="dev"+datetime.now().strftime("%Y%m%d") + env.Append(CPPDEFINES=[('GWDEVVERSION',version)]) + +def cleangenerated(source, target, env): + od=outPath() + if os.path.isdir(od): + print("#cleaning up %s"%od) + for f in os.listdir(od): + if f == "." or f == "..": + continue + fn=os.path.join(od,f) + os.unlink(f) + + +print("#prescript...") +prebuild(env) +board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper() +print("Board=#%s#"%board) +print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"]))) +env.Append( + LINKFLAGS=[ "-u", "custom_app_desc" ], + CPPDEFINES=[(board,"1")] +) +#script does not run on clean yet - maybe in the future +env.AddPostAction("clean",cleangenerated) diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index b6f653f..5e0d953 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -100,8 +100,8 @@ void displayFooter(CommonData &commonData); SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude, double longitude, double timezone); // Calulate sunset and sunrise void batteryGraphic(uint x, uint y, float percent, int pcolor, int bcolor); // Battery graphic with fill level -void solarGraphic(uint x, uint y, int pcolor, int bcolor); // Solar graphic with fill level -void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic with fill level +void solarGraphic(uint x, uint y, int pcolor, int bcolor); // Solar graphic +void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic void startLedTask(GwApi *api); void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request); diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index cd488ab..9fb6d90 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -89,11 +89,11 @@ void sensorTask(void *param){ double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat(); if(String(powsensor1) == "off"){ #ifdef VOLTAGE_SENSOR - sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 #else - sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 #endif - sensors.batteryVoltage = sensors.batteryVoltage * vslope + voffset; // Calibration + sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration #ifdef LIPO_ACCU_1200 sensors.BatteryChargeStatus = 0; // Set to discharging sensors.batteryLevelLiPo = 0; // Level 0...100% @@ -468,20 +468,14 @@ void sensorTask(void *param){ if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){ starttime5 = millis(); #ifdef VOLTAGE_SENSOR - sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 #else - sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 #endif - sensors.batteryVoltage = sensors.batteryVoltage * vslope + voffset; // Calibration + sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration #ifdef LIPO_ACCU_1200 - if(sensors.batteryVoltage > 4.1){ - sensors.BatteryChargeStatus = 1; // Charging active - } - else{ - sensors.BatteryChargeStatus = 0; // Discharging - } // Polynomfit for LiPo capacity calculation for 3,7V LiPo accus, 0...100% - sensors.batteryLevelLiPo = sensors.batteryVoltage * sensors.batteryVoltage * 174.9513 + sensors.batteryVoltage * 1147,7686 + 1868.5120; + sensors.batteryLevelLiPo = sensors.batteryVoltage * sensors.batteryVoltage * 174.9513 + sensors.batteryVoltage * 1147.7686 + 1868.5120; // Limiter if(sensors.batteryLevelLiPo > 100){ sensors.batteryLevelLiPo = 100; @@ -496,11 +490,28 @@ void sensorTask(void *param){ sensors.batteryVoltage10 = batV.getAvg(10) / 100.0; sensors.batteryVoltage60 = batV.getAvg(60) / 100.0; sensors.batteryVoltage300 = batV.getAvg(300) / 100.0; + // Charging detection + float deltaV = sensors.batteryVoltage - sensors.batteryVoltage10; + if(deltaV > 0.03){ + sensors.BatteryChargeStatus = 1; // Charging active + } + if(deltaV < -0.03){ + sensors.BatteryChargeStatus = 0; // Discharging + } + #ifdef BOARD_OBP40S3 + // Send to NMEA200 bus as instance 10 + if(!isnan(sensors.batteryVoltage)){ + SetN2kDCBatStatus(N2kMsg, 10, sensors.batteryVoltage, N2kDoubleNA, N2kDoubleNA, 0); + api->sendN2kMessage(N2kMsg); + } + #endif + #ifdef BOARD_OBP60S3 // Send to NMEA200 bus if(!isnan(sensors.batteryVoltage)){ SetN2kDCBatStatus(N2kMsg, 0, sensors.batteryVoltage, N2kDoubleNA, N2kDoubleNA, 1); api->sendN2kMessage(N2kMsg); } + #endif } // Send data from environment sensor all 2s diff --git a/lib/obp60task/PageVoltage.cpp b/lib/obp60task/PageVoltage.cpp index d05c903..fe1070b 100644 --- a/lib/obp60task/PageVoltage.cpp +++ b/lib/obp60task/PageVoltage.cpp @@ -205,6 +205,18 @@ public: getdisplay().setCursor(20, 100); getdisplay().print(name1); // Value name + #ifdef BOARD_OBP40S3 + // Show charge status + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(185, 100); + if(commonData->data.BatteryChargeStatus == true){ + getdisplay().print("Charge"); + } + else{ + getdisplay().print("Discharge"); + } + #endif + // Show unit getdisplay().setFont(&Ubuntu_Bold20pt7b); getdisplay().setCursor(270, 100); @@ -213,7 +225,12 @@ public: // Show battery type getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(295, 100); + #ifdef BOARD_OBP60S3 getdisplay().print(batType); + #endif + #ifdef BOARD_OBP40S3 + getdisplay().print("LiPo"); + #endif // Show average settings printAvg(average, 320, 84, true); diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 3a4b6bf..888c746 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -609,10 +609,10 @@ "label": "Undervoltage", "type": "boolean", "default": "false", - "description": "Switch off device if voltage drops below 9V [on|off]", + "description": "Switch off device if LiPo voltage drops below 3.65V [on|off]", "category": "OBP40 Hardware", "capabilities": { - "obp40": "false" + "obp40":"true" } }, { diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 51828e4..cad4867 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -309,30 +309,28 @@ void registerAllPages(PageList &list){ // Undervoltage detection for shutdown display void underVoltageDetection(GwApi *api, CommonData &common){ - float actVoltage = 0; - float minVoltage = 0; // Read settings float vslope = uint(api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asFloat()); float voffset = uint(api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asFloat()); // Read supply voltage #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 - actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 - minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu + float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu #else - actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 - minVoltage = MIN_VOLTAGE; + float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + float minVoltage = MIN_VOLTAGE; #endif - actVoltage = actVoltage * vslope + voffset; - if(actVoltage < minVoltage){ + float corrVoltage = actVoltage * vslope + voffset; // Calibration + if(corrVoltage < minVoltage){ #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 // Switch off all power lines setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off setFlashLED(false); // Flash LED Off buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms // Shutdown EInk display - getdisplay().setFullWindow(); // Set full Refresh + getdisplay().setFullWindow(); // Set full Refresh //getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update - getdisplay().fillScreen(common.bgcolor); // Clear screen + getdisplay().fillScreen(common.bgcolor);// Clear screen getdisplay().setTextColor(common.fgcolor); getdisplay().setFont(&Ubuntu_Bold20pt7b); getdisplay().setCursor(65, 150); From 9dc857056b1d1dd1762e3e92178aba14144346a5 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sat, 25 Jan 2025 18:04:58 +0100 Subject: [PATCH 11/15] Fix LiPo battery level and sensor pad --- lib/obp60task/OBP60Keypad.h | 4 ++-- lib/obp60task/OBPSensorTask.cpp | 22 ++++++++++------------ lib/obp60task/PageVoltage.cpp | 4 ++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/obp60task/OBP60Keypad.h b/lib/obp60task/OBP60Keypad.h index 937f44e..eafe3a2 100644 --- a/lib/obp60task/OBP60Keypad.h +++ b/lib/obp60task/OBP60Keypad.h @@ -277,8 +277,8 @@ void initKeys(CommonData &commonData) { starttime = millis(); // Start key pressed keycodeold = keycode; } - // If key pressed longer than 200ms - if(millis() > starttime + 200 && keycode == keycodeold) { + // If key pressed longer than 100ms + if(millis() > starttime + 100 && keycode == keycodeold) { if (use_syspage and keycode == 3) { keystatus = 12; } else { diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 9fb6d90..cbbfa15 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -473,9 +473,15 @@ void sensorTask(void *param){ float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 #endif sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration - #ifdef LIPO_ACCU_1200 + // Save new data in average array + batV.reading(int(sensors.batteryVoltage * 100)); + // Calculate the average values for different time lines from integer values + sensors.batteryVoltage10 = batV.getAvg(10) / 100.0; + sensors.batteryVoltage60 = batV.getAvg(60) / 100.0; + sensors.batteryVoltage300 = batV.getAvg(300) / 100.0; + #if defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR // Polynomfit for LiPo capacity calculation for 3,7V LiPo accus, 0...100% - sensors.batteryLevelLiPo = sensors.batteryVoltage * sensors.batteryVoltage * 174.9513 + sensors.batteryVoltage * 1147.7686 + 1868.5120; + sensors.batteryLevelLiPo = sensors.batteryVoltage60 * 203.8312 -738.1635; // Limiter if(sensors.batteryLevelLiPo > 100){ sensors.batteryLevelLiPo = 100; @@ -483,22 +489,14 @@ void sensorTask(void *param){ if(sensors.batteryLevelLiPo < 0){ sensors.batteryLevelLiPo = 0; } - #endif - // Save new data in average array - batV.reading(int(sensors.batteryVoltage * 100)); - // Calculate the average values for different time lines from integer values - sensors.batteryVoltage10 = batV.getAvg(10) / 100.0; - sensors.batteryVoltage60 = batV.getAvg(60) / 100.0; - sensors.batteryVoltage300 = batV.getAvg(300) / 100.0; // Charging detection float deltaV = sensors.batteryVoltage - sensors.batteryVoltage10; - if(deltaV > 0.03){ + if(deltaV > 0.045){ sensors.BatteryChargeStatus = 1; // Charging active } - if(deltaV < -0.03){ + if(deltaV < -0.04){ sensors.BatteryChargeStatus = 0; // Discharging } - #ifdef BOARD_OBP40S3 // Send to NMEA200 bus as instance 10 if(!isnan(sensors.batteryVoltage)){ SetN2kDCBatStatus(N2kMsg, 10, sensors.batteryVoltage, N2kDoubleNA, N2kDoubleNA, 0); diff --git a/lib/obp60task/PageVoltage.cpp b/lib/obp60task/PageVoltage.cpp index fe1070b..c98bc96 100644 --- a/lib/obp60task/PageVoltage.cpp +++ b/lib/obp60task/PageVoltage.cpp @@ -205,7 +205,7 @@ public: getdisplay().setCursor(20, 100); getdisplay().print(name1); // Value name - #ifdef BOARD_OBP40S3 + #if defined BOARD_OBP40S3 && defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR // Show charge status getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(185, 100); @@ -228,7 +228,7 @@ public: #ifdef BOARD_OBP60S3 getdisplay().print(batType); #endif - #ifdef BOARD_OBP40S3 + #if defined BOARD_OBP40S3 && defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR getdisplay().print("LiPo"); #endif From f0cc5d9966e3ca197eecfb1d1f667fbc4ccf9355 Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Sat, 25 Jan 2025 19:11:16 +0100 Subject: [PATCH 12/15] Integration of heartbeat and page number --- lib/obp60task/OBP60Extensions.cpp | 39 ++++++++++++++----------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 9f7df24..c08a61d 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -341,13 +341,9 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa static unsigned long tcpClTxOld = 0; static unsigned long n2kRxOld = 0; static unsigned long n2kTxOld = 0; - int textcolor = GxEPD_BLACK; if(commonData.config->getBool(commonData.config->statusLine) == true){ - // Header separator line (optional) - // getdisplay().drawLine(0, 19, 399, 19, commonData.fgcolor); - // Show status info getdisplay().setTextColor(commonData.fgcolor); getdisplay().setFont(&Ubuntu_Bold8pt7b); @@ -393,12 +389,17 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa } #endif - // Heartbeat as dot - getdisplay().setTextColor(commonData.fgcolor); - getdisplay().setFont(&Ubuntu_Bold32pt7b); - getdisplay().setCursor(205, 14); - getdisplay().print(heartbeat ? "." : " "); - heartbeat = !heartbeat; + // Heartbeat as page number + if (heartbeat) { + getdisplay().setTextColor(commonData.bgcolor); + getdisplay().fillRect(201, 0, 23, 19, commonData.fgcolor); + } else { + getdisplay().setTextColor(commonData.fgcolor); + getdisplay().drawRect(201, 0, 23, 19, commonData.fgcolor); + } + getdisplay().setFont(&Ubuntu_Bold8pt7b); + drawTextCenter(211, 9, String(commonData.data.actpage)); + heartbeat = !heartbeat; // Date and time getdisplay().setTextColor(commonData.fgcolor); @@ -442,17 +443,16 @@ void displayFooter(CommonData &commonData) { // horizontal elements const uint16_t top = 280; const uint16_t bottom = 299; + // horizontal stub lines getdisplay().drawLine(commonData.keydata[0].x, top, commonData.keydata[0].x+10, top, commonData.fgcolor); - getdisplay().drawLine(commonData.keydata[1].x-10, top, commonData.keydata[1].x+10, top, commonData.fgcolor); - getdisplay().drawLine(commonData.keydata[2].x-10, top, commonData.keydata[2].x+10, top, commonData.fgcolor); - getdisplay().drawLine(commonData.keydata[4].x-10, top, commonData.keydata[4].x+10, top, commonData.fgcolor); - getdisplay().drawLine(commonData.keydata[5].x-10, top, commonData.keydata[5].x+10, top, commonData.fgcolor); + for (int i = 1; i <= 5; i++) { + getdisplay().drawLine(commonData.keydata[i].x-10, top, commonData.keydata[i].x+10, top, commonData.fgcolor); + } getdisplay().drawLine(commonData.keydata[5].x + commonData.keydata[5].w - 10, top, commonData.keydata[5].x + commonData.keydata[5].w + 1, top, commonData.fgcolor); // vertical key separators - getdisplay().drawLine(commonData.keydata[0].x + commonData.keydata[0].w, top, commonData.keydata[0].x + commonData.keydata[0].w, bottom, commonData.fgcolor); - getdisplay().drawLine(commonData.keydata[1].x + commonData.keydata[1].w, top, commonData.keydata[1].x + commonData.keydata[1].w, bottom, commonData.fgcolor); - getdisplay().drawLine(commonData.keydata[3].x + commonData.keydata[3].w, top, commonData.keydata[3].x + commonData.keydata[3].w, bottom, commonData.fgcolor); - getdisplay().drawLine(commonData.keydata[4].x + commonData.keydata[4].w, top, commonData.keydata[4].x + commonData.keydata[4].w, bottom, commonData.fgcolor); + for (int i = 0; i <= 4; i++) { + getdisplay().drawLine(commonData.keydata[i].x + commonData.keydata[i].w, top, commonData.keydata[i].x + commonData.keydata[i].w, bottom, commonData.fgcolor); + } for (int i = 0; i < 6; i++) { uint16_t x, y; if (commonData.keydata[i].label.length() > 0) { @@ -476,9 +476,6 @@ void displayFooter(CommonData &commonData) { } } } - // Current page number in a small box - getdisplay().drawRect(190, 280, 23, 19, commonData.fgcolor); - drawTextCenter(200, 289, String(commonData.data.actpage)); } else { getdisplay().setCursor(65, 295); getdisplay().print("Press 1 and 6 fast to unlock keys"); From 38f1d5de4458f9a7f66a191086d2a08ffb67d688 Mon Sep 17 00:00:00 2001 From: Tobias E Date: Sun, 26 Jan 2025 13:09:35 +0000 Subject: [PATCH 13/15] Dont't display ILUM on Min Power --- lib/obp60task/Pagedata.h | 3 ++- lib/obp60task/obp60task.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index d511aee..b979b57 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -95,6 +95,7 @@ typedef struct{ uint16_t fgcolor; uint16_t bgcolor; bool keylock = false; + String powermode; } CommonData; //a base class that all pages must inherit from @@ -112,7 +113,7 @@ class Page{ commonData->keydata[2].label = "#LEFT"; commonData->keydata[3].label = "#RIGHT"; commonData->keydata[4].label = ""; - if (commonData->backlight.mode == KEY) { + if ((commonData->backlight.mode == KEY) && !(commonData->powermode == "Min Power")) { commonData->keydata[5].label = "ILUM"; } else { commonData->keydata[5].label = ""; diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index cad4867..1952553 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -555,6 +555,7 @@ void OBP60Task(GwApi *api){ commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString()); commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString()); commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt()); + commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString(); bool uvoltage = api->getConfig()->getConfigItem(api->getConfig()->underVoltage,true)->asBoolean(); String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString(); From e7d5ada6102e8f4afe455b51c86d89a8df1faa15 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sun, 26 Jan 2025 15:01:47 +0100 Subject: [PATCH 14/15] Fix undervoltage detection --- lib/obp60task/OBPSensorTask.cpp | 20 ++++++++++++++++---- lib/obp60task/obp60task.cpp | 10 +++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index cbbfa15..ac32b40 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -491,11 +491,23 @@ void sensorTask(void *param){ } // Charging detection float deltaV = sensors.batteryVoltage - sensors.batteryVoltage10; - if(deltaV > 0.045){ - sensors.BatteryChargeStatus = 1; // Charging active + // Higher limits for lower voltages + if(sensors.batteryVoltage10 < 4.0){ + if(deltaV > 0.045 && deltaV < 4,15){ + sensors.BatteryChargeStatus = 1; // Charging active + } + if(deltaV < -0.04 || deltaV >= 4,15){ // Charging stops by grater than 4,15V + sensors.BatteryChargeStatus = 0; // Discharging + } } - if(deltaV < -0.04){ - sensors.BatteryChargeStatus = 0; // Discharging + // Lower limits for higher voltages + else{ + if(deltaV > 0.03 && deltaV < 4,15){ + sensors.BatteryChargeStatus = 1; // Charging active + } + if(deltaV < -0.03 || deltaV >= 4,15){ // Charging stops by grater than 4,15V + sensors.BatteryChargeStatus = 0; // Discharging + } } // Send to NMEA200 bus as instance 10 if(!isnan(sensors.batteryVoltage)){ diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index cad4867..aa79930 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -310,8 +310,8 @@ void registerAllPages(PageList &list){ // Undervoltage detection for shutdown display void underVoltageDetection(GwApi *api, CommonData &common){ // Read settings - float vslope = uint(api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asFloat()); - float voffset = uint(api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asFloat()); + double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat(); + double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat(); // Read supply voltage #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 @@ -320,8 +320,8 @@ void underVoltageDetection(GwApi *api, CommonData &common){ float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 float minVoltage = MIN_VOLTAGE; #endif - float corrVoltage = actVoltage * vslope + voffset; // Calibration - if(corrVoltage < minVoltage){ + double calVoltage = actVoltage * vslope + voffset; // Calibration + if(calVoltage < minVoltage){ #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 // Switch off all power lines setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off @@ -350,7 +350,7 @@ void underVoltageDetection(GwApi *api, CommonData &common){ setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off // Shutdown EInk display getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update - getdisplay().fillScreen(common.bgcolor); // Clear screen + getdisplay().fillScreen(common.bgcolor);// Clear screen getdisplay().setTextColor(common.fgcolor); getdisplay().setFont(&Ubuntu_Bold20pt7b); getdisplay().setCursor(65, 150); From 175f525bcd44301bbb677dd3a7deb07a76163c68 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sun, 26 Jan 2025 22:11:55 +0100 Subject: [PATCH 15/15] Fix undervoltage detection, format error --- lib/obp60task/OBPSensorTask.cpp | 8 ++++---- lib/obp60task/obp60task.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index ac32b40..445fb21 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -493,19 +493,19 @@ void sensorTask(void *param){ float deltaV = sensors.batteryVoltage - sensors.batteryVoltage10; // Higher limits for lower voltages if(sensors.batteryVoltage10 < 4.0){ - if(deltaV > 0.045 && deltaV < 4,15){ + if(deltaV > 0.045 && deltaV < 4.15){ sensors.BatteryChargeStatus = 1; // Charging active } - if(deltaV < -0.04 || deltaV >= 4,15){ // Charging stops by grater than 4,15V + if(deltaV < -0.04 || deltaV >= 4.15){ // Charging stops by grater than 4,15V sensors.BatteryChargeStatus = 0; // Discharging } } // Lower limits for higher voltages else{ - if(deltaV > 0.03 && deltaV < 4,15){ + if(deltaV > 0.03 && deltaV < 4.15){ sensors.BatteryChargeStatus = 1; // Charging active } - if(deltaV < -0.03 || deltaV >= 4,15){ // Charging stops by grater than 4,15V + if(deltaV < -0.03 || deltaV >= 4.15){ // Charging stops by grater than 4,15V sensors.BatteryChargeStatus = 0; // Discharging } } diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index ad56df5..1b4b936 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -337,7 +337,7 @@ void underVoltageDetection(GwApi *api, CommonData &common){ getdisplay().print("Undervoltage"); getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(65, 175); - getdisplay().print("Charge battery and restart"); + getdisplay().print("Charge battery and restart system"); getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off setPortPin(OBP_POWER_EPD, false); // Power off ePaper display