From 54cd082bfe1d197b296ea77ec0dbb53cbec33f89 Mon Sep 17 00:00:00 2001 From: Girts Folkmanis Date: Thu, 26 Mar 2020 09:24:53 -0700 Subject: [PATCH] fix #49: make debug screen show real data * Break out debug screen to a separate class and make it thread-safe. * Break out power state to a separate class. * Show battery voltage, charging & USB status on debug screen. * Show GPS lock / no lock * Fix an off-by-one that I introduced earlier in `drawRows`. --- src/GPS.cpp | 1 - src/GPS.h | 5 +++ src/main.cpp | 53 +++++++++++++++++++++--------- src/power.h | 18 ++++++++++ src/screen.cpp | 89 +++++++++++++++++++++++++++++++------------------- src/screen.h | 82 +++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 196 insertions(+), 52 deletions(-) create mode 100644 src/power.h diff --git a/src/GPS.cpp b/src/GPS.cpp index 7ef74ca9..3265b9dd 100644 --- a/src/GPS.cpp +++ b/src/GPS.cpp @@ -16,7 +16,6 @@ static uint32_t timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock -static bool hasValidLocation; // default to false, until we complete our first read static bool wantNewLocation = true; GPS::GPS() : PeriodicTask() {} diff --git a/src/GPS.h b/src/GPS.h index 2c572277..5d0d4b87 100644 --- a/src/GPS.h +++ b/src/GPS.h @@ -45,8 +45,13 @@ class GPS : public PeriodicTask, public Observable /// Restart our lock attempt - try to get and broadcast a GPS reading ASAP void startLock(); + /// Returns ture if we have acquired GPS lock. + bool hasLock() const { return hasValidLocation; } + private: void readFromRTC(); + + bool hasValidLocation = false; // default to false, until we complete our first read }; extern GPS gps; diff --git a/src/main.cpp b/src/main.cpp index 127a2314..8196fb55 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,7 @@ #include "configuration.h" #include "esp32/pm.h" #include "esp_pm.h" +#include "power.h" #include "rom/rtc.h" #include "screen.h" #include "sleep.h" @@ -52,9 +53,8 @@ meshtastic::Screen screen(SSD1306_ADDRESS, I2C_SDA, I2C_SCL); meshtastic::Screen screen(SSD1306_ADDRESS, 0, 0); #endif -// these flags are all in bss so they default false -bool isCharging; -bool isUSBPowered; +// Global power status singleton +static meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; @@ -97,6 +97,21 @@ void scanI2Cdevice(void) DEBUG_MSG("done\n"); } +#ifdef T_BEAM_V10 +/// Reads power status to powerStatus singleton. +// +// TODO(girts): move this and other axp stuff to power.h/power.cpp. +void readPowerStatus() +{ + powerStatus.haveBattery = axp.isBatteryConnect(); + if (powerStatus.haveBattery) { + powerStatus.batteryVoltageMv = axp.getBattVoltage(); + } + powerStatus.usb = axp.isVBUSPlug(); + powerStatus.charging = axp.isChargeing(); +} +#endif // T_BEAM_V10 + /** * Init the power manager chip * @@ -167,9 +182,7 @@ void axp192Init() 1); axp.clearIRQ(); #endif - - isCharging = axp.isChargeing() ? 1 : 0; - isUSBPowered = axp.isVBUSPlug() ? 1 : 0; + readPowerStatus(); } else { DEBUG_MSG("AXP192 Begin FAIL\n"); } @@ -302,7 +315,7 @@ uint32_t ledBlinker() setLed(ledOn); // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that - return isCharging ? 1000 : (ledOn ? 2 : 1000); + return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000); } Periodic ledPeriodic(ledBlinker); @@ -310,18 +323,21 @@ Periodic ledPeriodic(ledBlinker); #if 0 // Turn off for now -uint32_t axpReads() +uint32_t axpDebugRead() { axp.debugCharging(); DEBUG_MSG("vbus current %f\n", axp.getVbusCurrent()); DEBUG_MSG("charge current %f\n", axp.getBattChargeCurrent()); DEBUG_MSG("bat voltage %f\n", axp.getBattVoltage()); DEBUG_MSG("batt pct %d\n", axp.getBattPercentage()); + DEBUG_MSG("is battery connected %d\n", axp.isBatteryConnect()); + DEBUG_MSG("is USB connected %d\n", axp.isVBUSPlug()); + DEBUG_MSG("is charging %d\n", axp.isChargeing()); return 30 * 1000; } -Periodic axpDebugOutput(axpReads); +Periodic axpDebugOutput(axpDebugRead); #endif void loop() @@ -330,7 +346,6 @@ void loop() powerFSM.run_machine(); gps.loop(); - screen.loop(); service.loop(); ledPeriodic.loop(); @@ -349,18 +364,16 @@ void loop() DEBUG_MSG("pmu irq!\n"); - isCharging = axp.isChargeing() ? 1 : 0; - isUSBPowered = axp.isVBUSPlug() ? 1 : 0; + readPowerStatus(); axp.clearIRQ(); } // FIXME AXP192 interrupt is not firing, remove this temporary polling of battery state - isCharging = axp.isChargeing() ? 1 : 0; - isUSBPowered = axp.isVBUSPlug() ? 1 : 0; -#endif + readPowerStatus(); +#endif // PMU_IRQ } -#endif +#endif // T_BEAM_V10 #ifdef BUTTON_PIN // if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of @@ -390,6 +403,14 @@ void loop() showingBootScreen = false; } + // Update the screen last, after we've figured out what to show. + screen.debug()->setNodeNumbersStatus(nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes()); + screen.debug()->setChannelNameStatus(channelSettings.name); + screen.debug()->setPowerStatus(powerStatus); + // TODO(#4): use something based on hdop to show GPS "signal" strength. + screen.debug()->setGPSStatus(gps.hasLock() ? "ok" : ":("); + screen.loop(); + // 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. // DEBUG_MSG("msecs %d\n", msecstosleep); diff --git a/src/power.h b/src/power.h new file mode 100644 index 00000000..9172592c --- /dev/null +++ b/src/power.h @@ -0,0 +1,18 @@ +#pragma once + +namespace meshtastic +{ + +/// Describes the state of the power system. +struct PowerStatus { + /// Whether we have a battery connected + bool haveBattery; + /// Battery voltage in mV, valid if haveBattery is true + int batteryVoltageMv; + /// Whether USB is connected + bool usb; + /// Whether we are charging the battery + bool charging; +}; + +} // namespace meshtastic diff --git a/src/screen.cpp b/src/screen.cpp index 6d06b020..8f81fbb6 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -135,15 +135,17 @@ static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char display->drawString(xo, yo, *f); xo += SCREEN_WIDTH / COLUMNS; // Wrap to next row, if needed. - if (++col > COLUMNS) { + if (++col >= COLUMNS) { xo = x; yo += FONT_HEIGHT; col = 0; } f++; } - - yo += FONT_HEIGHT; // include the last line in our total + if (col != 0) { + // Include last incomplete line in our total. + yo += FONT_HEIGHT; + } return yo; } @@ -375,36 +377,6 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ display->drawCircle(compassX, compassY, COMPASS_DIAM / 2); } -static void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -{ - display->setFont(ArialMT_Plain_10); - - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); - - static char usersStr[20]; - snprintf(usersStr, sizeof(usersStr), "Users %d/%d", nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes()); - - static char channelStr[20]; - snprintf(channelStr, sizeof(channelStr), "%s", channelSettings.name); - - // We don't show battery levels yet - for now just lie and show debug info - static char batStr[20]; - snprintf(batStr, sizeof(batStr), "Batt %x%%", (isCharging << 1) + isUSBPowered); - - static char gpsStr[20]; - if (myNodeInfo.has_gps) - snprintf(gpsStr, sizeof(gpsStr), "GPS %d%%", - 75); // FIXME, use something based on hdop - else - gpsStr[0] = '\0'; // Just show emptystring - - const char *fields[] = {batStr, gpsStr, usersStr, channelStr, NULL}; - uint32_t yo = drawRows(display, x, y, fields); - - display->drawLogBuffer(x, yo); -} - #if 0 void _screen_header() { @@ -467,6 +439,8 @@ void Screen::setup() ui.setFrameAnimation(SLIDE_LEFT); // Don't show the page swipe dots while in boot screen. ui.disableAllIndicators(); + // Store a pointer to Screen so we can get to it from static functions. + ui.getUiState()->userData = this; // Add frames. static FrameCallback bootFrames[] = {drawBootScreen}; @@ -573,6 +547,12 @@ void Screen::doTask() setPeriod(1000 / targetFramerate); } +void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + Screen *screen = reinterpret_cast(state->userData); + screen->debugInfo.drawFrame(display, state, x, y); +} + // restore our regular frame list void Screen::setFrames() { @@ -595,7 +575,10 @@ void Screen::setFrames() normalFrames[numframes++] = drawNodeInfo; // then the debug info - normalFrames[numframes++] = drawDebugInfo; + // + // Since frames are basic function pointers, we have to use a helper to + // call a method on debugInfo object. + normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline; ui.setFrames(normalFrames, numframes); ui.enableAllIndicators(); @@ -642,4 +625,42 @@ void Screen::handleOnPress() } } +void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setFont(ArialMT_Plain_10); + + // 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' : ' '); + } else { + snprintf(batStr, sizeof(batStr), "%s", powerStatus.usb ? "USB" : ""); + } + + if (!gpsStatus.empty()) { + snprintf(gpsStr, sizeof(gpsStr), "GPS %s", gpsStatus.c_str()); + } else { + gpsStr[0] = '\0'; // Just show empty string. + } + } + + const char *fields[] = {batStr, gpsStr, usersStr, channelStr, nullptr}; + uint32_t yo = drawRows(display, x, y, fields); + + display->drawLogBuffer(x, yo); +} + } // namespace meshtastic diff --git a/src/screen.h b/src/screen.h index 8ba86da6..05ab5333 100644 --- a/src/screen.h +++ b/src/screen.h @@ -7,14 +7,84 @@ #include "PeriodicTask.h" #include "TypedQueue.h" +#include "lock.h" +#include "power.h" namespace meshtastic { +// Forward declarations +class Screen; + +/// Handles gathering and displaying debug information. +class DebugInfo +{ + public: + DebugInfo(const DebugInfo &) = delete; + DebugInfo &operator=(const DebugInfo &) = delete; + + /// Sets user statistics. + void setNodeNumbersStatus(int online, int total) + { + LockGuard guard(&lock); + nodesOnline = online; + nodesTotal = total; + } + + /// Sets the name of the channel. + void setChannelNameStatus(const char *name) + { + LockGuard guard(&lock); + channelName = name; + } + + /// Sets battery/charging/etc status. + // + void setPowerStatus(const PowerStatus& status) + { + LockGuard guard(&lock); + powerStatus = status; + } + + /// Sets GPS status. + // + // If this function never gets called, we assume GPS does not exist on this + // device. + // TODO(girts): figure out what the format should be. + void setGPSStatus(const char *status) + { + LockGuard guard(&lock); + gpsStatus = status; + } + + private: + friend Screen; + + DebugInfo() {} + + /// Renders the debug screen. + void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + + int nodesOnline = 0; + int nodesTotal = 0; + + PowerStatus powerStatus; + + std::string channelName; + + std::string gpsStatus; + + /// Protects all of internal state. + Lock lock; +}; + /// Deals with showing things on the screen of the device. // // Other than setup(), this class is thread-safe. All state-changing calls are // queued and executed when the main loop calls us. +// +// This class is thread-safe (as long as drawFrame is not called multiple times +// simultaneously). class Screen : public PeriodicTask { public: @@ -66,6 +136,11 @@ class Screen : public PeriodicTask } } + /// Returns a handle to the DebugInfo screen. + // + // Use this handle to set things like battery status, user count, GPS status, etc. + DebugInfo *debug() { return &debugInfo; } + protected: /// Updates the UI. // @@ -108,7 +183,9 @@ class Screen : public PeriodicTask /// Rebuilds our list of frames (screens) to default ones. void setFrames(); - private: + /// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame. + static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + /// Queue of commands to execute in doTask. TypedQueue cmdQueue; /// Whether we are using a display @@ -118,6 +195,9 @@ class Screen : public PeriodicTask // Whether we are showing the regular screen (as opposed to booth screen or // Bluetooth PIN screen) bool showingNormalScreen = false; + + /// Holds state for debug information + DebugInfo debugInfo; /// Display device SSD1306Wire dispdev; /// UI helper for rendering to frames and switching between them