Compare commits

..

45 Commits
1.1.1 ... 1.1.5

Author SHA1 Message Date
Kevin Hester
7ef2cc8623 Merge pull request #478 from geeksville/dev
1.1.5
2020-10-13 18:11:15 -07:00
Kevin Hester
b41a32c6b6 1.1.5 2020-10-14 09:04:20 +08:00
Kevin Hester
1ebd7b0c3e make lightsleep default time 5 minutes, with 1 minute in for BLE syncing 2020-10-14 08:53:12 +08:00
Kevin Hester
5457541244 fix #477: sleep behavior was broken in app due to device bug since 1.1.1
+        // NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior.
+        // So even if we internally use 0 to represent 'use default' we still need to send the value we are
+        // using to the app (so that even old phone apps work with new device loads).
2020-10-14 08:45:29 +08:00
Kevin Hester
ef325289eb fix a doc error 2020-10-12 11:09:53 +08:00
Kevin Hester
40c63c0615 Merge pull request #472 from geeksville/dev
1.1.4
2020-10-11 18:39:02 -07:00
Kevin Hester
a9de8b9bb3 oops - only read axp on boards that have it 2020-10-12 09:33:15 +08:00
Kevin Hester
66a7f896c8 1.1.4 2020-10-12 09:27:48 +08:00
Kevin Hester
45a36f5571 fix POWER state entry/exit based on loss of USB power (tx @mc-hamster) 2020-10-12 09:27:07 +08:00
Kevin Hester
62493efc40 Merge pull request #471 from geeksville/coroutine
fix my breakage
2020-10-11 17:42:26 -07:00
Kevin Hester
2848b76cc9 Merge branch 'master' into coroutine 2020-10-11 17:28:52 -07:00
Kevin Hester
ef8bea478d Merge branch 'coroutine' of https://github.com/geeksville/Meshtastic-esp32 into coroutine 2020-10-12 08:25:33 +08:00
Kevin Hester
a8e4bbbe65 fix my breaking of button press behavior 2020-10-12 08:25:17 +08:00
Kevin Hester
9a414d9c77 fix my breakage of screen waking 2020-10-12 08:13:32 +08:00
Kevin Hester
3d21794039 Merge pull request #465 from geeksville/coroutine
Lightweight thread refactoring
2020-10-10 18:32:15 -07:00
Kevin Hester
beac614e65 Merge branch 'master' into coroutine 2020-10-10 18:19:40 -07:00
Kevin Hester
87f2673fc4 Merge pull request #463 from geeksville/dev
fix #462 publish immediately on any GPS state change
2020-10-10 18:19:22 -07:00
Kevin Hester
999b292717 fixes for the posix port 2020-10-11 09:18:47 +08:00
Kevin Hester
8330c3270e 1.1.3 2020-10-11 08:15:52 +08:00
Kevin Hester
0c8e0efed2 new threading finished- saves about 10mA for the high activity states 2020-10-11 08:12:53 +08:00
Kevin Hester
c44d8a0433 Merge remote-tracking branch 'root/master' into coroutine 2020-10-10 18:12:37 +08:00
Kevin Hester
49b4ed2a89 coroutine: kinda works now 2020-10-10 18:03:45 +08:00
Kevin Hester
4b9ea4f808 Merge branch 'dev' into coroutine 2020-10-10 09:22:23 +08:00
Kevin Hester
c3beca3e23 Merge branch 'master' into dev 2020-10-09 18:21:16 -07:00
Kevin Hester
95cb6b06e4 fix #462 publish immediately on any GPS state change
(don't wait until end of aquisition window)
2020-10-10 09:20:38 +08:00
Kevin Hester
c46a884558 concurrency wip 2020-10-10 08:28:00 +08:00
Kevin Hester
2044427e97 coroutines: wip compiles but does not link 2020-10-09 14:16:51 +08:00
Kevin Hester
514ebdf013 Merge pull request #460 from geeksville/dev
Dev
2020-10-08 19:09:12 -07:00
Kevin Hester
10f64590a9 Merge branch 'dev' into coroutine 2020-10-09 10:03:27 +08:00
Kevin Hester
4a70ba1f7a fix nodeinfo stored times (I think) for @lgoix 2020-10-09 10:01:13 +08:00
Kevin Hester
dd6a402ea0 coroutine: wip 2020-10-09 09:10:44 +08:00
Kevin Hester
bed7d8a619 threads: begin change to cooperative threading 2020-10-08 13:32:34 +08:00
Kevin Hester
1b6e8e36d3 Merge remote-tracking branch 'root/master' 2020-10-08 11:14:28 +08:00
Kevin Hester
7a4b8cde11 keep rf95 max power at 20 dBm so users don't smoke their boards 2020-10-08 10:51:31 +08:00
Kevin Hester
113859e791 increase sx1272 max power
+#define MAX_POWER 27
 // if we use 20 we are limited to 1% duty cycle or hw might overheat.  For continuous operation set a limit of 17
+// In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20.  So BIG WARNING
+// if you set power to something higher than 17 or 20 you might fry your board.
2020-10-08 09:57:59 +08:00
Kevin Hester
a6b82ccfd9 Merge pull request #457 from geeksville/master
fixes based on alpha tester report
2020-10-07 16:55:41 -07:00
Kevin Hester
e8b8ec69f1 1.1.2 2020-10-08 07:48:14 +08:00
Kevin Hester
023f1c24fb RTC: add notion of 'quality' for different time sources
Allow use of mesh based time until a GPS time arrives
2020-10-08 07:46:20 +08:00
Kevin Hester
f00d07baa3 RTC: pull rtc code into own file for cleanup 2020-10-08 07:28:57 +08:00
Kevin Hester
62c228b986 gps: don't stop lock attempts just because the main cpu is sleeping 2020-10-08 06:23:53 +08:00
Kevin Hester
1a3cc40c7e sx1262 better to check for header because preamble might never result in irq 2020-10-08 06:23:05 +08:00
Kevin Hester
bdcd5c3981 allow reporting # sats before we ahve a fix 2020-10-08 06:22:25 +08:00
Kevin Hester
fc82e872d6 don't require gps to have lock before we'll trust GPS time 2020-10-08 05:23:52 +08:00
Kevin Hester
b47c54b5b6 keep lora radio totally unpowered when in deep-sleep 2020-10-07 17:52:44 +08:00
Kevin Hester
c0c83ad389 If we are not supposed to share locations make sure phone doesn't either 2020-10-07 17:46:25 +08:00
70 changed files with 1116 additions and 1056 deletions

View File

@@ -1,3 +1,3 @@
export VERSION=1.1.1
export VERSION=1.1.5

View File

@@ -42,7 +42,6 @@ Expected sequence for initial download:
- Read a RadioConfig from "radio" - used to get the channel and radio settings
- Read a User from "user" - to get the username for this node
- Read a MyNodeInfo from "mynode" to get information about this local device
- Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine
- Read a series of NodeInfo packets to build the phone's copy of the current NodeDB for the mesh
- Read a endConfig packet that indicates that the entire state you need has been sent.
- Read a series of MeshPackets until it returns empty to get any messages that arrived for this node while the phone was away

View File

@@ -1,4 +1,4 @@
gps todo - bug 376
You probably don't care about this ugly file of personal notes ;-)
for taiwan region:
bin/run.sh --set region 8
@@ -6,40 +6,12 @@ bin/run.sh --set region 8
time only mode
./bin/run.sh --set gps_operation 3
increase acquisition time until ublox power management can be improved see 9.3.1
ublox parsing failure
record power measurements and update spreadsheet
fix has_gps based on new logic
make sure we are turning off lora radio in deep sleep
don't send locations if the user has forbidden that (lie to phone so phone won't either)
have loop methods return allowable sleep time (from their perspective)
increase main cpu sleep time
add set router mode in python tool - it will also set GPS to stationary
make sure location still gets set once per boot and stays marked as valid on the gui
send position updates super rarely
turn off checking for usb power and forcing always on
(which will shrink DARK and NB period to zero and
make light_sleep very long)
warn people about crummy gps antennas - add to faq
gps states
Active - for gps_attempt_time seconds
Sleeping - for (gps_update_rate or sleep forever) seconds
ForcedSleep - PowerFSM says we don't want to use GPS right now
(no need for sleep forever state)
gps triggers
GPS_TRIG_FORCE_SLEEP - from powerfsm
GPS_TRIG_FORCE_WAKE - from powerfsm
GPS_SETTINGS - if GPS settings changed, reset params and possibly become active

View File

@@ -38,7 +38,7 @@ From lower to higher power consumption.
- full on (ON) - Everything is on, can eventually timeout and lower to a lower power state
onEntry: setBluetoothOn(true), screen.setOn(true)
onExit: screen.setOn(false)
onExit: screen->setOn(false)
- has power (POWER) - Screen is on, device doesn't sleep, bluetooth on, will stay in this state as long as we have power
onEntry: setBluetooth off, screen on

View File

@@ -30,6 +30,7 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/n
-DHW_VERSION_${sysenv.COUNTRY}
-DAPP_VERSION=${sysenv.APP_VERSION}
-DHW_VERSION=${sysenv.HW_VERSION}
-DUSE_THREAD_NAMES
; leave this commented out to avoid breaking Windows
;upload_port = /dev/ttyUSB0
@@ -59,15 +60,16 @@ debug_tool = jlink
lib_deps =
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
1260 ; OneButton library for non-blocking button debounce
https://github.com/geeksville/OneButton.git ; OneButton library for non-blocking button debounce
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
https://github.com/meshtastic/arduino-fsm.git
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#cb8353dfddd1b0e205098f5e70d5f2a5f74b4838
https://github.com/meshtastic/RadioLib.git#1083c2e76f9906c5f80dfec726facebf8413eef0
https://github.com/meshtastic/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
https://github.com/meshtastic/RadioLib.git#8657380241bce681c33aab46598bbf13b11f876c
https://github.com/meshtastic/TinyGPSPlus.git
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
Wire ; explicitly needed here because the AXP202 library forgets to add it
SPI
https://github.com/geeksville/ArduinoThread.git#333ffd09b596977c217ba25da4258f588b462ac6
; Common settings for conventional (non Portduino) Ardino targets
[arduino_base]

View File

