From 9b25818a50060f52196c01f9987ad0d083a32b8f Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 12 Aug 2020 15:51:57 -0700 Subject: [PATCH 1/3] fix #249: report battery levels even if no GPS lock @professr I noticed you added a "newStatus" observable to the GPS class. Do you remember why you didn't remove the old GPS status (which seemed to be dumber). Is it just because you didn't want to risk breaking MeshService? (I assume) In this change I removed the old Observable and all seems well (just using newStatus everywhere). --- src/gps/GPS.h | 3 ++- src/gps/NEMAGPS.cpp | 4 ---- src/gps/UBloxGPS.cpp | 1 - src/mesh/MeshService.cpp | 8 +++++--- src/mesh/MeshService.h | 6 ++++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 46b4a5c0..409c6da9 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -25,7 +25,7 @@ void readFromRTC(); * * When new data is available it will notify observers. */ -class GPS : public Observable +class GPS { protected: bool hasValidLocation = false; // default to false, until we complete our first read @@ -48,6 +48,7 @@ class GPS : public Observable virtual ~GPS() {} + /** We will notify this observable anytime GPS state has changed meaningfully */ Observable newStatus; /** diff --git a/src/gps/NEMAGPS.cpp b/src/gps/NEMAGPS.cpp index 1645a165..a1d848ad 100644 --- a/src/gps/NEMAGPS.cpp +++ b/src/gps/NEMAGPS.cpp @@ -66,10 +66,6 @@ void NEMAGPS::loop() // expect gps pos lat=37.520825, lon=-122.309162, alt=158 DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d, hdop=%f, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5); - - hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 - if (hasValidLocation) - notifyObservers(NULL); } // Notify any status instances that are observing us diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 0ed506f6..c7a8501c 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -160,7 +160,6 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s { if (hasValidLocation) { wantNewLocation = false; - notifyObservers(NULL); // ublox.powerOff(); } } else // we didn't get a location update, go back to sleep and hope the characters show up diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 38babd90..f5d0ac17 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -68,7 +68,8 @@ void MeshService::init() sendOwnerPeriod.setup(); nodeDB.init(); - gpsObserver.observe(gps); + assert(gps); + gpsObserver.observe(&gps->newStatus); packetReceivedObserver.observe(&router.notifyPacketReceived); } @@ -283,9 +284,8 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) sendToMesh(p); } -int MeshService::onGPSChanged(void *unused) +int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused) { - // DEBUG_MSG("got gps notify\n"); // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = router.allocForSending(); @@ -305,6 +305,8 @@ int MeshService::onGPSChanged(void *unused) pos.battery_level = powerStatus->getBatteryChargePercent(); updateBatteryLevel(pos.battery_level); + // DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.latitude_i, pos.time, pos.battery_level); + // We limit our GPS broadcasts to a max rate static uint32_t lastGpsSend; uint32_t now = timing::millis(); diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index f6e688e1..7e881077 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -4,6 +4,7 @@ #include #include +#include "GPSStatus.h" #include "MemoryPool.h" #include "MeshRadio.h" #include "MeshTypes.h" @@ -17,7 +18,8 @@ */ class MeshService { - CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); + CallbackObserver gpsObserver = + CallbackObserver(this, &MeshService::onGPSChanged); CallbackObserver packetReceivedObserver = CallbackObserver(this, &MeshService::handleFromRadio); @@ -85,7 +87,7 @@ class MeshService /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh /// returns 0 to allow futher processing - int onGPSChanged(void *arg); + int onGPSChanged(const meshtastic::GPSStatus *arg); /// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it needs /// to keep the packet around it makes a copy From 6a402b13fa5afea1924af9b01a241ff4638da361 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 12 Aug 2020 17:03:36 -0700 Subject: [PATCH 2/3] Add battery sensing (mostly) for TBEAM0.7 However, disabled until someone with suitable hardware can test and report back. @slavino and @tschundler would you be willing to try it with your boards? You'll need to uncomment the following line in configuration.h // #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage --- platformio.ini | 6 +- src/Power.cpp | 154 ++++++++++++++++++++++++++++++++------------ src/configuration.h | 4 +- src/main.cpp | 2 - src/power.h | 22 +++---- 5 files changed, 131 insertions(+), 57 deletions(-) diff --git a/platformio.ini b/platformio.ini index f3a4db2a..1b11e1cb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +default_envs = tbeam [common] ; common is not currently used @@ -69,7 +69,8 @@ lib_deps = https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git https://github.com/meshtastic/RadioLib.git#d6b12f7eb0a06bd2414c79b437b25d377e3f603f https://github.com/meshtastic/TinyGPSPlus.git - + https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460 + ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] platform = espressif32 @@ -99,7 +100,6 @@ extends = esp32_base board = ttgo-t-beam lib_deps = ${env.lib_deps} - https://github.com/meshtastic/AXP202X_Library.git build_flags = ${esp32_base.build_flags} -D TBEAM_V10 diff --git a/src/Power.cpp b/src/Power.cpp index 23332698..7bfa42d2 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1,27 +1,92 @@ #include "power.h" #include "PowerFSM.h" #include "main.h" -#include "utils.h" #include "sleep.h" - -#ifdef TBEAM_V10 +#include "utils.h" // FIXME. nasty hack cleanup how we load axp192 #undef AXP192_SLAVE_ADDRESS #include "axp20x.h" + +#ifdef TBEAM_V10 AXP20X_Class axp; +#endif + bool pmu_irq = false; Power *power; -bool Power::setup() +/** + * If this board has a battery level sensor, set this to a valid implementation + */ +static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor + +/** + * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input + */ +class AnalogBatteryLevel : public HasBatteryLevel { + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + * + * FIXME - use a lipo lookup table, the current % full is super wrong + */ + virtual int getBattPercentage() + { + float v = getBattVoltage(); - axp192Init(); - concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device - setPeriod(1); + if (v < 2.1) + return -1; - return axp192_found; + return 100 * (getBattVoltage() - 3.27) / (4.2 - 3.27); + } + + /** + * The raw voltage of the battery or NAN if unknown + */ + virtual float getBattVoltage() + { + return +#ifdef BATTERY_PIN + analogRead(BATTERY_PIN) * 2.0 * (3.3 / 1024.0); +#else + NAN; +#endif + } + + /** + * return true if there is a battery installed in this unit + */ + virtual bool isBatteryConnect() { return true; } +} analogLevel; + +bool Power::analogInit() +{ +#ifdef BATTERY_PIN + DEBUG_MSG("Using analog input for battery level\n"); + adcAttachPin(BATTERY_PIN); + // adcStart(BATTERY_PIN); + analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution. + batteryLevel = &analogLevel; + return true; +#else + return false; +#endif +} + +bool Power::setup() +{ + bool found = axp192Init(); + + if (!found) { + found = analogInit(); + } + if (found) { + concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device + setPeriod(1); + } + + return found; } /// Reads power status to powerStatus singleton. @@ -29,42 +94,45 @@ bool Power::setup() // TODO(girts): move this and other axp stuff to power.h/power.cpp. void Power::readPowerStatus() { - bool hasBattery = axp.isBatteryConnect(); - int batteryVoltageMv = 0; - uint8_t batteryChargePercent = 0; - if (hasBattery) { - batteryVoltageMv = axp.getBattVoltage(); - // If the AXP192 returns a valid battery percentage, use it - if (axp.getBattPercentage() >= 0) { - 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 - batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100); + if (batteryLevel) { + bool hasBattery = batteryLevel->isBatteryConnect(); + int batteryVoltageMv = 0; + uint8_t batteryChargePercent = 0; + if (hasBattery) { + batteryVoltageMv = batteryLevel->getBattVoltage(); + // If the AXP192 returns a valid battery percentage, use it + if (batteryLevel->getBattPercentage() >= 0) { + batteryChargePercent = batteryLevel->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 + batteryChargePercent = + clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), + 0, 100); + } } + + // Notify any status instances that are observing us + const meshtastic::PowerStatus powerStatus = meshtastic::PowerStatus( + hasBattery, batteryLevel->isVBUSPlug(), batteryLevel->isChargeing(), batteryVoltageMv, batteryChargePercent); + newStatus.notifyObservers(&powerStatus); + + // If we have a battery at all and it is less than 10% full, force deep sleep + if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() && batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS) + powerFSM.trigger(EVENT_LOW_BATTERY); } - - // Notify any status instances that are observing us - const meshtastic::PowerStatus powerStatus = meshtastic::PowerStatus(hasBattery, axp.isVBUSPlug(), axp.isChargeing(), batteryVoltageMv, batteryChargePercent); - newStatus.notifyObservers(&powerStatus); - - // If we have a battery at all and it is less than 10% full, force deep sleep - if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() && - axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) - powerFSM.trigger(EVENT_LOW_BATTERY); } -void Power::doTask() +void Power::doTask() { readPowerStatus(); - + // Only read once every 20 seconds once the power status for the app has been initialized - if(statusHandler && statusHandler->isInitialized()) + if (statusHandler && statusHandler->isInitialized()) setPeriod(1000 * 20); } -#endif // TBEAM_V10 -#ifdef AXP192_SLAVE_ADDRESS /** * Init the power manager chip * @@ -74,10 +142,13 @@ void Power::doTask() 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS */ -void Power::axp192Init() +bool Power::axp192Init() { +#ifdef TBEAM_V10 if (axp192_found) { if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) { + batteryLevel = &axp; + DEBUG_MSG("AXP192 Begin PASS\n"); // axp.setChgLEDMode(LED_BLINK_4HZ); @@ -135,12 +206,16 @@ void Power::axp192Init() } else { DEBUG_MSG("AXP192 not found\n"); } -} + + return axp192_found; +#else + return false; #endif +} -void Power::loop() + +void Power::loop() { - #ifdef PMU_IRQ if (pmu_irq) { pmu_irq = false; @@ -174,6 +249,5 @@ void Power::loop() axp.clearIRQ(); } -#endif // T_BEAM_V10 - +#endif } diff --git a/src/configuration.h b/src/configuration.h index 89e8ef81..613f20d8 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -167,7 +167,6 @@ along with this program. If not, see . // Leave undefined to disable our PMU IRQ handler #define PMU_IRQ 35 - #define AXP192_SLAVE_ADDRESS 0x34 #elif defined(TBEAM_V07) @@ -180,6 +179,7 @@ along with this program. If not, see . #define I2C_SCL 22 #define BUTTON_PIN 39 +// #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #ifndef USE_JTAG #define RF95_RESET 23 @@ -278,6 +278,8 @@ along with this program. If not, see . #define GPS_RX_PIN 36 #define GPS_TX_PIN 39 +// #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage + #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 diff --git a/src/main.cpp b/src/main.cpp index b8ee0021..3cc4097d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -210,13 +210,11 @@ void setup() esp32Setup(); #endif -#ifdef TBEAM_V10 // Currently only the tbeam has a PMU power = new Power(); power->setup(); power->setStatusHandler(powerStatus); powerStatus->observe(&power->newStatus); -#endif #ifdef NRF52_SERIES nrf52Setup(); diff --git a/src/power.h b/src/power.h index dd6b8ce8..a779089a 100644 --- a/src/power.h +++ b/src/power.h @@ -1,6 +1,6 @@ #pragma once -#include "concurrency/PeriodicTask.h" #include "PowerStatus.h" +#include "concurrency/PeriodicTask.h" /** * Per @spattinson @@ -18,23 +18,23 @@ class Power : public concurrency::PeriodicTask { - public: - + public: Observable newStatus; void readPowerStatus(); void loop(); virtual bool setup(); virtual void doTask(); - void setStatusHandler(meshtastic::PowerStatus *handler) - { - statusHandler = handler; - } - - protected: - meshtastic::PowerStatus *statusHandler; - virtual void axp192Init(); + void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; } + protected: + meshtastic::PowerStatus *statusHandler; + + /// Setup a axp192, return true if found + bool axp192Init(); + + /// Setup a simple ADC input based battery sensor + bool analogInit(); }; extern Power *power; \ No newline at end of file From 8c7aa07c7000fda9042fc62ffca0b73547198891 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 12 Aug 2020 17:10:59 -0700 Subject: [PATCH 3/3] Only do AXP debugging on ESP32 targets --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 1b11e1cb..56efa748 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,7 +32,6 @@ board_build.partitions = partition-table.csv ; note: we add src to our include search path so that lmic_project_config can override ; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map - -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${sysenv.COUNTRY} -DAPP_VERSION=${sysenv.APP_VERSION} -DHW_VERSION=${sysenv.HW_VERSION} @@ -81,6 +80,7 @@ debug_init_break = tbreak setup build_flags = ${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue -lnimble -std=c++11 -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + -DAXP_DEBUG_PORT=Serial # Hmm - this doesn't work yet # board_build.ldscript = linker/esp32.extram.bss.ld lib_ignore = segger_rtt