diff --git a/README.md b/README.md
index cbe76494d..c5d7b1bbf 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ This software is 100% open source and developed by a group of hobbyist experimen
We currently support three models of radios.
- TTGO T-Beam
-
+ - [T-Beam V0.7 w/ NEO-6M](https://www.aliexpress.com/item/4000574335430.html)
- [T-Beam V1.0 w/ NEO-6M - special Meshtastic version](https://www.aliexpress.com/item/4001178678568.html) (Includes built-in OLED display and they have **preinstalled** the meshtastic software)
- [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
- 3D printable cases
@@ -40,9 +40,10 @@ We currently support three models of radios.
**Make sure to get the frequency for your country**
-- US/JP/AU/NZ - 915MHz
+- US/JP/AU/NZ/CA - 915MHz
- CN - 470MHz
- EU - 868MHz, 433MHz
+- full list of LoRa frequencies per region is available [here](https://www.thethingsnetwork.org/docs/lorawan/frequencies-by-country.html)
Getting a version that includes a screen is optional, but highly recommended.
diff --git a/bin/version.sh b/bin/version.sh
index 58db3c516..11037db32 100644
--- a/bin/version.sh
+++ b/bin/version.sh
@@ -1,3 +1,3 @@
-export VERSION=0.7.9
\ No newline at end of file
+export VERSION=0.7.10
\ No newline at end of file
diff --git a/docs/software/TODO.md b/docs/software/TODO.md
index 60d0e5b0a..e8355bcf1 100644
--- a/docs/software/TODO.md
+++ b/docs/software/TODO.md
@@ -2,7 +2,7 @@
You probably don't care about this section - skip to the next one.
-- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors
+- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors (plan for geofence mode and battery save mode)
- NRF52 BLE support
# Medium priority
@@ -48,7 +48,6 @@ Items after the first final candidate release.
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
- read the PMU battery fault indicators and blink/led/warn user on screen
- discard very old nodedb records (> 1wk)
-- add a watchdog timer
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
- report esp32 device code bugs back to the mothership via android
- change BLE bonding to something more secure. see comment by pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND)
diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md
index 2a2e4dd26..f9427cd21 100644
--- a/docs/software/mesh-alg.md
+++ b/docs/software/mesh-alg.md
@@ -32,8 +32,8 @@ optimizations / low priority:
- read this [this](http://pages.cs.wisc.edu/~suman/pubs/nadv-mobihoc05.pdf) paper and others and make our naive flood routing less naive
- read @cyclomies long email with good ideas on optimizations and reply
-- Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
-- make android app warn if firmware is too old or too new to talk to
+- DONE Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
+- DONE make android app warn if firmware is too old or too new to talk to
- change nodenums and packetids in protobuf to be fixed32
- low priority: think more careful about reliable retransmit intervals
- make ReliableRouter.pending threadsafe
diff --git a/platformio.ini b/platformio.ini
index bad647bfa..9f73a1aa1 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -23,6 +23,8 @@ default_envs = tbeam ; Note: the github actions CI test build can't yet build NR
[env]
+framework = arduino
+
; customize the partition table
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
board_build.partitions = partition-table.csv
@@ -72,13 +74,12 @@ lib_deps =
Wire ; explicitly needed here because the AXP202 library forgets to add it
https://github.com/meshtastic/arduino-fsm.git
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
- https://github.com/meshtastic/RadioLib.git
+ https://github.com/meshtastic/RadioLib.git#6aa38a85856012c99c4e9b4e7cee35e37671a4bc
https://github.com/meshtastic/TinyGPSPlus.git
; Common settings for ESP targes, mixin with extends = esp32_base
[esp32_base]
platform = espressif32
-framework = arduino
src_filter =
${env.src_filter} -
upload_speed = 921600
@@ -89,7 +90,7 @@ build_flags =
# board_build.ldscript = linker/esp32.extram.bss.ld
lib_ignore = segger_rtt
platform_packages =
- framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#f26c4f96fefd13ed0ed042e27954f8aba6328f6b
+ framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#71ed4002c953d8c87f44ed27e34fe0735f99013e
; The 1.0 release of the TBEAM board
[env:tbeam]
@@ -98,17 +99,16 @@ 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
; The original TBEAM board without the AXP power chip and a few other changes
; Note: I've heard reports this didn't work. Disabled until someone with a 0.7 can test and debug.
-;[env:tbeam0.7]
-;extends = esp32_base
-;board = ttgo-t-beam
-;build_flags =
-; ${esp32_base.build_flags} -D TBEAM_V07
+[env:tbeam0.7]
+extends = esp32_base
+board = ttgo-t-beam
+build_flags =
+ ${esp32_base.build_flags} -D TBEAM_V07
[env:heltec]
;build_type = debug ; to make it possible to step through our jtag debugger
@@ -133,7 +133,6 @@ build_flags =
; For more details see my post in the forum.
[env:cubecellplus]
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
-framework = arduino
board = cubecell_board_plus
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
@@ -143,7 +142,6 @@ src_filter =
; Common settings for NRF52 based targets
[nrf52_base]
platform = nordicnrf52
-framework = arduino
debug_tool = jlink
build_type = debug ; I'm debugging with ICE a lot now
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
diff --git a/src/GPSStatus.h b/src/GPSStatus.h
new file mode 100644
index 000000000..ed9e0fbc6
--- /dev/null
+++ b/src/GPSStatus.h
@@ -0,0 +1,126 @@
+#pragma once
+#include
+#include "Status.h"
+#include "configuration.h"
+
+namespace meshtastic {
+
+ /// Describes the state of the GPS system.
+ class GPSStatus : public Status
+ {
+
+ private:
+ CallbackObserver statusObserver = CallbackObserver(this, &GPSStatus::updateStatus);
+
+ 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)
+ uint32_t heading = 0;
+ uint32_t numSatellites = 0;
+
+ public:
+
+ GPSStatus() {
+ statusType = STATUS_TYPE_GPS;
+ }
+ GPSStatus( bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop, uint32_t heading, uint32_t numSatellites ) : Status()
+ {
+ this->hasLock = hasLock;
+ this->isConnected = isConnected;
+ this->latitude = latitude;
+ this->longitude = longitude;
+ this->altitude = altitude;
+ this->dop = dop;
+ this->heading = heading;
+ this->numSatellites = numSatellites;
+ }
+ GPSStatus(const GPSStatus &);
+ GPSStatus &operator=(const GPSStatus &);
+
+ void observe(Observable *source)
+ {
+ statusObserver.observe(source);
+ }
+
+ bool getHasLock() const
+ {
+ return hasLock;
+ }
+
+ bool getIsConnected() const
+ {
+ return isConnected;
+ }
+
+ int32_t getLatitude() const
+ {
+ return latitude;
+ }
+
+ int32_t getLongitude() const
+ {
+ return longitude;
+ }
+
+ int32_t getAltitude() const
+ {
+ return altitude;
+ }
+
+ uint32_t getDOP() const
+ {
+ return dop;
+ }
+
+ uint32_t getHeading() const
+ {
+ return heading;
+ }
+
+ uint32_t getNumSatellites() const
+ {
+ return numSatellites;
+ }
+
+ bool matches(const GPSStatus *newStatus) const
+ {
+ return (
+ newStatus->hasLock != hasLock ||
+ newStatus->isConnected != isConnected ||
+ newStatus->latitude != latitude ||
+ newStatus->longitude != longitude ||
+ newStatus->altitude != altitude ||
+ newStatus->dop != dop ||
+ newStatus->heading != heading ||
+ newStatus->numSatellites != numSatellites
+ );
+ }
+ int updateStatus(const GPSStatus *newStatus) {
+ // Only update the status if values have actually changed
+ bool isDirty;
+ {
+ isDirty = matches(newStatus);
+ initialized = true;
+ hasLock = newStatus->hasLock;
+ isConnected = newStatus->isConnected;
+ latitude = newStatus->latitude;
+ longitude = newStatus->longitude;
+ altitude = newStatus->altitude;
+ dop = newStatus->dop;
+ heading = newStatus->heading;
+ numSatellites = newStatus->numSatellites;
+ }
+ if(isDirty) {
+ DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5, numSatellites);
+ onNewStatus.notifyObservers(this);
+ }
+ 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 000000000..dc567fd2f
--- /dev/null
+++ b/src/NodeStatus.h
@@ -0,0 +1,83 @@
+#pragma once
+#include
+#include "Status.h"
+#include "configuration.h"
+
+namespace meshtastic {
+
+ /// Describes the state of the NodeDB system.
+ class NodeStatus : public Status
+ {
+
+ private:
+ CallbackObserver statusObserver = CallbackObserver(this, &NodeStatus::updateStatus);
+
+ uint8_t numOnline = 0;
+ uint8_t numTotal = 0;
+
+ uint8_t lastNumTotal = 0;
+
+ public:
+ bool forceUpdate = false;
+
+ NodeStatus() {
+ statusType = STATUS_TYPE_NODE;
+ }
+ NodeStatus( uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false ) : Status()
+ {
+ this->forceUpdate = forceUpdate;
+ this->numOnline = numOnline;
+ this->numTotal = numTotal;
+ }
+ NodeStatus(const NodeStatus &);
+ NodeStatus &operator=(const NodeStatus &);
+
+ void observe(Observable *source)
+ {
+ statusObserver.observe(source);
+ }
+
+ uint8_t getNumOnline() const
+ {
+ return numOnline;
+ }
+
+ uint8_t getNumTotal() const
+ {
+ return numTotal;
+ }
+
+ uint8_t getLastNumTotal() const
+ {
+ return lastNumTotal;
+ }
+
+ bool matches(const NodeStatus *newStatus) const
+ {
+ return (
+ newStatus->getNumOnline() != numOnline ||
+ newStatus->getNumTotal() != numTotal
+ );
+ }
+ int updateStatus(const NodeStatus *newStatus) {
+ // Only update the status if values have actually changed
+ lastNumTotal = numTotal;
+ bool isDirty;
+ {
+ isDirty = matches(newStatus);
+ initialized = true;
+ numOnline = newStatus->getNumOnline();
+ numTotal = newStatus->getNumTotal();
+ }
+ if(isDirty || newStatus->forceUpdate) {
+ DEBUG_MSG("Node status update: %d online, %d total\n", numOnline, numTotal);
+ onNewStatus.notifyObservers(this);
+ }
+ 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 000000000..23332698b
--- /dev/null
+++ b/src/Power.cpp
@@ -0,0 +1,179 @@
+#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();
+ concurrency::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()
+{
+ 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);
+ }
+ }
+
+ // 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()
+{
+ 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 000000000..f40d9445c
--- /dev/null
+++ b/src/PowerStatus.h
@@ -0,0 +1,103 @@
+#pragma once
+#include
+#include "Status.h"
+#include "configuration.h"
+
+namespace meshtastic {
+
+ /// Describes the state of the GPS system.
+ class PowerStatus : public Status
+ {
+
+ private:
+ CallbackObserver statusObserver = CallbackObserver(this, &PowerStatus::updateStatus);
+
+ /// 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
+ uint8_t batteryChargePercent;
+ /// Whether USB is connected
+ bool hasUSB;
+ /// Whether we are charging the battery
+ bool isCharging;
+
+ public:
+
+ PowerStatus() {
+ statusType = STATUS_TYPE_POWER;
+ }
+ PowerStatus( bool hasBattery, bool hasUSB, bool isCharging, int batteryVoltageMv, uint8_t batteryChargePercent ) : Status()
+ {
+ this->hasBattery = hasBattery;
+ this->hasUSB = hasUSB;
+ this->isCharging = isCharging;
+ this->batteryVoltageMv = batteryVoltageMv;
+ this->batteryChargePercent = batteryChargePercent;
+ }
+ PowerStatus(const PowerStatus &);
+ PowerStatus &operator=(const PowerStatus &);
+
+ void observe(Observable *source)
+ {
+ statusObserver.observe(source);
+ }
+
+ bool getHasBattery() const
+ {
+ return hasBattery;
+ }
+
+ bool getHasUSB() const
+ {
+ return hasUSB;
+ }
+
+ bool getIsCharging() const
+ {
+ return isCharging;
+ }
+
+ int getBatteryVoltageMv() const
+ {
+ return batteryVoltageMv;
+ }
+
+ uint8_t getBatteryChargePercent() const
+ {
+ return batteryChargePercent;
+ }
+
+ bool matches(const PowerStatus *newStatus) const
+ {
+ return (
+ newStatus->getHasBattery() != hasBattery ||
+ newStatus->getHasUSB() != hasUSB ||
+ newStatus->getBatteryVoltageMv() != batteryVoltageMv
+ );
+ }
+ int updateStatus(const PowerStatus *newStatus) {
+ // Only update the status if values have actually changed
+ bool isDirty;
+ {
+ isDirty = matches(newStatus);
+ initialized = true;
+ hasBattery = newStatus->getHasBattery();
+ batteryVoltageMv = newStatus->getBatteryVoltageMv();
+ batteryChargePercent = newStatus->getBatteryChargePercent();
+ hasUSB = newStatus->getHasUSB();
+ isCharging = newStatus->getIsCharging();
+ }
+ if(isDirty) {
+ DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
+ onNewStatus.notifyObservers(this);
+ }
+ return 0;
+ }
+
+ };
+
+}
+
+extern meshtastic::PowerStatus *powerStatus;
diff --git a/src/Status.h b/src/Status.h
new file mode 100644
index 000000000..eb41b60ce
--- /dev/null
+++ b/src/Status.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "Observer.h"
+
+// Constants for the various status types, so we can tell subclass instances apart
+#define STATUS_TYPE_BASE 0
+#define STATUS_TYPE_POWER 1
+#define STATUS_TYPE_GPS 2
+#define STATUS_TYPE_NODE 3
+
+
+namespace meshtastic
+{
+
+ // A base class for observable status
+ class Status
+ {
+ protected:
+ // Allows us to observe an Observable
+ CallbackObserver statusObserver = CallbackObserver(this, &Status::updateStatus);
+ bool initialized = false;
+ // Workaround for no typeid support
+ int statusType;
+
+ public:
+ // Allows us to generate observable events
+ Observable onNewStatus;
+
+ // Enable polymorphism ?
+ virtual ~Status() = default;
+
+ Status() {
+ if (!statusType)
+ {
+ statusType = STATUS_TYPE_BASE;
+ }
+ }
+
+ // Prevent object copy/move
+ Status(const Status &) = delete;
+ Status &operator=(const Status &) = delete;
+
+ // Start observing a source of data
+ void observe(Observable *source)
+ {
+ statusObserver.observe(source);
+ }
+
+ // Determines whether or not existing data matches the data in another Status instance
+ bool matches(const Status *otherStatus) const
+ {
+ return true;
+ }
+
+ bool isInitialized() const
+ {
+ return initialized;
+ }
+
+ int getStatusType() const
+ {
+ return statusType;
+ }
+
+ // Called when the Observable we're observing generates a new notification
+ int updateStatus(const Status *newStatus)
+ {
+ return 0;
+ }
+
+ };
+};
diff --git a/src/StatusHandler.h b/src/StatusHandler.h
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/concurrency/Thread.cpp b/src/concurrency/Thread.cpp
index 39fd8f6ed..99dd16300 100644
--- a/src/concurrency/Thread.cpp
+++ b/src/concurrency/Thread.cpp
@@ -1,4 +1,5 @@
#include "Thread.h"
+#include "timing.h"
namespace concurrency {
diff --git a/src/concurrency/Thread.h b/src/concurrency/Thread.h
index b297e40d1..bc8fe3951 100644
--- a/src/concurrency/Thread.h
+++ b/src/concurrency/Thread.h
@@ -1,6 +1,7 @@
#pragma once
#include "freertosinc.h"
+#include "esp_task_wdt.h"
namespace concurrency {
@@ -30,8 +31,26 @@ class Thread
*/
virtual void doRun() = 0;
+ /**
+ * All thread run methods must periodically call serviceWatchdog, or the system will declare them hung and panic.
+ *
+ * this only applies after startWatchdog() has been called. If you need to sleep for a long time call stopWatchdog()
+ */
+ void serviceWatchdog() { esp_task_wdt_reset(); }
+ void startWatchdog()
+ {
+ auto r = esp_task_wdt_add(taskHandle);
+ assert(r == ESP_OK);
+ }
+ void stopWatchdog()
+ {
+ auto r = esp_task_wdt_delete(taskHandle);
+ assert(r == ESP_OK);
+ }
+
private:
static void callRun(void *_this);
};
+
} // namespace concurrency
diff --git a/src/concurrency/WorkerThread.cpp b/src/concurrency/WorkerThread.cpp
index 8650b7b82..8ea1e6a85 100644
--- a/src/concurrency/WorkerThread.cpp
+++ b/src/concurrency/WorkerThread.cpp
@@ -5,21 +5,28 @@ namespace concurrency {
void WorkerThread::doRun()
{
+ startWatchdog();
+
while (!wantExit) {
+ stopWatchdog();
block();
+ startWatchdog();
+
+ // no need - startWatchdog is guaranteed to give us one full watchdog interval
+ // serviceWatchdog(); // Let our loop worker have one full watchdog interval (at least) to run
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;
if (timing::millis() - lastPrint > 10 * 1000L) {
lastPrint = timing::millis();
- uint32_t taskHandle = reinterpret_cast(xTaskGetCurrentTaskHandle());
- DEBUG_MSG("printThreadInfo(%s) task: %" PRIx32 " core id: %u min free stack: %u\n", "thread", taskHandle, xPortGetCoreID(),
- uxTaskGetStackHighWaterMark(nullptr));
+ meshtastic::printThreadInfo("net");
}
#endif
loop();
}
+
+ stopWatchdog();
}
} // namespace concurrency
diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp
index e0d53eab1..dcc80aa66 100644
--- a/src/esp32/main-esp32.cpp
+++ b/src/esp32/main-esp32.cpp
@@ -2,11 +2,11 @@
#include "MeshBluetoothService.h"
#include "PowerFSM.h"
#include "configuration.h"
+#include "esp_task_wdt.h"
#include "main.h"
-#include "power.h"
#include "sleep.h"
-#include "utils.h"
#include "target_specific.h"
+#include "utils.h"
bool bluetoothOn;
@@ -60,111 +60,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 +85,15 @@ void esp32Setup()
// enableModemSleep();
-#ifdef AXP192_SLAVE_ADDRESS
- axp192Init();
-#endif
+// Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any
+// false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now.
+#define APP_WATCHDOG_SECS 45
+
+ auto res = esp_task_wdt_init(APP_WATCHDOG_SECS, true);
+ assert(res == ESP_OK);
+
+ res = esp_task_wdt_add(NULL);
+ assert(res == ESP_OK);
}
#if 0
@@ -215,51 +116,12 @@ uint32_t axpDebugRead()
Periodic axpDebugOutput(axpDebugRead);
#endif
-
/// loop code specific to ESP32 targets
void esp32Loop()
{
+ esp_task_wdt_reset(); // service our app level watchdog
loopBLE();
// 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 97d696986..14e6c9dfe 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -1,6 +1,8 @@
#pragma once
#include "Observer.h"
+#include "GPSStatus.h"
+#include "../concurrency/PeriodicTask.h"
#include "sys/time.h"
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
@@ -34,11 +36,15 @@ class GPS : public Observable
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)
+ uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
+ uint32_t numSatellites = 0;
bool isConnected = false; // Do we have a GPS we are talking to
virtual ~GPS() {}
+ Observable newStatus;
+
/**
* Returns true if we succeeded
*/
diff --git a/src/gps/NEMAGPS.cpp b/src/gps/NEMAGPS.cpp
index c4453c910..144b5e6b5 100644
--- a/src/gps/NEMAGPS.cpp
+++ b/src/gps/NEMAGPS.cpp
@@ -55,16 +55,26 @@ void NEMAGPS::loop()
longitude = toDegInt(loc.lng);
}
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
- if(reader.hdop.isValid()) {
+ if (reader.hdop.isValid()) {
dop = reader.hdop.value();
}
+ if (reader.course.isValid()) {
+ heading = reader.course.value() * 1e3; //Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
+ }
+ if (reader.satellites.isValid()) {
+ numSatellites = reader.satellites.value();
+ }
// 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\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2);
+ 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
+ const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
+ newStatus.notifyObservers(&status);
}
}
\ No newline at end of file
diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp
index 5ba7da307..8c3bb9bac 100644
--- a/src/gps/UBloxGPS.cpp
+++ b/src/gps/UBloxGPS.cpp
@@ -116,7 +116,8 @@ 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);
+ heading = ublox.getHeading(0);
+ numSatellites = ublox.getSIV(0);
// 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 +130,10 @@ 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;
+ // Notify any status instances that are observing us
+ const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
+ 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 8ef075953..19bd4740f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -38,8 +38,8 @@
#include "screen.h"
#include "sleep.h"
#include "timing.h"
-#include
#include
+#include
// #include
#ifndef NO_ESP32
@@ -57,8 +57,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::PowerStatus *powerStatus = new meshtastic::PowerStatus();
+
+// Global GPS status
+meshtastic::GPSStatus *gpsStatus = new meshtastic::GPSStatus();
+
+// Global Node status
+meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
bool ssd1306_found;
bool axp192_found;
@@ -122,22 +128,24 @@ 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 powerStatus->getIsCharging() ? 1000 : (ledOn ? 2 : 1000);
}
concurrency::Periodic ledPeriodic(ledBlinker);
// Prepare for button presses
#ifdef BUTTON_PIN
- OneButton userButton;
+OneButton userButton;
#endif
#ifdef BUTTON_PIN_ALT
- OneButton userButtonAlt;
+OneButton userButtonAlt;
#endif
-void userButtonPressed() {
+void userButtonPressed()
+{
powerFSM.trigger(EVENT_PRESS);
}
-void userButtonPressedLong(){
+void userButtonPressedLong()
+{
screen.adjustBrightness();
}
@@ -226,6 +234,14 @@ 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();
#endif
@@ -254,9 +270,10 @@ void setup()
gps = new NEMAGPS();
gps->setup();
#endif
+ gpsStatus->observe(&gps->newStatus);
+ nodeStatus->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();
@@ -342,6 +359,9 @@ void loop()
#ifndef NO_ESP32
esp32Loop();
#endif
+#ifdef TBEAM_V10
+ power->loop();
+#endif
#ifdef BUTTON_PIN
userButton.tick();
@@ -366,9 +386,8 @@ void loop()
#endif
// Update the screen last, after we've figured out what to show.
- screen.debug_info()->setNodeNumbersStatus(nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes());
screen.debug_info()->setChannelNameStatus(channelSettings.name);
- screen.debug_info()->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 9d0cde8db..471ba2e85 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::PowerStatus *powerStatus;
+//extern meshtastic::GPSStatus *gpsStatus;
+//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 21d30bcd0..645cefc38 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -304,7 +304,7 @@ int MeshService::onGPSChanged(void *unused)
}
// Include our current battery voltage in our position announcement
- pos.battery_level = powerStatus.batteryChargePercent;
+ pos.battery_level = powerStatus->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 ca28c17a5..fe816a202 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -339,12 +339,8 @@ void NodeDB::updateFrom(const MeshPacket &mp)
const SubPacket &p = mp.decoded;
DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
- int oldNumNodes = *numNodes;
NodeInfo *info = getOrCreateNode(mp.from);
- if (oldNumNodes != *numNodes)
- updateGUI = true; // we just created a nodeinfo
-
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
info->position.time = mp.rx_time;
@@ -360,6 +356,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
info->position.time = oldtime;
info->has_position = true;
updateGUIforNode = info;
+ notifyObservers(true); //Force an update whether or not our node counts have changed
break;
}
@@ -374,6 +371,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
devicestate.has_rx_text_message = true;
updateTextMessage = true;
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
+ notifyObservers(true); //Force an update whether or not our node counts have changed
}
}
break;
@@ -392,6 +390,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
if (changed) {
updateGUIforNode = info;
powerFSM.trigger(EVENT_NODEDB_UPDATED);
+ notifyObservers(true); //Force an update whether or not our node counts have changed
// Not really needed - we will save anyways when we go to sleep
// We just changed something important about the user, store our DB
@@ -399,6 +398,10 @@ void NodeDB::updateFrom(const MeshPacket &mp)
}
break;
}
+
+ default: {
+ notifyObservers(); //If the node counts have changed, notify observers
+ }
}
}
}
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index 611024e22..2465c2021 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,13 @@ 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(bool forceUpdate = false) {
+ // Notify observers of the current node state
+ const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineNodes(), getNumNodes(), forceUpdate);
+ newStatus.notifyObservers(&status);
+ }
+
/// read our db from flash
void loadFromDisk();
diff --git a/src/power.h b/src/power.h
index 21c913d50..dd6b8ce87 100644
--- a/src/power.h
+++ b/src/power.h
@@ -1,4 +1,6 @@
#pragma once
+#include "concurrency/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 concurrency::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::PowerStatus *handler)
+ {
+ statusHandler = handler;
+ }
+
+ protected:
+ meshtastic::PowerStatus *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 6a210beb5..60bfaadb0 100644
--- a/src/screen.cpp
+++ b/src/screen.cpp
@@ -35,14 +35,11 @@ along with this program. If not, see .
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
-#ifdef USE_SH1106
-#define SCREEN_WIDTH 132
-#else
+// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
#define SCREEN_WIDTH 128
-#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
@@ -55,7 +52,16 @@ 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};
+uint8_t imgBattery[16] = { 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C };
+uint8_t imgSatellite[8] = { 0x70, 0x71, 0x22, 0xFA, 0xFA, 0x22, 0x71, 0x70 };
+
+uint32_t dopThresholds[5] = { 2000, 1000, 500, 200, 100 };
+
+// if defined a pixel will blink to show redraws
+// #define SHOW_REDRAWS
+#ifdef SHOW_REDRAWS
+static bool heartbeat = false;
+#endif
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@@ -143,38 +149,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, const PowerStatus *powerStatus)
{
static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD};
static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85};
@@ -183,12 +191,12 @@ 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 (powerStatus->getIsCharging()) {
memcpy(imgBuffer + 3, lightning, 8);
// If not charging, Draw power bars
} else {
for (int i = 0; i < 4; i++) {
- if (powerStatus->batteryChargePercent >= 25 * i)
+ if (powerStatus->getBatteryChargePercent() >= 25 * i)
memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
}
}
@@ -196,49 +204,50 @@ static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *img
}
// 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, NodeStatus *nodeStatus)
{
char usersString[20];
- sprintf(usersString, "%d/%d", nodesOnline, nodesTotal);
+ sprintf(usersString, "%d/%d", nodeStatus->getNumOnline(), nodeStatus->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)
+static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
- if (!gps->isConnected) {
+ if (!gps->getIsConnected())
+ {
display->drawString(x, y - 2, "No GPS");
return;
}
- display->drawFastImage(x, y, 6, 8, gps->hasLock() ? imgPositionSolid : imgPositionEmpty);
- if (!gps->hasLock()) {
+ display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty);
+ if (!gps->getHasLock())
+ {
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;
+ }
+ else
+ {
+ char satsString[3];
+ uint8_t bar[2] = { 0 };
+
+ //Draw DOP signal bars
+ for(int i = 0; i < 5; i++)
+ {
+ if (gps->getDOP() <= dopThresholds[i])
+ bar[0] = ~((1 << (5 - i)) - 1);
+ else
+ bar[0] = 0b10000000;
+ //bar[1] = bar[0];
+ display->drawFastImage(x + 9 + (i * 2), y, 2, 8, bar);
+ }
+
+ //Draw satellite image
+ display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
+
+ //Draw the number of satellites
+ sprintf(satsString, "%d", gps->getNumSatellites());
+ display->drawString(x + 34, y - 2, satsString);
}
}
@@ -381,28 +390,41 @@ static bool hasPosition(NodeInfo *n)
static size_t nodeIndex;
static int8_t prevFrame = -1;
-// Draw the compass and arrow pointing to location
-static void drawCompass(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian)
+// Draw the arrow pointing to a node's location
+static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian)
{
- // display->drawXbm(compassX, compassY, compass_width, compass_height,
- // (const uint8_t *)compass_bits);
-
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
- Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
+
+ Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow};
for (int i = 0; i < 4; i++) {
- points[i]->rotate(headingRadian);
- points[i]->scale(COMPASS_DIAM * 0.6);
- points[i]->translate(compassX, compassY);
+ arrowPoints[i]->rotate(headingRadian);
+ arrowPoints[i]->scale(COMPASS_DIAM * 0.6);
+ arrowPoints[i]->translate(compassX, compassY);
}
drawLine(display, tip, tail);
drawLine(display, leftArrow, tip);
drawLine(display, rightArrow, tip);
+}
- display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
+// Draw the compass heading
+static void drawCompassHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
+{
+ Point N1(-0.04f, -0.65f), N2( 0.04f, -0.65f);
+ Point N3(-0.04f, -0.55f), N4( 0.04f, -0.55f);
+ Point *rosePoints[] = {&N1, &N2, &N3, &N4};
+
+ for (int i = 0; i < 4; i++) {
+ rosePoints[i]->rotate(myHeading);
+ rosePoints[i]->scale(COMPASS_DIAM);
+ rosePoints[i]->translate(compassX, compassY);
+ }
+ drawLine(display, N1, N3);
+ drawLine(display, N2, N4);
+ drawLine(display, N1, N4);
}
/// Convert an integer GPS coords to a floating point
@@ -422,10 +444,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
n = nodeDB.getNodeByIndex(nodeIndex);
}
-
- // We just changed to a new node screen, ask that node for updated state
displayedNodeNum = n->num;
- service.sendNetworkPing(displayedNodeNum, true);
+
+ // We just changed to a new node screen, ask that node for updated state if it's older than 2 minutes
+ if(sinceLastSeen(n) > 120)
+ {
+ service.sendNetworkPing(displayedNodeNum, true);
+ }
}
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
@@ -456,29 +481,40 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
// coordinates for the center of the compass/circle
- int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
+ int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 5, compassY = y + SCREEN_HEIGHT / 2;
+ bool hasNodeHeading = false;
- if (ourNode && hasPosition(ourNode) && hasPosition(node)) { // display direction toward node
- Position &op = ourNode->position, &p = node->position;
- float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
- if (d < 2000)
- snprintf(distStr, sizeof(distStr), "%.0f m", d);
- else
- snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
+ if(ourNode && hasPosition(ourNode))
+ {
+ Position &op = ourNode->position;
+ float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
+ drawCompassHeading(display, compassX, compassY, myHeading);
- // FIXME, also keep the guess at the operators heading and add/substract
- // it. currently we don't do this and instead draw north up only.
- float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
- float myHeading = estimatedHeading(DegD(p.latitude_i), DegD(p.longitude_i));
- headingRadian = bearingToOther - myHeading;
- drawCompass(display, compassX, compassY, headingRadian);
- } else { // direction to node is unknown so display question mark
+ if(hasPosition(node))
+ {
+ // display direction toward node
+ hasNodeHeading = true;
+ Position &p = node->position;
+ float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
+ if (d < 2000)
+ snprintf(distStr, sizeof(distStr), "%.0f m", d);
+ else
+ snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
+
+ // FIXME, also keep the guess at the operators heading and add/substract
+ // it. currently we don't do this and instead draw north up only.
+ float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
+ headingRadian = bearingToOther - myHeading;
+ drawNodeHeading(display, compassX, compassY, headingRadian);
+ }
+ }
+ if(!hasNodeHeading)
+ // direction to node is unknown so display question mark
// Debug info for gps lock errors
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
-
display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?");
- display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
- }
+ display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
+
// Must be after distStr is populated
drawColumns(display, x, y, fields);
@@ -576,6 +612,11 @@ void Screen::setup()
// twice initially.
ui.update();
ui.update();
+
+ // Subscribe to status updates
+ powerStatusObserver.observe(&powerStatus->onNewStatus);
+ gpsStatusObserver.observe(&gpsStatus->onNewStatus);
+ nodeStatusObserver.observe(&nodeStatus->onNewStatus);
}
void Screen::doTask()
@@ -637,14 +678,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();
@@ -669,8 +703,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 = nodeStatus->getNumTotal();
if (numnodes > 0)
numnodes--;
@@ -750,20 +784,26 @@ 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);
+ if (powerStatus->getHasBattery())
+ drawBattery(display, x, y + 2, imgBattery, powerStatus);
else
- display->drawFastImage(x, y + 2, 16, 8, powerStatus.usb ? imgUSB : imgPower);
+ display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? 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, nodeStatus);
// Display GPS status
- drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gps);
+ drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
}
- 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));
+
+ /* Display a heartbeat pixel that blinks every time the frame is redrawn */
+#ifdef SHOW_REDRAWS
+ if (heartbeat)
+ display->setPixel(0, 0);
+ heartbeat = !heartbeat;
+#endif
}
// adjust Brightness cycle trough 1 to 254 as long as attachDuringLongPress is true
@@ -781,4 +821,20 @@ void Screen::adjustBrightness()
dispdev.setBrightness(brightness);
}
+int Screen::handleStatusUpdate(const Status *arg)
+{
+ //DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
+ switch(arg->getStatusType())
+ {
+ case STATUS_TYPE_NODE:
+ if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal())
+ setFrames();
+ prevFrame = -1;
+ nodeDB.updateGUI = false;
+ nodeDB.updateTextMessage = false;
+ break;
+ }
+ setPeriod(1); // Update the screen right away
+ return 0;
+}
} // namespace meshtastic
diff --git a/src/screen.h b/src/screen.h
index fe09d68f0..4f24aaef0 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -29,14 +29,6 @@ class DebugInfo
DebugInfo(const DebugInfo &) = delete;
DebugInfo &operator=(const DebugInfo &) = delete;
- /// Sets user statistics.
- void setNodeNumbersStatus(int online, int total)
- {
- concurrency::LockGuard guard(&lock);
- nodesOnline = online;
- nodesTotal = total;
- }
-
/// Sets the name of the channel.
void setChannelNameStatus(const char *name)
{
@@ -44,25 +36,6 @@ class DebugInfo
channelName = name;
}
- /// Sets battery/charging/etc status.
- //
- void setPowerStatus(const PowerStatus &status)
- {
- concurrency::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)
- {
- concurrency::LockGuard guard(&lock);
- gpsStatus = status;
- }
-
private:
friend Screen;
@@ -71,15 +44,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.
concurrency::Lock lock;
};
@@ -93,6 +59,10 @@ class DebugInfo
// simultaneously).
class Screen : public concurrency::PeriodicTask
{
+ CallbackObserver powerStatusObserver = CallbackObserver(this, &Screen::handleStatusUpdate);
+ CallbackObserver gpsStatusObserver = CallbackObserver(this, &Screen::handleStatusUpdate);
+ CallbackObserver nodeStatusObserver = CallbackObserver(this, &Screen::handleStatusUpdate);
+
public:
Screen(uint8_t address, int sda = -1, int scl = -1);
@@ -119,7 +89,7 @@ class Screen : public concurrency::PeriodicTask
// Implementation to Adjust Brightness
void adjustBrightness();
- int brightness = 150;
+ uint8_t brightness = 150;
/// Starts showing the Bluetooth PIN screen.
//
@@ -189,6 +159,8 @@ class Screen : public concurrency::PeriodicTask
// Use this handle to set things like battery status, user count, GPS status, etc.
DebugInfo* debug_info() { return &debugInfo; }
+ int handleStatusUpdate(const meshtastic::Status *arg);
+
protected:
/// Updates the UI.
//