@@ -62,6 +62,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
virtual bool isBatteryConnect() { return getBattVoltage() != -1; }
} analogLevel;
Power::Power() : OSThread("Power") {}
bool Power::analogInit()
{
#ifdef BATTERY_PIN
@@ -86,10 +88,7 @@ bool Power::setup()
if (!found) {
found = analogInit();
}
if (found) {
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
setPeriod(1);
}
enabled = found;
return found;
}
@@ -135,13 +134,47 @@ void Power::readPowerStatus()
}
}
void Power::doTask()
int32_t Power::runOnce()
{
readPowerStatus();
#ifdef TBEAM_V10
// WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
// the IRQ status by reading the registers over I2C
axp.readIRQ();
if (axp.isVbusRemoveIRQ()) {
DEBUG_MSG("USB unplugged\n");
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
}
if (axp.isVbusPlugInIRQ()) {
DEBUG_MSG("USB plugged In\n");
powerFSM.trigger(EVENT_POWER_CONNECTED);
}
/*
Other things we could check if we cared...
if (axp.isChargingIRQ()) {
DEBUG_MSG("Battery start charging\n");
}
if (axp.isChargingDoneIRQ()) {
DEBUG_MSG("Battery fully charged\n");
}
if (axp.isBattPlugInIRQ()) {
DEBUG_MSG("Battery inserted\n");
}
if (axp.isBattRemoveIRQ()) {
DEBUG_MSG("Battery removed\n");
}
if (axp.isPEKShortPressIRQ()) {
DEBUG_MSG("PEK short button press\n");
}
*/
axp.clearIRQ();
#endif
// Only read once every 20 seconds once the power status for the app has been initialized
if (statusHandler && statusHandler->isInitialized())
setPeriod(1000 * 20);
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
}
/**
@@ -208,8 +241,7 @@ bool Power::axp192Init()
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
// we don't look for AXP202_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
// we don't look at AXP202_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
axp.enableIRQ(AXP202_BATT_CONNECT_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
1);
axp.enableIRQ(AXP202_BATT_CONNECT_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, 1);
axp.clearIRQ();
#endif
@@ -226,43 +258,3 @@ bool Power::axp192Init()
return false;
#endif
}
void Power::loop()
{
#ifdef PMU_IRQ
if (pmu_irq) {
pmu_irq = false;
axp.readIRQ();
DEBUG_MSG("pmu irq!\n");
if (axp.isChargingIRQ()) {
DEBUG_MSG("Battery start charging\n");
}
if (axp.isChargingDoneIRQ()) {
DEBUG_MSG("Battery fully charged\n");
}
if (axp.isVbusRemoveIRQ()) {
DEBUG_MSG("USB unplugged\n");
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
}
if (axp.isVbusPlugInIRQ()) {
DEBUG_MSG("USB plugged In\n");
powerFSM.trigger(EVENT_POWER_CONNECTED);
}
if (axp.isBattPlugInIRQ()) {
DEBUG_MSG("Battery inserted\n");
}
if (axp.isBattRemoveIRQ()) {
DEBUG_MSG("Battery removed\n");
}
if (axp.isPEKShortPressIRQ()) {
DEBUG_MSG("PEK short button press\n");
}
readPowerStatus();
axp.clearIRQ();
}
#endif
}

View File

@@ -22,7 +22,7 @@ static uint32_t secsSlept;
static void lsEnter()
{
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", getPref_ls_secs());
screen.setOn(false);
screen->setOn(false);
secsSlept = 0; // How long have we been sleeping this time
DEBUG_MSG("lsEnter end\n");
@@ -102,7 +102,7 @@ static void lsExit()
static void nbEnter()
{
screen.setOn(false);
screen->setOn(false);
setBluetoothEnable(false);
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
@@ -111,24 +111,33 @@ static void nbEnter()
static void darkEnter()
{
setBluetoothEnable(true);
screen.setOn(false);
screen->setOn(false);
}
static void serialEnter()
{
setBluetoothEnable(false);
screen.setOn(true);
screen->setOn(true);
screen->print("Using API...\n");
}
static void powerEnter()
{
screen.setOn(true);
screen->setOn(true);
setBluetoothEnable(true);
screen->print("Powered...\n");
}
static void powerExit()
{
screen->setOn(true);
setBluetoothEnable(true);
screen->print("Unpowered...\n");
}
static void onEnter()
{
screen.setOn(true);
screen->setOn(true);
setBluetoothEnable(true);
static uint32_t lastPingMs;
@@ -144,7 +153,7 @@ static void onEnter()
static void screenPress()
{
screen.onPress();
screen->onPress();
}
static void bootEnter() {}
@@ -156,7 +165,7 @@ State stateDARK(darkEnter, NULL, NULL, "DARK");
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
State stateON(onEnter, NULL, NULL, "ON");
State statePOWER(powerEnter, NULL, NULL, "POWER");
State statePOWER(powerEnter, NULL, powerExit, "POWER");
Fsm powerFSM(&stateBOOT);
void PowerFSM_setup()

View File

@@ -20,5 +20,6 @@
#define EVENT_POWER_DISCONNECTED 14
extern Fsm powerFSM;
extern State statePOWER, stateSERIAL;
void PowerFSM_setup();

View File

@@ -1,44 +0,0 @@
#pragma once
#include "WorkerThread.h"
namespace concurrency {
/**
* @brief A worker thread that waits on a freertos notification
*/
class BaseNotifiedWorkerThread : public WorkerThread
{
public:
/**
* Notify this thread so it can run
*/
virtual void notify(uint32_t v = 0, eNotifyAction action = eNoAction) = 0;
/**
* Notify from an ISR
*
* This must be inline or IRAM_ATTR on ESP32
*/
virtual void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction) { notify(v, action); }
protected:
/**
* The notification that was most recently used to wake the thread. Read from loop()
*/
uint32_t notification = 0;
/**
* What notification bits should be cleared just after we read and return them in notification?
*
* Defaults to clear all of them.
*/
uint32_t clearOnRead = UINT32_MAX;
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block() = 0;
};
} // namespace concurrency

View File

@@ -1,12 +0,0 @@
#include "Thread.h"
#include <assert.h>
namespace concurrency
{
void BaseThread::callRun(void *_this)
{
((BaseThread *)_this)->doRun();
}
} // namespace concurrency

View File

@@ -1,47 +0,0 @@
#pragma once
#include <cstdlib>
#include <stdint.h>
#include "freertosinc.h"
namespace concurrency
{
/**
* @brief Base threading
*/
class BaseThread
{
protected:
/**
* set this to true to ask thread to cleanly exit asap
*/
volatile bool wantExit = false;
public:
virtual void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY) = 0;
virtual ~BaseThread() {}
// uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); }
protected:
/**
* The method that will be called when start is called.
*/
virtual void doRun() = 0;
/**
* All thread run methods must periodically call serviceWatchdog, or the system will declare them hung and panic.
*
* this only applies after startWatchdog() has been called. If you need to sleep for a long time call stopWatchdog()
*/
virtual void serviceWatchdog() {}
virtual void startWatchdog() {}
virtual void stopWatchdog() {}
static void callRun(void *_this);
};
} // namespace concurrency

View File

@@ -0,0 +1,39 @@
#include "concurrency/BinarySemaphoreFreeRTOS.h"
#include "configuration.h"
#ifdef HAS_FREE_RTOS
namespace concurrency
{
BinarySemaphoreFreeRTOS::BinarySemaphoreFreeRTOS()
{
semaphore = xSemaphoreCreateBinary();
}
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

View File

@@ -0,0 +1,31 @@
#pragma once
#include "configuration.h"
#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
}

View File

@@ -0,0 +1,36 @@
#include "concurrency/BinarySemaphorePosix.h"
#include "configuration.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

View File

@@ -0,0 +1,31 @@
#pragma once
#include "configuration.h"
#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
}

View File

@@ -1,23 +0,0 @@
#include "NotifiedWorkerThread.h"
#ifdef HAS_FREE_RTOS
namespace concurrency {
/**
* Notify this thread so it can run
*/
void FreeRtosNotifiedWorkerThread::notify(uint32_t v, eNotifyAction action)
{
xTaskNotify(taskHandle, v, action);
}
void FreeRtosNotifiedWorkerThread::block()
{
xTaskNotifyWait(0, // don't clear notification on entry
clearOnRead, &notification, portMAX_DELAY); // Wait forever
}
} // namespace concurrency
#endif

View File

@@ -1,40 +0,0 @@
#pragma once
#include "BaseNotifiedWorkerThread.h"
#ifdef HAS_FREE_RTOS
namespace concurrency {
/**
* @brief A worker thread that waits on a freertos notification
*/
class FreeRtosNotifiedWorkerThread : public BaseNotifiedWorkerThread
{
public:
/**
* Notify this thread so it can run
*/
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
/**
* Notify from an ISR
*
* This must be inline or IRAM_ATTR on ESP32
*/
inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction)
{
xTaskNotifyFromISR(taskHandle, v, action, highPriWoken);
}
protected:
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block();
};
} // namespace concurrency
#endif

View File

@@ -1,45 +0,0 @@
#include "FreeRtosThread.h"
#ifdef HAS_FREE_RTOS
#include <assert.h>
#ifdef ARDUINO_ARCH_ESP32
#include "esp_task_wdt.h"
#endif
namespace concurrency
{
void FreeRtosThread::start(const char *name, size_t stackSize, uint32_t priority)
{
auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle);
assert(r == pdPASS);
}
void FreeRtosThread::serviceWatchdog()
{
#ifdef ARDUINO_ARCH_ESP32
esp_task_wdt_reset();
#endif
}
void FreeRtosThread::startWatchdog()
{
#ifdef ARDUINO_ARCH_ESP32
auto r = esp_task_wdt_add(taskHandle);
assert(r == ESP_OK);
#endif
}
void FreeRtosThread::stopWatchdog()
{
#ifdef ARDUINO_ARCH_ESP32
auto r = esp_task_wdt_delete(taskHandle);
assert(r == ESP_OK);
#endif
}
} // namespace concurrency
#endif

View File

