mirror of
https://github.com/ddv2005/AirsoftTracker.git
synced 2026-01-10 21:16:54 +00:00
Add new files for version 1.0.1332
This commit is contained in:
90
src/atScreenLCDGfx.cpp
Normal file
90
src/atScreenLCDGfx.cpp
Normal file
@@ -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
|
||||||
27
src/atScreenLCDGfx.h
Normal file
27
src/atScreenLCDGfx.h
Normal file
@@ -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();
|
||||||
|
};
|
||||||
82
src/atScreenTFTeSPI.cpp
Normal file
82
src/atScreenTFTeSPI.cpp
Normal file
@@ -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
|
||||||
21
src/atScreenTFTeSPI.h
Normal file
21
src/atScreenTFTeSPI.h
Normal file
@@ -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();
|
||||||
|
};
|
||||||
40
src/concurrency/BinarySemaphoreFreeRTOS.cpp
Normal file
40
src/concurrency/BinarySemaphoreFreeRTOS.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "concurrency/BinarySemaphoreFreeRTOS.h"
|
||||||
|
#include "../freertosinc.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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
|
||||||
30
src/concurrency/BinarySemaphoreFreeRTOS.h
Normal file
30
src/concurrency/BinarySemaphoreFreeRTOS.h
Normal file
@@ -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
|
||||||
28
src/concurrency/BinarySemaphorePosix.cpp
Normal file
28
src/concurrency/BinarySemaphorePosix.cpp
Normal file
@@ -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
|
||||||
30
src/concurrency/BinarySemaphorePosix.h
Normal file
30
src/concurrency/BinarySemaphorePosix.h
Normal file
@@ -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
|
||||||
35
src/concurrency/InterruptableDelay.cpp
Normal file
35
src/concurrency/InterruptableDelay.cpp
Normal file
@@ -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
|
||||||
41
src/concurrency/InterruptableDelay.h
Normal file
41
src/concurrency/InterruptableDelay.h
Normal file
@@ -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
|
||||||
143
src/concurrency/OSThread.cpp
Normal file
143
src/concurrency/OSThread.cpp
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#include "OSThread.h"
|
||||||
|
#include "../config.h"
|
||||||
|
#include "../freertosinc.h"
|
||||||
|
#include "memGet.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
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
|
||||||
90
src/concurrency/OSThread.h
Normal file
90
src/concurrency/OSThread.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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
|
||||||
73
src/concurrency/memGet.cpp
Normal file
73
src/concurrency/memGet.cpp
Normal file
@@ -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
|
||||||
|
}
|
||||||
18
src/concurrency/memGet.h
Normal file
18
src/concurrency/memGet.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef _MT_MEMGET_H
|
||||||
|
#define _MT_MEMGET_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class MemGet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
uint32_t getFreeHeap();
|
||||||
|
uint32_t getHeapSize();
|
||||||
|
uint32_t getFreePsram();
|
||||||
|
uint32_t getPsramSize();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MemGet memGet;
|
||||||
|
|
||||||
|
#endif
|
||||||
241
src/gps/GenericGPS.cpp
Normal file
241
src/gps/GenericGPS.cpp
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
46
src/gps/GenericGPS.h
Normal file
46
src/gps/GenericGPS.h
Normal file
@@ -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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user