Merge remote-tracking branch 'origin/master' into develop

This commit is contained in:
Ben Meadors
2026-05-07 11:45:04 -05:00
31 changed files with 133 additions and 105 deletions
+15 -18
View File
@@ -26,6 +26,8 @@ SOFTWARE.*/
#include "DebugConfiguration.h" #include "DebugConfiguration.h"
#include <memory>
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h" #include "platform/portduino/PortduinoGlue.h"
#endif #endif
@@ -119,27 +121,22 @@ bool Syslog::vlogf(uint16_t pri, const char *fmt, va_list args)
bool Syslog::vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) bool Syslog::vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args)
{ {
char *message; // First measure the formatted length using a copy of args; passing args directly
size_t initialLen; // to vsnprintf consumes it, and reusing a consumed va_list is undefined behavior.
size_t len; va_list args_measure;
bool result; va_copy(args_measure, args);
int needed = vsnprintf(nullptr, 0, fmt, args_measure);
va_end(args_measure);
initialLen = strlen(fmt); if (needed < 0)
return false; // encoding error
message = new char[initialLen + 1]; auto message = std::unique_ptr<char[]>(new char[static_cast<size_t>(needed) + 1]);
int written = vsnprintf(message.get(), static_cast<size_t>(needed) + 1, fmt, args);
if (written < 0)
return false;
len = vsnprintf(message, initialLen + 1, fmt, args); return this->_sendLog(pri, appName, message.get());
if (len > initialLen) {
delete[] message;
message = new char[len + 1];
vsnprintf(message, len + 1, fmt, args);
}
result = this->_sendLog(pri, appName, message);
delete[] message;
return result;
} }
inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *message) inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *message)
+7
View File
@@ -14,6 +14,11 @@ Lock::Lock() : handle(xSemaphoreCreateBinary())
} }
} }
Lock::~Lock()
{
vSemaphoreDelete(handle);
}
void Lock::lock() void Lock::lock()
{ {
if (xSemaphoreTake(handle, portMAX_DELAY) == false) { if (xSemaphoreTake(handle, portMAX_DELAY) == false) {
@@ -30,6 +35,8 @@ void Lock::unlock()
#else #else
Lock::Lock() {} Lock::Lock() {}
Lock::~Lock() {}
void Lock::lock() {} void Lock::lock() {}
void Lock::unlock() {} void Lock::unlock() {}
+1
View File
@@ -12,6 +12,7 @@ class Lock
{ {
public: public:
Lock(); Lock();
~Lock();
Lock(const Lock &) = delete; Lock(const Lock &) = delete;
Lock &operator=(const Lock &) = delete; Lock &operator=(const Lock &) = delete;
+5 -2
View File
@@ -1025,10 +1025,13 @@ void GPS::up()
setPowerState(GPS_ACTIVE); setPowerState(GPS_ACTIVE);
} }
// We've got a GPS lock. Enter a low power state, potentially. // We've finished a GPS search cycle (lock or timeout). Enter a low power state, potentially.
void GPS::down() void GPS::down()
{ {
scheduling.informGotLock(); if (hasValidLocation)
scheduling.informGotLock();
else
scheduling.informSearchFailed();
uint32_t predictedSearchDuration = scheduling.predictedSearchDurationMs(); uint32_t predictedSearchDuration = scheduling.predictedSearchDurationMs();
uint32_t sleepTime = scheduling.msUntilNextSearch(); uint32_t sleepTime = scheduling.msUntilNextSearch();
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval); uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
+34 -2
View File
@@ -15,6 +15,19 @@ void GPSUpdateScheduling::informGotLock()
searchEndedMs = millis(); searchEndedMs = millis();
LOG_DEBUG("Took %us to get lock", (searchEndedMs - searchStartedMs) / 1000); LOG_DEBUG("Took %us to get lock", (searchEndedMs - searchStartedMs) / 1000);
updateLockTimePrediction(); updateLockTimePrediction();
consecutiveFailures = 0; // Drop back to fast cadence as soon as we acquire any fix
}
// Search finished without obtaining a fix. We still need to mark the end time so
// the next sleep is timed correctly, but we must not feed the timeout duration
// into predictedMsToGetLock — doing so poisons msUntilNextSearch() and causes
// down() to fall into GPS_IDLE, leaving the chip awake on subsequent indoor cycles.
void GPSUpdateScheduling::informSearchFailed()
{
searchEndedMs = millis();
consecutiveFailures++;
LOG_DEBUG("GPS search ended without fix after %us (consecutive failures: %u)", (searchEndedMs - searchStartedMs) / 1000,
consecutiveFailures);
} }
// Clear old lock-time prediction data. // Clear old lock-time prediction data.
@@ -25,6 +38,7 @@ void GPSUpdateScheduling::reset()
searchEndedMs = 0; searchEndedMs = 0;
searchCount = 0; searchCount = 0;
predictedMsToGetLock = 0; predictedMsToGetLock = 0;
consecutiveFailures = 0;
} }
// How many milliseconds before we should next search for GPS position // How many milliseconds before we should next search for GPS position
@@ -36,6 +50,20 @@ uint32_t GPSUpdateScheduling::msUntilNextSearch()
// Target interval (seconds), between GPS updates // Target interval (seconds), between GPS updates
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval, default_gps_update_interval); uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval, default_gps_update_interval);
// After a failed search, back off: indoors / no-sky environments will keep failing,
// so wake at most once per broadcast interval rather than once per gps_update_interval.
// Capped at 1 hour so a user-configured very-long broadcast interval still retries
// periodically (in case conditions change). Reset on any successful lock.
if (consecutiveFailures > 0) {
constexpr uint32_t failureRetryCapMs = 60UL * 60UL * 1000UL; // 1 hour cap
uint32_t failureSleepMs =
Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs);
if (failureSleepMs > failureRetryCapMs)
failureSleepMs = failureRetryCapMs;
if (updateInterval < failureSleepMs)
updateInterval = failureSleepMs;
}
// Check how long until we should start searching, to hopefully hit our target interval // Check how long until we should start searching, to hopefully hit our target interval
uint32_t dueAtMs = searchEndedMs + updateInterval; uint32_t dueAtMs = searchEndedMs + updateInterval;
uint32_t compensatedStart = dueAtMs - predictedMsToGetLock; uint32_t compensatedStart = dueAtMs - predictedMsToGetLock;
@@ -71,14 +99,18 @@ bool GPSUpdateScheduling::isUpdateDue()
bool GPSUpdateScheduling::searchedTooLong() bool GPSUpdateScheduling::searchedTooLong()
{ {
constexpr uint32_t oneMinuteMs = 60UL * 1000UL; constexpr uint32_t oneMinuteMs = 60UL * 1000UL;
constexpr uint32_t maxSearchClampMs = 15UL * oneMinuteMs; // Hard cap: 15 minutes is always too long constexpr uint32_t maxSearchClampMs = 15UL * oneMinuteMs; // Hard cap: 15 minutes is always too long
constexpr uint32_t postFailureSearchMs = 5UL * oneMinuteMs; // Tighter dwell once we know the environment is hostile
uint32_t elapsed = elapsedSearchMs(); uint32_t elapsed = elapsedSearchMs();
// Anything over 15 minutes is too long, regardless of the broadcast interval. // Anything over 15 minutes is too long, regardless of the broadcast interval.
// TODO: Make a smarter algorithm that backs off the search dwell time when not getting a lock.
if (elapsed > maxSearchClampMs) if (elapsed > maxSearchClampMs)
return true; return true;
// After a prior failed search, shorten the dwell
if (consecutiveFailures > 0 && elapsed > postFailureSearchMs)
return true;
uint32_t minimumOrConfiguredSecs = uint32_t minimumOrConfiguredSecs =
Default::getConfiguredOrMinimumValue(config.position.position_broadcast_secs, default_broadcast_interval_secs); Default::getConfiguredOrMinimumValue(config.position.position_broadcast_secs, default_broadcast_interval_secs);
uint32_t maxSearchMs = Default::getConfiguredOrDefaultMs(minimumOrConfiguredSecs, default_broadcast_interval_secs); uint32_t maxSearchMs = Default::getConfiguredOrDefaultMs(minimumOrConfiguredSecs, default_broadcast_interval_secs);
+3 -1
View File
@@ -8,7 +8,8 @@ class GPSUpdateScheduling
public: public:
// Marks the time of these events, for calculation use // Marks the time of these events, for calculation use
void informSearching(); void informSearching();
void informGotLock(); // Predicted lock-time is recalculated here void informGotLock(); // Predicted lock-time is recalculated here
void informSearchFailed(); // Search ended without a fix; prediction is left untouched
void reset(); // Reset the prediction - after GPS::disable() / GPS::enable() void reset(); // Reset the prediction - after GPS::disable() / GPS::enable()
bool isUpdateDue(); // Is it time to begin searching for a GPS position? bool isUpdateDue(); // Is it time to begin searching for a GPS position?
@@ -24,6 +25,7 @@ class GPSUpdateScheduling
uint32_t searchEndedMs = 0; uint32_t searchEndedMs = 0;
uint32_t searchCount = 0; uint32_t searchCount = 0;
uint32_t predictedMsToGetLock = 0; uint32_t predictedMsToGetLock = 0;
uint32_t consecutiveFailures = 0; // Count of search cycles that ended without a fix; reset on lock
const float weighting = 0.2; // Controls exponential smoothing of lock-times prediction. 20% weighting of "latest lock-time". const float weighting = 0.2; // Controls exponential smoothing of lock-times prediction. 20% weighting of "latest lock-time".
}; };
+3 -8
View File
@@ -13,7 +13,6 @@ RotaryEncoderImpl *rotaryEncoderImpl;
RotaryEncoderImpl::RotaryEncoderImpl() RotaryEncoderImpl::RotaryEncoderImpl()
{ {
rotary = nullptr;
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
isFirstInit = true; isFirstInit = true;
#endif #endif
@@ -23,11 +22,6 @@ RotaryEncoderImpl::~RotaryEncoderImpl()
{ {
LOG_DEBUG("RotaryEncoderImpl destructor"); LOG_DEBUG("RotaryEncoderImpl destructor");
detachRotaryEncoderInterrupts(); detachRotaryEncoderInterrupts();
if (rotary != nullptr) {
delete rotary;
rotary = nullptr;
}
} }
bool RotaryEncoderImpl::init() bool RotaryEncoderImpl::init()
@@ -43,8 +37,9 @@ bool RotaryEncoderImpl::init()
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press); eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
if (rotary == nullptr) { if (rotary == nullptr) {
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b, rotary.reset(new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a,
moduleConfig.canned_message.inputbroker_pin_press); moduleConfig.canned_message.inputbroker_pin_b,
moduleConfig.canned_message.inputbroker_pin_press));
} }
attachRotaryEncoderInterrupts(); attachRotaryEncoderInterrupts();
+2 -1
View File
@@ -5,6 +5,7 @@
#include "InputBroker.h" #include "InputBroker.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "mesh/NodeDB.h" #include "mesh/NodeDB.h"
#include <memory>
class RotaryEncoder; class RotaryEncoder;
@@ -28,7 +29,7 @@ class RotaryEncoderImpl final : public InputPollable
input_broker_event eventCcw = INPUT_BROKER_NONE; input_broker_event eventCcw = INPUT_BROKER_NONE;
input_broker_event eventPressed = INPUT_BROKER_NONE; input_broker_event eventPressed = INPUT_BROKER_NONE;
RotaryEncoder *rotary; std::unique_ptr<RotaryEncoder> rotary;
private: private:
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
+26 -41
View File
@@ -17,7 +17,7 @@
#define VERBOSE_PACKET_HISTORY 0 // Set to 1 for verbose logging, 2 for heavy debugging #define VERBOSE_PACKET_HISTORY 0 // Set to 1 for verbose logging, 2 for heavy debugging
#define PACKET_HISTORY_TRACE_AGING 1 // Set to 1 to enable logging of the age of re/used history slots #define PACKET_HISTORY_TRACE_AGING 1 // Set to 1 to enable logging of the age of re/used history slots
PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPackets(NULL) // Initialize members PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0) // Initialize members
{ {
if (size < 4 || size > PACKETHISTORY_MAX) { // Copilot suggested - makes sense if (size < 4 || size > PACKETHISTORY_MAX) { // Copilot suggested - makes sense
LOG_WARN("Packet History - Invalid size %d, using default %d", size, PACKETHISTORY_MAX); LOG_WARN("Packet History - Invalid size %d, using default %d", size, PACKETHISTORY_MAX);
@@ -34,7 +34,7 @@ PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPa
// Allocate memory for the recent packets array // Allocate memory for the recent packets array
recentPacketsCapacity = size; recentPacketsCapacity = size;
recentPackets = new PacketRecord[recentPacketsCapacity]; recentPackets.reset(new PacketRecord[recentPacketsCapacity]);
if (!recentPackets) { // No logging here, console/log probably uninitialized yet. if (!recentPackets) { // No logging here, console/log probably uninitialized yet.
LOG_ERROR("Packet History - Memory allocation failed for size=%d entries / %d Bytes", size, LOG_ERROR("Packet History - Memory allocation failed for size=%d entries / %d Bytes", size,
sizeof(PacketRecord) * recentPacketsCapacity); sizeof(PacketRecord) * recentPacketsCapacity);
@@ -43,33 +43,20 @@ PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPa
} }
// Initialize the recent packets array to zero // Initialize the recent packets array to zero
memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity); memset(recentPackets.get(), 0, sizeof(PacketRecord) * recentPacketsCapacity);
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH #if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
// Allocate hash index with load factor <= 0.5 for short probe chains // Allocate hash index with load factor <= 0.5 for short probe chains
hashCapacity = nextPowerOf2(recentPacketsCapacity * 2); hashCapacity = nextPowerOf2(recentPacketsCapacity * 2);
hashMask = hashCapacity - 1; hashMask = hashCapacity - 1;
hashIndex = new uint16_t[hashCapacity]; hashIndex.reset(new uint16_t[hashCapacity]);
if (!hashIndex) { if (!hashIndex) {
LOG_ERROR("Packet History - Hash index allocation failed for %d entries", hashCapacity); LOG_ERROR("Packet History - Hash index allocation failed for %d entries", hashCapacity);
hashCapacity = 0; hashCapacity = 0;
hashMask = 0; hashMask = 0;
return; return;
} }
memset(hashIndex, 0xFF, sizeof(uint16_t) * hashCapacity); // Fill with HASH_EMPTY (0xFFFF) memset(hashIndex.get(), 0xFF, sizeof(uint16_t) * hashCapacity); // Fill with HASH_EMPTY (0xFFFF)
#endif
}
PacketHistory::~PacketHistory()
{
recentPacketsCapacity = 0;
delete[] recentPackets;
recentPackets = NULL;
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
delete[] hashIndex;
hashIndex = NULL;
hashCapacity = 0;
hashMask = 0;
#endif #endif
} }
@@ -285,7 +272,7 @@ void PacketHistory::hashRebuild()
{ {
if (!hashIndex) if (!hashIndex)
return; return;
memset(hashIndex, 0xFF, sizeof(uint16_t) * hashCapacity); memset(hashIndex.get(), 0xFF, sizeof(uint16_t) * hashCapacity);
for (uint32_t i = 0; i < recentPacketsCapacity; i++) { for (uint32_t i = 0; i < recentPacketsCapacity; i++) {
if (recentPackets[i].rxTimeMsec != 0) if (recentPackets[i].rxTimeMsec != 0)
hashInsert(recentPackets[i].sender, recentPackets[i].id, (uint16_t)i); hashInsert(recentPackets[i].sender, recentPackets[i].id, (uint16_t)i);
@@ -332,7 +319,8 @@ PacketHistory::PacketRecord *PacketHistory::find(NodeNum sender, PacketId id)
#endif #endif
// Linear scan (sole path when hash excluded, fallback when hash allocation failed) // Linear scan (sole path when hash excluded, fallback when hash allocation failed)
for (PacketRecord *it = recentPackets; it < (recentPackets + recentPacketsCapacity); ++it) { PacketRecord *base = recentPackets.get();
for (PacketRecord *it = base; it < (base + recentPacketsCapacity); ++it) {
if (it->id == id && it->sender == sender) { if (it->id == id && it->sender == sender) {
return it; return it;
} }
@@ -346,39 +334,38 @@ void PacketHistory::insert(const PacketRecord &r)
{ {
uint32_t now_millis = millis(); // Should not jump with time changes uint32_t now_millis = millis(); // Should not jump with time changes
uint32_t OldtrxTimeMsec = 0; uint32_t OldtrxTimeMsec = 0;
PacketRecord *base = recentPackets.get();
PacketRecord *tu = NULL; // Will insert here. PacketRecord *tu = NULL; // Will insert here.
PacketRecord *it = NULL; PacketRecord *it = NULL;
// Find a free, matching or oldest used slot in the recentPackets array // Find a free, matching or oldest used slot in the recentPackets array
for (it = recentPackets; it < (recentPackets + recentPacketsCapacity); ++it) { for (it = base; it < (base + recentPacketsCapacity); ++it) {
if (it->id == 0 && it->sender == 0 /*&& rxTimeMsec == 0*/) { // Record is empty if (it->id == 0 && it->sender == 0 /*&& rxTimeMsec == 0*/) { // Record is empty
tu = it; // Remember the free slot tu = it; // Remember the free slot
#if VERBOSE_PACKET_HISTORY >= 2 #if VERBOSE_PACKET_HISTORY >= 2
LOG_DEBUG("Packet History - insert: Free slot@ %d/%d", tu - recentPackets, recentPacketsCapacity); LOG_DEBUG("Packet History - insert: Free slot@ %d/%d", tu - base, recentPacketsCapacity);
#endif #endif
// We have that, Exit the loop // We have that, Exit the loop
it = (recentPackets + recentPacketsCapacity); it = (base + recentPacketsCapacity);
} else if (it->id == r.id && it->sender == r.sender) { // Record matches the packet we want to insert } else if (it->id == r.id && it->sender == r.sender) { // Record matches the packet we want to insert
tu = it; // Remember the matching slot tu = it; // Remember the matching slot
OldtrxTimeMsec = now_millis - it->rxTimeMsec; // ..and save current entry's age OldtrxTimeMsec = now_millis - it->rxTimeMsec; // ..and save current entry's age
#if VERBOSE_PACKET_HISTORY >= 2 #if VERBOSE_PACKET_HISTORY >= 2
LOG_DEBUG("Packet History - insert: Matched slot@ %d/%d age=%d", tu - recentPackets, recentPacketsCapacity, LOG_DEBUG("Packet History - insert: Matched slot@ %d/%d age=%d", tu - base, recentPacketsCapacity, OldtrxTimeMsec);
OldtrxTimeMsec);
#endif #endif
// We have that, Exit the loop // We have that, Exit the loop
it = (recentPackets + recentPacketsCapacity); it = (base + recentPacketsCapacity);
} else { } else {
if (it->rxTimeMsec == 0) { if (it->rxTimeMsec == 0) {
LOG_WARN( LOG_WARN(
"Packet History - insert: Found packet s=%08x id=%08x with rxTimeMsec = 0, slot %d/%d. Should never happen!", "Packet History - insert: Found packet s=%08x id=%08x with rxTimeMsec = 0, slot %d/%d. Should never happen!",
it->sender, it->id, it - recentPackets, recentPacketsCapacity); it->sender, it->id, it - base, recentPacketsCapacity);
} }
if ((now_millis - it->rxTimeMsec) > OldtrxTimeMsec) { // 49.7 days rollover friendly if ((now_millis - it->rxTimeMsec) > OldtrxTimeMsec) { // 49.7 days rollover friendly
OldtrxTimeMsec = now_millis - it->rxTimeMsec; OldtrxTimeMsec = now_millis - it->rxTimeMsec;
tu = it; // remember the oldest packet tu = it; // remember the oldest packet
#if VERBOSE_PACKET_HISTORY >= 2 #if VERBOSE_PACKET_HISTORY >= 2
LOG_DEBUG("Packet History - insert: Older slot@ %d/%d age=%d", tu - recentPackets, recentPacketsCapacity, LOG_DEBUG("Packet History - insert: Older slot@ %d/%d age=%d", tu - base, recentPacketsCapacity, OldtrxTimeMsec);
OldtrxTimeMsec);
#endif #endif
} }
// keep looking for oldest till entire array is checked // keep looking for oldest till entire array is checked
@@ -393,13 +380,11 @@ void PacketHistory::insert(const PacketRecord &r)
#if VERBOSE_PACKET_HISTORY #if VERBOSE_PACKET_HISTORY
if (tu->id == 0 && tu->sender == 0) { if (tu->id == 0 && tu->sender == 0) {
LOG_DEBUG("Packet History - insert: slot@ %d/%d is NEW", tu - recentPackets, recentPacketsCapacity); LOG_DEBUG("Packet History - insert: slot@ %d/%d is NEW", tu - base, recentPacketsCapacity);
} else if (tu->id == r.id && tu->sender == r.sender) { } else if (tu->id == r.id && tu->sender == r.sender) {
LOG_DEBUG("Packet History - insert: slot@ %d/%d MATCHED, age=%d", tu - recentPackets, recentPacketsCapacity, LOG_DEBUG("Packet History - insert: slot@ %d/%d MATCHED, age=%d", tu - base, recentPacketsCapacity, OldtrxTimeMsec);
OldtrxTimeMsec);
} else { } else {
LOG_DEBUG("Packet History - insert: slot@ %d/%d REUSE OLDEST, age=%d", tu - recentPackets, recentPacketsCapacity, LOG_DEBUG("Packet History - insert: slot@ %d/%d REUSE OLDEST, age=%d", tu - base, recentPacketsCapacity, OldtrxTimeMsec);
OldtrxTimeMsec);
} }
#endif #endif
@@ -432,9 +417,9 @@ void PacketHistory::insert(const PacketRecord &r)
#endif #endif
#if VERBOSE_PACKET_HISTORY #if VERBOSE_PACKET_HISTORY
LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d BEFORE", LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d BEFORE", tu - base,
tu - recentPackets, recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1], recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1], tu->relayed_by[2],
tu->relayed_by[2], tu->rxTimeMsec); tu->rxTimeMsec);
#endif #endif
if (r.rxTimeMsec == 0) { if (r.rxTimeMsec == 0) {
@@ -454,16 +439,16 @@ void PacketHistory::insert(const PacketRecord &r)
*tu = r; // store the packet *tu = r; // store the packet
if (!isMatchingSlot) { if (!isMatchingSlot) {
hashInsert(r.sender, r.id, (uint16_t)(tu - recentPackets)); hashInsert(r.sender, r.id, (uint16_t)(tu - base));
} }
#else #else
*tu = r; // store the packet *tu = r; // store the packet
#endif #endif
#if VERBOSE_PACKET_HISTORY #if VERBOSE_PACKET_HISTORY
LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d AFTER", LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d AFTER", tu - base,
tu - recentPackets, recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1], recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1], tu->relayed_by[2],
tu->relayed_by[2], tu->rxTimeMsec); tu->rxTimeMsec);
#endif #endif
} }
+4 -6
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "NodeDB.h" #include "NodeDB.h"
#include <memory>
// Number of relayers we keep track of. Use 6 to be efficient with memory alignment of PacketRecord to 20 bytes // Number of relayers we keep track of. Use 6 to be efficient with memory alignment of PacketRecord to 20 bytes
#define NUM_RELAYERS 6 #define NUM_RELAYERS 6
@@ -26,7 +27,7 @@ class PacketHistory
uint32_t recentPacketsCapacity = uint32_t recentPacketsCapacity =
0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets. 0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
PacketRecord *recentPackets = NULL; // Simple and fixed in size. Debloat. std::unique_ptr<PacketRecord[]> recentPackets; // Simple and fixed in size. Debloat.
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH #if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
// Open-addressing hash table for O(1) lookup in find(), replacing the O(N) linear scan. // Open-addressing hash table for O(1) lookup in find(), replacing the O(N) linear scan.
@@ -34,7 +35,7 @@ class PacketHistory
// The load factor invariant holds permanently: hashCapacity = 2 * nextPowerOf2(recentPacketsCapacity), // The load factor invariant holds permanently: hashCapacity = 2 * nextPowerOf2(recentPacketsCapacity),
// and at most recentPacketsCapacity entries can ever be live (one per recentPackets[] slot). // and at most recentPacketsCapacity entries can ever be live (one per recentPackets[] slot).
static constexpr uint16_t HASH_EMPTY = 0xFFFF; static constexpr uint16_t HASH_EMPTY = 0xFFFF;
uint16_t *hashIndex = NULL; std::unique_ptr<uint16_t[]> hashIndex;
uint32_t hashCapacity = 0; // Always a power of 2 uint32_t hashCapacity = 0; // Always a power of 2
uint32_t hashMask = 0; // hashCapacity - 1, for fast modular indexing uint32_t hashMask = 0; // hashCapacity - 1, for fast modular indexing
@@ -64,11 +65,8 @@ class PacketHistory
uint8_t getOurTxHopLimit(const PacketRecord &r); uint8_t getOurTxHopLimit(const PacketRecord &r);
void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit); void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit);
PacketHistory(const PacketHistory &); // non construction-copyable
PacketHistory &operator=(const PacketHistory &); // non copyable
public: public:
explicit PacketHistory(uint32_t size = -1); // Constructor with size parameter, default is PACKETHISTORY_MAX explicit PacketHistory(uint32_t size = -1); // Constructor with size parameter, default is PACKETHISTORY_MAX
~PacketHistory();
/** /**
* Update recentBroadcasts and return true if we have already seen this packet * Update recentBroadcasts and return true if we have already seen this packet
@@ -100,5 +98,5 @@ class PacketHistory
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender); void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
// To check if the PacketHistory was initialized correctly by constructor // To check if the PacketHistory was initialized correctly by constructor
bool initOk(void) { return recentPackets != NULL && recentPacketsCapacity != 0; } bool initOk(void) { return recentPackets != nullptr && recentPacketsCapacity != 0; }
}; };
+2 -2
View File
@@ -80,9 +80,9 @@ void StoreForwardModule::populatePSRAM()
(this->records ? this->records : (((memGet.getFreePsram() / 4) * 3) / sizeof(PacketHistoryStruct))); (this->records ? this->records : (((memGet.getFreePsram() / 4) * 3) / sizeof(PacketHistoryStruct)));
this->records = numberOfPackets; this->records = numberOfPackets;
#if defined(ARCH_ESP32) #if defined(ARCH_ESP32)
this->packetHistory = static_cast<PacketHistoryStruct *>(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct))); this->packetHistory.reset(static_cast<PacketHistoryStruct *>(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct))));
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
this->packetHistory = static_cast<PacketHistoryStruct *>(calloc(numberOfPackets, sizeof(PacketHistoryStruct))); this->packetHistory.reset(static_cast<PacketHistoryStruct *>(calloc(numberOfPackets, sizeof(PacketHistoryStruct))));
#endif #endif
+8 -1
View File
@@ -7,6 +7,7 @@
#include "configuration.h" #include "configuration.h"
#include <Arduino.h> #include <Arduino.h>
#include <functional> #include <functional>
#include <memory>
#include <unordered_map> #include <unordered_map>
struct PacketHistoryStruct { struct PacketHistoryStruct {
@@ -29,11 +30,17 @@ struct PacketHistoryStruct {
class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<meshtastic_StoreAndForward> class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<meshtastic_StoreAndForward>
{ {
// packetHistory is allocated with ps_calloc / calloc, so it must be released with free(),
// not delete[].
struct CFreeDeleter {
void operator()(PacketHistoryStruct *p) const noexcept { free(p); }
};
bool busy = 0; bool busy = 0;
uint32_t busyTo = 0; uint32_t busyTo = 0;
char routerMessage[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; char routerMessage[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0};
PacketHistoryStruct *packetHistory = 0; std::unique_ptr<PacketHistoryStruct[], CFreeDeleter> packetHistory;
uint32_t packetHistoryTotalCount = 0; uint32_t packetHistoryTotalCount = 0;
uint32_t last_time = 0; uint32_t last_time = 0;
uint32_t requestCount = 0; uint32_t requestCount = 0;
+4 -4
View File
@@ -18,6 +18,7 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <memory>
#include <set> #include <set>
#include <stdexcept> #include <stdexcept>
#include <sys/ioctl.h> #include <sys/ioctl.h>
@@ -301,10 +302,9 @@ void portduinoSetup()
// Try CH341 // Try CH341
try { try {
std::cout << "autoconf: Looking for CH341 device..." << std::endl; std::cout << "autoconf: Looking for CH341 device..." << std::endl;
ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid, auto probe = std::unique_ptr<Ch341Hal>(new Ch341Hal(0, portduino_config.lora_usb_serial_num,
portduino_config.lora_usb_pid); portduino_config.lora_usb_vid, portduino_config.lora_usb_pid));
ch341Hal->getProductString(autoconf_product, 95); probe->getProductString(autoconf_product, 95);
delete ch341Hal;
std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl; std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl;
found_ch341 = true; found_ch341 = true;
+1 -1
View File
@@ -12,4 +12,4 @@ build_flags =
lib_deps = lib_deps =
${esp32_base.lib_deps} ${esp32_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
+1 -1
View File
@@ -71,7 +71,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@1.4.3 h2zero/NimBLE-Arduino@1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip https://github.com/dbinfrago/libpax/archive/df424747f9acb86ab07c5a206ded1e8e3650726a.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.3 lewisxhe/XPowersLib@0.3.3
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
+1 -1
View File
@@ -35,4 +35,4 @@ lib_ignore =
lib_deps = lib_deps =
${esp32_base.lib_deps} ${esp32_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
+1 -1
View File
@@ -11,7 +11,7 @@ build_flags =
lib_deps = lib_deps =
${esp32_base.lib_deps} ${esp32_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
# renovate: datasource=custom.pio depName=SX1509 IO Expander packageName=sparkfun/library/SX1509 IO Expander # renovate: datasource=custom.pio depName=SX1509 IO Expander packageName=sparkfun/library/SX1509 IO Expander
sparkfun/SX1509 IO Expander@3.0.6 sparkfun/SX1509 IO Expander@3.0.6
# renovate: datasource=custom.pio depName=APA102 packageName=pololu/library/APA102 # renovate: datasource=custom.pio depName=APA102 packageName=pololu/library/APA102
+1 -1
View File
@@ -133,6 +133,6 @@ build_flags =
lib_deps = ${heltec_v4_base.lib_deps} lib_deps = ${heltec_v4_base.lib_deps}
${device-ui_base.lib_deps} ${device-ui_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
# renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master # renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master
https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
+1 -1
View File
@@ -140,6 +140,6 @@ build_flags =
lib_deps = ${heltec_v4_r8_base.lib_deps} lib_deps = ${heltec_v4_r8_base.lib_deps}
${device-ui_base.lib_deps} ${device-ui_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
# renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master # renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master
https://github.com/Quency-D/chsc6x/archive/3b2b6cebf3177b3e2c33d06e07909b0b10159516.zip https://github.com/Quency-D/chsc6x/archive/3b2b6cebf3177b3e2c33d06e07909b0b10159516.zip
@@ -24,4 +24,4 @@ build_flags =
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
@@ -22,4 +22,4 @@ build_flags =
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
@@ -21,4 +21,4 @@ build_flags =
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
+1 -1
View File
@@ -55,7 +55,7 @@ lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
${device-ui_base.lib_deps} ${device-ui_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
[mesh_tab_xpt2046] [mesh_tab_xpt2046]
extends = mesh_tab_base extends = mesh_tab_base
@@ -25,7 +25,7 @@ build_flags =
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
build_src_filter = build_src_filter =
${esp32s3_base.build_src_filter} ${esp32s3_base.build_src_filter}
@@ -37,7 +37,7 @@ build_flags =
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
[env:rak_wismesh_tap_v2-tft] [env:rak_wismesh_tap_v2-tft]
extends = env:rak_wismesh_tap_v2 extends = env:rak_wismesh_tap_v2
+1 -1
View File
@@ -29,7 +29,7 @@ build_flags = ${esp32s3_base.build_flags}
lib_deps = ${esp32s3_base.lib_deps} lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
# renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix # renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix
https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM
+1 -1
View File
@@ -22,7 +22,7 @@ build_flags = ${esp32s3_base.build_flags}
lib_deps = ${esp32s3_base.lib_deps} lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.4 lewisxhe/SensorLib@0.3.4
# renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library # renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library
+1 -1
View File
@@ -33,7 +33,7 @@ build_flags = ${esp32s3_base.build_flags}
lib_deps = ${esp32s3_base.lib_deps} lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
# renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix # renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix
https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM
+2 -2
View File
@@ -22,7 +22,7 @@ build_flags =
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
[env:tracksenger-lcd] [env:tracksenger-lcd]
custom_meshtastic_hw_model = 48 custom_meshtastic_hw_model = 48
@@ -48,7 +48,7 @@ build_flags =
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
[env:tracksenger-oled] [env:tracksenger-oled]
custom_meshtastic_hw_model = 48 custom_meshtastic_hw_model = 48
+1 -1
View File
@@ -37,7 +37,7 @@ build_src_filter =
lib_deps = ${esp32s3_base.lib_deps} lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
# TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0
https://gitlab.com/hamishcunningham/unphonelibrary/-/archive/meshtastic/unphonelibrary-meshtastic.zip https://gitlab.com/hamishcunningham/unphonelibrary/-/archive/meshtastic/unphonelibrary-meshtastic.zip
+1 -1
View File
@@ -27,7 +27,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0 rweather/Crypto@0.4.0
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19 lovyan03/LovyanGFX@1.2.21
; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main ; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library