@@ -1,44 +0,0 @@
#pragma once
#include "BaseThread.h"
#include "freertosinc.h"
#ifdef HAS_FREE_RTOS
namespace concurrency
{
/**
* @brief Base threading
*/
class FreeRtosThread : public BaseThread
{
protected:
TaskHandle_t taskHandle = NULL;
public:
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY);
virtual ~FreeRtosThread() { vTaskDelete(taskHandle); }
// uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); }
protected:
/**
* The method that will be called when start is called.
*/
virtual void doRun() = 0;
/**
* All thread run methods must periodically call serviceWatchdog, or the system will declare them hung and panic.
*
* this only applies after startWatchdog() has been called. If you need to sleep for a long time call stopWatchdog()
*/
void serviceWatchdog();
void startWatchdog();
void stopWatchdog();
};
} // namespace concurrency
#endif

View File

@@ -0,0 +1,35 @@
#include "concurrency/InterruptableDelay.h"
#include "configuration.h"
namespace concurrency
{
InterruptableDelay::InterruptableDelay() {}
InterruptableDelay::~InterruptableDelay() {}
/**
* Returns false if we were interrupted
*/
bool InterruptableDelay::delay(uint32_t msec)
{
// DEBUG_MSG("delay %u ", msec);
// sem take will return false if we timed out (i.e. were not interrupted)
bool r = semaphore.take(msec);
// DEBUG_MSG("interrupt=%d\n", r);
return !r;
}
void InterruptableDelay::interrupt()
{
semaphore.give();
}
IRAM_ATTR void InterruptableDelay::interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken)
{
semaphore.giveFromISR(pxHigherPriorityTaskWoken);
}
} // namespace concurrency

View File

@@ -0,0 +1,42 @@
#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 implmented 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

View File

@@ -0,0 +1,85 @@
#include "NotifiedWorkerThread.h"
#include "configuration.h"
#include <assert.h>
namespace concurrency
{
static bool debugNotification;
/**
* Notify this thread so it can run
*/
bool NotifiedWorkerThread::notify(uint32_t v, bool overwrite)
{
bool r = notifyCommon(v, overwrite);
if (r)
mainDelay.interrupt();
return r;
}
/**
* Notify this thread so it can run
*/
IRAM_ATTR bool NotifiedWorkerThread::notifyCommon(uint32_t v, bool overwrite)
{
if (overwrite || notification == 0) {
enabled = true;
setInterval(0); // Run ASAP
notification = v;
if (debugNotification)
DEBUG_MSG("setting notification %d\n", v);
return true;
} else {
if (debugNotification)
DEBUG_MSG("dropping notification %d\n", v);
return false;
}
}
/**
* Notify from an ISR
*
* This must be inline or IRAM_ATTR on ESP32
*/
IRAM_ATTR bool NotifiedWorkerThread::notifyFromISR(BaseType_t *highPriWoken, uint32_t v, bool overwrite)
{
bool r = notifyCommon(v, overwrite);
if (r)
mainDelay.interruptFromISR(highPriWoken);
return r;
}
/**
* Schedule a notification to fire in delay msecs
*/
bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrite)
{
bool didIt = notify(v, overwrite);
if (didIt) { // If we didn't already have something queued, override the delay to be larger
setIntervalFromNow(delay); // a new version of setInterval relative to the current time
if (debugNotification)
DEBUG_MSG("delaying notification %u\n", delay);
}
return didIt;
}
int32_t NotifiedWorkerThread::runOnce()
{
auto n = notification;
enabled = false; // Only run once per notification
notification = 0; // clear notification
if (n) {
onNotify(n);
}
return RUN_SAME;
}
} // namespace concurrency

View File

@@ -1,17 +1,50 @@
#pragma once
#include "FreeRtosNotifiedWorkerThread.h"
#include "PosixNotifiedWorkerThread.h"
#include "OSThread.h"
namespace concurrency
{
#ifdef HAS_FREE_RTOS
typedef FreeRtosNotifiedWorkerThread NotifiedWorkerThread;
#endif
/**
* @brief A worker thread that waits on a freertos notification
*/
class NotifiedWorkerThread : public OSThread
{
/**
* The notification that was most recently used to wake the thread. Read from runOnce()
*/
uint32_t notification = 0;
#ifdef __unix__
typedef PosixNotifiedWorkerThread NotifiedWorkerThread;
#endif
public:
NotifiedWorkerThread(const char *name) : OSThread(name) {}
/**
* Notify this thread so it can run
*/
bool notify(uint32_t v, bool overwrite);
/**
* Notify from an ISR
*
* This must be inline or IRAM_ATTR on ESP32
*/
bool notifyFromISR(BaseType_t *highPriWoken, uint32_t v, bool overwrite);
/**
* Schedule a notification to fire in delay msecs
*/
bool notifyLater(uint32_t delay, uint32_t v, bool overwrite);
protected:
virtual void onNotify(uint32_t notification) = 0;
virtual int32_t runOnce();
private:
/**
* Notify this thread so it can run
*/
bool notifyCommon(uint32_t v, bool overwrite);
};
} // namespace concurrency

View File

@@ -0,0 +1,79 @@
#include "OSThread.h"
#include "configuration.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;
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)
{
ThreadName = _name;
if (controller)
controller->add(this);
}
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()
{
auto newDelay = runOnce();
runned();
if (newDelay >= 0)
setInterval(newDelay);
}
} // namespace concurrency

View File

@@ -0,0 +1,70 @@
#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:
OSThread(const char *name, uint32_t period = 0, ThreadController *controller = &mainController);
virtual ~OSThread();
virtual bool shouldRun(unsigned long time);
static void setup();
/**
* 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();
};
} // namespace concurrency

View File

@@ -1,26 +1,24 @@
#pragma once
#include "PeriodicTask.h"
#include "concurrency/OSThread.h"
namespace concurrency {
namespace concurrency
{
/**
* @brief Periodically invoke a callback. This just provides C-style callback conventions
* rather than a virtual function - FIXME, remove?
*/
class Periodic : public PeriodicTask
class Periodic : public OSThread
{
uint32_t (*callback)();
int32_t (*callback)();
public:
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {}
protected:
void doTask() {
uint32_t p = callback();
setPeriod(p);
}
int32_t runOnce() { return callback(); }
};
} // namespace concurrency

View File

@@ -1,34 +0,0 @@
#include "PeriodicScheduler.h"
#include "PeriodicTask.h"
#include "LockGuard.h"
namespace concurrency {
/// call this from loop
void PeriodicScheduler::loop()
{
LockGuard lg(&lock);
uint32_t now = millis();
for (auto t : tasks) {
if (t->period && (now - t->lastMsec) >= t->period) {
t->doTask();
t->lastMsec = now;
}
}
}
void PeriodicScheduler::schedule(PeriodicTask *t)
{
LockGuard lg(&lock);
tasks.insert(t);
}
void PeriodicScheduler::unschedule(PeriodicTask *t)
{
LockGuard lg(&lock);
tasks.erase(t);
}
} // namespace concurrency

View File

@@ -1,40 +0,0 @@
#pragma once
#include "Lock.h"
#include <cstdint>
#include <unordered_set>
namespace concurrency {
class PeriodicTask;
/**
* @brief Runs all PeriodicTasks in the system. Currently called from main loop()
* but eventually should be its own thread blocked on a freertos timer.
*/
class PeriodicScheduler
{
friend class PeriodicTask;
/**
* This really should be some form of heap, and when the period gets changed on a task it should get
* rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check
* _every_ tasks. If it was a heap we'd only have to check the first task.
*/
std::unordered_set<PeriodicTask *> tasks;
// Protects the above variables.
Lock lock;
public:
/// Run any next tasks which are due for execution
void loop();
private:
void schedule(PeriodicTask *t);
void unschedule(PeriodicTask *t);
};
extern PeriodicScheduler periodicScheduler;
} // namespace concurrency

View File

@@ -1,16 +0,0 @@
#include "PeriodicTask.h"
#include "Periodic.h"
#include "LockGuard.h"
namespace concurrency {
PeriodicScheduler periodicScheduler;
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
void PeriodicTask::setup()
{
periodicScheduler.schedule(this);
}
} // namespace concurrency

View File

@@ -1,56 +0,0 @@
#pragma once
#include <Arduino.h>
#include "PeriodicScheduler.h"
namespace concurrency {
/**
* @brief A base class for tasks that want their doTask() method invoked periodically
*
* @todo currently just syntatic sugar for polling in loop (you must call .loop), but eventually
* generalize with the freertos scheduler so we can save lots of power by having everything either in
* something like this or triggered off of an irq.
*/
class PeriodicTask
{
friend class PeriodicScheduler;
uint32_t lastMsec = 0;
uint32_t period = 1; // call soon after creation
public:
virtual ~PeriodicTask() { periodicScheduler.unschedule(this); }
/**
* Constructor (will schedule with the global PeriodicScheduler)
*/
PeriodicTask(uint32_t initialPeriod = 1);
/**
* MUST be be called once at startup (but after threading is running - i.e. not from a constructor)
*/
void setup();
/**
* Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
* While zero this task is disabled and will not run
*/
void setPeriod(uint32_t p)
{
lastMsec = millis(); // reset starting from now
period = p;
}
uint32_t getPeriod() const { return period; }
/**
* Syntatic sugar for suspending tasks
*/
void disable() { setPeriod(0); }
protected:
virtual void doTask() = 0;
};
} // namespace concurrency

View File

@@ -1,19 +0,0 @@
#include "PosixNotifiedWorkerThread.h"
#ifdef __unix__
#include <Utility.h>
using namespace concurrency;
/**
* Notify this thread so it can run
*/
void PosixNotifiedWorkerThread::notify(uint32_t v, eNotifyAction action) NOT_IMPLEMENTED("notify");
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
void PosixNotifiedWorkerThread::block() NOT_IMPLEMENTED("block");
#endif

View File

@@ -1,26 +0,0 @@
#pragma once
#include "BaseNotifiedWorkerThread.h"
namespace concurrency {
/**
* @brief A worker thread that waits on a freertos notification
*/
class PosixNotifiedWorkerThread : public BaseNotifiedWorkerThread
{
public:
/**
* Notify this thread so it can run
*/
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
protected:
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block();
};
} // namespace concurrency

View File

