From 395f1a9be7175a6f2482f3bbab2b8fb58f18746b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 14 Nov 2025 17:35:21 -0500 Subject: [PATCH] Add new files for version 1.0.1332 --- src/atScreenLCDGfx.cpp | 90 ++++++++ src/atScreenLCDGfx.h | 27 +++ src/atScreenTFTeSPI.cpp | 82 +++++++ src/atScreenTFTeSPI.h | 21 ++ src/concurrency/BinarySemaphoreFreeRTOS.cpp | 40 ++++ src/concurrency/BinarySemaphoreFreeRTOS.h | 30 +++ src/concurrency/BinarySemaphorePosix.cpp | 28 +++ src/concurrency/BinarySemaphorePosix.h | 30 +++ src/concurrency/InterruptableDelay.cpp | 35 +++ src/concurrency/InterruptableDelay.h | 41 ++++ src/concurrency/OSThread.cpp | 143 ++++++++++++ src/concurrency/OSThread.h | 90 ++++++++ src/concurrency/memGet.cpp | 73 ++++++ src/concurrency/memGet.h | 18 ++ src/gps/GenericGPS.cpp | 241 ++++++++++++++++++++ src/gps/GenericGPS.h | 46 ++++ 16 files changed, 1035 insertions(+) create mode 100644 src/atScreenLCDGfx.cpp create mode 100644 src/atScreenLCDGfx.h create mode 100644 src/atScreenTFTeSPI.cpp create mode 100644 src/atScreenTFTeSPI.h create mode 100644 src/concurrency/BinarySemaphoreFreeRTOS.cpp create mode 100644 src/concurrency/BinarySemaphoreFreeRTOS.h create mode 100644 src/concurrency/BinarySemaphorePosix.cpp create mode 100644 src/concurrency/BinarySemaphorePosix.h create mode 100644 src/concurrency/InterruptableDelay.cpp create mode 100644 src/concurrency/InterruptableDelay.h create mode 100644 src/concurrency/OSThread.cpp create mode 100644 src/concurrency/OSThread.h create mode 100644 src/concurrency/memGet.cpp create mode 100644 src/concurrency/memGet.h create mode 100644 src/gps/GenericGPS.cpp create mode 100644 src/gps/GenericGPS.h diff --git a/src/atScreenLCDGfx.cpp b/src/atScreenLCDGfx.cpp new file mode 100644 index 0000000..89d85f5 --- /dev/null +++ b/src/atScreenLCDGfx.cpp @@ -0,0 +1,90 @@ +#include "config.h" +#ifdef USE_SCREEN_LCD_GFX +#include "atScreenLCDGfx.h" +#include "global.h" + +cATScreenLCDGfx::cATScreenLCDGfx(atGlobal &global):cATScreen(global) +{ + +} + +cATScreenLCDGfx::~cATScreenLCDGfx() +{ + +} + +bool cATScreenLCDGfx::init() +{ +#ifdef VEXT_EN + pinMode(VEXT_EN, OUTPUT); + digitalWrite(VEXT_EN, HIGH); +#endif + m_display = new DisplaySH1106_128x64_I2C(-1); + m_display->begin(); + m_display->fill(BLACK); + m_display->setFixedFont(ssd1306xled_font6x8); + m_display->setColor(WHITE); + drawCentreString("Booting", m_display->width()/2, m_display->height()/2); + return true; +} + +void cATScreenLCDGfx::showShutdownScreen() +{ + m_display->fill(BLACK); + drawCentreString("Shutdown...", m_display->width()/2, m_display->height()/2); +} + +void cATScreenLCDGfx::loop() +{ + +} + +void cATScreenLCDGfx::drawCentreString(const char *buf, int x, int y) { + // Get the display's width and the string's dimensions + int16_t x1, y1; + uint16_t text_width, text_height; + NanoFont & font = m_display->getFont(); + lcduint_t textHeight = 0; + lcduint_t textWidth = font.getTextSize(buf,&textHeight); + + m_display->printFixed(x-textWidth/2,y-textHeight/2,buf,STYLE_NORMAL); +} + +void cATScreenLCDGfx::showBootScreen(const char * msg1,const char * msg2) +{ + m_display->fill(BLACK); + if(msg1) + drawCentreString(msg1, m_display->width()/2, 10); +} + +void cATScreenLCDGfx::closeBootScreen() +{ + m_display->fill(BLACK); +} + +void cATScreenLCDGfx::beginMainScreen() +{ + +} + +void cATScreenLCDGfx::screenOn() +{ + m_display->getInterface().displayOn(); +} + +void cATScreenLCDGfx::screenOff() +{ + m_display->getInterface().displayOff(); +} + +void cATScreenLCDGfx::endMainScreen() +{ + char buffer[32]; + //m_tft->fillScreen(TFT_BLACK); + snprintf(buffer,sizeof(buffer),"BAT: %0.2fV, SAT: %02d ", m_global.getPowerStatus()->getBatteryVoltageMv()/1000.0,m_global.m_gpsStatus->getNumSatellites()); + m_display->printFixed(0, 10,buffer,STYLE_NORMAL); +} + + + +#endif \ No newline at end of file diff --git a/src/atScreenLCDGfx.h b/src/atScreenLCDGfx.h new file mode 100644 index 0000000..53710b8 --- /dev/null +++ b/src/atScreenLCDGfx.h @@ -0,0 +1,27 @@ +#pragma once +#include "atscreen.h" +#include "lcdgfx.h" +#include "lcdgfx_gui.h" + + +class cATScreenLCDGfx:public cATScreen +{ +protected: +#ifdef LCD_SH1106 + DisplaySH1106_128x64_I2C *m_display; +#endif + void drawCentreString(const char *buf, int x, int y); +public: + cATScreenLCDGfx(atGlobal &global); + virtual ~cATScreenLCDGfx(); + + virtual bool init(); + virtual void loop(); + virtual void showBootScreen(const char * msg1,const char * msg2); + virtual void closeBootScreen(); + virtual void beginMainScreen(); + virtual void endMainScreen(); + virtual void screenOn(); + virtual void screenOff(); + virtual void showShutdownScreen(); +}; \ No newline at end of file diff --git a/src/atScreenTFTeSPI.cpp b/src/atScreenTFTeSPI.cpp new file mode 100644 index 0000000..6069296 --- /dev/null +++ b/src/atScreenTFTeSPI.cpp @@ -0,0 +1,82 @@ +#include "config.h" +#ifdef USE_SCREEN_TFT_ESPI +#include "atScreenTFTeSPI.h" +#include "global.h" + +cATScreenTFTeSPI::cATScreenTFTeSPI(atGlobal &global):cATScreen(global) +{ + +} + +cATScreenTFTeSPI::~cATScreenTFTeSPI() +{ + +} + +bool cATScreenTFTeSPI::init() +{ +#ifdef VEXT_EN + pinMode(VEXT_EN, OUTPUT); + digitalWrite(VEXT_EN, HIGH); +#endif + + digitalWrite(TFT_BL,HIGH); + m_tft = new TFT_eSPI(TFT_WIDTH, TFT_HEIGHT); + m_tft->init(); + m_tft->setRotation(1); + m_tft->fillScreen(TFT_BLACK); + m_tft->setTextSize(1); + m_tft->setTextColor(TFT_WHITE, TFT_BLACK); + m_tft->drawCentreString("Booting", m_tft->width()/2, m_tft->height()/2, 2); + + return true; +} + +void cATScreenTFTeSPI::loop() +{ + +} + +void cATScreenTFTeSPI::showBootScreen(const char * msg1,const char * msg2) +{ + m_tft->setTextSize(1); + m_tft->setTextColor(TFT_WHITE, TFT_BLACK); + m_tft->fillScreen(TFT_BLACK); + if(msg1) + m_tft->drawCentreString(msg1, m_tft->width()/2, 10, 2); +} + +void cATScreenTFTeSPI::closeBootScreen() +{ + m_tft->fillScreen(TFT_BLACK); +} + +void cATScreenTFTeSPI::beginMainScreen() +{ + +} + +void cATScreenTFTeSPI::screenOn() +{ + digitalWrite(TFT_BL,HIGH); +} + +void cATScreenTFTeSPI::screenOff() +{ + digitalWrite(TFT_BL,LOW); +} + +void cATScreenTFTeSPI::endMainScreen() +{ + char buffer[32]; + m_tft->setTextSize(1); + m_tft->setTextColor(TFT_WHITE, TFT_BLACK); + //m_tft->fillScreen(TFT_BLACK); + snprintf(buffer,sizeof(buffer),"BAT: %0.2fV, SAT: %02d", m_global.getPowerStatus()->getBatteryVoltageMv()/1000.0,m_global.m_gpsStatus->getNumSatellites()); + m_tft->drawString(buffer, 0, 10, 2); + +} + + + +#endif \ No newline at end of file diff --git a/src/atScreenTFTeSPI.h b/src/atScreenTFTeSPI.h new file mode 100644 index 0000000..aebd7d2 --- /dev/null +++ b/src/atScreenTFTeSPI.h @@ -0,0 +1,21 @@ +#pragma once +#include "atscreen.h" +#include "TFT_eSPI.h" + +class cATScreenTFTeSPI:public cATScreen +{ +protected: + TFT_eSPI *m_tft; +public: + cATScreenTFTeSPI(atGlobal &global); + virtual ~cATScreenTFTeSPI(); + + virtual bool init(); + virtual void loop(); + virtual void showBootScreen(const char * msg1,const char * msg2); + virtual void closeBootScreen(); + virtual void beginMainScreen(); + virtual void endMainScreen(); + virtual void screenOn(); + virtual void screenOff(); +}; \ No newline at end of file diff --git a/src/concurrency/BinarySemaphoreFreeRTOS.cpp b/src/concurrency/BinarySemaphoreFreeRTOS.cpp new file mode 100644 index 0000000..1b48474 --- /dev/null +++ b/src/concurrency/BinarySemaphoreFreeRTOS.cpp @@ -0,0 +1,40 @@ +#include "concurrency/BinarySemaphoreFreeRTOS.h" +#include "../freertosinc.h" +#include + +#ifdef HAS_FREE_RTOS + +namespace concurrency +{ + +BinarySemaphoreFreeRTOS::BinarySemaphoreFreeRTOS() : semaphore(xSemaphoreCreateBinary()) +{ + assert(semaphore); +} + +BinarySemaphoreFreeRTOS::~BinarySemaphoreFreeRTOS() +{ + vSemaphoreDelete(semaphore); +} + +/** + * Returns false if we were interrupted + */ +bool BinarySemaphoreFreeRTOS::take(uint32_t msec) +{ + return xSemaphoreTake(semaphore, pdMS_TO_TICKS(msec)); +} + +void BinarySemaphoreFreeRTOS::give() +{ + xSemaphoreGive(semaphore); +} + +IRAM_ATTR void BinarySemaphoreFreeRTOS::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken) +{ + xSemaphoreGiveFromISR(semaphore, pxHigherPriorityTaskWoken); +} + +} // namespace concurrency + +#endif diff --git a/src/concurrency/BinarySemaphoreFreeRTOS.h b/src/concurrency/BinarySemaphoreFreeRTOS.h new file mode 100644 index 0000000..2883151 --- /dev/null +++ b/src/concurrency/BinarySemaphoreFreeRTOS.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../freertosinc.h" + +namespace concurrency +{ + +#ifdef HAS_FREE_RTOS + +class BinarySemaphoreFreeRTOS +{ + SemaphoreHandle_t semaphore; + + public: + BinarySemaphoreFreeRTOS(); + ~BinarySemaphoreFreeRTOS(); + + /** + * Returns false if we timed out + */ + bool take(uint32_t msec); + + void give(); + + void giveFromISR(BaseType_t *pxHigherPriorityTaskWoken); +}; + +#endif + +} // namespace concurrency \ No newline at end of file diff --git a/src/concurrency/BinarySemaphorePosix.cpp b/src/concurrency/BinarySemaphorePosix.cpp new file mode 100644 index 0000000..aada99a --- /dev/null +++ b/src/concurrency/BinarySemaphorePosix.cpp @@ -0,0 +1,28 @@ +#include "concurrency/BinarySemaphorePosix.h" +#include "../freertosinc.h" + +#ifndef HAS_FREE_RTOS + +namespace concurrency +{ + +BinarySemaphorePosix::BinarySemaphorePosix() {} + +BinarySemaphorePosix::~BinarySemaphorePosix() {} + +/** + * Returns false if we timed out + */ +bool BinarySemaphorePosix::take(uint32_t msec) +{ + delay(msec); // FIXME + return false; +} + +void BinarySemaphorePosix::give() {} + +IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken) {} + +} // namespace concurrency + +#endif \ No newline at end of file diff --git a/src/concurrency/BinarySemaphorePosix.h b/src/concurrency/BinarySemaphorePosix.h new file mode 100644 index 0000000..475b298 --- /dev/null +++ b/src/concurrency/BinarySemaphorePosix.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../freertosinc.h" + +namespace concurrency +{ + +#ifndef HAS_FREE_RTOS + +class BinarySemaphorePosix +{ + // SemaphoreHandle_t semaphore; + + public: + BinarySemaphorePosix(); + ~BinarySemaphorePosix(); + + /** + * Returns false if we timed out + */ + bool take(uint32_t msec); + + void give(); + + void giveFromISR(BaseType_t *pxHigherPriorityTaskWoken); +}; + +#endif + +} // namespace concurrency \ No newline at end of file diff --git a/src/concurrency/InterruptableDelay.cpp b/src/concurrency/InterruptableDelay.cpp new file mode 100644 index 0000000..47e3de4 --- /dev/null +++ b/src/concurrency/InterruptableDelay.cpp @@ -0,0 +1,35 @@ +#include "concurrency/InterruptableDelay.h" +#include "../freertosinc.h" + +namespace concurrency +{ + +InterruptableDelay::InterruptableDelay() {} + +InterruptableDelay::~InterruptableDelay() {} + +/** + * Returns false if we were interrupted + */ +bool InterruptableDelay::delay(uint32_t msec) +{ + // LOG_DEBUG("delay %u ", msec); + + // sem take will return false if we timed out (i.e. were not interrupted) + bool r = semaphore.take(msec); + + // LOG_DEBUG("interrupt=%d\n", r); + return !r; +} + +void InterruptableDelay::interrupt() +{ + semaphore.give(); +} + +IRAM_ATTR void InterruptableDelay::interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken) +{ + semaphore.giveFromISR(pxHigherPriorityTaskWoken); +} + +} // namespace concurrency \ No newline at end of file diff --git a/src/concurrency/InterruptableDelay.h b/src/concurrency/InterruptableDelay.h new file mode 100644 index 0000000..41bc40a --- /dev/null +++ b/src/concurrency/InterruptableDelay.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../freertosinc.h" + +#ifdef HAS_FREE_RTOS +#include "concurrency/BinarySemaphoreFreeRTOS.h" +#define BinarySemaphore BinarySemaphoreFreeRTOS +#else +#include "concurrency/BinarySemaphorePosix.h" +#define BinarySemaphore BinarySemaphorePosix +#endif + +namespace concurrency +{ + +/** + * An object that provides delay(msec) like functionality, but can be interrupted by calling interrupt(). + * + * Useful for they top level loop() delay call to keep the CPU powered down until our next scheduled event or some external event. + * + * This is implemented for FreeRTOS but should be easy to port to other operating systems. + */ +class InterruptableDelay +{ + BinarySemaphore semaphore; + + public: + InterruptableDelay(); + ~InterruptableDelay(); + + /** + * Returns false if we were interrupted + */ + bool delay(uint32_t msec); + + void interrupt(); + + void interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken); +}; + +} // namespace concurrency \ No newline at end of file diff --git a/src/concurrency/OSThread.cpp b/src/concurrency/OSThread.cpp new file mode 100644 index 0000000..fa98e82 --- /dev/null +++ b/src/concurrency/OSThread.cpp @@ -0,0 +1,143 @@ +#include "OSThread.h" +#include "../config.h" +#include "../freertosinc.h" +#include "memGet.h" +#include + +namespace concurrency +{ + +/// Show debugging info for disabled threads +bool OSThread::showDisabled; + +/// Show debugging info for threads when we run them +bool OSThread::showRun = false; + +/// Show debugging info for threads we decide not to run; +bool OSThread::showWaiting = false; + +const OSThread *OSThread::currentThread; + +ThreadController mainController, timerController; +InterruptableDelay mainDelay; + +void OSThread::setup() +{ + mainController.ThreadName = "mainController"; + timerController.ThreadName = "timerController"; +} + +OSThread::OSThread(const char *_name, uint32_t period, ThreadController *_controller) + : Thread(NULL, period), controller(_controller) +{ + assertIsSetup(); + + ThreadName = _name; + + if (controller) { + bool added = controller->add(this); + assert(added); + } +} + +OSThread::~OSThread() +{ + if (controller) + controller->remove(this); +} + +/** + * Wait a specified number msecs starting from the current time (rather than the last time we were run) + */ +void OSThread::setIntervalFromNow(unsigned long _interval) +{ + // Save interval + interval = _interval; + + // Cache the next run based on the last_run + _cached_next_run = millis() + interval; +} + +bool OSThread::shouldRun(unsigned long time) +{ + bool r = Thread::shouldRun(time); + + if (showRun && r) { + DEBUG_MSG("Thread %s: run\n", ThreadName.c_str()); + } + + if (showWaiting && enabled && !r) { + DEBUG_MSG("Thread %s: wait %lu\n", ThreadName.c_str(), interval); + } + + if (showDisabled && !enabled) { + DEBUG_MSG("Thread %s: disabled\n", ThreadName.c_str()); + } + + return r; +} + +void OSThread::run() +{ +#ifdef DEBUG_HEAP + auto heap = memGet.getFreeHeap(); +#endif + currentThread = this; + auto newDelay = runOnce(); +#ifdef DEBUG_HEAP + auto newHeap = memGet.getFreeHeap(); + if (newHeap < heap) + DEBUG_MSG("------ Thread %s leaked heap %d -> %d (%d) ------\n", ThreadName.c_str(), heap, newHeap, newHeap - heap); + if (heap < newHeap) + DEBUG_MSG("++++++ Thread %s freed heap %d -> %d (%d) ++++++\n", ThreadName.c_str(), heap, newHeap, newHeap - heap); +#endif + + runned(); + + if (newDelay >= 0) + setInterval(newDelay); + + currentThread = NULL; +} + +int32_t OSThread::disable() +{ + enabled = false; + setInterval(INT32_MAX); + + return INT32_MAX; +} + +/** + * This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls. + * Call assertIsSetup() to force a crash if someone tries to create an instance too early. + * + * it is super important to never allocate those object statically. instead, you should explicitly + * new them at a point where you are guaranteed that other objects that this instance + * depends on have already been created. + * + * in particular, for OSThread that means "all instances must be declared via new() in setup() or later" - + * this makes it guaranteed that the global mainController is fully constructed first. + */ +bool hasBeenSetup; + +void assertIsSetup() +{ + + /** + * Dear developer comrade - If this assert fails() that means you need to fix the following: + * + * This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls. + * Call assertIsSetup() to force a crash if someone tries to create an instance too early. + * + * it is super important to never allocate those object statically. instead, you should explicitly + * new them at a point where you are guaranteed that other objects that this instance + * depends on have already been created. + * + * in particular, for OSThread that means "all instances must be declared via new() in setup() or later" - + * this makes it guaranteed that the global mainController is fully constructed first. + */ + assert(hasBeenSetup); +} + +} // namespace concurrency diff --git a/src/concurrency/OSThread.h b/src/concurrency/OSThread.h new file mode 100644 index 0000000..aa8e3e2 --- /dev/null +++ b/src/concurrency/OSThread.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +#include "Thread.h" +#include "ThreadController.h" +#include "concurrency/InterruptableDelay.h" + +namespace concurrency +{ + +extern ThreadController mainController, timerController; +extern InterruptableDelay mainDelay; + +#define RUN_SAME -1 + +/** + * @brief Base threading + * + * This is a pseudo threading layer that is super easy to port, well suited to our slow network and very ram & power efficient. + * + * TODO FIXME @geeksville + * + * move more things into OSThreads + * remove lock/lockguard + * + * move typedQueue into concurrency + * remove freertos from typedqueue + */ +class OSThread : public Thread +{ + ThreadController *controller; + + /// Show debugging info for disabled threads + static bool showDisabled; + + /// Show debugging info for threads when we run them + static bool showRun; + + /// Show debugging info for threads we decide not to run; + static bool showWaiting; + + public: + /// For debug printing only (might be null) + static const OSThread *currentThread; + + OSThread(const char *name, uint32_t period = 0, ThreadController *controller = &mainController); + + virtual ~OSThread(); + + virtual bool shouldRun(unsigned long time); + + static void setup(); + + int32_t disable(); + + /** + * Wait a specified number msecs starting from the current time (rather than the last time we were run) + */ + void setIntervalFromNow(unsigned long _interval); + + protected: + /** + * The method that will be called each time our thread gets a chance to run + * + * Returns desired period for next invocation (or RUN_SAME for no change) + */ + virtual int32_t runOnce() = 0; + + // Do not override this + virtual void run(); +}; + +/** + * This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls. + * Call assertIsSetup() to force a crash if someone tries to create an instance too early. + * + * it is super important to never allocate those object statically. instead, you should explicitly + * new them at a point where you are guaranteed that other objects that this instance + * depends on have already been created. + * + * in particular, for OSThread that means "all instances must be declared via new() in setup() or later" - + * this makes it guaranteed that the global mainController is fully constructed first. + */ +extern bool hasBeenSetup; + +void assertIsSetup(); + +} // namespace concurrency diff --git a/src/concurrency/memGet.cpp b/src/concurrency/memGet.cpp new file mode 100644 index 0000000..c14a4c6 --- /dev/null +++ b/src/concurrency/memGet.cpp @@ -0,0 +1,73 @@ +/** + * @file memGet.cpp + * @brief Implementation of MemGet class that provides functions to get memory information. + * + * This file contains the implementation of MemGet class that provides functions to get + * information about free heap, heap size, free psram and psram size. The functions are + * implemented for ESP32 and NRF52 architectures. If the platform does not have heap + * management function implemented, the functions return UINT32_MAX or 0. + */ +#include "memGet.h" +#include "../freertosinc.h" + +MemGet memGet; + +/** + * Returns the amount of free heap memory in bytes. + * @return uint32_t The amount of free heap memory in bytes. + */ +uint32_t MemGet::getFreeHeap() +{ +#ifdef ARCH_ESP32 + return ESP.getFreeHeap(); +#elif defined(ARCH_NRF52) + return dbgHeapFree(); +#else + // this platform does not have heap management function implemented + return UINT32_MAX; +#endif +} + +/** + * Returns the size of the heap memory in bytes. + * @return uint32_t The size of the heap memory in bytes. + */ +uint32_t MemGet::getHeapSize() +{ +#ifdef ARCH_ESP32 + return ESP.getHeapSize(); +#elif defined(ARCH_NRF52) + return dbgHeapTotal(); +#else + // this platform does not have heap management function implemented + return UINT32_MAX; +#endif +} + +/** + * Returns the amount of free psram memory in bytes. + * + * @return The amount of free psram memory in bytes. + */ +uint32_t MemGet::getFreePsram() +{ +#ifdef ARCH_ESP32 + return ESP.getFreePsram(); +#else + return 0; +#endif +} + +/** + * @brief Returns the size of the PSRAM memory. + * + * @return uint32_t The size of the PSRAM memory. + */ +uint32_t MemGet::getPsramSize() +{ +#ifdef ARCH_ESP32 + return ESP.getPsramSize(); +#else + return 0; +#endif +} \ No newline at end of file diff --git a/src/concurrency/memGet.h b/src/concurrency/memGet.h new file mode 100644 index 0000000..130d2ca --- /dev/null +++ b/src/concurrency/memGet.h @@ -0,0 +1,18 @@ +#pragma once +#ifndef _MT_MEMGET_H +#define _MT_MEMGET_H + +#include + +class MemGet +{ + public: + uint32_t getFreeHeap(); + uint32_t getHeapSize(); + uint32_t getFreePsram(); + uint32_t getPsramSize(); +}; + +extern MemGet memGet; + +#endif diff --git a/src/gps/GenericGPS.cpp b/src/gps/GenericGPS.cpp new file mode 100644 index 0000000..eb3a45d --- /dev/null +++ b/src/gps/GenericGPS.cpp @@ -0,0 +1,241 @@ +#include "GenericGPS.h" +#include "global.h" + +#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) + +static int32_t toDegInt(RawDegrees d) +{ + int32_t degMult = 10000000; // 1e7 + int32_t r = d.deg * degMult + d.billionths / 100; + if (d.negative) + r *= -1; + return r; +} + + +bool GenericGPS::setup() +{ +#ifdef VEXT_EN + pinMode(VEXT_EN, OUTPUT); + digitalWrite(VEXT_EN, HIGH); +#endif + +#ifdef VGNSS_CTRL + pinMode(VGNSS_CTRL, OUTPUT); + digitalWrite(VGNSS_CTRL, LOW); +#endif + delay(10); + +#ifdef PIN_GPS_RESET + digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms + pinMode(PIN_GPS_RESET, OUTPUT); + delay(10); + digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE); +#endif + + if(serialInitialized) + { + _serial_gps.end(); + delay(10); + serialInitialized = false; + } + if(!serialInitialized) + { + _serial_gps.setRxBufferSize(2048); +#ifdef GPS_RX_PIN + _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); +#else + _serial_gps.begin(GPS_BAUDRATE); +#endif + serialInitialized = true; + delay(30); + } + gnssModel = probe(); + + if (gnssModel == GNSS_MODEL_MTK) { + _serial_gps.write("$PCAS04,7*1E\r\n"); + delay(250); + // only ask for RMC and GGA + _serial_gps.write("$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0*02\r\n"); + delay(250); + // Switch to Vehicle Mode, since SoftRF enables Aviation < 2g + _serial_gps.write("$PCAS11,3*1E\r\n"); + delay(250); + } else if (gnssModel == GNSS_MODEL_UC6580) { + _serial_gps.write("$CFGSYS,h35155\r\n"); + //_serial_gps.write("$CFGSYS,h15\r\n"); + delay(250); + } + return true; +} + +GnssModel_t GenericGPS::probe() +{ +#if defined(GPS_L76K) + return GNSS_MODEL_MTK; +#elif defined(GPS_UC6580) + return GNSS_MODEL_UC6580; +#else + return GNSS_MODEL_UNKNOWN; +#endif +} + +bool GenericGPS::whileIdle() +{ + bool isValid = false; + while (_serial_gps.available() > 0) { + int c = _serial_gps.read(); +// DEBUG_MSG("%c",c); + isValid |= reader.encode(c); + } + + return isValid; +} + +bool GenericGPS::lookForTime() +{ + auto ti = reader.time; + auto d = reader.date; + if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed + /* Convert to unix time +The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 +(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). +*/ + struct tm t; + t.tm_sec = ti.second(); + t.tm_min = ti.minute(); + t.tm_hour = ti.hour(); + t.tm_mday = d.day(); + t.tm_mon = d.month() - 1; + t.tm_year = d.year() - 1900; + t.tm_isdst = false; + if (t.tm_mon > -1) { + perhapsSetRTC(t,ti.centisecond()*10); + return true; + } else + return false; + } else + return false; +} + +bool GenericGPS::hasLock() const +{ + if (fixQual >= 1 && fixQual <= 5) { + return true; + } + + return false; +} + +bool GenericGPS::lookForLocation() +{ + // By default, TinyGPS++ does not parse GPGSA lines, which give us + // the 2D/3D fixType (see NMEAGPS.h) + // At a minimum, use the fixQuality indicator in GPGGA (FIXME?) + fixQual = reader.fixQuality(); + + // check if GPS has an acceptable lock + if (!hasLock()) + return false; + +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("AGE: LOC=%d FIX=%d DATE=%d TIME=%d\n", reader.location.age(), +#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS + gsafixtype.age(), +#else + 0, +#endif + reader.date.age(), reader.time.age()); +#endif // GPS_EXTRAVERBOSE + + // check if a complete GPS solution set is available for reading + // tinyGPSDatum::age() also includes isValid() test + // FIXME + if (!((reader.location.age() < GPS_SOL_EXPIRY_MS) && + (reader.time.age() < GPS_SOL_EXPIRY_MS) && (reader.date.age() < GPS_SOL_EXPIRY_MS))) { + DEBUG_MSG("SOME data is TOO OLD: LOC %u, TIME %u, DATE %u\n", reader.location.age(), reader.time.age(), reader.date.age()); + return false; + } + + // Is this a new point or are we re-reading the previous one? + if (!reader.location.isUpdated()) + return false; + + // We know the solution is fresh and valid, so just read the data + auto loc = reader.location.value(); + + // Bail out EARLY to avoid overwriting previous good data (like #857) + if (toDegInt(loc.lat) > 900000000) { +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("Bail out EARLY on LAT %i\n", toDegInt(loc.lat)); +#endif + return false; + } + if (toDegInt(loc.lng) > 1800000000) { +#ifdef GPS_EXTRAVERBOSE + LOG_DEBUG("Bail out EARLY on LNG %i\n", toDegInt(loc.lng)); +#endif + return false; + } + + dop = reader.hdop.value(); + + latitude = toDegInt(loc.lat); + longitude = toDegInt(loc.lng); + + altitude = reader.altitude.meters(); + + // Nice to have, if available + if (reader.satellites.isUpdated()) { + numSatellites = reader.satellites.value(); + } + + if (reader.course.isUpdated() && reader.course.isValid()) { + if (reader.course.value() < 36000) { // sanity check + heading = + reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5 + } else { + DEBUG_MSG("BOGUS course.value() REJECTED: %d\n", reader.course.value()); + } + } + + return true; +} + +void GenericGPS::shutdown() +{ +} + +int32_t GenericGPS::runOnce() +{ + if (whileIdle()) { + // if we have received valid NMEA claim we are connected + isConnected = true; + }; + + // If we are overdue for an update, turn on the GPS and at least publish the current status + uint32_t now = millis(); + + if ((now - lastWhileActiveMsec) > 5000) { + lastWhileActiveMsec = now; + whileActive(); + } + + bool gotTime = false; + if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time + gotTime = true; + } + + bool gotLoc = lookForLocation(); + if (gotLoc && !hasValidLocation) { // declare that we have location ASAP + hasValidLocation = true; + } + + DEBUG_MSG("GPS numSatellites %d, %d\n", numSatellites, fixQual); + const GPSStatus status = GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, hacc, heading, numSatellites); + newStatus.notifyObservers(&status); + + return 1000; +} + diff --git a/src/gps/GenericGPS.h b/src/gps/GenericGPS.h new file mode 100644 index 0000000..98fe7d7 --- /dev/null +++ b/src/gps/GenericGPS.h @@ -0,0 +1,46 @@ +#pragma once + +#include "GPS.h" +#include "Observer.h" +#include "concurrency/OSThread.h" +#include "TinyGPS++.h" + +typedef enum { + GNSS_MODEL_MTK, + GNSS_MODEL_UC6580, + GNSS_MODEL_UNKNOWN, +} GnssModel_t; + + +class GenericGPS : public GPS, private concurrency::OSThread +{ + uint32_t lastWhileActiveMsec = 0; + TinyGPSPlus reader; + uint8_t fixQual = 0; // fix quality from GPGGA + uint32_t lastChecksumFailCount = 0; + + GnssModel_t gnssModel = GNSS_MODEL_UNKNOWN; + bool serialInitialized = false; + public: + GenericGPS():concurrency::OSThread("GPS") {}; + virtual ~GenericGPS(){}; + + GnssModel_t probe(); + + virtual bool setup(); + + virtual int32_t runOnce() override; + + /** + * Restart our lock attempt - try to get and broadcast a GPS reading ASAP + * called after the CPU wakes from light-sleep state */ + virtual void startLock(){}; + virtual void reset(){}; + virtual void loop(){}; + virtual void shutdown(); + virtual bool whileIdle(); + virtual void whileActive() {} + virtual bool lookForTime(); + virtual bool lookForLocation(); + virtual bool hasLock() const; +};