From fe4f86bc845f3fc4ad044460b94479ae5563a77b Mon Sep 17 00:00:00 2001 From: Professr Date: Sun, 21 Jun 2020 16:21:34 -0700 Subject: [PATCH 1/6] Added battery charge percent estimation --- src/esp32/main-esp32.cpp | 20 +++++++++++--------- src/power.h | 15 +++++++++++++++ src/screen.cpp | 8 +++++--- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 45e2a123..f9aaf127 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -6,6 +6,7 @@ #include "power.h" #include "sleep.h" #include "target_specific.h" +#include bool bluetoothOn; @@ -75,6 +76,16 @@ void readPowerStatus() powerStatus.haveBattery = axp.isBatteryConnect(); if (powerStatus.haveBattery) { powerStatus.batteryVoltageMv = axp.getBattVoltage(); + // If the AXP192 returns a valid battery percentage, use it + if (axp.getBattPercentage() >= 0) { + powerStatus.batteryChargePercent = axp.getBattPercentage(); + } else { + // If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error + // In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h + int calculatedPercentage = ((powerStatus.batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY); + powerStatus.batteryChargePercent = (calculatedPercentage < 0) ? 0 : (calculatedPercentage > 100) ? 100 : calculatedPercentage; + } + DEBUG_MSG("Battery %dmV %d%%\n", powerStatus.batteryVoltageMv, powerStatus.batteryChargePercent); } powerStatus.usb = axp.isVBUSPlug(); powerStatus.charging = axp.isChargeing(); @@ -205,15 +216,6 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif -/** - * Per @spattinson - * MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other - * charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph - * in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html) - * looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks - * to be a shade above 3.2V - */ -#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ /// loop code specific to ESP32 targets void esp32Loop() diff --git a/src/power.h b/src/power.h index 6b190cbf..21c913d5 100644 --- a/src/power.h +++ b/src/power.h @@ -1,5 +1,18 @@ #pragma once +/** + * Per @spattinson + * MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other + * charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph + * in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html) + * looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks + * to be a shade above 3.2V + */ +#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ + +#define BAT_MILLIVOLTS_FULL 4100 +#define BAT_MILLIVOLTS_EMPTY 3500 + namespace meshtastic { @@ -9,6 +22,8 @@ struct PowerStatus { bool haveBattery; /// Battery voltage in mV, valid if haveBattery is true int batteryVoltageMv; + /// Battery charge percentage, either read directly or estimated + int batteryChargePercent; /// Whether USB is connected bool usb; /// Whether we are charging the battery diff --git a/src/screen.cpp b/src/screen.cpp index 1837ef79..e31d5ae0 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -658,9 +658,11 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 snprintf(channelStr, sizeof(channelStr), "%s", channelName.c_str()); if (powerStatus.haveBattery) { // TODO: draw a battery icon instead of letter "B". - int batV = powerStatus.batteryVoltageMv / 1000; - int batCv = (powerStatus.batteryVoltageMv % 1000) / 10; - snprintf(batStr, sizeof(batStr), "B %01d.%02dV%c%c", batV, batCv, powerStatus.charging ? '+' : ' ', + //int batV = powerStatus.batteryVoltageMv / 1000; + //int batCv = (powerStatus.batteryVoltageMv % 1000) / 10; + //snprintf(batStr, sizeof(batStr), "B %01d.%02dV%c%c", batV, batCv, powerStatus.charging ? '+' : ' ', + // powerStatus.usb ? 'U' : ' '); + snprintf(batStr, sizeof(batStr), "B %d%%%c%c", powerStatus.batteryChargePercent, powerStatus.charging ? '+' : ' ', powerStatus.usb ? 'U' : ' '); } else { snprintf(batStr, sizeof(batStr), "%s", powerStatus.usb ? "USB" : ""); From 364fc84aaab3cd40d44e8e65baa10d4906f3616c Mon Sep 17 00:00:00 2001 From: Professr Date: Sun, 21 Jun 2020 16:31:09 -0700 Subject: [PATCH 2/6] Removed unnecessary include, ready for consideration for issue #196 --- src/esp32/main-esp32.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index f9aaf127..8b08118c 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -6,7 +6,6 @@ #include "power.h" #include "sleep.h" #include "target_specific.h" -#include bool bluetoothOn; From c66e064f423d6d913bc40061f2f975a422c4a600 Mon Sep 17 00:00:00 2001 From: Professr Date: Sun, 21 Jun 2020 17:28:37 -0700 Subject: [PATCH 3/6] Replaced battery, node, and gps text with a graphical header. Added hash to the beginning of the channel name. --- src/images.h | 7 ++++- src/screen.cpp | 77 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/images.h b/src/images.h index 9a1c143b..2a706494 100644 --- a/src/images.h +++ b/src/images.h @@ -4,7 +4,12 @@ const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0 0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54, 0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E}; -const +const uint8_t imgUSB[] PROGMEM = { 0x60, 0x60, 0x30, 0x18, 0x18, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x24, 0x24, 0x24, 0x3C }; +const uint8_t imgPower[] PROGMEM = { 0x40, 0x40, 0x40, 0x58, 0x48, 0x08, 0x08, 0x08, 0x1C, 0x22, 0x22, 0x41, 0x7F, 0x22, 0x22, 0x22 }; +const uint8_t imgUser[] PROGMEM = { 0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C }; +const uint8_t imgPositionEmpty[] PROGMEM = { 0x20, 0x30, 0x28, 0x24, 0x42, 0xFF }; +const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF }; + #include "icon.xbm" // We now programmatically draw our compass diff --git a/src/screen.cpp b/src/screen.cpp index e31d5ae0..6cc2c70a 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -54,6 +54,8 @@ static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; static uint32_t targetFramerate = IDLE_FRAMERATE; static char btPIN[16] = "888888"; +uint8_t imgBattery[16] = { 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C }; + static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // draw an xbm image. @@ -156,6 +158,49 @@ static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char return yo; } +// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. +static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, PowerStatus *powerStatus) { + // Make sure the image buffer is large enough + //if(sizeof(imgBuffer) < 16) return; + static const uint8_t powerBar[3] = { 0x81, 0xBD, 0xBD }; + static const uint8_t lightning[8] = { 0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85 }; + // Clear the bar area on the battery image + for (int i = 1; i < 14; i++) { + imgBuffer[i] = 0x81; + } + // If charging, draw a charging indicator + if (powerStatus->charging) { + memcpy(imgBuffer + 3, lightning, 8); + // If not charging, Draw power bars + } else { + for (int i = 0; i < 4; i++) { + if(powerStatus->batteryChargePercent >= 25 * (i + 1)) memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); + } + } + display->drawFastImage(x, y, 16, 8, imgBuffer); +} + +// Draw nodes status +static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, int nodesOnline, int nodesTotal) { + char usersString[20]; + sprintf(usersString, "%d/%d", nodesOnline, nodesTotal); + display->drawFastImage(x, y, 8, 8, imgUser); + display->drawString(x + 10, y - 2, usersString); +} + +// Draw GPS status summary +static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, GPS *gps) { + if(!gps->isConnected) { display->drawString(x, y - 2, "No GPS"); return; } + display->drawFastImage(x, y, 6, 8, gps->hasLock() ? imgPositionSolid : imgPositionEmpty ); + if(!gps->hasLock()) { display->drawString(x + 8, y - 2, "No sats"); return; } + if(gps->dop <= 100) { display->drawString(x + 8, y - 2, "Ideal"); return; } + if(gps->dop <= 200) { display->drawString(x + 8, y - 2, "Exc."); return; } + if(gps->dop <= 500) { display->drawString(x + 8, y - 2, "Good"); return; } + if(gps->dop <= 1000) { display->drawString(x + 8, y - 2, "Mod."); return; } + if(gps->dop <= 2000) { display->drawString(x + 8, y - 2, "Fair"); return; } + if(gps->dop > 0) { display->drawString(x + 8, y - 2, "Poor"); return; } +} + /// Ported from my old java code, returns distance in meters along the globe /// surface (by magic?) static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b) @@ -648,35 +693,21 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); - char usersStr[20]; char channelStr[20]; - char batStr[20]; - char gpsStr[20]; { LockGuard guard(&lock); - snprintf(usersStr, sizeof(usersStr), "Users %d/%d", nodesOnline, nodesTotal); - snprintf(channelStr, sizeof(channelStr), "%s", channelName.c_str()); - if (powerStatus.haveBattery) { - // TODO: draw a battery icon instead of letter "B". - //int batV = powerStatus.batteryVoltageMv / 1000; - //int batCv = (powerStatus.batteryVoltageMv % 1000) / 10; - //snprintf(batStr, sizeof(batStr), "B %01d.%02dV%c%c", batV, batCv, powerStatus.charging ? '+' : ' ', - // powerStatus.usb ? 'U' : ' '); - snprintf(batStr, sizeof(batStr), "B %d%%%c%c", powerStatus.batteryChargePercent, powerStatus.charging ? '+' : ' ', - powerStatus.usb ? 'U' : ' '); - } else { - snprintf(batStr, sizeof(batStr), "%s", powerStatus.usb ? "USB" : ""); - } + snprintf(channelStr, sizeof(channelStr), "#%s", channelName.c_str()); - if (!gpsStatus.empty()) { - snprintf(gpsStr, sizeof(gpsStr), "%s", gpsStatus.c_str()); - } else { - gpsStr[0] = '\0'; // Just show empty string. - } + // Display power status + if (powerStatus.haveBattery) drawBattery(display, x, y + 2, imgBattery, &powerStatus); else display->drawFastImage(x, y + 2, 16, 8, powerStatus.usb ? imgUSB : imgPower); + // Display nodes status + drawNodes(display, x + (SCREEN_WIDTH * 0.33), y + 2, nodesOnline, nodesTotal); + // Display GPS status + drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gps); } - const char *fields[] = {batStr, gpsStr, usersStr, channelStr, nullptr}; - uint32_t yo = drawRows(display, x, y, fields); + const char *fields[] = {channelStr, nullptr}; + uint32_t yo = drawRows(display, x, y + 12, fields); display->drawLogBuffer(x, yo); } From d8287e9cdb3dd69a58bbcf8a1dbdabb0a045a57a Mon Sep 17 00:00:00 2001 From: Professr Date: Sun, 21 Jun 2020 17:43:34 -0700 Subject: [PATCH 4/6] Removed DOP to string utility function from GPS.cpp since it's now drawn directly in screen.cpp --- src/gps/GPS.cpp | 13 ------------- src/main.cpp | 1 - 2 files changed, 14 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 5b0ad0d7..bb2d30b5 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -68,19 +68,6 @@ void perhapsSetRTC(struct tm &t) perhapsSetRTC(&tv); } -// Generate a string representation of the DOP -// based on Wikipedia "meaning of DOP values" https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation)#Meaning_of_DOP_Values -const char *getDOPString(uint32_t dop) { - dop = dop / 100; - if(dop <= 1) return "GPS Ideal"; - if(dop <= 2) return "GPS Exc."; - if(dop <= 5) return "GPS Good"; - if(dop <= 10) return "GPS Mod."; - if(dop <= 20) return "GPS Fair"; - if(dop > 0) return "GPS Poor"; - return "invalid dop"; -} - #include uint32_t getTime() diff --git a/src/main.cpp b/src/main.cpp index 4f55b593..78a16245 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -334,7 +334,6 @@ void loop() screen.debug()->setNodeNumbersStatus(nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes()); screen.debug()->setChannelNameStatus(channelSettings.name); screen.debug()->setPowerStatus(powerStatus); - screen.debug()->setGPSStatus(gps->isConnected ? (gps->hasLock() ? getDOPString(gps->dop) : "No Sats") : "No GPS"); // No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in) // i.e. don't just keep spinning in loop as fast as we can. From 5c9f22bc18f8bac63a31deeb9e270fdc9b78cad6 Mon Sep 17 00:00:00 2001 From: Professr Date: Sun, 21 Jun 2020 19:44:32 -0700 Subject: [PATCH 5/6] Moved node count graphic slightly to the left, to allow room for triple-digit node counts --- src/screen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screen.cpp b/src/screen.cpp index 6cc2c70a..acbf9532 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -701,7 +701,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Display power status if (powerStatus.haveBattery) drawBattery(display, x, y + 2, imgBattery, &powerStatus); else display->drawFastImage(x, y + 2, 16, 8, powerStatus.usb ? imgUSB : imgPower); // Display nodes status - drawNodes(display, x + (SCREEN_WIDTH * 0.33), y + 2, nodesOnline, nodesTotal); + drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodesOnline, nodesTotal); // Display GPS status drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gps); } From e45d0c4dcf8bf6cf87d5847b1cf663095e827e6b Mon Sep 17 00:00:00 2001 From: Professr Date: Mon, 22 Jun 2020 00:10:04 -0700 Subject: [PATCH 6/6] Offset battery bars by 1 to reflect room between BAT_MILLIVOLTS_EMPTY and MIN_BAT_MILLIVOLTS --- src/screen.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/screen.cpp b/src/screen.cpp index acbf9532..9085dc0e 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -160,8 +160,6 @@ static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char // Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage. static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, PowerStatus *powerStatus) { - // Make sure the image buffer is large enough - //if(sizeof(imgBuffer) < 16) return; static const uint8_t powerBar[3] = { 0x81, 0xBD, 0xBD }; static const uint8_t lightning[8] = { 0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85 }; // Clear the bar area on the battery image @@ -174,7 +172,7 @@ static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *img // If not charging, Draw power bars } else { for (int i = 0; i < 4; i++) { - if(powerStatus->batteryChargePercent >= 25 * (i + 1)) memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); + if(powerStatus->batteryChargePercent >= 25 * i) memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); } } display->drawFastImage(x, y, 16, 8, imgBuffer);