@@ -1,33 +0,0 @@
#pragma once
#include "BaseThread.h"
#ifdef __unix__
namespace concurrency
{
/**
* @brief Base threading
*/
class PosixThread : public BaseThread
{
protected:
public:
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY) {}
virtual ~PosixThread() {}
// uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); }
protected:
/**
* The method that will be called when start is called.
*/
virtual void doRun() = 0;
};
} // namespace concurrency
#endif

View File

@@ -1,17 +0,0 @@
#pragma once
#include "FreeRtosThread.h"
#include "PosixThread.h"
namespace concurrency
{
#ifdef HAS_FREE_RTOS
typedef FreeRtosThread Thread;
#endif
#ifdef __unix__
typedef PosixThread Thread;
#endif
} // namespace concurrency

View File

@@ -1,31 +0,0 @@
#include "WorkerThread.h"
namespace concurrency {
void WorkerThread::doRun()
{
startWatchdog();
while (!wantExit) {
stopWatchdog();
block();
startWatchdog();
// no need - startWatchdog is guaranteed to give us one full watchdog interval
// serviceWatchdog(); // Let our loop worker have one full watchdog interval (at least) to run
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;
if (millis() - lastPrint > 10 * 1000L) {
lastPrint = millis();
meshtastic::printThreadInfo("net");
}
#endif
loop();
}
stopWatchdog();
}
} // namespace concurrency

View File

@@ -1,29 +0,0 @@
#pragma once
#include "Thread.h"
namespace concurrency {
/**
* @brief This wraps threading (FreeRTOS for now) with a blocking API intended for efficiently converting
* old-school arduino loop() code. Use as a mixin base class for the classes you want to convert.
*
* @link https://www.freertos.org/RTOS_Task_Notification_As_Mailbox.html
*/
class WorkerThread : public Thread
{
protected:
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block() = 0;
virtual void loop() = 0;
/**
* The method that will be called when start is called.
*/
virtual void doRun();
};
} // namespace concurrency

View File

@@ -40,7 +40,7 @@ void WiFiServerAPI::loop()
#define MESHTASTIC_PORTNUM 4403
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM) {}
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM), concurrency::OSThread("ApiServer") {}
void WiFiServerPort::init()
{
@@ -48,7 +48,7 @@ void WiFiServerPort::init()
begin();
}
void WiFiServerPort::loop()
int32_t WiFiServerPort::runOnce()
{
auto client = available();
if (client) {
@@ -59,7 +59,10 @@ void WiFiServerPort::loop()
openAPI = new WiFiServerAPI(client);
}
if (openAPI)
if (openAPI) {
// Allow idle processing so the API can read from its incoming stream
openAPI->loop();
return 0; // run fast while our API server is running
} else
return 100; // only check occasionally for incoming connections
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "StreamAPI.h"
#include "concurrency/OSThread.h"
#include <WiFi.h>
/**
@@ -27,7 +28,7 @@ class WiFiServerAPI : public StreamAPI
/**
* Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed
*/
class WiFiServerPort : public WiFiServer
class WiFiServerPort : public WiFiServer, private concurrency::OSThread
{
/** The currently open port
*
@@ -41,5 +42,5 @@ class WiFiServerPort : public WiFiServer
void init();
void loop();
int32_t runOnce();
};

View File

@@ -1,10 +1,10 @@
#include "GPS.h"
#include "NodeDB.h"
#include "RTC.h"
#include "configuration.h"
#include "sleep.h"
#include <assert.h>
#include <time.h>
// If we have a serial GPS port it will not be null
#ifdef GPS_RX_PIN
@@ -23,76 +23,8 @@ uint8_t GPS::i2cAddress = GPS_I2C_ADDRESS;
uint8_t GPS::i2cAddress = 0;
#endif
bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep
GPS *gps;
// stuff that really should be in in the instance instead...
static uint32_t
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
void readFromRTC()
{
struct timeval tv; /* btw settimeofday() is helpfull here too*/
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
}
}
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
bool perhapsSetRTC(const struct timeval *tv)
{
if (!timeSetFromGPS) {
timeSetFromGPS = true;
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
#ifndef NO_ESP32
settimeofday(tv, NULL);
#else
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
#endif
readFromRTC();
return true;
} else {
return false;
}
}
bool perhapsSetRTC(struct tm &t)
{
/* 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).
*/
time_t res = mktime(&t);
struct timeval tv;
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
// DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
if (t.tm_year < 0 || t.tm_year >= 300) {
// DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
return false;
} else {
return perhapsSetRTC(&tv);
}
}
uint32_t getTime()
{
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
}
uint32_t getValidTime()
{
return timeSetFromGPS ? getTime() : 0;
}
bool GPS::setup()
{
setAwake(true); // Wake GPS power before doing any init
@@ -104,6 +36,23 @@ bool GPS::setup()
return ok;
}
/// Record that we have a GPS
void GPS::setConnected()
{
if (!hasGPS) {
hasGPS = true;
shouldPublish = true;
}
}
void GPS::setNumSatellites(uint8_t n)
{
if (n != numSatellites) {
numSatellites = n;
shouldPublish = true;
}
}
/**
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
*
@@ -151,7 +100,7 @@ uint32_t GPS::getWakeTime() const
return t; // already maxint
if (t == 0)
t = 5 * 60; // Allow up to 5 mins for each attempt (probably will be much less if we can find sats)
t = 15 * 60; // Allow up to 5 mins for each attempt (probably will be much less if we can find sats)
t *= 1000; // msecs
@@ -165,7 +114,8 @@ uint32_t GPS::getSleepTime() const
uint32_t t = radioConfig.preferences.gps_update_interval;
auto op = getGpsOp();
if ((timeSetFromGPS && op == GpsOperation_GpsOpTimeOnly) || (op == GpsOperation_GpsOpDisabled))
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
if ((gotTime && op == GpsOperation_GpsOpTimeOnly) || (op == GpsOperation_GpsOpDisabled))
t = UINT32_MAX; // Sleep forever now
if (t == UINT32_MAX)
@@ -181,19 +131,23 @@ uint32_t GPS::getSleepTime() const
void GPS::publishUpdate()
{
if (shouldPublish) {
shouldPublish = false;
DEBUG_MSG("publishing GPS lock=%d\n", hasLock());
// Notify any status instances that are observing us
const meshtastic::GPSStatus status =
meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
meshtastic::GPSStatus(hasLock(), isConnected(), latitude, longitude, altitude, dop, heading, numSatellites);
newStatus.notifyObservers(&status);
}
}
void GPS::loop()
int32_t GPS::runOnce()
{
if (whileIdle()) {
// if we have received valid NMEA claim we are connected
isConnected = true;
setConnected();
}
// If we are overdue for an update, turn on the GPS and at least publish the current status
@@ -208,14 +162,23 @@ void GPS::loop()
// While we are awake
if (isAwake) {
// DEBUG_MSG("looking for location\n");
if ((now - lastWhileActiveMsec) > 1000) {
if ((now - lastWhileActiveMsec) > 5000) {
lastWhileActiveMsec = now;
whileActive();
}
// If we've already set time from the GPS, no need to ask the GPS
bool gotTime = timeSetFromGPS || lookForTime();
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
gotTime = true;
shouldPublish = true;
}
bool gotLoc = lookForLocation();
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
hasValidLocation = true;
shouldPublish = true;
}
// We've been awake too long - force sleep
auto wakeTime = getWakeTime();
@@ -224,9 +187,7 @@ void GPS::loop()
// Once we get a location we no longer desperately want an update
// or if we got a time and we are in GpsOpTimeOnly mode
// DEBUG_MSG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime);
if (gotLoc || tooLong || (gotTime && getGpsOp() == GpsOperation_GpsOpTimeOnly)) {
if (gotLoc)
hasValidLocation = true;
if ((gotLoc && gotTime) || tooLong || (gotTime && getGpsOp() == GpsOperation_GpsOpTimeOnly)) {
if (tooLong) {
// we didn't get a location during this ack window, therefore declare loss of lock
@@ -234,9 +195,16 @@ void GPS::loop()
}
setAwake(false);
publishUpdate(); // 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
publishUpdate();
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
return isAwake ? 100 : 5000;
}
void GPS::forceWake(bool on)
@@ -247,7 +215,10 @@ void GPS::forceWake(bool on)
wakeAllowed = true;
} else {
wakeAllowed = false;
setAwake(false);
// Note: if the gps was already awake, we DO NOT shut it down, because we want to allow it to complete its lock
// attempt even if we are in light sleep. Once the attempt succeeds (or times out) we'll then shut it down.
// setAwake(false);
}
}

View File

@@ -1,31 +1,18 @@
#pragma once
#include "../concurrency/PeriodicTask.h"
#include "GPSStatus.h"
#include "Observer.h"
#include "sys/time.h"
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
bool perhapsSetRTC(const struct timeval *tv);
bool perhapsSetRTC(struct tm &t);
#include "concurrency/OSThread.h"
// Generate a string representation of DOP
const char *getDOPString(uint32_t dop);
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
uint32_t getTime();
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
uint32_t getValidTime();
void readFromRTC();
/**
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
*
* When new data is available it will notify observers.
*/
class GPS
class GPS : private concurrency::OSThread
{
private:
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastWhileActiveMsec = 0;
@@ -36,9 +23,14 @@ class GPS
bool wakeAllowed = true; // false if gps must be forced to sleep regardless of what time it is
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
bool hasGPS = false; // Do we have a GPS we are talking to
uint8_t numSatellites = 0;
CallbackObserver<GPS, void *> notifySleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareSleep);
protected:
public:
/** If !NULL we will use this serial port to construct our GPS */
static HardwareSerial *_serial_gps;
@@ -51,9 +43,8 @@ class GPS
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
// scaling before use)
uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
uint32_t numSatellites = 0;
bool isConnected = false; // Do we have a GPS we are talking to
GPS() : concurrency::OSThread("GPS") {}
virtual ~GPS() {} // FIXME, we really should unregister our sleep observer
@@ -65,11 +56,12 @@ class GPS
*/
virtual bool setup();
virtual void loop();
/// Returns ture if we have acquired GPS lock.
bool hasLock() const { return hasValidLocation; }
/// Return true if we are connected to a GPS
bool isConnected() const { return hasGPS; }
/**
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
* called after the CPU wakes from light-sleep state
@@ -113,6 +105,11 @@ class GPS
*/
virtual bool lookForLocation() = 0;
/// Record that we have a GPS
void setConnected();
void setNumSatellites(uint8_t n);
private:
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
/// always returns 0 to indicate okay to sleep
@@ -139,6 +136,8 @@ class GPS
* Tell users we have new GPS readings
*/
void publishUpdate();
virtual int32_t runOnce();
};
extern GPS *gps;

