From 542b8b26ce35962a9b511ca1e93c5c0df2a4ac60 Mon Sep 17 00:00:00 2001 From: Professr Date: Sat, 27 Jun 2020 21:19:49 -0700 Subject: [PATCH 1/3] Abstracted statuses, made display event-driven --- src/GPSStatus.h | 79 +++++++++++++++++ src/NodeStatus.h | 62 ++++++++++++++ src/Power.cpp | 178 +++++++++++++++++++++++++++++++++++++++ src/PowerStatus.h | 77 +++++++++++++++++ src/esp32/main-esp32.cpp | 148 -------------------------------- src/gps/GPS.h | 3 + src/gps/NEMAGPS.cpp | 8 ++ src/gps/UBloxGPS.cpp | 10 ++- src/main.cpp | 26 ++++-- src/main.h | 8 ++ src/mesh/MeshService.cpp | 2 +- src/mesh/NodeDB.cpp | 3 +- src/mesh/NodeDB.h | 12 +++ src/power.h | 37 ++++---- src/screen.cpp | 131 ++++++++++++++++------------ src/screen.h | 47 +++-------- 16 files changed, 565 insertions(+), 266 deletions(-) create mode 100644 src/GPSStatus.h create mode 100644 src/NodeStatus.h create mode 100644 src/Power.cpp create mode 100644 src/PowerStatus.h diff --git a/src/GPSStatus.h b/src/GPSStatus.h new file mode 100644 index 00000000..b6e7cf3a --- /dev/null +++ b/src/GPSStatus.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include "lock.h" +#include "configuration.h" + +namespace meshtastic { + +/// Describes the state of the GPS system. +struct GPSStatus +{ + + bool isDirty = false; + bool hasLock = false; // default to false, until we complete our first read + bool isConnected = false; // Do we have a GPS we are talking to + + int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double + int32_t altitude = 0; + uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs scaling before use) + +}; + +class GPSStatusHandler +{ + + private: + GPSStatus status; + CallbackObserver gpsObserver = CallbackObserver(this, &GPSStatusHandler::updateStatus); + bool initialized = false; + /// Protects all of internal state. + Lock lock; + + public: + Observable onNewStatus; + + void observe(Observable *source) + { + gpsObserver.observe(source); + } + + bool isInitialized() { LockGuard guard(&lock); return initialized; } + bool hasLock() { LockGuard guard(&lock); return status.hasLock; } + bool isConnected() { LockGuard guard(&lock); return status.isConnected; } + int32_t getLatitude() { LockGuard guard(&lock); return status.latitude; } + int32_t getLongitude() { LockGuard guard(&lock); return status.longitude; } + int32_t getAltitude() { LockGuard guard(&lock); return status.altitude; } + uint32_t getDOP() { LockGuard guard(&lock); return status.dop; } + + int updateStatus(const GPSStatus newStatus) { + // Only update the status if values have actually changed + status.isDirty = ( + newStatus.hasLock != status.hasLock || + newStatus.isConnected != status.isConnected || + newStatus.latitude != status.latitude || + newStatus.longitude != status.longitude || + newStatus.altitude != status.altitude || + newStatus.dop != status.latitude + ); + { + LockGuard guard(&lock); + initialized = true; + status.hasLock = newStatus.hasLock; + status.isConnected = newStatus.isConnected; + status.latitude = newStatus.latitude; + status.longitude = newStatus.longitude; + status.altitude = newStatus.altitude; + status.dop = newStatus.dop; + } + if(status.isDirty) { + DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f\n", status.latitude * 1e-7, status.longitude * 1e-7, status.altitude, status.dop * 1e-2); + onNewStatus.notifyObservers(NULL); + } + return 0; + } + +}; + +} + +extern meshtastic::GPSStatus gpsStatus; \ No newline at end of file diff --git a/src/NodeStatus.h b/src/NodeStatus.h new file mode 100644 index 00000000..b8429b94 --- /dev/null +++ b/src/NodeStatus.h @@ -0,0 +1,62 @@ +#pragma once +#include +#include "lock.h" +#include "configuration.h" + +namespace meshtastic { + +/// Describes the state of the GPS system. +struct NodeStatus +{ + + bool isDirty = false; + size_t numOnline; // Number of nodes online + size_t numTotal; // Number of nodes total + +}; + +class NodeStatusHandler +{ + + private: + NodeStatus status; + CallbackObserver nodeObserver = CallbackObserver(this, &NodeStatusHandler::updateStatus); + bool initialized = false; + /// Protects all of internal state. + Lock lock; + + public: + Observable onNewStatus; + + void observe(Observable *source) + { + nodeObserver.observe(source); + } + + bool isInitialized() const { return initialized; } + size_t getNumOnline() const { return status.numOnline; } + size_t getNumTotal() const { return status.numTotal; } + + int updateStatus(const NodeStatus newStatus) + { + // Only update the status if values have actually changed + status.isDirty = ( + newStatus.numOnline != status.numOnline || + newStatus.numTotal != status.numTotal + ); + LockGuard guard(&lock); + initialized = true; + status.numOnline = newStatus.numOnline; + status.numTotal = newStatus.numTotal; + if(status.isDirty) { + DEBUG_MSG("Node status update: %d online, %d total\n", status.numOnline, status.numTotal); + onNewStatus.notifyObservers(NULL); + } + return 0; + } + +}; + +} + +extern meshtastic::NodeStatus nodeStatus; \ No newline at end of file diff --git a/src/Power.cpp b/src/Power.cpp new file mode 100644 index 00000000..63a4566d --- /dev/null +++ b/src/Power.cpp @@ -0,0 +1,178 @@ +#include "Power.h" +#include "PowerFSM.h" +#include "main.h" +#include "utils.h" +#include "sleep.h" + + +#ifdef TBEAM_V10 + +// FIXME. nasty hack cleanup how we load axp192 +#undef AXP192_SLAVE_ADDRESS +#include "axp20x.h" +AXP20X_Class axp; +bool pmu_irq = false; + +Power *power; + +bool Power::setup() +{ + + axp192Init(); + PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device + setPeriod(1); + + return axp192_found; +} + +/// Reads power status to powerStatus singleton. +// +// TODO(girts): move this and other axp stuff to power.h/power.cpp. +void Power::readPowerStatus() +{ + meshtastic::PowerStatus status; + status.hasBattery = axp.isBatteryConnect(); + if (status.hasBattery) { + status.batteryVoltageMv = axp.getBattVoltage(); + // If the AXP192 returns a valid battery percentage, use it + if (axp.getBattPercentage() >= 0) { + status.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 + status.batteryChargePercent = clamp((int)(((status.batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100); + } + } + status.hasUSB = axp.isVBUSPlug(); + status.isCharging = axp.isChargeing(); + newStatus.notifyObservers(status); + + // If we have a battery at all and it is less than 10% full, force deep sleep + if (status.hasBattery && !status.hasUSB && + axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) + powerFSM.trigger(EVENT_LOW_BATTERY); +} + +void Power::doTask() +{ + readPowerStatus(); + + // Only read once every 20 seconds once the power status for the app has been initialized + if(statusHandler && statusHandler->isInitialized()) + setPeriod(1000 * 20); +} +#endif // TBEAM_V10 + +#ifdef AXP192_SLAVE_ADDRESS +/** + * Init the power manager chip + * + * axp192 power + DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 + share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1 + 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() +{ + if (axp192_found) { + if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) { + DEBUG_MSG("AXP192 Begin PASS\n"); + + // axp.setChgLEDMode(LED_BLINK_4HZ); + DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("----------------------------------------\n"); + + axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio + axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power + axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON); + axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON); + axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON); + axp.setDCDC1Voltage(3300); // for the OLED power + + DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); + + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA +#if 0 + + // Not connected + //val = 0xfc; + //axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection + + //not used + //val = 0x46; + //axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection +#endif + axp.debugCharging(); + +#ifdef PMU_IRQ + pinMode(PMU_IRQ, INPUT); + attachInterrupt( + PMU_IRQ, [] { pmu_irq = true; }, FALLING); + + axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1); + axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ | + AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, + 1); + + axp.clearIRQ(); +#endif + readPowerStatus(); + } else { + DEBUG_MSG("AXP192 Begin FAIL\n"); + } + } else { + DEBUG_MSG("AXP192 not found\n"); + } +} +#endif + +void Power::loop() +{ + +#ifdef PMU_IRQ + if (pmu_irq) { + pmu_irq = false; + axp.readIRQ(); + + DEBUG_MSG("pmu irq!\n"); + + if (axp.isChargingIRQ()) { + DEBUG_MSG("Battery start charging\n"); + } + if (axp.isChargingDoneIRQ()) { + DEBUG_MSG("Battery fully charged\n"); + } + if (axp.isVbusRemoveIRQ()) { + DEBUG_MSG("USB unplugged\n"); + } + if (axp.isVbusPlugInIRQ()) { + DEBUG_MSG("USB plugged In\n"); + } + if (axp.isBattPlugInIRQ()) { + DEBUG_MSG("Battery inserted\n"); + } + if (axp.isBattRemoveIRQ()) { + DEBUG_MSG("Battery removed\n"); + } + if (axp.isPEKShortPressIRQ()) { + DEBUG_MSG("PEK short button press\n"); + } + + readPowerStatus(); + axp.clearIRQ(); + } + +#endif // T_BEAM_V10 + +} diff --git a/src/PowerStatus.h b/src/PowerStatus.h new file mode 100644 index 00000000..e5deb727 --- /dev/null +++ b/src/PowerStatus.h @@ -0,0 +1,77 @@ +#pragma once +#include "lock.h" +#include "configuration.h" + +namespace meshtastic +{ + +/// Describes the state of the power system. +struct PowerStatus +{ + + /// Whether or not values have changed since last read + bool isDirty = false; + /// Whether we have a battery connected + bool hasBattery; + /// 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 hasUSB; + /// Whether we are charging the battery + bool isCharging; + +}; + +class PowerStatusHandler +{ + + private: + PowerStatus status; + CallbackObserver powerObserver = CallbackObserver(this, &PowerStatusHandler::updateStatus); + bool initialized = false; + /// Protects all of internal state. + Lock lock; + + public: + + Observable onNewStatus; + + void observe(Observable *source) + { + powerObserver.observe(source); + } + + bool isInitialized() { LockGuard guard(&lock); return initialized; } + bool isCharging() { LockGuard guard(&lock); return status.isCharging; } + bool hasUSB() { LockGuard guard(&lock); return status.hasUSB; } + bool hasBattery() { LockGuard guard(&lock); return status.hasBattery; } + int getBatteryVoltageMv() { LockGuard guard(&lock); return status.batteryVoltageMv; } + int getBatteryChargePercent() { LockGuard guard(&lock); return status.batteryChargePercent; } + + int updateStatus(const PowerStatus newStatus) { + // Only update the status if values have actually changed + status.isDirty = ( + newStatus.hasBattery != status.hasBattery || + newStatus.hasUSB != status.hasUSB || + newStatus.batteryVoltageMv != status.batteryVoltageMv + ); + LockGuard guard(&lock); + initialized = true; + status.hasBattery = newStatus.hasBattery; + status.batteryVoltageMv = newStatus.batteryVoltageMv; + status.batteryChargePercent = newStatus.batteryChargePercent; + status.hasUSB = newStatus.hasUSB; + status.isCharging = newStatus.isCharging; + if(status.isDirty) { + DEBUG_MSG("Battery %dmV %d%%\n", status.batteryVoltageMv, status.batteryChargePercent); + onNewStatus.notifyObservers(this); + } + return 0; + } +}; + +} // namespace meshtastic + +extern meshtastic::PowerStatus powerStatus; diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index e0d53eab..3b0b56d7 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -3,7 +3,6 @@ #include "PowerFSM.h" #include "configuration.h" #include "main.h" -#include "power.h" #include "sleep.h" #include "utils.h" #include "target_specific.h" @@ -60,111 +59,6 @@ void getMacAddr(uint8_t *dmac) assert(esp_efuse_mac_get_default(dmac) == ESP_OK); } -#ifdef TBEAM_V10 - -// FIXME. nasty hack cleanup how we load axp192 -#undef AXP192_SLAVE_ADDRESS -#include "axp20x.h" -AXP20X_Class axp; -bool pmu_irq = false; - -/// 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(); - // 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 - powerStatus.batteryChargePercent = clamp((int)(((powerStatus.batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100); - } - DEBUG_MSG("Battery %dmV %d%%\n", powerStatus.batteryVoltageMv, powerStatus.batteryChargePercent); - } - powerStatus.usb = axp.isVBUSPlug(); - powerStatus.charging = axp.isChargeing(); -} -#endif // TBEAM_V10 - -#ifdef AXP192_SLAVE_ADDRESS -/** - * Init the power manager chip - * - * axp192 power - DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 - share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1 - 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 axp192Init() -{ - if (axp192_found) { - if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) { - DEBUG_MSG("AXP192 Begin PASS\n"); - - // axp.setChgLEDMode(LED_BLINK_4HZ); - DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("----------------------------------------\n"); - - axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio - axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power - axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON); - axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON); - axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON); - axp.setDCDC1Voltage(3300); // for the OLED power - - DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); - - axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA -#if 0 - - // Not connected - //val = 0xfc; - //axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection - - //not used - //val = 0x46; - //axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection -#endif - axp.debugCharging(); - -#ifdef PMU_IRQ - pinMode(PMU_IRQ, INPUT); - attachInterrupt( - PMU_IRQ, [] { pmu_irq = true; }, FALLING); - - axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1); - axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ | - AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, - 1); - - axp.clearIRQ(); -#endif - readPowerStatus(); - } else { - DEBUG_MSG("AXP192 Begin FAIL\n"); - } - } else { - DEBUG_MSG("AXP192 not found\n"); - } -} -#endif - /* static void printBLEinfo() { int dev_num = esp_ble_get_bond_device_num(); @@ -190,9 +84,6 @@ void esp32Setup() // enableModemSleep(); -#ifdef AXP192_SLAVE_ADDRESS - axp192Init(); -#endif } #if 0 @@ -223,43 +114,4 @@ void esp32Loop() // for debug printing // radio.radioIf.canSleep(); - -#ifdef PMU_IRQ - if (pmu_irq) { - pmu_irq = false; - axp.readIRQ(); - - DEBUG_MSG("pmu irq!\n"); - - if (axp.isChargingIRQ()) { - DEBUG_MSG("Battery start charging\n"); - } - if (axp.isChargingDoneIRQ()) { - DEBUG_MSG("Battery fully charged\n"); - } - if (axp.isVbusRemoveIRQ()) { - DEBUG_MSG("USB unplugged\n"); - } - if (axp.isVbusPlugInIRQ()) { - DEBUG_MSG("USB plugged In\n"); - } - if (axp.isBattPlugInIRQ()) { - DEBUG_MSG("Battery inserted\n"); - } - if (axp.isBattRemoveIRQ()) { - DEBUG_MSG("Battery removed\n"); - } - if (axp.isPEKShortPressIRQ()) { - DEBUG_MSG("PEK short button press\n"); - } - - readPowerStatus(); - axp.clearIRQ(); - } - - if (powerStatus.haveBattery && !powerStatus.usb && - axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep - powerFSM.trigger(EVENT_LOW_BATTERY); - -#endif // T_BEAM_V10 } \ No newline at end of file diff --git a/src/gps/GPS.h b/src/gps/GPS.h index b2d1916d..d03bd350 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -1,6 +1,7 @@ #pragma once #include "Observer.h" +#include "GPSStatus.h" #include "PeriodicTask.h" #include "sys/time.h" @@ -40,6 +41,8 @@ class GPS : public Observable virtual ~GPS() {} + Observable newStatus; + /** * Returns true if we succeeded */ diff --git a/src/gps/NEMAGPS.cpp b/src/gps/NEMAGPS.cpp index baba0424..b13fa9b9 100644 --- a/src/gps/NEMAGPS.cpp +++ b/src/gps/NEMAGPS.cpp @@ -65,5 +65,13 @@ void NEMAGPS::loop() if (hasValidLocation) notifyObservers(NULL); } + meshtastic::GPSStatus status; + status.hasLock = hasLock(); + status.isConnected = isConnected; + status.latitude = latitude; + status.longitude = longitude; + status.altitude = altitude; + status.dop = dop; + newStatus.notifyObservers(status); } } \ No newline at end of file diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 01221129..199657e8 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -116,7 +116,6 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s longitude = ublox.getLongitude(0); altitude = ublox.getAltitude(0) / 1000; // in mm convert to meters dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it - DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d, pdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2); // bogus lat lon is reported as 0 or 0 (can be bogus just for one) // Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg! @@ -129,6 +128,15 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s } else // we didn't get a location update, go back to sleep and hope the characters show up wantNewLocation = true; + meshtastic::GPSStatus status; + status.hasLock = hasLock(); + status.isConnected = isConnected; + status.latitude = latitude; + status.longitude = longitude; + status.altitude = altitude; + status.dop = dop; + newStatus.notifyObservers(status); + // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over // the serial setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); diff --git a/src/main.cpp b/src/main.cpp index 667c1069..0dac6af8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,7 @@ #include "UBloxGPS.h" #include "configuration.h" #include "error.h" -#include "power.h" +#include "Power.h" // #include "rom/rtc.h" #include "DSRRouter.h" #include "debug.h" @@ -56,8 +56,14 @@ // We always create a screen object, but we only init it if we find the hardware meshtastic::Screen screen(SSD1306_ADDRESS); -// Global power status singleton -meshtastic::PowerStatus powerStatus; +// Global power status +meshtastic::PowerStatusHandler *powerStatusHandler = new meshtastic::PowerStatusHandler(); + +// Global GPS status +meshtastic::GPSStatusHandler *gpsStatusHandler = new meshtastic::GPSStatusHandler(); + +// Global Node status +meshtastic::NodeStatusHandler *nodeStatusHandler = new meshtastic::NodeStatusHandler(); bool ssd1306_found; bool axp192_found; @@ -121,7 +127,7 @@ static 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 powerStatus.charging ? 1000 : (ledOn ? 2 : 1000); + return powerStatusHandler->isCharging() ? 1000 : (ledOn ? 2 : 1000); } Periodic ledPeriodic(ledBlinker); @@ -223,6 +229,10 @@ void setup() ssd1306_found = false; // forget we even have the hardware esp32Setup(); + power = new Power(); + power->setup(); + power->setStatusHandler(powerStatusHandler); + powerStatusHandler->observe(&power->newStatus); #endif #ifdef NRF52_SERIES @@ -253,9 +263,10 @@ void setup() gps = new NEMAGPS(); gps->setup(); #endif + gpsStatusHandler->observe(&gps->newStatus); + nodeStatusHandler->observe(&nodeDB.newStatus); service.init(); - #ifndef NO_ESP32 // Must be after we init the service, because the wifi settings are loaded by NodeDB (oops) initWifi(); @@ -295,6 +306,7 @@ void setup() // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS + // setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state setCPUFast(false); // 80MHz is fine for our slow peripherals } @@ -340,6 +352,7 @@ void loop() #ifndef NO_ESP32 esp32Loop(); + power->loop(); #endif #ifdef BUTTON_PIN @@ -365,9 +378,8 @@ void loop() #endif // 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); + //screen.debug()->setPowerStatus(powerStatus); // 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. diff --git a/src/main.h b/src/main.h index 9d0cde8d..26e63cf7 100644 --- a/src/main.h +++ b/src/main.h @@ -1,6 +1,9 @@ #pragma once #include "screen.h" +#include "PowerStatus.h" +#include "GPSStatus.h" +#include "NodeStatus.h" extern bool axp192_found; extern bool ssd1306_found; @@ -9,6 +12,11 @@ extern bool isUSBPowered; // Global Screen singleton. extern meshtastic::Screen screen; +extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class + +extern meshtastic::PowerStatusHandler *powerStatusHandler; +extern meshtastic::GPSStatusHandler *gpsStatusHandler; +extern meshtastic::NodeStatusHandler *nodeStatusHandler; // Return a human readable string of the form "Meshtastic_ab13" const char *getDeviceName(); diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 70e20f80..83739b32 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -303,7 +303,7 @@ int MeshService::onGPSChanged(void *unused) } // Include our current battery voltage in our position announcement - pos.battery_level = powerStatus.batteryChargePercent; + pos.battery_level = powerStatusHandler->getBatteryChargePercent(); updateBatteryLevel(pos.battery_level); // We limit our GPS broadcasts to a max rate diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index ca28c17a..00d3f569 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -342,8 +342,7 @@ void NodeDB::updateFrom(const MeshPacket &mp) int oldNumNodes = *numNodes; NodeInfo *info = getOrCreateNode(mp.from); - if (oldNumNodes != *numNodes) - updateGUI = true; // we just created a nodeinfo + notifyObservers(); if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen info->has_position = true; // at least the time is valid diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 611024e2..12a023d6 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -2,9 +2,11 @@ #include #include +#include "Observer.h" #include "MeshTypes.h" #include "mesh-pb-constants.h" +#include "NodeStatus.h" extern DeviceState devicestate; extern MyNodeInfo &myNodeInfo; @@ -32,6 +34,7 @@ class NodeDB bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI bool updateTextMessage = false; // if true, the GUI should show a new text message + Observable newStatus; /// don't do mesh based algoritm for node id assignment (initially) /// instead just store in flash - possibly even in the initial alpha release do this hack @@ -91,6 +94,15 @@ class NodeDB /// Find a node in our DB, create an empty NodeInfo if missing NodeInfo *getOrCreateNode(NodeNum n); + /// Notify observers of changes to the DB + void notifyObservers() { + // Notify observers of the current node state + meshtastic::NodeStatus status; + status.numOnline = getNumOnlineNodes(); + status.numTotal = getNumNodes(); + newStatus.notifyObservers(status); + } + /// read our db from flash void loadFromDisk(); diff --git a/src/power.h b/src/power.h index 21c913d5..63929cc2 100644 --- a/src/power.h +++ b/src/power.h @@ -1,4 +1,6 @@ #pragma once +#include "PeriodicTask.h" +#include "PowerStatus.h" /** * Per @spattinson @@ -13,23 +15,26 @@ #define BAT_MILLIVOLTS_FULL 4100 #define BAT_MILLIVOLTS_EMPTY 3500 -namespace meshtastic +class Power : public PeriodicTask { -/// 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; - /// Battery charge percentage, either read directly or estimated - int batteryChargePercent; - /// Whether USB is connected - bool usb; - /// Whether we are charging the battery - bool charging; + public: + + Observable newStatus; + + void readPowerStatus(); + void loop(); + virtual bool setup(); + virtual void doTask(); + void setStatusHandler(meshtastic::PowerStatusHandler *handler) + { + statusHandler = handler; + } + + protected: + meshtastic::PowerStatusHandler *statusHandler; + virtual void axp192Init(); + }; -} // namespace meshtastic - -extern meshtastic::PowerStatus powerStatus; +extern Power *power; \ No newline at end of file diff --git a/src/screen.cpp b/src/screen.cpp index f64f3784..d655a924 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -42,7 +42,7 @@ along with this program. If not, see . #endif #define SCREEN_HEIGHT 64 #define TRANSITION_FRAMERATE 30 // fps -#define IDLE_FRAMERATE 10 // in fps +#define IDLE_FRAMERATE 1 // in fps #define COMPASS_DIAM 44 #define NUM_EXTRA_FRAMES 2 // text message and debug frame @@ -56,6 +56,7 @@ 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 bool heartbeat = false; static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -142,38 +143,40 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char * } } -/// Draw a series of fields in a row, wrapping to multiple rows if needed -/// @return the max y we ended up printing to -static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) -{ - // The coordinates define the left starting point of the text - display->setTextAlignment(TEXT_ALIGN_LEFT); +#if 0 + /// Draw a series of fields in a row, wrapping to multiple rows if needed + /// @return the max y we ended up printing to + static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields) + { + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); - const char **f = fields; - int xo = x, yo = y; - const int COLUMNS = 2; // hardwired for two columns per row.... - int col = 0; // track which column we are on - while (*f) { - display->drawString(xo, yo, *f); - xo += SCREEN_WIDTH / COLUMNS; - // Wrap to next row, if needed. - if (++col >= COLUMNS) { - xo = x; - yo += FONT_HEIGHT; - col = 0; + const char **f = fields; + int xo = x, yo = y; + const int COLUMNS = 2; // hardwired for two columns per row.... + int col = 0; // track which column we are on + while (*f) { + display->drawString(xo, yo, *f); + xo += SCREEN_WIDTH / COLUMNS; + // Wrap to next row, if needed. + if (++col >= COLUMNS) { + xo = x; + yo += FONT_HEIGHT; + col = 0; + } + f++; + } + if (col != 0) { + // Include last incomplete line in our total. + yo += FONT_HEIGHT; } - f++; - } - if (col != 0) { - // Include last incomplete line in our total. - yo += FONT_HEIGHT; - } - return yo; -} + return yo; + } +#endif // 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) { +static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, PowerStatusHandler *powerStatusHandler) { 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 @@ -181,36 +184,36 @@ static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *img imgBuffer[i] = 0x81; } // If charging, draw a charging indicator - if (powerStatus->charging) { + if (powerStatusHandler->isCharging()) { memcpy(imgBuffer + 3, lightning, 8); // If not charging, Draw power bars } else { for (int i = 0; i < 4; i++) { - if(powerStatus->batteryChargePercent >= 25 * i) memcpy(imgBuffer + 1 + (i * 3), powerBar, 3); + if(powerStatusHandler->getBatteryChargePercent() >= 25 * i) 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) { +static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatusHandler *nodeStatusHandler) { char usersString[20]; - sprintf(usersString, "%d/%d", nodesOnline, nodesTotal); + sprintf(usersString, "%d/%d", nodeStatusHandler->getNumOnline(), nodeStatusHandler->getNumTotal()); 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; } +static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, GPSStatusHandler *gpsStatusHandler) { + if(!gpsStatusHandler->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; } + if(!gpsStatusHandler->hasLock()) { display->drawString(x + 8, y - 2, "No sats"); return; } + if(gpsStatusHandler->getDOP() <= 100) { display->drawString(x + 8, y - 2, "Ideal"); return; } + if(gpsStatusHandler->getDOP() <= 200) { display->drawString(x + 8, y - 2, "Exc."); return; } + if(gpsStatusHandler->getDOP() <= 500) { display->drawString(x + 8, y - 2, "Good"); return; } + if(gpsStatusHandler->getDOP() <= 1000) { display->drawString(x + 8, y - 2, "Mod."); return; } + if(gpsStatusHandler->getDOP() <= 2000) { display->drawString(x + 8, y - 2, "Fair"); return; } + if(gpsStatusHandler->getDOP() > 0) { display->drawString(x + 8, y - 2, "Poor"); return; } } /// Ported from my old java code, returns distance in meters along the globe @@ -545,6 +548,11 @@ void Screen::setup() // twice initially. ui.update(); ui.update(); + + // Subscribe to status updates + powerStatusObserver.observe(&powerStatusHandler->onNewStatus); + gpsStatusObserver.observe(&gpsStatusHandler->onNewStatus); + nodeStatusObserver.observe(&nodeStatusHandler->onNewStatus); } void Screen::doTask() @@ -606,14 +614,7 @@ void Screen::doTask() // While showing the bootscreen or Bluetooth pair screen all of our // standard screen switching is stopped. if (showingNormalScreen) { - // TODO(girts): decouple nodeDB from screen. - // standard screen loop handling ehre - // If the # nodes changes, we need to regen our list of screens - if (nodeDB.updateGUI || nodeDB.updateTextMessage) { - setFrames(); - nodeDB.updateGUI = false; - nodeDB.updateTextMessage = false; - } + // standard screen loop handling here } ui.update(); @@ -638,8 +639,8 @@ void Screen::setFrames() DEBUG_MSG("showing standard frames\n"); showingNormalScreen = true; - size_t numnodes = nodeDB.getNumNodes(); // We don't show the node info our our node (if we have it yet - we should) + size_t numnodes = nodeStatusHandler->getNumTotal(); if (numnodes > 0) numnodes--; @@ -719,17 +720,19 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 snprintf(channelStr, sizeof(channelStr), "#%s", channelName.c_str()); // 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); + if (powerStatusHandler->hasBattery()) drawBattery(display, x, y + 2, imgBattery, powerStatusHandler); else display->drawFastImage(x, y + 2, 16, 8, powerStatusHandler->hasUSB() ? imgUSB : imgPower); // Display nodes status - drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodesOnline, nodesTotal); + drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatusHandler); // Display GPS status - drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gps); + drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gpsStatusHandler); } - const char *fields[] = {channelStr, nullptr}; - uint32_t yo = drawRows(display, x, y + FONT_HEIGHT, fields); + display->drawString(x, y + FONT_HEIGHT, channelStr); - display->drawLogBuffer(x, yo); + display->drawLogBuffer(x, y + (FONT_HEIGHT * 2)); + + if(heartbeat) display->setPixel(0, 0); + heartbeat = !heartbeat; } //adjust Brightness cycle trough 1 to 254 as long as attachDuringLongPress is true @@ -747,4 +750,20 @@ void Screen::adjustBrightness(){ dispdev.setBrightness(brightness); } +int Screen::handlePowerStatusUpdate(void *arg) { + //DEBUG_MSG("Screen got power status update\n"); + setPeriod(1); // Update the screen right away + return 0; +} +int Screen::handleGPSStatusUpdate(void *arg) { + //DEBUG_MSG("Screen got gps status update\n"); + setPeriod(1); // Update the screen right away + return 0; +} +int Screen::handleNodeStatusUpdate(void *arg) { + //DEBUG_MSG("Screen got node status update\n"); + setFrames(); // Update our frames if the node database has changed + setPeriod(1); // Update the screen right away + return 0; +} } // namespace meshtastic diff --git a/src/screen.h b/src/screen.h index 6d4d8b37..8c76a576 100644 --- a/src/screen.h +++ b/src/screen.h @@ -13,7 +13,10 @@ #include "PeriodicTask.h" #include "TypedQueue.h" #include "lock.h" -#include "power.h" +#include "PowerStatus.h" +#include "GPSStatus.h" +#include "NodeStatus.h" +#include "Observer.h" #include namespace meshtastic @@ -29,14 +32,6 @@ class DebugInfo 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) { @@ -44,25 +39,6 @@ class DebugInfo 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; @@ -71,15 +47,8 @@ class 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; }; @@ -93,6 +62,10 @@ class DebugInfo // simultaneously). class Screen : public PeriodicTask { + CallbackObserver powerStatusObserver = CallbackObserver(this, &Screen::handlePowerStatusUpdate); + CallbackObserver gpsStatusObserver = CallbackObserver(this, &Screen::handleGPSStatusUpdate); + CallbackObserver nodeStatusObserver = CallbackObserver(this, &Screen::handleNodeStatusUpdate); + public: Screen(uint8_t address, int sda = -1, int scl = -1); @@ -189,6 +162,10 @@ class Screen : public PeriodicTask // Use this handle to set things like battery status, user count, GPS status, etc. DebugInfo *debug() { return &debugInfo; } + int handlePowerStatusUpdate(void *arg); + int handleGPSStatusUpdate(void *arg); + int handleNodeStatusUpdate(void *arg); + protected: /// Updates the UI. // From 8fa44c3590bdfe5591cfbf31df2603443d8c198b Mon Sep 17 00:00:00 2001 From: Professr Date: Sun, 28 Jun 2020 18:55:51 -0700 Subject: [PATCH 2/3] Disabled the display heartbeat pixel for pull request --- src/screen.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/screen.cpp b/src/screen.cpp index 7ba15384..74f0065d 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -765,8 +765,10 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->drawLogBuffer(x, y + (FONT_HEIGHT * 2)); + /* Display a heartbeat pixel that blinks every time the frame is redrawn if(heartbeat) display->setPixel(0, 0); heartbeat = !heartbeat; + */ } // adjust Brightness cycle trough 1 to 254 as long as attachDuringLongPress is true From f2e6c6de5865b38d77ab4f2906efe788786114bd Mon Sep 17 00:00:00 2001 From: Professr Date: Sun, 28 Jun 2020 19:03:39 -0700 Subject: [PATCH 3/3] Fixed filename case sensitivity --- src/Power.cpp | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index a5f258ce..4b622630 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1,4 +1,4 @@ -#include "Power.h" +#include "power.h" #include "PowerFSM.h" #include "main.h" #include "utils.h" diff --git a/src/main.cpp b/src/main.cpp index 63770e13..56856f6e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,7 @@ #include "UBloxGPS.h" #include "configuration.h" #include "error.h" -#include "Power.h" +#include "power.h" // #include "rom/rtc.h" #include "DSRRouter.h" #include "debug.h"