Add Transmit history persistence for respecting traffic intervals between reboots (#9748)

* Add transmit history for throttling that persists between reboots

* Fix RAK long press detection to prevent phantom shutdowns from floating pins

* Update test/test_transmit_history/test_main.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Test fixes and placeholder for content handler tests

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Ben Meadors
2026-02-25 20:41:07 -06:00
committed by GitHub
co-authored by GitHub Copilot
parent 3b1b308357
commit 3a74e049ab
23 changed files with 689 additions and 35 deletions
+2 -1
View File
@@ -276,7 +276,8 @@ int32_t ButtonThread::runOnce()
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
// Require press started after boot holdoff to avoid phantom shutdown from floating pins
if (millis() > 30000 && buttonPressStartTime > 30000 && _longLongPress != INPUT_BROKER_NONE &&
(millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
evt.inputEvent = _longLongPress;
this->notifyObservers(&evt);
+4
View File
@@ -9,6 +9,7 @@
#include "PowerMon.h"
#include "RadioLibInterface.h"
#include "ReliableRouter.h"
#include "TransmitHistory.h"
#include "airtime.h"
#include "buzz.h"
#include "power/PowerHAL.h"
@@ -703,6 +704,9 @@ void setup()
// We do this as early as possible because this loads preferences from flash
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
// Initialize transmit history to persist broadcast throttle timers across reboots
TransmitHistory::getInstance()->loadFromDisk();
#if HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
tftSetup();
+202
View File
@@ -0,0 +1,202 @@
#include "TransmitHistory.h"
#include "FSCommon.h"
#include "RTC.h"
#include "SPILock.h"
#include <Throttle.h>
#ifdef FSCom
TransmitHistory *transmitHistory = nullptr;
TransmitHistory *TransmitHistory::getInstance()
{
if (!transmitHistory) {
transmitHistory = new TransmitHistory();
}
return transmitHistory;
}
void TransmitHistory::loadFromDisk()
{
spiLock->lock();
auto file = FSCom.open(FILENAME, FILE_O_READ);
if (file) {
FileHeader header{};
if (file.read((uint8_t *)&header, sizeof(header)) == sizeof(header) && header.magic == MAGIC &&
header.version == VERSION && header.count <= MAX_ENTRIES) {
for (uint8_t i = 0; i < header.count; i++) {
Entry entry{};
if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry)) {
if (entry.epochSeconds > 0) {
history[entry.key] = entry.epochSeconds;
// Seed in-memory millis so throttle works even without RTC/GPS.
// Treating stored entries as "just sent" is safe — worst case the
// node waits one full interval before its first broadcast.
lastMillis[entry.key] = millis();
}
}
}
LOG_INFO("TransmitHistory: loaded %u entries from disk", header.count);
} else {
LOG_WARN("TransmitHistory: invalid file header, starting fresh");
}
file.close();
} else {
LOG_INFO("TransmitHistory: no history file found, starting fresh");
}
spiLock->unlock();
dirty = false;
}
void TransmitHistory::setLastSentToMesh(uint16_t key)
{
lastMillis[key] = millis();
uint32_t now = getTime();
if (now >= 2) {
history[key] = now;
dirty = true;
// Don't flush to disk on every transmit — flash has limited write endurance.
// The in-memory lastMillis map handles throttle during normal operation.
// Disk is flushed: before deep sleep (sleep.cpp) and periodically here,
// throttled to at most once per 5 minutes. Always save the first time
// after boot so a crash-reboot loop can't avoid persisting.
if (lastDiskSave == 0 || !Throttle::isWithinTimespanMs(lastDiskSave, SAVE_INTERVAL_MS)) {
if (saveToDisk()) {
lastDiskSave = millis();
}
}
}
}
uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const
{
auto it = history.find(key);
if (it != history.end()) {
return it->second;
}
return 0;
}
uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
{
// Prefer runtime millis value (accurate within this boot)
auto mit = lastMillis.find(key);
if (mit != lastMillis.end()) {
return mit->second;
}
// Fall back to epoch conversion (loaded from disk after reboot)
uint32_t storedEpoch = getLastSentToMeshEpoch(key);
if (storedEpoch == 0) {
return 0; // No stored time — module has never sent
}
uint32_t now = getTime();
if (now < 2) {
// No valid RTC time yet — can't convert to millis. Return 0 so throttle doesn't block.
return 0;
}
if (storedEpoch > now) {
// Stored time is in the future (clock went backwards?) — treat as stale
return 0;
}
uint32_t secondsAgo = now - storedEpoch;
uint32_t msAgo = secondsAgo * 1000;
// Guard against overflow: if the transmit was very long ago, just return 0 (won't throttle)
if (secondsAgo > 86400 || msAgo / 1000 != secondsAgo) {
return 0;
}
// Convert to a millis()-relative timestamp: millis() - msAgo
// This gives a value that, when passed to Throttle::isWithinTimespanMs(value, interval),
// correctly reports whether the transmit was within interval ms.
return millis() - msAgo;
}
bool TransmitHistory::saveToDisk()
{
if (!dirty) {
return true;
}
spiLock->lock();
FSCom.mkdir("/prefs");
// Remove old file first
if (FSCom.exists(FILENAME)) {
FSCom.remove(FILENAME);
}
auto file = FSCom.open(FILENAME, FILE_O_WRITE);
if (file) {
FileHeader header{};
header.magic = MAGIC;
header.version = VERSION;
header.count = (uint8_t)min((size_t)MAX_ENTRIES, history.size());
file.write((uint8_t *)&header, sizeof(header));
uint8_t written = 0;
for (auto &pair : history) {
if (written >= MAX_ENTRIES)
break;
Entry entry{};
entry.key = pair.first;
entry.epochSeconds = pair.second;
file.write((uint8_t *)&entry, sizeof(entry));
written++;
}
file.flush();
file.close();
LOG_DEBUG("TransmitHistory: saved %u entries to disk", written);
dirty = false;
spiLock->unlock();
return true;
} else {
LOG_WARN("TransmitHistory: failed to open file for writing");
}
spiLock->unlock();
return false;
}
#else
// No filesystem available — provide stub with in-memory tracking
TransmitHistory *transmitHistory = nullptr;
TransmitHistory *TransmitHistory::getInstance()
{
if (!transmitHistory) {
transmitHistory = new TransmitHistory();
}
return transmitHistory;
}
void TransmitHistory::loadFromDisk() {}
void TransmitHistory::setLastSentToMesh(uint16_t key)
{
lastMillis[key] = millis();
}
uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const
{
return 0;
}
uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
{
auto mit = lastMillis.find(key);
return (mit != lastMillis.end()) ? mit->second : 0;
}
bool TransmitHistory::saveToDisk()
{
return true;
}
#endif
+88
View File
@@ -0,0 +1,88 @@
#pragma once
#include "configuration.h"
#include <Arduino.h>
#include <map>
/**
* TransmitHistory persists the last broadcast transmit time (epoch seconds) per portnum
* to the filesystem so that throttle checks survive reboots/crashes.
*
* On boot, modules call getLastSentToMeshMillis() to recover a millis()-relative timestamp
* from the stored epoch time, which plugs directly into existing throttle logic.
*
* On every broadcast transmit, modules call setLastSentToMesh() which updates the
* in-memory cache and flushes to disk.
*
* Keys are meshtastic_PortNum values (one entry per portnum).
*/
#include "mesh/generated/meshtastic/portnums.pb.h"
class TransmitHistory
{
public:
static TransmitHistory *getInstance();
/**
* Load persisted transmit times from disk. Call once during init after filesystem is ready.
*/
void loadFromDisk();
/**
* Record that a broadcast was sent for the given key right now.
* Stores epoch seconds and flushes to disk.
*/
void setLastSentToMesh(uint16_t key);
/**
* Get the last transmit epoch seconds for a given key, or 0 if unknown.
*/
uint32_t getLastSentToMeshEpoch(uint16_t key) const;
/**
* Convert a stored epoch timestamp into a millis()-relative timestamp suitable
* for use with Throttle::isWithinTimespanMs().
*
* Returns 0 if no valid time is stored or if the stored time is in the future
* (which shouldn't happen but guards against clock weirdness).
*
* Example: if the stored epoch is 300 seconds ago, and millis() is currently 10000,
* this returns 10000 - 300000 (wrapped appropriately for uint32_t arithmetic).
*/
uint32_t getLastSentToMeshMillis(uint16_t key) const;
/**
* Flush dirty entries to disk. Called periodically or on demand.
*
* @return true if the data is persisted (or there was nothing to write), false on write/open failure.
*/
bool saveToDisk();
private:
TransmitHistory() = default;
static constexpr const char *FILENAME = "/prefs/transmit_history.dat";
static constexpr uint32_t MAGIC = 0x54485354; // "THST"
static constexpr uint8_t VERSION = 1;
static constexpr uint8_t MAX_ENTRIES = 16;
static constexpr uint32_t SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
struct __attribute__((packed)) Entry {
uint16_t key;
uint32_t epochSeconds;
};
struct __attribute__((packed)) FileHeader {
uint32_t magic;
uint8_t version;
uint8_t count;
};
std::map<uint16_t, uint32_t> history; // key -> epoch seconds (for disk persistence)
std::map<uint16_t, uint32_t> lastMillis; // key -> millis() value (for runtime throttle)
bool dirty = false;
uint32_t lastDiskSave = 0; // millis() of last disk flush
};
extern TransmitHistory *transmitHistory;
+6 -3
View File
@@ -5,6 +5,7 @@
#include "NodeStatus.h"
#include "RTC.h"
#include "Router.h"
#include "TransmitHistory.h"
#include "configuration.h"
#include "main.h"
#include <Throttle.h>
@@ -133,11 +134,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
// Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient)
uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline());
if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) {
uint32_t lastNodeInfo = transmitHistory ? transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP) : 0;
if (!shorterTimeout && lastNodeInfo && Throttle::isWithinTimespanMs(lastNodeInfo, timeoutMs)) {
LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000);
ignoreRequest = true; // Mark it as ignored for MeshModule
return NULL;
} else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) {
} else if (shorterTimeout && lastNodeInfo && Throttle::isWithinTimespanMs(lastNodeInfo, 60 * 1000)) {
// For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout
LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago");
ignoreRequest = true;
@@ -159,7 +161,8 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
strcpy(u.id, nodeDB->getNodeId().c_str());
LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
lastSentToMesh = millis();
if (transmitHistory)
transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP);
return allocDataProtobuf(u);
}
}
-1
View File
@@ -42,7 +42,6 @@ class NodeInfoModule : public ProtobufModule<meshtastic_User>, private concurren
virtual int32_t runOnce() override;
private:
uint32_t lastSentToMesh = 0; // Last time we sent our NodeInfo to the mesh
bool shorterTimeout = false;
bool suppressReplyForCurrentRequest = false;
std::map<NodeNum, uint32_t> lastNodeInfoSeen;
+12
View File
@@ -6,6 +6,7 @@
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
#include "TransmitHistory.h"
#include "TypeConversions.h"
#include "airtime.h"
#include "configuration.h"
@@ -27,6 +28,15 @@ PositionModule::PositionModule()
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
// Seed throttle timer from persisted transmit history so we don't re-broadcast immediately after reboot
if (transmitHistory) {
uint32_t restored = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP);
if (restored != 0) {
lastGpsSend = restored;
LOG_INFO("Position: restored lastGpsSend from transmit history");
}
}
if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
setIntervalFromNow(setStartDelay());
@@ -438,6 +448,8 @@ int32_t PositionModule::runOnce()
lastGpsLatitude = node->position.latitude_i;
lastGpsLongitude = node->position.longitude_i;
if (transmitHistory)
transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP);
sendOurPosition();
if (config.device.role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) {
sendLostAndFoundText();
+13 -7
View File
@@ -11,6 +11,7 @@
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
#include "TransmitHistory.h"
#include "UnitConversions.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
@@ -19,6 +20,8 @@
#include "sleep.h"
#include <Throttle.h>
static constexpr uint16_t TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY = 0x8004;
// Sensors
#include "Sensor/AddI2CSensorTemplate.h"
#include "Sensor/PMSA003ISensor.h"
@@ -108,11 +111,13 @@ int32_t AirQualityTelemetryModule::runOnce()
// Wake up the sensors that need it
LOG_INFO("Waking up sensors...");
uint32_t lastTelemetry =
transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY) : 0;
for (TelemetrySensor *sensor : sensors) {
if (!sensor->canSleep()) {
LOG_DEBUG("%s sensor doesn't have sleep feature. Skipping", sensor->sensorName);
} else if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh - sensor->wakeUpTimeMs(),
} else if (((lastTelemetry == 0) ||
!Throttle::isWithinTimespanMs(lastTelemetry - sensor->wakeUpTimeMs(),
Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
@@ -131,14 +136,15 @@ int32_t AirQualityTelemetryModule::runOnce()
}
}
if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
if (((lastTelemetry == 0) ||
!Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = millis();
if (transmitHistory)
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY);
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
(service->isToPhoneQueueEmpty())) {
// Just send to phone when it's not our time to send to mesh yet
@@ -68,7 +68,6 @@ class AirQualityTelemetryModule : private concurrency::OSThread,
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
// uint32_t sendToPhoneIntervalMs = 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
};
+9 -5
View File
@@ -7,6 +7,7 @@
#include "RTC.h"
#include "RadioLibInterface.h"
#include "Router.h"
#include "TransmitHistory.h"
#include "configuration.h"
#include "main.h"
#include "memGet.h"
@@ -15,20 +16,23 @@
#include <meshUtils.h>
#define MAGIC_USB_BATTERY_LEVEL 101
static constexpr uint16_t TX_HISTORY_KEY_DEVICE_TELEMETRY = 0x8001;
int32_t DeviceTelemetryModule::runOnce()
{
refreshUptime();
uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_DEVICE_TELEMETRY) : 0;
bool isImpoliteRole = isSensorOrRouterRole();
if (((lastSentToMesh == 0) ||
((uptimeLastMs - lastSentToMesh) >=
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
if (((lastTelemetry == 0) ||
((uptimeLastMs - lastTelemetry) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
default_telemetry_broadcast_interval_secs,
numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() &&
config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN &&
moduleConfig.telemetry.device_telemetry_enabled) {
sendTelemetry();
lastSentToMesh = uptimeLastMs;
if (transmitHistory)
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_DEVICE_TELEMETRY);
} else if (service->isToPhoneQueueEmpty()) {
// Just send to phone when it's not our time to send to mesh yet
// Only send while queue is empty (phone assumed connected)
-1
View File
@@ -51,7 +51,6 @@ class DeviceTelemetryModule : private concurrency::OSThread,
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t sendStatsToPhoneIntervalMs = 15 * SECONDS_IN_MINUTE * 1000; // Send stats to phone every 15 minutes
uint32_t lastSentStatsToPhone = 0;
uint32_t lastSentToMesh = 0;
void refreshUptime()
{
+11 -5
View File
@@ -10,6 +10,7 @@
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
#include "TransmitHistory.h"
#include "UnitConversions.h"
#include "buzz.h"
#include "graphics/SharedUIDisplay.h"
@@ -145,6 +146,8 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "graphics/ScreenFonts.h"
#include <Throttle.h>
static constexpr uint16_t TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY = 0x8002;
void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
@@ -318,14 +321,17 @@ int32_t EnvironmentTelemetryModule::runOnce()
}
}
if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.environment_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
uint32_t lastTelemetry =
transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY) : 0;
if (((lastTelemetry == 0) ||
!Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.environment_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = millis();
if (transmitHistory)
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY);
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
(service->isToPhoneQueueEmpty())) {
// Just send to phone when it's not our time to send to mesh yet
@@ -68,7 +68,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
bool firstTime = 1;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
};
+10 -5
View File
@@ -10,6 +10,7 @@
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
#include "TransmitHistory.h"
#include "UnitConversions.h"
#include "main.h"
#include "power.h"
@@ -33,6 +34,8 @@ MLX90614Sensor mlx90614Sensor;
#endif
#include <Throttle.h>
static constexpr uint16_t TX_HISTORY_KEY_HEALTH_TELEMETRY = 0x8003;
int32_t HealthTelemetryModule::runOnce()
{
if (sleepOnNextExecution == true) {
@@ -69,14 +72,16 @@ int32_t HealthTelemetryModule::runOnce()
return disable();
}
if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.health_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_HEALTH_TELEMETRY) : 0;
if (((lastTelemetry == 0) ||
!Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.health_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = millis();
if (transmitHistory)
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_HEALTH_TELEMETRY);
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
(service->isToPhoneQueueEmpty())) {
// Just send to phone when it's not our time to send to mesh yet
-1
View File
@@ -55,7 +55,6 @@ class HealthTelemetryModule : private concurrency::OSThread,
bool firstTime = 1;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
uint32_t sensor_read_error_count = 0;
};
+7 -2
View File
@@ -10,6 +10,7 @@
#include "PowerTelemetry.h"
#include "RTC.h"
#include "Router.h"
#include "TransmitHistory.h"
#include "graphics/SharedUIDisplay.h"
#include "main.h"
#include "power.h"
@@ -22,6 +23,8 @@
#include "graphics/ScreenFonts.h"
#include <Throttle.h>
static constexpr uint16_t TX_HISTORY_KEY_POWER_TELEMETRY = 0x8005;
namespace graphics
{
extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert,
@@ -88,10 +91,12 @@ int32_t PowerTelemetryModule::runOnce()
if (!moduleConfig.telemetry.power_measurement_enabled)
return disable();
if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, sendToMeshIntervalMs)) &&
uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_POWER_TELEMETRY) : 0;
if (((lastTelemetry == 0) || !Throttle::isWithinTimespanMs(lastTelemetry, sendToMeshIntervalMs)) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = millis();
if (transmitHistory)
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_POWER_TELEMETRY);
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
(service->isToPhoneQueueEmpty())) {
// Just send to phone when it's not our time to send to mesh yet
-1
View File
@@ -54,7 +54,6 @@ class PowerTelemetryModule : private concurrency::OSThread,
bool firstTime = 1;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
uint32_t sensor_read_error_count = 0;
};
+5
View File
@@ -9,6 +9,7 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerMon.h"
#include "TransmitHistory.h"
#include "detect/LoRaRadioType.h"
#include "error.h"
#include "main.h"
@@ -245,6 +246,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
nodeDB->saveToDisk();
}
// Persist broadcast transmit times so throttle survives reboot
if (transmitHistory)
transmitHistory->saveToDisk();
#ifdef PIN_POWER_EN
digitalWrite(PIN_POWER_EN, LOW);
pinMode(PIN_POWER_EN, INPUT); // power off peripherals