View File

@@ -1,5 +1,6 @@
#include "NMEAGPS.h"
#include "configuration.h"
#include "RTC.h"
static int32_t toDegInt(RawDegrees d)
{
@@ -44,7 +45,7 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
t.tm_mon = d.month() - 1;
t.tm_year = d.year() - 1900;
t.tm_isdst = false;
perhapsSetRTC(t);
perhapsSetRTC(RTCQualityGPS, t);
return true;
} else
@@ -83,7 +84,7 @@ bool NMEAGPS::lookForLocation()
heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
}
if (reader.satellites.isValid()) {
numSatellites = reader.satellites.value();
setNumSatellites(reader.satellites.value());
}
// expect gps pos lat=37.520825, lon=-122.309162, alt=158

View File

@@ -1,6 +1,5 @@
#pragma once
#include "../concurrency/PeriodicTask.h"
#include "GPS.h"
#include "Observer.h"
#include "TinyGPS++.h"

77
src/gps/RTC.cpp Normal file
View File

@@ -0,0 +1,77 @@
#include "RTC.h"
#include "configuration.h"
#include <sys/time.h>
#include <time.h>
static RTCQuality currentQuality = RTCQualityNone;
RTCQuality getRTCQuality()
{
return currentQuality;
}
// stuff that really should be in in the instance instead...
static uint32_t
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
void readFromRTC()
{
struct timeval tv; /* btw settimeofday() is helpfull here too*/
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
DEBUG_MSG("Read RTC time as %ld (cur millis %u) quality=%d\n", tv.tv_sec, now, currentQuality);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
}
}
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
{
if (q > currentQuality) {
currentQuality = q;
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
#ifndef NO_ESP32
settimeofday(tv, NULL);
#else
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
#endif
readFromRTC();
return true;
} else {
return false;
}
}
bool perhapsSetRTC(RTCQuality q, struct tm &t)
{
/* 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).
*/
time_t res = mktime(&t);
struct timeval tv;
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
// DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
if (t.tm_year < 0 || t.tm_year >= 300) {
// DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
return false;
} else {
return perhapsSetRTC(q, &tv);
}
}
uint32_t getTime()
{
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
}
uint32_t getValidTime(RTCQuality minQuality)
{
return (currentQuality >= minQuality) ? getTime() : 0;
}

30
src/gps/RTC.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include "configuration.h"
#include "sys/time.h"
#include <Arduino.h>
enum RTCQuality {
/// We haven't had our RTC set yet
RTCQualityNone = 0,
/// Some other node gave us a time we can use
RTCQualityFromNet = 1,
/// Our time is based on our own GPS
RTCQualityGPS = 2
};
RTCQuality getRTCQuality();
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
bool perhapsSetRTC(RTCQuality q, struct tm &t);
/// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero
uint32_t getTime();
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
uint32_t getValidTime(RTCQuality minQuality);
void readFromRTC();

View File

