mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-09 14:42:05 +00:00
GPS Power State tidy-up (#4161)
* Refactor GPSPowerState enum Identifies a case where the GPS hardware is awake, but an update is not yet desired * Change terminology * Clear old lock-time prediction on triple press * Use exponential smoothing to predict lock time * Rename averageLockTime to predictedLockTime * Attempt: Send PMREQ with duration 0 on MCU deep-sleep * Attempt 2: Send PMREQ with duration 0 on MCU deep-sleep * Revert "Attempt 2: Send PMREQ with duration 0 on MCU deep-sleep" This reverts commit8b697cd2a4
. * Revert "Attempt: Send PMREQ with duration 0 on MCU deep-sleep" This reverts commit9d29ec7603
. * Remove unused notifyGPSSleep Observable Handled with notifyDeepSleep, and enable() / disable() * WIP: simplify GPS power management An initial attempt only. * Honor #3e9e0fd * No-op when moving between GPS_IDLE and GPS_ACTIVE * Ensure U-blox GPS is awake to receive indefinite sleep command * Longer pause when waking U-blox to send sleep command * Actually implement soft and hard sleep.. * Dynamically estimate the threshold for GPS_HARDSLEEP * Fallback to GPS_HARDSLEEP, if GPS_SOFTSLEEP unsupported * Move "excessive search time" behavior to scheduler class * Minor logging adjustments * Promote log to warning * Gratuitous buffer clearing on boot * Fix inverted standby pin logic Specifically the standby pin for L76B, L76K and clones Discovered during T-Echo testing: totally broken function, probe method failing. * Remove redundant pin init Now handled by setPowerState * Replace max() with if statements Avoid those platform specific implementations.. * Trunk formatting New round of settings.json changes keep catching me out, have to remember to re-enable my "clang-format" for windows workaround. * Remove some asserts from setPowerState Original aim was to prevent sending a 0 second PMREQ to U-blox hardware as part of a timed sleep (GPS_HARDSLEEP, GPS_SOFTSLEEP). I'm not sure this is super important, and it feels tidier to just allow the 0 second sleeptime here, rather than fudge the sleeptime further up. * Fix an error determining whether GPS_SOFTSLEEP is supported * Clarify a log entry * Set PIN_STANDBY for MCU deep-sleep Required to reach TTGO's advertised 0.25mA sleep current for T-Echo. Without this change: ~6mA.
This commit is contained in:
parent
11bca437fd
commit
33831cd41c
469
src/gps/GPS.cpp
469
src/gps/GPS.cpp
@ -9,6 +9,7 @@
|
|||||||
#include "main.h" // pmu_found
|
#include "main.h" // pmu_found
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
|
|
||||||
|
#include "GPSUpdateScheduling.h"
|
||||||
#include "cas.h"
|
#include "cas.h"
|
||||||
#include "ubx.h"
|
#include "ubx.h"
|
||||||
|
|
||||||
@ -22,19 +23,6 @@
|
|||||||
#define GPS_RESET_MODE HIGH
|
#define GPS_RESET_MODE HIGH
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// How many minutes of sleep make it worthwhile to power-off the GPS
|
|
||||||
// Shorter than this, and GPS will only enter standby
|
|
||||||
// Affected by lock-time, and config.position.gps_update_interval
|
|
||||||
#ifndef GPS_STANDBY_THRESHOLD_MINUTES
|
|
||||||
#define GPS_STANDBY_THRESHOLD_MINUTES 15
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// How many seconds of sleep make it worthwhile for the GPS to use powered-on standby
|
|
||||||
// Shorter than this, and we'll just wait instead
|
|
||||||
#ifndef GPS_IDLE_THRESHOLD_SECONDS
|
|
||||||
#define GPS_IDLE_THRESHOLD_SECONDS 10
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
|
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
|
||||||
HardwareSerial *GPS::_serial_gps = &Serial1;
|
HardwareSerial *GPS::_serial_gps = &Serial1;
|
||||||
#else
|
#else
|
||||||
@ -43,6 +31,8 @@ HardwareSerial *GPS::_serial_gps = NULL;
|
|||||||
|
|
||||||
GPS *gps = nullptr;
|
GPS *gps = nullptr;
|
||||||
|
|
||||||
|
GPSUpdateScheduling scheduling;
|
||||||
|
|
||||||
/// Multiple GPS instances might use the same serial port (in sequence), but we can
|
/// Multiple GPS instances might use the same serial port (in sequence), but we can
|
||||||
/// only init that port once.
|
/// only init that port once.
|
||||||
static bool didSerialInit;
|
static bool didSerialInit;
|
||||||
@ -52,6 +42,25 @@ uint8_t uBloxProtocolVersion;
|
|||||||
#define GPS_SOL_EXPIRY_MS 5000 // in millis. give 1 second time to combine different sentences. NMEA Frequency isn't higher anyway
|
#define GPS_SOL_EXPIRY_MS 5000 // in millis. give 1 second time to combine different sentences. NMEA Frequency isn't higher anyway
|
||||||
#define NMEA_MSG_GXGSA "GNGSA" // GSA message (GPGSA, GNGSA etc)
|
#define NMEA_MSG_GXGSA "GNGSA" // GSA message (GPGSA, GNGSA etc)
|
||||||
|
|
||||||
|
// For logging
|
||||||
|
const char *getGPSPowerStateString(GPSPowerState state)
|
||||||
|
{
|
||||||
|
switch (state) {
|
||||||
|
case GPS_ACTIVE:
|
||||||
|
return "ACTIVE";
|
||||||
|
case GPS_IDLE:
|
||||||
|
return "IDLE";
|
||||||
|
case GPS_SOFTSLEEP:
|
||||||
|
return "SOFTSLEEP";
|
||||||
|
case GPS_HARDSLEEP:
|
||||||
|
return "HARDSLEEP";
|
||||||
|
case GPS_OFF:
|
||||||
|
return "OFF";
|
||||||
|
default:
|
||||||
|
assert(false); // Unhandled enum value..
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GPS::UBXChecksum(uint8_t *message, size_t length)
|
void GPS::UBXChecksum(uint8_t *message, size_t length)
|
||||||
{
|
{
|
||||||
uint8_t CK_A = 0, CK_B = 0;
|
uint8_t CK_A = 0, CK_B = 0;
|
||||||
@ -767,7 +776,6 @@ bool GPS::setup()
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
||||||
notifyGPSSleepObserver.observe(¬ifyGPSSleep);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -776,62 +784,116 @@ GPS::~GPS()
|
|||||||
{
|
{
|
||||||
// we really should unregister our sleep observer
|
// we really should unregister our sleep observer
|
||||||
notifyDeepSleepObserver.unobserve(¬ifyDeepSleep);
|
notifyDeepSleepObserver.unobserve(¬ifyDeepSleep);
|
||||||
notifyGPSSleepObserver.observe(¬ifyGPSSleep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *GPS::powerStateToString()
|
// Put the GPS hardware into a specified state
|
||||||
|
void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
||||||
{
|
{
|
||||||
switch (powerState) {
|
// Update the stored GPSPowerstate, and create local copies
|
||||||
case GPS_OFF:
|
GPSPowerState oldState = powerState;
|
||||||
return "OFF";
|
powerState = newState;
|
||||||
case GPS_IDLE:
|
LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState));
|
||||||
return "IDLE";
|
|
||||||
case GPS_STANDBY:
|
switch (newState) {
|
||||||
return "STANDBY";
|
|
||||||
case GPS_ACTIVE:
|
case GPS_ACTIVE:
|
||||||
return "ACTIVE";
|
case GPS_IDLE:
|
||||||
default:
|
if (oldState == GPS_ACTIVE || oldState == GPS_IDLE) // If hardware already awake, no changes needed
|
||||||
return "UNKNOWN";
|
break;
|
||||||
|
if (oldState != GPS_ACTIVE && oldState != GPS_IDLE) // If hardware just waking now, clear buffer
|
||||||
|
clearBuffer();
|
||||||
|
powerMon->setState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing)
|
||||||
|
writePinEN(true); // Power (EN pin): on
|
||||||
|
setPowerPMU(true); // Power (PMU): on
|
||||||
|
writePinStandby(false); // Standby (pin): awake (not standby)
|
||||||
|
setPowerUBLOX(true); // Standby (UBLOX): awake
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GPS_SOFTSLEEP:
|
||||||
|
powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing)
|
||||||
|
writePinEN(true); // Power (EN pin): on
|
||||||
|
setPowerPMU(true); // Power (PMU): on
|
||||||
|
writePinStandby(true); // Standby (pin): asleep (not awake)
|
||||||
|
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GPS_HARDSLEEP:
|
||||||
|
powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing)
|
||||||
|
writePinEN(false); // Power (EN pin): off
|
||||||
|
setPowerPMU(false); // Power (PMU): off
|
||||||
|
writePinStandby(true); // Standby (pin): asleep (not awake)
|
||||||
|
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GPS_OFF:
|
||||||
|
assert(sleepTime == 0); // This is an indefinite sleep
|
||||||
|
powerMon->clearState(meshtastic_PowerMon_State_GPS_Active); // Report change for power monitoring (during testing)
|
||||||
|
writePinEN(false); // Power (EN pin): off
|
||||||
|
setPowerPMU(false); // Power (PMU): off
|
||||||
|
writePinStandby(true); // Standby (pin): asleep
|
||||||
|
setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
|
// Set power with EN pin, if relevant
|
||||||
|
void GPS::writePinEN(bool on)
|
||||||
{
|
{
|
||||||
// Record the current powerState
|
// Abort: if conflict with Canned Messages when using Wisblock(?)
|
||||||
if (on)
|
if (HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1))
|
||||||
powerState = GPS_ACTIVE;
|
|
||||||
else if (!enabled) // User has disabled with triple press
|
|
||||||
powerState = GPS_OFF;
|
|
||||||
else if (sleepTime <= GPS_IDLE_THRESHOLD_SECONDS * 1000UL)
|
|
||||||
powerState = GPS_IDLE;
|
|
||||||
else if (standbyOnly)
|
|
||||||
powerState = GPS_STANDBY;
|
|
||||||
else
|
|
||||||
powerState = GPS_OFF;
|
|
||||||
|
|
||||||
LOG_DEBUG("GPS::powerState=%s\n", powerStateToString());
|
|
||||||
|
|
||||||
// If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out.
|
|
||||||
if (!on && powerState == GPS_IDLE)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (on) {
|
// Abort: if pin unset
|
||||||
powerMon->setState(meshtastic_PowerMon_State_GPS_Active);
|
if (!en_gpio)
|
||||||
clearBuffer(); // drop any old data waiting in the buffer before re-enabling
|
|
||||||
if (en_gpio)
|
|
||||||
digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE); // turn this on if defined, every time
|
|
||||||
} else {
|
|
||||||
powerMon->clearState(meshtastic_PowerMon_State_GPS_Active);
|
|
||||||
}
|
|
||||||
isInPowersave = !on;
|
|
||||||
if (!standbyOnly && en_gpio != 0 &&
|
|
||||||
!(HW_VENDOR == meshtastic_HardwareModel_RAK4631 && (rotaryEncoderInterruptImpl1 || upDownInterruptImpl1))) {
|
|
||||||
LOG_DEBUG("GPS powerdown using GPS_EN_ACTIVE\n");
|
|
||||||
digitalWrite(en_gpio, on ? GPS_EN_ACTIVE : !GPS_EN_ACTIVE);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
#ifdef HAS_PMU // We only have PMUs on the T-Beam, and that board has a tiny battery to save GPS ephemera, so treat as a standby.
|
// Determine new value for the pin
|
||||||
if (pmu_found && PMU) {
|
bool val = GPS_EN_ACTIVE ? on : !on;
|
||||||
|
|
||||||
|
// Write and log
|
||||||
|
pinMode(en_gpio, OUTPUT);
|
||||||
|
digitalWrite(en_gpio, val);
|
||||||
|
#ifdef GPS_EXTRAVERBOSE
|
||||||
|
LOG_DEBUG("Pin EN %s\n", val == HIGH ? "HIGH" : "LOW");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value of the STANDBY pin, if relevant
|
||||||
|
// true for standby state, false for awake
|
||||||
|
void GPS::writePinStandby(bool standby)
|
||||||
|
{
|
||||||
|
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
|
||||||
|
|
||||||
|
// Determine the new value for the pin
|
||||||
|
// Normally: active HIGH for awake
|
||||||
|
#if PIN_GPS_STANDBY_INVERTED
|
||||||
|
bool val = standby;
|
||||||
|
#else
|
||||||
|
bool val = !standby;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Write and log
|
||||||
|
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||||
|
digitalWrite(PIN_GPS_STANDBY, val);
|
||||||
|
#ifdef GPS_EXTRAVERBOSE
|
||||||
|
LOG_DEBUG("Pin STANDBY %s\n", val == HIGH ? "HIGH" : "LOW");
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable / Disable GPS with PMU, if present
|
||||||
|
void GPS::setPowerPMU(bool on)
|
||||||
|
{
|
||||||
|
// We only have PMUs on the T-Beam, and that board has a tiny battery to save GPS ephemera,
|
||||||
|
// so treat as a standby.
|
||||||
|
#ifdef HAS_PMU
|
||||||
|
// Abort: if no PMU
|
||||||
|
if (!pmu_found)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Abort: if PMU not initialized
|
||||||
|
if (!PMU)
|
||||||
|
return;
|
||||||
|
|
||||||
uint8_t model = PMU->getChipModel();
|
uint8_t model = PMU->getChipModel();
|
||||||
if (model == XPOWERS_AXP2101) {
|
if (model == XPOWERS_AXP2101) {
|
||||||
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
||||||
@ -845,55 +907,62 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
|
|||||||
// t-beam v1.1 GNSS power channel
|
// t-beam v1.1 GNSS power channel
|
||||||
on ? PMU->enablePowerOutput(XPOWERS_LDO3) : PMU->disablePowerOutput(XPOWERS_LDO3);
|
on ? PMU->enablePowerOutput(XPOWERS_LDO3) : PMU->disablePowerOutput(XPOWERS_LDO3);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
#ifdef GPS_EXTRAVERBOSE
|
||||||
|
LOG_DEBUG("PMU %s\n", on ? "on" : "off");
|
||||||
#endif
|
#endif
|
||||||
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set UBLOX power, if relevant
|
||||||
|
void GPS::setPowerUBLOX(bool on, uint32_t sleepMs)
|
||||||
|
{
|
||||||
|
// Abort: if not UBLOX hardware
|
||||||
|
if (gnssModel != GNSS_MODEL_UBLOX)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If waking
|
||||||
if (on) {
|
if (on) {
|
||||||
LOG_INFO("Waking GPS\n");
|
|
||||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
|
||||||
// Some PCB's use an inverse logic due to a transistor driver
|
|
||||||
// Example for this is the Pico-Waveshare Lora+GPS HAT
|
|
||||||
#ifdef PIN_GPS_STANDBY_INVERTED
|
|
||||||
digitalWrite(PIN_GPS_STANDBY, 0);
|
|
||||||
#else
|
|
||||||
digitalWrite(PIN_GPS_STANDBY, 1);
|
|
||||||
#endif
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
LOG_INFO("GPS entering sleep\n");
|
|
||||||
// notifyGPSSleep.notifyObservers(NULL);
|
|
||||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
|
||||||
#ifdef PIN_GPS_STANDBY_INVERTED
|
|
||||||
digitalWrite(PIN_GPS_STANDBY, 1);
|
|
||||||
#else
|
|
||||||
digitalWrite(PIN_GPS_STANDBY, 0);
|
|
||||||
#endif
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (!on) {
|
|
||||||
if (gnssModel == GNSS_MODEL_UBLOX) {
|
|
||||||
uint8_t msglen;
|
|
||||||
LOG_DEBUG("Sleep Time: %i\n", sleepTime);
|
|
||||||
if (strncmp(info.hwVersion, "000A0000", 8) != 0) {
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
gps->_message_PMREQ[0 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet
|
|
||||||
}
|
|
||||||
msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ);
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
gps->_message_PMREQ_10[4 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet
|
|
||||||
}
|
|
||||||
msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10);
|
|
||||||
}
|
|
||||||
gps->_serial_gps->write(gps->UBXscratch, msglen);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (gnssModel == GNSS_MODEL_UBLOX) {
|
|
||||||
gps->_serial_gps->write(0xFF);
|
gps->_serial_gps->write(0xFF);
|
||||||
clearBuffer(); // This often returns old data, so drop it
|
clearBuffer(); // This often returns old data, so drop it
|
||||||
|
#ifdef GPS_EXTRAVERBOSE
|
||||||
|
LOG_DEBUG("UBLOX: wake\n");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If putting to sleep
|
||||||
|
else {
|
||||||
|
uint8_t msglen;
|
||||||
|
|
||||||
|
// If we're being asked to sleep indefinitely, make *sure* we're awake first, to process the new sleep command
|
||||||
|
if (sleepMs == 0) {
|
||||||
|
setPowerUBLOX(true);
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine hardware version
|
||||||
|
if (strncmp(info.hwVersion, "000A0000", 8) != 0) {
|
||||||
|
// Encode the sleep time in millis into the packet
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
gps->_message_PMREQ[0 + i] = sleepMs >> (i * 8);
|
||||||
|
|
||||||
|
// Record the message length
|
||||||
|
msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ);
|
||||||
|
} else {
|
||||||
|
// Encode the sleep time in millis into the packet
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
gps->_message_PMREQ_10[4 + i] = sleepMs >> (i * 8);
|
||||||
|
|
||||||
|
// Record the message length
|
||||||
|
msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the UBX packet
|
||||||
|
gps->_serial_gps->write(gps->UBXscratch, msglen);
|
||||||
|
|
||||||
|
#ifdef GPS_EXTRAVERBOSE
|
||||||
|
LOG_DEBUG("UBLOX: sleep for %dmS\n", sleepMs);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -906,106 +975,52 @@ void GPS::setConnected()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// We want a GPS lock. Wake the hardware
|
||||||
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
void GPS::up()
|
||||||
*
|
|
||||||
* calls sleep/wake
|
|
||||||
*/
|
|
||||||
void GPS::setAwake(bool wantAwake)
|
|
||||||
{
|
{
|
||||||
|
scheduling.informSearching();
|
||||||
|
setPowerState(GPS_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
// If user has disabled GPS, make sure it is off, not just in standby or idle
|
// We've got a GPS lock. Enter a low power state, potentially.
|
||||||
if (!wantAwake && !enabled && powerState != GPS_OFF) {
|
void GPS::down()
|
||||||
setGPSPower(false, false, 0);
|
{
|
||||||
return;
|
scheduling.informGotLock();
|
||||||
}
|
uint32_t predictedSearchDuration = scheduling.predictedSearchDurationMs();
|
||||||
|
uint32_t sleepTime = scheduling.msUntilNextSearch();
|
||||||
|
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
|
||||||
|
|
||||||
// If GPS power state needs to change
|
LOG_DEBUG("%us until next search\n", sleepTime / 1000);
|
||||||
if ((wantAwake && powerState != GPS_ACTIVE) || (!wantAwake && powerState == GPS_ACTIVE)) {
|
|
||||||
LOG_DEBUG("WANT GPS=%d\n", wantAwake);
|
|
||||||
|
|
||||||
// Calculate how long it takes to get a GPS lock
|
// If update interval less than 10 seconds, no attempt to sleep
|
||||||
if (wantAwake) {
|
if (updateInterval <= 10 * 1000UL)
|
||||||
// Record the time we start looking for a lock
|
setPowerState(GPS_IDLE);
|
||||||
lastWakeStartMsec = millis();
|
|
||||||
} else {
|
|
||||||
// Record by how much we missed our ideal target postion.gps_update_interval (for logging only)
|
|
||||||
// Need to calculate this before we update lastSleepStartMsec, to make the new prediction
|
|
||||||
int32_t lateByMsec = (int32_t)(millis() - lastSleepStartMsec) - (int32_t)getSleepTime();
|
|
||||||
|
|
||||||
// Record the time we finish looking for a lock
|
|
||||||
lastSleepStartMsec = millis();
|
|
||||||
|
|
||||||
// How long did it take to get GPS lock this time?
|
|
||||||
uint32_t lockTime = lastSleepStartMsec - lastWakeStartMsec;
|
|
||||||
|
|
||||||
// Update the lock-time prediction
|
|
||||||
// Used pre-emptively, attempting to hit target of gps.position_update_interval
|
|
||||||
switch (GPSCycles) {
|
|
||||||
case 0:
|
|
||||||
LOG_DEBUG("Initial GPS lock took %ds\n", lockTime / 1000);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
predictedLockTime = lockTime; // Avoid slow ramp-up - start with a real value
|
|
||||||
LOG_DEBUG("GPS Lock took %ds\n", lockTime / 1000);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Predict lock-time using exponential smoothing: respond slowly to changes
|
|
||||||
predictedLockTime = (lockTime * 0.2) + (predictedLockTime * 0.8); // Latest lock time has 20% weight on prediction
|
|
||||||
LOG_INFO("GPS Lock took %ds. %s by %ds. Next lock predicted to take %ds.\n", lockTime / 1000,
|
|
||||||
(lateByMsec > 0) ? "Late" : "Early", abs(lateByMsec) / 1000, predictedLockTime / 1000);
|
|
||||||
}
|
|
||||||
GPSCycles++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// How long to wait before attempting next GPS update
|
|
||||||
// Aims to hit position.gps_update_interval by using the lock-time prediction
|
|
||||||
uint32_t compensatedSleepTime = (getSleepTime() > predictedLockTime) ? (getSleepTime() - predictedLockTime) : 0;
|
|
||||||
|
|
||||||
// If long interval between updates: power off between updates
|
|
||||||
if (compensatedSleepTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) {
|
|
||||||
setGPSPower(wantAwake, false, getSleepTime() - predictedLockTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If waking relatively frequently: don't power off. Would use more energy trying to reacquire lock each time
|
|
||||||
// We'll either use a "powered-on" standby, or just wait it out, depending on how soon the next update is due
|
|
||||||
// Will decide which inside setGPSPower method
|
|
||||||
else {
|
else {
|
||||||
#ifdef GPS_UC6580
|
// Check whether the GPS hardware is capable of GPS_SOFTSLEEP
|
||||||
setGPSPower(wantAwake, false, compensatedSleepTime);
|
// If not, fallback to GPS_HARDSLEEP instead
|
||||||
#else
|
bool softsleepSupported = false;
|
||||||
setGPSPower(wantAwake, true, compensatedSleepTime);
|
if (gnssModel == GNSS_MODEL_UBLOX) // U-blox is supported via PMREQ
|
||||||
|
softsleepSupported = true;
|
||||||
|
#ifdef PIN_GPS_STANDBY // L76B, L76K and clones have a standby pin
|
||||||
|
softsleepSupported = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// How long does gps_update_interval need to be, for GPS_HARDSLEEP to become more efficient than GPS_SOFTSLEEP?
|
||||||
|
// Heuristic equation. A compromise manually fitted to power observations from U-blox NEO-6M and M10050
|
||||||
|
// https://www.desmos.com/calculator/6gvjghoumr
|
||||||
|
// This is not particularly accurate, but probably an impromevement over a single, fixed threshold
|
||||||
|
uint32_t hardsleepThreshold = (2750 * pow(predictedSearchDuration / 1000, 1.22));
|
||||||
|
LOG_DEBUG("gps_update_interval >= %us needed to justify hardsleep\n", hardsleepThreshold / 1000);
|
||||||
|
|
||||||
|
// If update interval too short: softsleep (if supported by hardware)
|
||||||
|
if (softsleepSupported && updateInterval < hardsleepThreshold)
|
||||||
|
setPowerState(GPS_SOFTSLEEP, sleepTime);
|
||||||
|
|
||||||
|
// If update interval long enough (or softsleep unsupported): hardsleep instead
|
||||||
|
else
|
||||||
|
setPowerState(GPS_HARDSLEEP, sleepTime);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get how long we should stay looking for each acquisition in msecs
|
|
||||||
*/
|
|
||||||
uint32_t GPS::getWakeTime() const
|
|
||||||
{
|
|
||||||
uint32_t t = config.position.position_broadcast_secs;
|
|
||||||
|
|
||||||
if (t == UINT32_MAX)
|
|
||||||
return t; // already maxint
|
|
||||||
|
|
||||||
return Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get how long we should sleep between aqusition attempts in msecs
|
|
||||||
*/
|
|
||||||
uint32_t GPS::getSleepTime() const
|
|
||||||
{
|
|
||||||
uint32_t t = config.position.gps_update_interval;
|
|
||||||
|
|
||||||
// We'll not need the GPS thread to wake up again after first acq. with fixed position.
|
|
||||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED || config.position.fixed_position)
|
|
||||||
t = UINT32_MAX; // Sleep forever now
|
|
||||||
|
|
||||||
if (t == UINT32_MAX)
|
|
||||||
return t; // already maxint
|
|
||||||
|
|
||||||
return Default::getConfiguredOrDefaultMs(t, default_gps_update_interval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPS::publishUpdate()
|
void GPS::publishUpdate()
|
||||||
@ -1056,13 +1071,13 @@ int32_t GPS::runOnce()
|
|||||||
return disable();
|
return disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (whileIdle()) {
|
if (whileActive()) {
|
||||||
// if we have received valid NMEA claim we are connected
|
// if we have received valid NMEA claim we are connected
|
||||||
setConnected();
|
setConnected();
|
||||||
} else {
|
} else {
|
||||||
if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && (gnssModel == GNSS_MODEL_UBLOX)) {
|
if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && (gnssModel == GNSS_MODEL_UBLOX)) {
|
||||||
// reset the GPS on next bootup
|
// reset the GPS on next bootup
|
||||||
if (devicestate.did_gps_reset && (millis() - lastWakeStartMsec > 60000) && !hasFlow()) {
|
if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) {
|
||||||
LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n");
|
LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n");
|
||||||
devicestate.did_gps_reset = false;
|
devicestate.did_gps_reset = false;
|
||||||
nodeDB->saveDeviceStateToDisk();
|
nodeDB->saveDeviceStateToDisk();
|
||||||
@ -1077,20 +1092,10 @@ int32_t GPS::runOnce()
|
|||||||
// gps->factoryReset();
|
// gps->factoryReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are overdue for an update, turn on the GPS and at least publish the current status
|
// If we're due for an update, wake the GPS
|
||||||
uint32_t now = millis();
|
if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue())
|
||||||
uint32_t timeAsleep = now - lastSleepStartMsec;
|
up();
|
||||||
|
|
||||||
auto sleepTime = getSleepTime();
|
|
||||||
if (powerState != GPS_ACTIVE && (sleepTime != UINT32_MAX) &&
|
|
||||||
((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - predictedLockTime)))) {
|
|
||||||
// We now want to be awake - so wake up the GPS
|
|
||||||
setAwake(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// While we are awake
|
|
||||||
if (powerState == GPS_ACTIVE) {
|
|
||||||
// LOG_DEBUG("looking for location\n");
|
|
||||||
// If we've already set time from the GPS, no need to ask the GPS
|
// If we've already set time from the GPS, no need to ask the GPS
|
||||||
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||||
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
||||||
@ -1105,9 +1110,9 @@ int32_t GPS::runOnce()
|
|||||||
shouldPublish = true;
|
shouldPublish = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
now = millis();
|
bool tooLong = scheduling.searchedTooLong();
|
||||||
auto wakeTime = getWakeTime();
|
if (tooLong)
|
||||||
bool tooLong = wakeTime != UINT32_MAX && (now - lastWakeStartMsec) > wakeTime;
|
LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time.\n");
|
||||||
|
|
||||||
// Once we get a location we no longer desperately want an update
|
// Once we get a location we no longer desperately want an update
|
||||||
// LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime);
|
// LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime);
|
||||||
@ -1116,16 +1121,15 @@ int32_t GPS::runOnce()
|
|||||||
if (tooLong) {
|
if (tooLong) {
|
||||||
// we didn't get a location during this ack window, therefore declare loss of lock
|
// we didn't get a location during this ack window, therefore declare loss of lock
|
||||||
if (hasValidLocation) {
|
if (hasValidLocation) {
|
||||||
LOG_DEBUG("hasValidLocation FALLING EDGE (last read: %d)\n", gotLoc);
|
LOG_DEBUG("hasValidLocation FALLING EDGE\n");
|
||||||
}
|
}
|
||||||
p = meshtastic_Position_init_default;
|
p = meshtastic_Position_init_default;
|
||||||
hasValidLocation = false;
|
hasValidLocation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAwake(false);
|
down();
|
||||||
shouldPublish = true; // publish our update for this just finished acquisition window
|
shouldPublish = true; // publish our update for this just finished acquisition window
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If state has changed do a publish
|
// If state has changed do a publish
|
||||||
publishUpdate();
|
publishUpdate();
|
||||||
@ -1150,9 +1154,7 @@ void GPS::clearBuffer()
|
|||||||
int GPS::prepareDeepSleep(void *unused)
|
int GPS::prepareDeepSleep(void *unused)
|
||||||
{
|
{
|
||||||
LOG_INFO("GPS deep sleep!\n");
|
LOG_INFO("GPS deep sleep!\n");
|
||||||
|
disable();
|
||||||
setAwake(false);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1348,12 +1350,6 @@ GPS *GPS::createGps()
|
|||||||
new_gps->tx_gpio = _tx_gpio;
|
new_gps->tx_gpio = _tx_gpio;
|
||||||
new_gps->en_gpio = _en_gpio;
|
new_gps->en_gpio = _en_gpio;
|
||||||
|
|
||||||
if (_en_gpio != 0) {
|
|
||||||
LOG_DEBUG("Setting %d to output.\n", _en_gpio);
|
|
||||||
pinMode(_en_gpio, OUTPUT);
|
|
||||||
digitalWrite(_en_gpio, !GPS_EN_ACTIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PIN_GPS_PPS
|
#ifdef PIN_GPS_PPS
|
||||||
// pulse per second
|
// pulse per second
|
||||||
pinMode(PIN_GPS_PPS, INPUT);
|
pinMode(PIN_GPS_PPS, INPUT);
|
||||||
@ -1368,7 +1364,8 @@ GPS *GPS::createGps()
|
|||||||
LOG_DEBUG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP\n");
|
LOG_DEBUG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
new_gps->setGPSPower(true, false, 0);
|
// Make sure the GPS is awake before performing any init.
|
||||||
|
new_gps->up();
|
||||||
|
|
||||||
#ifdef PIN_GPS_RESET
|
#ifdef PIN_GPS_RESET
|
||||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||||
@ -1376,7 +1373,6 @@ GPS *GPS::createGps()
|
|||||||
delay(10);
|
delay(10);
|
||||||
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
||||||
#endif
|
#endif
|
||||||
new_gps->setAwake(true); // Wake GPS power before doing any init
|
|
||||||
|
|
||||||
if (_serial_gps) {
|
if (_serial_gps) {
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
@ -1662,13 +1658,13 @@ bool GPS::hasFlow()
|
|||||||
return reader.passedChecksum() > 0;
|
return reader.passedChecksum() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPS::whileIdle()
|
bool GPS::whileActive()
|
||||||
{
|
{
|
||||||
unsigned int charsInBuf = 0;
|
unsigned int charsInBuf = 0;
|
||||||
bool isValid = false;
|
bool isValid = false;
|
||||||
if (powerState != GPS_ACTIVE) {
|
if (powerState != GPS_ACTIVE) {
|
||||||
clearBuffer();
|
clearBuffer();
|
||||||
return (powerState == GPS_ACTIVE);
|
return false;
|
||||||
}
|
}
|
||||||
#ifdef SERIAL_BUFFER_SIZE
|
#ifdef SERIAL_BUFFER_SIZE
|
||||||
if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) {
|
if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) {
|
||||||
@ -1699,20 +1695,21 @@ bool GPS::whileIdle()
|
|||||||
}
|
}
|
||||||
void GPS::enable()
|
void GPS::enable()
|
||||||
{
|
{
|
||||||
// Clear the old lock-time prediction
|
// Clear the old scheduling info (reset the lock-time prediction)
|
||||||
GPSCycles = 0;
|
scheduling.reset();
|
||||||
predictedLockTime = 0;
|
|
||||||
|
|
||||||
enabled = true;
|
enabled = true;
|
||||||
setInterval(GPS_THREAD_INTERVAL);
|
setInterval(GPS_THREAD_INTERVAL);
|
||||||
setAwake(true);
|
|
||||||
|
scheduling.informSearching();
|
||||||
|
setPowerState(GPS_ACTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t GPS::disable()
|
int32_t GPS::disable()
|
||||||
{
|
{
|
||||||
enabled = false;
|
enabled = false;
|
||||||
setInterval(INT32_MAX);
|
setInterval(INT32_MAX);
|
||||||
setAwake(false);
|
setPowerState(GPS_OFF);
|
||||||
|
|
||||||
return INT32_MAX;
|
return INT32_MAX;
|
||||||
}
|
}
|
||||||
@ -1721,11 +1718,11 @@ void GPS::toggleGpsMode()
|
|||||||
{
|
{
|
||||||
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||||
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED;
|
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED;
|
||||||
LOG_DEBUG("Flag set to false for gps power. GpsMode: DISABLED\n");
|
LOG_INFO("User toggled GpsMode. Now DISABLED.\n");
|
||||||
disable();
|
disable();
|
||||||
} else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) {
|
} else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) {
|
||||||
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
|
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
|
||||||
LOG_DEBUG("Flag set to true to restore power. GpsMode: ENABLED\n");
|
LOG_INFO("User toggled GpsMode. Now ENABLED\n");
|
||||||
enable();
|
enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,10 +39,11 @@ typedef enum {
|
|||||||
} GPS_RESPONSE;
|
} GPS_RESPONSE;
|
||||||
|
|
||||||
enum GPSPowerState : uint8_t {
|
enum GPSPowerState : uint8_t {
|
||||||
GPS_OFF = 0, // Physically powered off
|
GPS_ACTIVE, // Awake and want a position
|
||||||
GPS_ACTIVE = 1, // Awake and want a position
|
GPS_IDLE, // Awake, but not wanting another position yet
|
||||||
GPS_STANDBY = 2, // Physically powered on, but soft-sleeping
|
GPS_SOFTSLEEP, // Physically powered on, but soft-sleeping
|
||||||
GPS_IDLE = 3, // Awake, but not wanting another position yet
|
GPS_HARDSLEEP, // Physically powered off, but scheduled to wake
|
||||||
|
GPS_OFF // Powered off indefinitely
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate a string representation of DOP
|
// Generate a string representation of DOP
|
||||||
@ -67,14 +68,11 @@ class GPS : private concurrency::OSThread
|
|||||||
uint8_t fixType = 0; // fix type from GPGSA
|
uint8_t fixType = 0; // fix type from GPGSA
|
||||||
#endif
|
#endif
|
||||||
private:
|
private:
|
||||||
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0;
|
|
||||||
const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600};
|
const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600};
|
||||||
|
|
||||||
uint32_t rx_gpio = 0;
|
uint32_t rx_gpio = 0;
|
||||||
uint32_t tx_gpio = 0;
|
uint32_t tx_gpio = 0;
|
||||||
uint32_t en_gpio = 0;
|
uint32_t en_gpio = 0;
|
||||||
uint32_t predictedLockTime = 0;
|
|
||||||
uint32_t GPSCycles = 0;
|
|
||||||
|
|
||||||
int speedSelect = 0;
|
int speedSelect = 0;
|
||||||
int probeTries = 2;
|
int probeTries = 2;
|
||||||
@ -99,7 +97,6 @@ class GPS : private concurrency::OSThread
|
|||||||
uint8_t numSatellites = 0;
|
uint8_t numSatellites = 0;
|
||||||
|
|
||||||
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
|
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
|
||||||
CallbackObserver<GPS, void *> notifyGPSSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** If !NULL we will use this serial port to construct our GPS */
|
/** If !NULL we will use this serial port to construct our GPS */
|
||||||
@ -175,7 +172,8 @@ class GPS : private concurrency::OSThread
|
|||||||
// toggle between enabled/disabled
|
// toggle between enabled/disabled
|
||||||
void toggleGpsMode();
|
void toggleGpsMode();
|
||||||
|
|
||||||
void setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime);
|
// Change the power state of the GPS - for power saving / shutdown
|
||||||
|
void setPowerState(GPSPowerState newState, uint32_t sleepMs = 0);
|
||||||
|
|
||||||
/// Returns true if we have acquired GPS lock.
|
/// Returns true if we have acquired GPS lock.
|
||||||
virtual bool hasLock();
|
virtual bool hasLock();
|
||||||
@ -206,18 +204,18 @@ class GPS : private concurrency::OSThread
|
|||||||
|
|
||||||
GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis);
|
GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis);
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
|
||||||
*
|
|
||||||
* calls sleep/wake
|
|
||||||
*/
|
|
||||||
void setAwake(bool on);
|
|
||||||
virtual bool factoryReset();
|
virtual bool factoryReset();
|
||||||
|
|
||||||
// Creates an instance of the GPS class.
|
// Creates an instance of the GPS class.
|
||||||
// Returns the new instance or null if the GPS is not present.
|
// Returns the new instance or null if the GPS is not present.
|
||||||
static GPS *createGps();
|
static GPS *createGps();
|
||||||
|
|
||||||
|
// Wake the GPS hardware - ready for an update
|
||||||
|
void up();
|
||||||
|
|
||||||
|
// Let the GPS hardware save power between updates
|
||||||
|
void down();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
@ -240,7 +238,7 @@ class GPS : private concurrency::OSThread
|
|||||||
*
|
*
|
||||||
* Return true if we received a valid message from the GPS
|
* Return true if we received a valid message from the GPS
|
||||||
*/
|
*/
|
||||||
virtual bool whileIdle();
|
virtual bool whileActive();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
@ -267,13 +265,21 @@ class GPS : private concurrency::OSThread
|
|||||||
void UBXChecksum(uint8_t *message, size_t length);
|
void UBXChecksum(uint8_t *message, size_t length);
|
||||||
void CASChecksum(uint8_t *message, size_t length);
|
void CASChecksum(uint8_t *message, size_t length);
|
||||||
|
|
||||||
/** Get how long we should stay looking for each aquisition
|
/** Set power with EN pin, if relevant
|
||||||
*/
|
*/
|
||||||
uint32_t getWakeTime() const;
|
void writePinEN(bool on);
|
||||||
|
|
||||||
/** Get how long we should sleep between aqusition attempts
|
/** Set the value of the STANDBY pin, if relevant
|
||||||
*/
|
*/
|
||||||
uint32_t getSleepTime() const;
|
void writePinStandby(bool standby);
|
||||||
|
|
||||||
|
/** Set GPS power with PMU, if relevant
|
||||||
|
*/
|
||||||
|
void setPowerPMU(bool on);
|
||||||
|
|
||||||
|
/** Set UBLOX power, if relevant
|
||||||
|
*/
|
||||||
|
void setPowerUBLOX(bool on, uint32_t sleepMs = 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell users we have new GPS readings
|
* Tell users we have new GPS readings
|
||||||
|
118
src/gps/GPSUpdateScheduling.cpp
Normal file
118
src/gps/GPSUpdateScheduling.cpp
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#include "GPSUpdateScheduling.h"
|
||||||
|
|
||||||
|
#include "Default.h"
|
||||||
|
|
||||||
|
// Mark the time when searching for GPS position begins
|
||||||
|
void GPSUpdateScheduling::informSearching()
|
||||||
|
{
|
||||||
|
searchStartedMs = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the time when searching for GPS is complete,
|
||||||
|
// then update the predicted lock-time
|
||||||
|
void GPSUpdateScheduling::informGotLock()
|
||||||
|
{
|
||||||
|
searchEndedMs = millis();
|
||||||
|
LOG_DEBUG("Took %us to get lock\n", (searchEndedMs - searchStartedMs) / 1000);
|
||||||
|
updateLockTimePrediction();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear old lock-time prediction data.
|
||||||
|
// When re-enabling GPS with user button.
|
||||||
|
void GPSUpdateScheduling::reset()
|
||||||
|
{
|
||||||
|
searchStartedMs = 0;
|
||||||
|
searchEndedMs = 0;
|
||||||
|
searchCount = 0;
|
||||||
|
predictedMsToGetLock = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How many milliseconds before we should next search for GPS position
|
||||||
|
// Used by GPS hardware directly, to enter timed hardware sleep
|
||||||
|
uint32_t GPSUpdateScheduling::msUntilNextSearch()
|
||||||
|
{
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
// Target interval (seconds), between GPS updates
|
||||||
|
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval, default_gps_update_interval);
|
||||||
|
|
||||||
|
// Check how long until we should start searching, to hopefully hit our target interval
|
||||||
|
uint32_t dueAtMs = searchEndedMs + updateInterval;
|
||||||
|
uint32_t compensatedStart = dueAtMs - predictedMsToGetLock;
|
||||||
|
int32_t remainingMs = compensatedStart - now;
|
||||||
|
|
||||||
|
// If we should have already started (negative value), start ASAP
|
||||||
|
if (remainingMs < 0)
|
||||||
|
remainingMs = 0;
|
||||||
|
|
||||||
|
return (uint32_t)remainingMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How long have we already been searching?
|
||||||
|
// Used to abort a search in progress, if it runs unnaceptably long
|
||||||
|
uint32_t GPSUpdateScheduling::elapsedSearchMs()
|
||||||
|
{
|
||||||
|
// If searching
|
||||||
|
if (searchStartedMs > searchEndedMs)
|
||||||
|
return millis() - searchStartedMs;
|
||||||
|
|
||||||
|
// If not searching - 0ms. We shouldn't really consume this value
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it now time to begin searching for a GPS position?
|
||||||
|
bool GPSUpdateScheduling::isUpdateDue()
|
||||||
|
{
|
||||||
|
return (msUntilNextSearch() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have we been searching for a GPS position for too long?
|
||||||
|
bool GPSUpdateScheduling::searchedTooLong()
|
||||||
|
{
|
||||||
|
uint32_t maxSearchMs =
|
||||||
|
Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs);
|
||||||
|
|
||||||
|
// If broadcast interval set to max, no such thing as "too long"
|
||||||
|
if (maxSearchMs == UINT32_MAX)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If we've been searching longer than our position broadcast interval: that's too long
|
||||||
|
else if (elapsedSearchMs() > maxSearchMs)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Otherwise, not too long yet!
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the predicted time-to-get-lock, by exponentially smoothing the latest observation
|
||||||
|
void GPSUpdateScheduling::updateLockTimePrediction()
|
||||||
|
{
|
||||||
|
|
||||||
|
// How long did it take to get GPS lock this time?
|
||||||
|
// Duration between down() calls
|
||||||
|
int32_t lockTime = searchEndedMs - searchStartedMs;
|
||||||
|
if (lockTime < 0)
|
||||||
|
lockTime = 0;
|
||||||
|
|
||||||
|
// Ignore the first lock-time: likely to be long, will skew data
|
||||||
|
|
||||||
|
// Second locktime: likely stable. Use to intialize the smoothing filter
|
||||||
|
if (searchCount == 1)
|
||||||
|
predictedMsToGetLock = lockTime;
|
||||||
|
|
||||||
|
// Third locktime and after: predict using exponential smoothing. Respond slowly to changes
|
||||||
|
else if (searchCount > 1)
|
||||||
|
predictedMsToGetLock = (lockTime * weighting) + (predictedMsToGetLock * (1 - weighting));
|
||||||
|
|
||||||
|
searchCount++; // Only tracked so we can diregard initial lock-times
|
||||||
|
|
||||||
|
LOG_DEBUG("Predicting %us to get next lock\n", predictedMsToGetLock / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// How long do we expect to spend searching for a lock?
|
||||||
|
uint32_t GPSUpdateScheduling::predictedSearchDurationMs()
|
||||||
|
{
|
||||||
|
return GPSUpdateScheduling::predictedMsToGetLock;
|
||||||
|
}
|
29
src/gps/GPSUpdateScheduling.h
Normal file
29
src/gps/GPSUpdateScheduling.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
// Encapsulates code responsible for the timing of GPS updates
|
||||||
|
class GPSUpdateScheduling
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Marks the time of these events, for calculation use
|
||||||
|
void informSearching();
|
||||||
|
void informGotLock(); // Predicted lock-time is recalculated here
|
||||||
|
|
||||||
|
void reset(); // Reset the prediction - after GPS::disable() / GPS::enable()
|
||||||
|
bool isUpdateDue(); // Is it time to begin searching for a GPS position?
|
||||||
|
bool searchedTooLong(); // Have we been searching for too long?
|
||||||
|
|
||||||
|
uint32_t msUntilNextSearch(); // How long until we need to begin searching for a GPS? Info provided to GPS hardware for sleep
|
||||||
|
uint32_t elapsedSearchMs(); // How long have we been searching so far?
|
||||||
|
uint32_t predictedSearchDurationMs(); // How long do we expect to spend searching for a lock?
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateLockTimePrediction(); // Called from informGotLock
|
||||||
|
uint32_t searchStartedMs = 0;
|
||||||
|
uint32_t searchEndedMs = 0;
|
||||||
|
uint32_t searchCount = 0;
|
||||||
|
uint32_t predictedMsToGetLock = 0;
|
||||||
|
|
||||||
|
const float weighting = 0.2; // Controls exponential smoothing of lock-times prediction. 20% weighting of "latest lock-time".
|
||||||
|
};
|
@ -37,10 +37,7 @@ Observable<void *> preflightSleep;
|
|||||||
|
|
||||||
/// Called to tell observers we are now entering sleep and you should prepare. Must return 0
|
/// Called to tell observers we are now entering sleep and you should prepare. Must return 0
|
||||||
/// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep
|
/// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep
|
||||||
/// notifyGPSSleep will be called when config.position.gps_enabled is set to 0 or from buttonthread when GPS_POWER_TOGGLE is
|
|
||||||
/// enabled.
|
|
||||||
Observable<void *> notifySleep, notifyDeepSleep;
|
Observable<void *> notifySleep, notifyDeepSleep;
|
||||||
Observable<void *> notifyGPSSleep;
|
|
||||||
|
|
||||||
// deep sleep support
|
// deep sleep support
|
||||||
RTC_DATA_ATTR int bootCount = 0;
|
RTC_DATA_ATTR int bootCount = 0;
|
||||||
@ -240,11 +237,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false)
|
|||||||
#ifdef PIN_POWER_EN
|
#ifdef PIN_POWER_EN
|
||||||
pinMode(PIN_POWER_EN, INPUT); // power off peripherals
|
pinMode(PIN_POWER_EN, INPUT); // power off peripherals
|
||||||
// pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
|
// pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
|
||||||
#endif
|
|
||||||
#if HAS_GPS
|
|
||||||
// Kill GPS power completely (even if previously we just had it in sleep mode)
|
|
||||||
if (gps)
|
|
||||||
gps->setGPSPower(false, false, 0);
|
|
||||||
#endif
|
#endif
|
||||||
setLed(false);
|
setLed(false);
|
||||||
|
|
||||||
|
@ -41,8 +41,6 @@ extern Observable<void *> notifySleep;
|
|||||||
/// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0
|
/// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0
|
||||||
extern Observable<void *> notifyDeepSleep;
|
extern Observable<void *> notifyDeepSleep;
|
||||||
|
|
||||||
/// Called to tell GPS thread to enter deep sleep independently of LoRa/MCU sleep, prior to full poweroff. Must return 0
|
|
||||||
extern Observable<void *> notifyGPSSleep;
|
|
||||||
void enableModemSleep();
|
void enableModemSleep();
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
void enableLoraInterrupt();
|
void enableLoraInterrupt();
|
||||||
|
Loading…
Reference in New Issue
Block a user