@@ -1,4 +1,5 @@
#include "UBloxGPS.h"
#include "RTC.h"
#include "error.h"
#include "sleep.h"
#include <assert.h>
@@ -7,20 +8,23 @@ UBloxGPS::UBloxGPS() {}
bool UBloxGPS::tryConnect()
{
isConnected = false;
bool c = false;
if (_serial_gps)
isConnected = ublox.begin(*_serial_gps);
c = ublox.begin(*_serial_gps);
if (!isConnected && i2cAddress) {
if (!c && i2cAddress) {
extern bool neo6M; // Super skanky - if we are talking to the device i2c we assume it is a neo7 on a RAK815, which
// supports the newer API
neo6M = true;
isConnected = ublox.begin(Wire, i2cAddress);
c = ublox.begin(Wire, i2cAddress);
}
return isConnected;
if (c)
setConnected();
return c;
}
bool UBloxGPS::setupGPS()
@@ -32,7 +36,7 @@ bool UBloxGPS::setupGPS()
_serial_gps->begin(GPS_BAUDRATE);
#endif
#ifndef NO_ESP32
_serial_gps->setRxBufferSize(1024); // the default is 256
_serial_gps->setRxBufferSize(2048); // the default is 256
#endif
}
@@ -44,7 +48,7 @@ bool UBloxGPS::setupGPS()
for (int i = 0; (i < 3) && !tryConnect(); i++)
delay(500);
if (isConnected) {
if (isConnected()) {
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
if (!setUBXMode())
@@ -105,8 +109,8 @@ bool UBloxGPS::factoryReset()
for (int i = 0; (i < 3) && !tryConnect(); i++)
delay(500);
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected);
if (isConnected)
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected());
if (isConnected())
ok = setUBXMode();
return ok;
@@ -126,7 +130,7 @@ void UBloxGPS::whileActive()
// Update fixtype
if (ublox.moduleQueried.fixType) {
fixType = ublox.getFixType(0);
DEBUG_MSG("GPS fix type %d\n", fixType);
// DEBUG_MSG("GPS fix type %d, numSats %d\n", fixType, numSatellites);
}
}
@@ -138,7 +142,6 @@ void UBloxGPS::whileActive()
*/
bool UBloxGPS::lookForTime()
{
if (fixType >= 2) {
if (ublox.moduleQueried.gpsSecond) {
/* 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
@@ -152,10 +155,9 @@ bool UBloxGPS::lookForTime()
t.tm_mon = ublox.getMonth(0) - 1;
t.tm_year = ublox.getYear(0) - 1900;
t.tm_isdst = false;
perhapsSetRTC(t);
perhapsSetRTC(RTCQualityGPS, t);
return true;
}
}
return false;
}
@@ -170,6 +172,12 @@ bool UBloxGPS::lookForLocation()
{
bool foundLocation = false;
if (ublox.moduleQueried.SIV)
setNumSatellites(ublox.getSIV(0));
if (ublox.moduleQueried.pDOP)
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
// we only notify if position has changed due to a new fix
if ((fixType >= 3 && fixType <= 4)) {
if (ublox.moduleQueried.latitude) // rd fixes only
@@ -177,17 +185,14 @@ bool UBloxGPS::lookForLocation()
latitude = ublox.getLatitude(0);
longitude = ublox.getLongitude(0);
altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
// Note: heading is only currently implmented in the ublox for the 8m chipset - therefore
// don't read it here - it will generate an ignored getPVT command on the 6ms
// heading = ublox.getHeading(0);
numSatellites = ublox.getSIV(0);
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
foundLocation =
(latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000) && (numSatellites > 0);
foundLocation = (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000);
}
}

View File

@@ -561,7 +561,9 @@ void _screen_header()
}
#endif
Screen::Screen(uint8_t address, int sda, int scl) : cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {}
Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {
cmdQueue.setReader(this);
}
void Screen::handleSetOn(bool on)
{
@@ -573,9 +575,12 @@ void Screen::handleSetOn(bool on)
DEBUG_MSG("Turning on screen\n");
dispdev.displayOn();
dispdev.displayOn();
enabled = true;
setInterval(0); // Draw ASAP
} else {
DEBUG_MSG("Turning off screen\n");
dispdev.displayOff();
enabled = false;
}
screenOn = on;
}
@@ -583,8 +588,6 @@ void Screen::handleSetOn(bool on)
void Screen::setup()
{
concurrency::PeriodicTask::setup();
// We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
useDisplay = true;
@@ -642,14 +645,24 @@ void Screen::setup()
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
}
void Screen::doTask()
int32_t Screen::runOnce()
{
// If we don't have a screen, don't ever spend any CPU for us.
if (!useDisplay) {
setPeriod(0);
return;
enabled = false;
return RUN_SAME;
}
// Show boot screen for first 3 seconds, then switch to normal operation.
static bool showingBootScreen = true;
if (showingBootScreen && (millis() > 3000)) {
stopBootScreen();
showingBootScreen = false;
}
// Update the screen last, after we've figured out what to show.
debug_info()->setChannelNameStatus(getChannelName());
// Process incoming commands.
for (;;) {
ScreenCmd cmd;
@@ -684,8 +697,8 @@ void Screen::doTask()
if (!screenOn) { // If we didn't just wake and the screen is still off, then
// stop updating until it is on again
setPeriod(0);
return;
enabled = false;
return 0;
}
// Switch to a low framerate (to save CPU) when we are not in transition
@@ -711,7 +724,7 @@ void Screen::doTask()
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
// as fast as we really need so that any rounding errors still result with
// the correct framerate
setPeriod(1000 / targetFramerate);
return (1000 / targetFramerate);
}
void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -801,7 +814,7 @@ void Screen::handleOnPress()
// If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it.
if (ui.getUiState()->frameState == FIXED) {
setPeriod(1); // redraw ASAP
setInterval(0); // redraw ASAP
ui.nextFrame();
DEBUG_MSG("Setting fast framerate\n");
@@ -1062,7 +1075,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
setFrames(); // Regen the list of screens
prevFrame = -1; // Force a GUI update
setPeriod(1); // Update the screen right away
setInterval(0); // Update the screen right away
}
nodeDB.updateGUI = false;
nodeDB.updateTextMessage = false;

View File

@@ -15,7 +15,7 @@
#include "TypedQueue.h"
#include "commands.h"
#include "concurrency/LockGuard.h"
#include "concurrency/PeriodicTask.h"
#include "concurrency/OSThread.h"
#include "power.h"
#include <string>
@@ -62,7 +62,7 @@ class DebugInfo
* multiple times simultaneously. All state-changing calls are queued and executed
* when the main loop calls us.
*/
class Screen : public concurrency::PeriodicTask
class Screen : public concurrency::OSThread
{
CallbackObserver<Screen, const meshtastic::Status *> powerStatusObserver =
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
@@ -184,7 +184,7 @@ class Screen : public concurrency::PeriodicTask
/// Updates the UI.
//
// Called periodically from the main loop.
void doTask() final;
int32_t runOnce() final;
private:
struct ScreenCmd {
@@ -202,7 +202,7 @@ class Screen : public concurrency::PeriodicTask
return true; // claim success if our display is not in use
else {
bool success = cmdQueue.enqueue(cmd, 0);
setPeriod(1); // handle ASAP
enabled = true; // handle ASAP (we are the registered reader for cmdQueue, but might have been disabled)
return success;
}
}

View File

@@ -1,25 +1,3 @@
/*
Main module
# Modified by Kyle T. Gabriel to fix issue with incorrect GPS data for TTNMapper
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Air530GPS.h"
#include "MeshRadio.h"
@@ -27,14 +5,16 @@
#include "NodeDB.h"
#include "PowerFSM.h"
#include "UBloxGPS.h"
#include "concurrency/Periodic.h"
#include "configuration.h"
#include "error.h"
#include "power.h"
// #include "rom/rtc.h"
#include "DSRRouter.h"
// #include "debug.h"
#include "RTC.h"
#include "SPILock.h"
#include "concurrency/OSThread.h"
#include "concurrency/Periodic.h"
#include "graphics/Screen.h"
#include "main.h"
#include "meshwifi/meshhttp.h"
@@ -56,8 +36,10 @@
#include "variant.h"
#endif
using namespace concurrency;
// We always create a screen object, but we only init it if we find the hardware
graphics::Screen screen(SSD1306_ADDRESS);
graphics::Screen *screen;
// Global power status
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
@@ -71,8 +53,7 @@ meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
bool ssd1306_found;
bool axp192_found;
DSRRouter realRouter;
Router &router = realRouter; // Users of router don't care what sort of subclass implements that API
Router *router = NULL; // Users of router don't care what sort of subclass implements that API
// -----------------------------------------------------------------------------
// Application
@@ -122,7 +103,7 @@ const char *getDeviceName()
return name;
}
static uint32_t ledBlinker()
static int32_t ledBlinker()
{
static bool ledOn;
ledOn ^= 1;
@@ -130,26 +111,105 @@ static uint32_t ledBlinker()
setLed(ledOn);
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 2 : 1000);
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000);
}
concurrency::Periodic ledPeriodic(ledBlinker);
/// Wrapper to convert our powerFSM stuff into a 'thread'
class PowerFSMThread : public OSThread
{
public:
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
PowerFSMThread() : OSThread("PowerFSM") {}
protected:
int32_t runOnce()
{
powerFSM.run_machine();
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake
/// cpu for serial rx - FIXME)
auto state = powerFSM.getState();
canSleep = (state != &statePOWER) && (state != &stateSERIAL);
return 10;
}
};
/**
* Watch a GPIO and if we get an IRQ, wake the main thread.
* Use to add wake on button press
*/
void wakeOnIrq(int irq, int mode)
{
attachInterrupt(
irq,
[] {
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
FALLING);
}
class ButtonThread : public OSThread
{
// Prepare for button presses
#ifdef BUTTON_PIN
OneButton userButton;
OneButton userButton;
#endif
#ifdef BUTTON_PIN_ALT
OneButton userButtonAlt;
OneButton userButtonAlt;
#endif
void userButtonPressed()
{
public:
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
ButtonThread() : OSThread("Button")
{
#ifdef BUTTON_PIN
userButton = OneButton(BUTTON_PIN, true, true);
userButton.attachClick(userButtonPressed);
userButton.attachDuringLongPress(userButtonPressedLong);
wakeOnIrq(BUTTON_PIN, FALLING);
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
userButtonAlt.attachClick(userButtonPressed);
userButton.attachDuringLongPress(userButtonPressedLong);
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
#endif
}
protected:
/// If the button is pressed we suppress CPU sleep until release
int32_t runOnce()
{
canSleep = true; // Assume we should not keep the board awake
#ifdef BUTTON_PIN
userButton.tick();
canSleep &= userButton.isIdle();
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt.tick();
canSleep &= userButton.isIdle();
#endif
// if(!canSleep) DEBUG_MSG("Supressing sleep!\n");
return 5;
}
private:
static void userButtonPressed()
{
// DEBUG_MSG("press!\n");
powerFSM.trigger(EVENT_PRESS);
}
void userButtonPressedLong()
{
screen.adjustBrightness();
}
}
static void userButtonPressedLong() { screen->adjustBrightness(); }
};
static Periodic *ledPeriodic;
static OSThread *powerFSMthread, *buttonThread;
RadioInterface *rIf = NULL;
void setup()
{
@@ -174,6 +234,12 @@ void setup()
digitalWrite(RESET_OLED, 1);
#endif
OSThread::setup();
ledPeriodic = new Periodic("Blink", ledBlinker);
router = new DSRRouter();
#ifdef I2C_SDA
Wire.begin(I2C_SDA, I2C_SCL);
#else
@@ -185,23 +251,12 @@ void setup()
#endif
// Buttons & LED
#ifdef BUTTON_PIN
userButton = OneButton(BUTTON_PIN, true, true);
userButton.attachClick(userButtonPressed);
userButton.attachDuringLongPress(userButtonPressedLong);
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
userButtonAlt.attachClick(userButtonPressed);
userButton.attachDuringLongPress(userButtonPressedLong);
#endif
buttonThread = new ButtonThread();
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
#endif
ledPeriodic.setup();
// Hello
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION));
@@ -234,14 +289,15 @@ void setup()
#endif
// Initialize the screen first so we can show the logo while we start up everything else.
screen = new graphics::Screen(SSD1306_ADDRESS);
#if defined(ST7735_CS) || defined(HAS_EINK)
screen.setup();
screen->setup();
#else
if (ssd1306_found)
screen.setup();
screen->setup();
#endif
screen.print("Started...\n");
screen->print("Started...\n");
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
@@ -298,8 +354,7 @@ void setup()
digitalWrite(SX1262_ANT_SW, 1);
#endif
// MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
RadioInterface *rIf = NULL;
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
#if defined(RF95_IRQ)
if (!rIf) {
@@ -340,10 +395,11 @@ void setup()
if (!rIf)
recordCriticalError(ErrNoRadio);
else
router.addInterface(rIf);
router->addInterface(rIf);
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
powerFSMthread = new PowerFSMThread();
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
setCPUFast(false); // 80MHz is fine for our slow peripherals
@@ -366,21 +422,12 @@ uint32_t axpDebugRead()
return 30 * 1000;
}
concurrency::Periodic axpDebugOutput(axpDebugRead);
Periodic axpDebugOutput(axpDebugRead);
axpDebugOutput.setup();
#endif
void loop()
{
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
if (gps)
gps->loop(); // FIXME, remove from main, instead block on read
router.loop();
powerFSM.run_machine();
service.loop();
concurrency::periodicScheduler.loop();
// axpDebugOutput.loop();
#ifdef DEBUG_PORT
@@ -392,25 +439,9 @@ void loop()
#ifndef NO_ESP32
esp32Loop();
#endif
#ifdef TBEAM_V10
power->loop();
#endif
#ifdef BUTTON_PIN
userButton.tick();
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt.tick();
#endif
loopWifi();
// Show boot screen for first 3 seconds, then switch to normal operation.
static bool showingBootScreen = true;
if (showingBootScreen && (millis() > 3000)) {
screen.stopBootScreen();
showingBootScreen = false;
}
// For debugging
// if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving();
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;
@@ -420,19 +451,18 @@ void loop()
}
#endif
// Update the screen last, after we've figured out what to show.
screen.debug_info()->setChannelNameStatus(getChannelName());
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
// i.e. don't just keep spinning in loop as fast as we can.
// DEBUG_MSG("msecs %d\n", msecstosleep);
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons
// feel slow
msecstosleep = 10; // FIXME, stop early if something happens and sleep much longer
// TODO: This should go into a thread handled by FreeRTOS.
handleWebResponse();
delay(msecstosleep);
service.loop();
long delayMsec = mainController.runOrDelay();
/* if (mainController.nextThread && delayMsec)
DEBUG_MSG("Next %s in %ld\n", mainController.nextThread->ThreadName.c_str(),
mainController.nextThread->tillRun(millis())); */
// We want to sleep as long as possible here - because it saves power
mainDelay.delay(delayMsec);
// if (didWake) DEBUG_MSG("wake!\n");
}

View File

@@ -1,28 +1,24 @@
#pragma once
#include "graphics/Screen.h"
#include "PowerStatus.h"
#include "GPSStatus.h"
#include "NodeStatus.h"
#include "PowerStatus.h"
#include "graphics/Screen.h"
extern bool axp192_found;
extern bool ssd1306_found;
extern bool isCharging;
extern bool isUSBPowered;
// Global Screen singleton.
extern graphics::Screen screen;
//extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
extern graphics::Screen *screen;
// extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
//extern meshtastic::PowerStatus *powerStatus;
//extern meshtastic::GPSStatus *gpsStatus;
//extern meshtastic::NodeStatusHandler *nodeStatusHandler;
// extern meshtastic::PowerStatus *powerStatus;
// extern meshtastic::GPSStatus *gpsStatus;
// extern meshtastic::NodeStatusHandler *nodeStatusHandler;
// Return a human readable string of the form "Meshtastic_ab13"
const char *getDeviceName();
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();

View File

@@ -1,7 +1,6 @@
#pragma once
#include "PacketHistory.h"
#include "../concurrency/PeriodicTask.h"
#include "Router.h"
/**

View File

@@ -10,6 +10,7 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "RTC.h"
#include "main.h"
#include "mesh-pb-constants.h"
#include "power.h"
@@ -48,14 +49,14 @@ MeshService service;
#include "Router.h"
static uint32_t sendOwnerCb()
static int32_t sendOwnerCb()
{
service.sendOurOwner();
return getPref_send_owner_interval() * getPref_position_broadcast_secs() * 1000;
}
static concurrency::Periodic sendOwnerPeriod(sendOwnerCb);
static concurrency::Periodic *sendOwnerPeriod;
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
{
@@ -64,17 +65,18 @@ MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
void MeshService::init()
{
sendOwnerPeriod.setup();
sendOwnerPeriod = new concurrency::Periodic("SendOwner", sendOwnerCb);
nodeDB.init();
if (gps)
gpsObserver.observe(&gps->newStatus);
packetReceivedObserver.observe(&router.notifyPacketReceived);
packetReceivedObserver.observe(&router->notifyPacketReceived);
}
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
{
MeshPacket *p = router.allocForSending();
MeshPacket *p = router->allocForSending();
p->to = dest;
p->decoded.want_response = wantReplies;
p->decoded.which_payload = SubPacket_user_tag;
@@ -121,7 +123,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
sendOurOwner(mp->from);
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
screen.print(lcd.c_str());
screen->print(lcd.c_str());
}
return mp;
@@ -139,7 +141,7 @@ void MeshService::handleIncomingPosition(const MeshPacket *mp)
tv.tv_sec = secs;
tv.tv_usec = 0;
perhapsSetRTC(&tv);
perhapsSetRTC(RTCQualityFromNet, &tv);
}
} else {
DEBUG_MSG("Ignoring incoming packet - not a position\n");
@@ -150,12 +152,8 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
{
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
// If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work)
if (!gps->isConnected)
// If it is a position packet, perhaps set our clock - this must be before nodeDB.updateFrom
handleIncomingPosition(mp);
else {
DEBUG_MSG("Ignoring incoming time, because we have a GPS\n");
}
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
mp = handleFromRadioUser(mp);
@@ -229,7 +227,7 @@ void MeshService::handleToRadio(MeshPacket &p)
if (p.id == 0)
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
p.rx_time = getValidTime(); // Record the time the packet arrived from the phone
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
// (so we update our nodedb for the local node)
// Send the packet into the mesh
@@ -250,10 +248,10 @@ void MeshService::sendToMesh(MeshPacket *p)
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
// nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless
// nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless
// devices can get time.
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) {
if (!gps->isConnected) {
if (getRTCQuality() < RTCQualityGPS) {
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
p->decoded.position.time = 0;
} else
@@ -261,7 +259,7 @@ void MeshService::sendToMesh(MeshPacket *p)
}
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
router.sendLocal(p);
router->sendLocal(p);
}
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
@@ -283,12 +281,13 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
assert(node->has_position);
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = router.allocForSending();
MeshPacket *p = router->allocForSending();
p->to = dest;
p->decoded.which_payload = SubPacket_position_tag;
p->decoded.position = node->position;
p->decoded.want_response = wantReplies;
p->decoded.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
p->decoded.position.time =
getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
sendToMesh(p);
}
@@ -296,7 +295,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
{
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = router.allocForSending();
MeshPacket *p = router->allocForSending();
p->decoded.which_payload = SubPacket_position_tag;
Position &pos = p->decoded.position;
@@ -306,9 +305,10 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
pos.altitude = gps->altitude;
pos.latitude_i = gps->latitude;
pos.longitude_i = gps->longitude;
pos.time = getValidTime();
}
pos.time = getValidTime(RTCQualityGPS);
// Include our current battery voltage in our position announcement
pos.battery_level = powerStatus->getBatteryChargePercent();
updateBatteryLevel(pos.battery_level);

View File

@@ -10,6 +10,7 @@
#include "NodeDB.h"
#include "PacketHistory.h"
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include "error.h"
@@ -414,10 +415,10 @@ void NodeDB::updateFrom(const MeshPacket &mp)
switch (p.which_payload) {
case SubPacket_position_tag: {
// we carefully preserve the old time, because we always trust our local timestamps more
uint32_t oldtime = info->position.time;
// we always trust our local timestamps more
info->position = p.position;
info->position.time = oldtime;
if (mp.rx_time)
info->position.time = mp.rx_time;
info->has_position = true;
updateGUIforNode = info;
notifyObservers(true); // Force an update whether or not our node counts have changed

View File

@@ -144,11 +144,18 @@ const char *getChannelName();
PREF_GET(send_owner_interval, 4)
PREF_GET(position_broadcast_secs, 15 * 60)
PREF_GET(wait_bluetooth_secs, 120)
// Each time we wake into the DARK state allow 1 minute to send and receive BLE packets to the phone
PREF_GET(wait_bluetooth_secs, 60)
PREF_GET(screen_on_secs, 60)
PREF_GET(mesh_sds_timeout_secs, 2 * 60 * 60)
PREF_GET(phone_sds_timeout_sec, 2 * 60 * 60)
PREF_GET(sds_secs, 365 * 24 * 60 * 60)
PREF_GET(ls_secs, 60 * 60)
// We default to sleeping (with bluetooth off for 5 minutes at a time). This seems to be a good tradeoff between
// latency for the user sending messages and power savings because of not having to run (expensive) ESP32 bluetooth
PREF_GET(ls_secs, 5 * 60)
PREF_GET(phone_timeout_secs, 15 * 60)
PREF_GET(min_wake_secs, 10)

View File

@@ -109,7 +109,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
break;
case STATE_SEND_MY_INFO:
myNodeInfo.has_gps = gps && gps->isConnected; // Update with latest GPS connect info
// If the user has specified they don't want our node to share its location, make sure to tell the phone
// app not to send locations on our behalf.
myNodeInfo.has_gps = (radioConfig.preferences.location_share == LocationSharing_LocDisabled)
? true
: (gps && gps->isConnected()); // Update with latest GPS connect info
fromRadioScratch.which_variant = FromRadio_my_info_tag;
fromRadioScratch.variant.my_info = myNodeInfo;
state = STATE_SEND_RADIO;
@@ -117,7 +121,14 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_RADIO:
fromRadioScratch.which_variant = FromRadio_radio_tag;
fromRadioScratch.variant.radio = radioConfig;
// NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior.
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
// using to the app (so that even old phone apps work with new device loads).
fromRadioScratch.variant.radio.preferences.ls_secs = getPref_ls_secs();
state = STATE_SEND_NODEINFO;
break;

View File

@@ -5,6 +5,8 @@
#define MAX_POWER 20
// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17
// In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING
// if you set power to something higher than 17 or 20 you might fry your board.
#define POWER_DEFAULT 17 // How much power to use if the user hasn't set a power level

View File

@@ -121,9 +121,6 @@ bool RadioInterface::init()
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
// time.
// we want this thread to run at very high priority, because it is effectively running as a user space ISR
start("radio", RADIO_STACK_SIZE, configMAX_PRIORITIES - 1); // Start our worker thread
return true;
}

View File

@@ -36,7 +36,7 @@ typedef struct {
*
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
*/
class RadioInterface : protected concurrency::NotifiedWorkerThread
class RadioInterface
{
friend class MeshRadio; // for debugging we let that class touch pool
PointerQueue<MeshPacket> *rxDest = NULL;
@@ -72,6 +72,8 @@ class RadioInterface : protected concurrency::NotifiedWorkerThread
*/
RadioInterface();
virtual ~RadioInterface() {}
/**
* Set where to deliver received packets. This method should only be used by the Router class
*/
@@ -117,8 +119,6 @@ class RadioInterface : protected concurrency::NotifiedWorkerThread
*/
size_t beginSending(MeshPacket *p);
virtual void loop() {} // Idle processing
/**
* Some regulatory regions limit xmit power.
* This function should be called by subclasses after setting their desired power. It might lower it

View File

@@ -19,17 +19,11 @@ void LockingModule::SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi, PhysicalLayer *_iface)
: concurrency::PeriodicTask(0), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
: NotifiedWorkerThread("RadioIf"), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
{
instance = this;
}
bool RadioLibInterface::init()
{
setup(); // init our timer
return RadioInterface::init();
}
#ifndef NO_ESP32
// ESP32 doesn't use that flag
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
@@ -41,9 +35,8 @@ void INTERRUPT_ATTR RadioLibInterface::isrLevel0Common(PendingISR cause)
{
instance->disableInterrupt();
instance->pending = cause;
BaseType_t xHigherPriorityTaskWoken;
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, eSetValueWithOverwrite);
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, true);
/* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE.
The macro used to do this is dependent on the port and may be called
@@ -191,10 +184,8 @@ transmitters that we are potentially stomping on. Requires further thought.
FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later.
*/
void RadioLibInterface::loop()
void RadioLibInterface::onNotify(uint32_t notification)
{
pending = ISR_NONE;
switch (notification) {
case ISR_TX:
handleTransmitInterrupt();
@@ -209,6 +200,8 @@ void RadioLibInterface::loop()
startTransmitTimer();
break;
case TRANSMIT_DELAY_COMPLETED:
// DEBUG_MSG("delay done\n");
// If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread
// has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode?
if (!txQueue.isEmpty()) {
@@ -229,25 +222,14 @@ void RadioLibInterface::loop()
}
}
void RadioLibInterface::doTask()
{
disable(); // Don't call this callback again
// We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the
// ISR handler will restart our timer)
notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite);
}
void RadioLibInterface::startTransmitTimer(bool withDelay)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (getPeriod() == 0 && !txQueue.isEmpty()) {
if (!txQueue.isEmpty()) {
uint32_t delay =
!withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
// DEBUG_MSG("xmit timer %d\n", delay);
// DEBUG_MSG("delaying %u\n", delay);
setPeriod(delay);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "../concurrency/PeriodicTask.h"
#include "../concurrency/OSThread.h"
#include "RadioInterface.h"
#ifdef CubeCell_BoardPlus
@@ -59,13 +59,11 @@ class LockingModule : public Module
virtual void SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes);
};
class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTask
class RadioLibInterface : public RadioInterface, protected concurrency::NotifiedWorkerThread
{
/// Used as our notification from the ISR
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED };
volatile PendingISR pending = ISR_NONE;
/**
* Raw ISR handler that just calls our polymorphic method
*/
@@ -137,6 +135,11 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
*/
virtual void startReceive() = 0;
/** are we actively receiving a packet (only called during receiving state)
* This method is only public to facilitate debugging. Do not call.
*/
virtual bool isActivelyReceiving() = 0;
private:
/** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing
* the transmit
@@ -150,7 +153,7 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
static void timerCallback(void *p1, uint32_t p2);
virtual void doTask();
virtual void onNotify(uint32_t notification);
/** start an immediate transmit
* This method is virtual so subclasses can hook as needed, subclasses should not call directly
@@ -158,10 +161,6 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
virtual void startSend(MeshPacket *txp);
protected:
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool init();
/** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */
virtual void configHardwareForSend() {}
@@ -176,9 +175,6 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately();
/** are we actively receiving a packet (only called during receiving state) */
virtual bool isActivelyReceiving() = 0;
/**
* Raw ISR handler that just calls our polymorphic method
*/
@@ -193,7 +189,5 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
*/
virtual void addReceiveMetadata(MeshPacket *mp) = 0;
virtual void loop(); // Idle processing
virtual void setStandby() = 0;
};

View File

@@ -160,9 +160,10 @@ PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p)
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*/
void ReliableRouter::doRetransmissions()
int32_t ReliableRouter::doRetransmissions()
{
uint32_t now = millis();
int32_t d = INT32_MAX;
// FIXME, we should use a better datastructure rather than walking through this map.
// for(auto el: pending) {
@@ -192,5 +193,13 @@ void ReliableRouter::doRetransmissions()
p.setNextTx();
}
}
else {
// Not yet time
int32_t t = p.nextTxMsec - now;
d = min(t, d);
}
}
return d;
}

View File

@@ -1,7 +1,6 @@
#pragma once
#include "FloodingRouter.h"
#include "../concurrency/PeriodicTask.h"
#include <unordered_map>
/**
@@ -80,10 +79,13 @@ class ReliableRouter : public FloodingRouter
virtual ErrorCode send(MeshPacket *p);
/** Do our retransmission handling */
virtual void loop()
virtual int32_t runOnce()
{
doRetransmissions();
FloodingRouter::loop();
auto d = FloodingRouter::runOnce();
int32_t r = doRetransmissions();
return min(d, r);
}
protected:
@@ -124,6 +126,8 @@ class ReliableRouter : public FloodingRouter
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
*/
void doRetransmissions();
int32_t doRetransmissions();
};

View File

@@ -1,6 +1,6 @@
#include "Router.h"
#include "CryptoEngine.h"
#include "GPS.h"
#include "RTC.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include <NodeDB.h>
@@ -34,25 +34,29 @@ Allocator<MeshPacket> &packetPool = staticPool;
*
* Currently we only allow one interface, that may change in the future
*/
Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO)
Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRADIO)
{
// This is called pre main(), don't touch anything here, the following code is not safe
/* DEBUG_MSG("Size of NodeInfo %d\n", sizeof(NodeInfo));
DEBUG_MSG("Size of SubPacket %d\n", sizeof(SubPacket));
DEBUG_MSG("Size of MeshPacket %d\n", sizeof(MeshPacket)); */
fromRadioQueue.setReader(this);
}
/**
* do idle processing
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
*/
void Router::loop()
int32_t Router::runOnce()
{
MeshPacket *mp;
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
perhapsHandleReceived(mp);
}
return INT32_MAX; // Wait a long time - until we get woken for the message queue
}
/// Generate a unique packet id
@@ -89,7 +93,7 @@ MeshPacket *Router::allocForSending()
p->to = NODENUM_BROADCAST;
p->hop_limit = HOP_RELIABLE;
p->id = generatePacketId();
p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp
p->rx_time = getValidTime(RTCQualityFromNet); // Just in case we process the packet locally - make sure it has a valid timestamp
return p;
}
@@ -198,9 +202,8 @@ NodeNum Router::getNodeNum()
*/
void Router::handleReceived(MeshPacket *p)
{
// FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(); // store the arrival timestamp for the phone
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
// Take those raw bytes and convert them back into a well structured protobuf we can understand
if (perhapsDecode(p)) {

View File

@@ -5,12 +5,13 @@
#include "Observer.h"
#include "PointerQueue.h"
#include "RadioInterface.h"
#include "concurrency/OSThread.h"
#include "mesh.pb.h"
/**
* A mesh aware router that supports multiple interfaces.
*/
class Router
class Router : protected concurrency::OSThread
{
private:
RadioInterface *iface;
@@ -44,7 +45,7 @@ class Router
* do idle processing
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
*/
virtual void loop();
virtual int32_t runOnce();
/**
* Works like send, but if we are sending to the local node, we directly put the message in the receive queue
@@ -113,7 +114,7 @@ class Router
void handleReceived(MeshPacket *p);
};
extern Router &router;
extern Router *router;
/// Generate a unique packet id
// FIXME, move this someplace better

View File

@@ -179,16 +179,18 @@ void SX1262Interface::startReceive()
/** Could we send right now (i.e. either not actively receving or transmitting)? */
bool SX1262Interface::isActivelyReceiving()
{
// The IRQ status will be cleared when we start our read operation. Check if we've started a preamble, but haven't yet
// The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
// received and handled the interrupt for reading the packet/handling errors.
// FIXME: it would be better to check for preamble, but we currently have our ISR not set to fire for packets that
// never even get a valid header, so we don't want preamble to get set and stay set due to noise on the network.
uint16_t irq = lora.getIrqStatus();
bool hasPreamble = (irq & SX126X_IRQ_PREAMBLE_DETECTED);
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
// this is not correct - often always true - need to add an extra conditional
// size_t bytesPending = lora.getPacketLength();
// if (hasPreamble || bytesPending) DEBUG_MSG("rx hasPre %d, bytes %d\n", hasPreamble, bytesPending);
// if (hasPreamble) DEBUG_MSG("rx hasPreamble\n");
return hasPreamble;
}

View File

@@ -3,6 +3,7 @@
#include <cassert>
#include <type_traits>
#include "concurrency/OSThread.h"
#include "freertosinc.h"
#ifdef HAS_FREE_RTOS
@@ -15,6 +16,7 @@ template <class T> class TypedQueue
{
static_assert(std::is_pod<T>::value, "T must be pod");
QueueHandle_t h;
concurrency::OSThread *reader = NULL;
public:
TypedQueue(int maxElements)
@@ -29,13 +31,35 @@ template <class T> class TypedQueue
bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { return xQueueSendToBack(h, &x, maxWait) == pdTRUE; }
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
{
if (reader) {
reader->setInterval(0);
concurrency::mainDelay.interrupt();
}
return xQueueSendToBack(h, &x, maxWait) == pdTRUE;
}
bool enqueueFromISR(T x, BaseType_t *higherPriWoken) { return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE; }
bool enqueueFromISR(T x, BaseType_t *higherPriWoken)
{
if (reader) {
reader->setInterval(0);
concurrency::mainDelay.interruptFromISR(higherPriWoken);
}
return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE;
}
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
/**
* Set a thread that is reading from this queue
* If a message is pushed to this queue that thread will be scheduled to run ASAP.
*
* Note: thread will not be automatically enabled, just have its interval set to 0
*/
void setReader(concurrency::OSThread *t) { reader = t; }
};
#else
@@ -49,6 +73,7 @@ template <class T> class TypedQueue
template <class T> class TypedQueue
{
std::queue<T> q;
concurrency::OSThread *reader = NULL;
public:
TypedQueue(int maxElements) {}
@@ -59,6 +84,11 @@ template <class T> class TypedQueue
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
{
if (reader) {
reader->setInterval(0);
concurrency::mainDelay.interrupt();
}
q.push(x);
return true;
}
@@ -77,5 +107,7 @@ template <class T> class TypedQueue
}
// bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
void setReader(concurrency::OSThread *t) { reader = t; }
};
#endif

View File

@@ -121,13 +121,7 @@ void initWifi()
DEBUG_MSG("Not using WIFI\n");
}
/// Perform idle loop processing required by the wifi layer
void loopWifi()
{
// FIXME, once we have coroutines - just use a coroutine instead of this nasty loopWifi()
if (apiPort)
apiPort->loop();
}
static void initApiServer()
{

View File

@@ -12,9 +12,6 @@
void initWifi();
void deinitWifi();
/// Perform idle loop processing required by the wifi layer
void loopWifi();
bool isWifiAvailable();
void handleDNSResponse();

View File

@@ -3,16 +3,16 @@
#include "NimbleBluetoothAPI.h"
#include "PhoneAPI.h"
#include "PowerFSM.h"
#include <WiFi.h>
#include "configuration.h"
#include "esp_bt.h"
#include "host/util/util.h"
#include "main.h"
#include "meshwifi/meshwifi.h"
#include "nimble/NimbleDefs.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include <Arduino.h>
#include "meshwifi/meshwifi.h"
#include <WiFi.h>
static bool pinShowing;
@@ -20,14 +20,14 @@ static void startCb(uint32_t pin)
{
pinShowing = true;
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
screen.startBluetoothPinScreen(pin);
screen->startBluetoothPinScreen(pin);
};
static void stopCb()
{
if (pinShowing) {
pinShowing = false;
screen.stopBluetoothPinScreen();
screen->stopBluetoothPinScreen();
}
};

View File

@@ -1,6 +1,6 @@
#pragma once
#include "PowerStatus.h"
#include "concurrency/PeriodicTask.h"
#include "concurrency/OSThread.h"
/**
* Per @spattinson
@@ -15,16 +15,17 @@
#define BAT_MILLIVOLTS_FULL 4100
#define BAT_MILLIVOLTS_EMPTY 3500
class Power : public concurrency::PeriodicTask
class Power : private concurrency::OSThread
{
public:
Observable<const meshtastic::PowerStatus *> newStatus;
Power();
void readPowerStatus();
void loop();
virtual bool setup();
virtual void doTask();
virtual int32_t runOnce();
void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; }
protected:

View File

@@ -151,7 +151,7 @@ void doDeepSleep(uint64_t msecToWake)
notifySleep.notifyObservers(NULL); // also tell the regular sleep handlers
notifyDeepSleep.notifyObservers(NULL);
screen.setOn(false); // datasheet says this will draw only 10ua
screen->setOn(false); // datasheet says this will draw only 10ua
nodeDB.saveToDisk();
@@ -170,17 +170,18 @@ void doDeepSleep(uint64_t msecToWake)
#ifdef TBEAM_V10
if (axp192_found) {
// Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep.
// We no longer do that, because our light-sleep current draws are low enough and it provides fast start/low cost
// wake. We currently use deep sleep only for 'we want our device to actually be off - because our battery is
// critically low'. So in deep sleep we DO shut down power to LORA (and when we boot later we completely reinit it)
//
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
// leave floating input for the IRQ line
// If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting
// in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets
// all the time.
// axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
// now done by UBloxGPS.cpp
// setGPSPower(false);
axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
}
#endif