Merge pull request #7862 from meshtastic/master
Some checks are pending
CI / setup (check) (push) Waiting to run
CI / setup (esp32) (push) Waiting to run
CI / setup (esp32c3) (push) Waiting to run
CI / setup (esp32c6) (push) Waiting to run
CI / setup (esp32s3) (push) Waiting to run
CI / setup (nrf52840) (push) Waiting to run
CI / setup (rp2040) (push) Waiting to run
CI / setup (rp2350) (push) Waiting to run
CI / setup (stm32) (push) Waiting to run
CI / version (push) Waiting to run
CI / check (push) Blocked by required conditions
CI / build-esp32 (push) Blocked by required conditions
CI / build-esp32s3 (push) Blocked by required conditions
CI / build-esp32c3 (push) Blocked by required conditions
CI / build-esp32c6 (push) Blocked by required conditions
CI / build-nrf52840 (push) Blocked by required conditions
CI / build-rp2040 (push) Blocked by required conditions
CI / build-rp2350 (push) Blocked by required conditions
CI / build-stm32 (push) Blocked by required conditions
CI / build-debian-src (push) Waiting to run
CI / package-pio-deps-native-tft (push) Waiting to run
CI / test-native (push) Waiting to run
CI / docker-deb-amd64 (push) Waiting to run
CI / docker-deb-amd64-tft (push) Waiting to run
CI / docker-alp-amd64 (push) Waiting to run
CI / docker-alp-amd64-tft (push) Waiting to run
CI / docker-deb-arm64 (push) Waiting to run
CI / docker-deb-armv7 (push) Waiting to run
CI / gather-artifacts (esp32) (push) Blocked by required conditions
CI / gather-artifacts (esp32c3) (push) Blocked by required conditions
CI / gather-artifacts (esp32c6) (push) Blocked by required conditions
CI / gather-artifacts (esp32s3) (push) Blocked by required conditions
CI / gather-artifacts (nrf52840) (push) Blocked by required conditions
CI / gather-artifacts (rp2040) (push) Blocked by required conditions
CI / gather-artifacts (rp2350) (push) Blocked by required conditions
CI / gather-artifacts (stm32) (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions
CI / release-firmware (esp32) (push) Blocked by required conditions
CI / release-firmware (esp32c3) (push) Blocked by required conditions
CI / release-firmware (esp32c6) (push) Blocked by required conditions
CI / release-firmware (esp32s3) (push) Blocked by required conditions
CI / release-firmware (nrf52840) (push) Blocked by required conditions
CI / release-firmware (rp2040) (push) Blocked by required conditions
CI / release-firmware (rp2350) (push) Blocked by required conditions
CI / release-firmware (stm32) (push) Blocked by required conditions
CI / publish-firmware (push) Blocked by required conditions

Backmerge from Master into develop
This commit is contained in:
Tom Fifield 2025-09-08 11:23:22 +10:00 committed by GitHub
commit 227d0fa7dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 251 additions and 94 deletions

View File

@ -23,7 +23,7 @@ runs:
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: 3.x python-version: 3.x
cache: pip cache: pip

View File

@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-python@v5 - uses: actions/setup-python@v6
with: with:
python-version: 3.x python-version: 3.x
cache: pip cache: pip
@ -374,7 +374,7 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: 3.x python-version: 3.x
@ -443,7 +443,7 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: 3.x python-version: 3.x
@ -498,7 +498,7 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: 3.x python-version: 3.x

View File

@ -31,7 +31,7 @@ jobs:
repository: ${{github.event.pull_request.head.repo.full_name}} repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: 3.x python-version: 3.x

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Check for PR labels - name: Check for PR labels
uses: actions/github-script@v7 uses: actions/github-script@v8
with: with:
script: | script: |
const labels = context.payload.pull_request.labels.map(label => label.name); const labels = context.payload.pull_request.labels.map(label => label.name);

View File

@ -177,7 +177,7 @@ jobs:
- name: Comment test results on PR - name: Comment test results on PR
if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped' if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped'
uses: actions/github-script@v7 uses: actions/github-script@v8
with: with:
script: | script: |
const fs = require('fs'); const fs = require('fs');

View File

@ -63,7 +63,7 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: 3.x python-version: 3.x

View File

@ -17,7 +17,7 @@ jobs:
steps: steps:
- name: Stale PR+Issues - name: Stale PR+Issues
uses: actions/stale@v9.1.0 uses: actions/stale@v10.0.0
with: with:
days-before-stale: 45 days-before-stale: 45
exempt-issue-labels: pinned,3.0 exempt-issue-labels: pinned,3.0

View File

@ -47,7 +47,7 @@ jobs:
pio upgrade pio upgrade
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v5
with: with:
node-version: 22 node-version: 22

View File

@ -39,7 +39,7 @@ jobs:
git push git push
- name: Comment on PR - name: Comment on PR
uses: actions/github-script@v7 uses: actions/github-script@v8
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
script: | script: |

View File

@ -9,12 +9,12 @@ plugins:
lint: lint:
enabled: enabled:
- checkov@3.2.469 - checkov@3.2.469
- renovate@41.93.2 - renovate@41.94.0
- prettier@3.6.2 - prettier@3.6.2
- trufflehog@3.90.5 - trufflehog@3.90.5
- yamllint@1.37.1 - yamllint@1.37.1
- bandit@1.8.6 - bandit@1.8.6
- trivy@0.65.0 - trivy@0.66.0
- taplo@0.10.0 - taplo@0.10.0
- ruff@0.12.11 - ruff@0.12.11
- isort@6.0.1 - isort@6.0.1
@ -23,7 +23,7 @@ lint:
- svgo@4.0.0 - svgo@4.0.0
- actionlint@1.7.7 - actionlint@1.7.7
- flake8@7.3.0 - flake8@7.3.0
- hadolint@2.12.1-beta - hadolint@2.13.1
- shfmt@3.6.0 - shfmt@3.6.0
- shellcheck@0.11.0 - shellcheck@0.11.0
- black@25.1.0 - black@25.1.0

View File

@ -119,11 +119,10 @@ IF NOT "__%PYTHON%__"=="____" (
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..." CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
!ESPTOOL_CMD! >nul 2>&1 !ESPTOOL_CMD! >nul 2>&1
IF %ERRORLEVEL% GEQ 2 ( IF %ERRORLEVEL% EQU 9009 (
@REM esptool exits with code 1 if help is displayed. @REM 9009 = command not found on Windows
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!" CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
EXIT /B 1 EXIT /B 1
GOTO eof
) )
IF %DEBUG% EQU 1 ( IF %DEBUG% EQU 1 (
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps." CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."

View File

@ -5,6 +5,10 @@ BPS_RESET=false
TFT_BUILD=false TFT_BUILD=false
MCU="" MCU=""
# Constants
RESET_BAUD=1200
FIRMWARE_OFFSET=0x00
# Variant groups # Variant groups
BIGDB_8MB=( BIGDB_8MB=(
"picomputer-s3" "picomputer-s3"
@ -121,7 +125,7 @@ while [ $# -gt 0 ]; do
done done
if [[ $BPS_RESET == true ]]; then if [[ $BPS_RESET == true ]]; then
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
exit 0 exit 0
fi fi
@ -202,7 +206,7 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
echo "Trying to flash ${FILENAME}, but first erasing and writing system information" echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
$ESPTOOL_CMD erase-flash $ESPTOOL_CMD erase-flash
$ESPTOOL_CMD write-flash 0x00 "${FILENAME}" $ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}" echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
$ESPTOOL_CMD write-flash $OTA_OFFSET "${OTAFILE}" $ESPTOOL_CMD write-flash $OTA_OFFSET "${OTAFILE}"
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}" echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"

View File

@ -6,6 +6,8 @@ SET "SCRIPT_NAME=%~nx0"
SET "DEBUG=0" SET "DEBUG=0"
SET "PYTHON=" SET "PYTHON="
SET "ESPTOOL_BAUD=115200" SET "ESPTOOL_BAUD=115200"
SET "RESET_BAUD=1200"
SET "UPDATE_OFFSET=0x10000"
SET "ESPTOOL_CMD=" SET "ESPTOOL_CMD="
SET "LOGCOUNTER=0" SET "LOGCOUNTER=0"
SET "CHANGE_MODE=0" SET "CHANGE_MODE=0"
@ -85,14 +87,13 @@ IF "!FILENAME:update=!"=="!FILENAME!" (
) )
:skip-filename :skip-filename
SET "ESPTOOL_BAUD=1200"
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..." CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
IF NOT "__%PYTHON%__"=="____" ( IF NOT "__%PYTHON%__"=="____" (
SET "ESPTOOL_CMD=!PYTHON! -m esptool" SET "ESPTOOL_CMD=""!PYTHON!"" -m esptool"
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied." CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
) ELSE ( ) ELSE (
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool... CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool..."
WHERE esptool >nul 2>&1 WHERE esptool >nul 2>&1
IF %ERRORLEVEL% EQU 0 ( IF %ERRORLEVEL% EQU 0 (
@REM WHERE exits with code 0 if esptool is found. @REM WHERE exits with code 0 if esptool is found.
@ -105,11 +106,11 @@ IF NOT "__%PYTHON%__"=="____" (
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..." CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
!ESPTOOL_CMD! >nul 2>&1 !ESPTOOL_CMD! >nul 2>&1
IF %ERRORLEVEL% GEQ 2 ( CALL :LOG_MESSAGE DEBUG "esptool exit code: %ERRORLEVEL%"
@REM esptool exits with code 1 if help is displayed. IF %ERRORLEVEL% EQU 9009 (
@REM 9009 = command not found on Windows
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!" CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
EXIT /B 1 EXIT /B 1
GOTO eof
) )
IF %DEBUG% EQU 1 ( IF %DEBUG% EQU 1 (
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps." CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
@ -127,13 +128,13 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
IF %CHANGE_MODE% EQU 1 ( IF %CHANGE_MODE% EQU 1 (
@REM Attempt to change mode via 1200bps Reset. @REM Attempt to change mode via 1200bps Reset.
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status CALL :RUN_ESPTOOL !RESET_BAUD! --after no_reset read_flash_status
GOTO eof GOTO eof
) )
@REM Flashing operations. @REM Flashing operations.
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..." CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET !UPDATE_OFFSET!..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash 0x10000 "!FILENAME!" || GOTO eof CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !UPDATE_OFFSET! "!FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Script complete!." CALL :LOG_MESSAGE INFO "Script complete!."

View File

@ -3,6 +3,11 @@
PYTHON=${PYTHON:-$(which python3 python|head -n 1)} PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
CHANGE_MODE=false CHANGE_MODE=false
# Constants
FLASH_BAUD=115200
RESET_BAUD=1200
UPDATE_OFFSET=0x10000
# Determine the correct esptool command to use # Determine the correct esptool command to use
if "$PYTHON" -m esptool version >/dev/null 2>&1; then if "$PYTHON" -m esptool version >/dev/null 2>&1; then
ESPTOOL_CMD="$PYTHON -m esptool" ESPTOOL_CMD="$PYTHON -m esptool"
@ -64,7 +69,7 @@ done
shift "$((OPTIND-1))" shift "$((OPTIND-1))"
if [ "$CHANGE_MODE" = true ]; then if [ "$CHANGE_MODE" = true ]; then
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
exit 0 exit 0
fi fi
@ -75,7 +80,7 @@ fi
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
echo "Trying to flash update ${FILENAME}" echo "Trying to flash update ${FILENAME}"
$ESPTOOL_CMD --baud 115200 write-flash 0x10000 "${FILENAME}" $ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
else else
show_help show_help
echo "Invalid file: ${FILENAME}" echo "Invalid file: ${FILENAME}"

View File

@ -833,16 +833,25 @@ void Power::readPowerStatus()
newStatus.notifyObservers(&powerStatus2); newStatus.notifyObservers(&powerStatus2);
#ifdef DEBUG_HEAP #ifdef DEBUG_HEAP
if (lastheap != memGet.getFreeHeap()) { if (lastheap != memGet.getFreeHeap()) {
std::string threadlist = "Threads running:"; // Use stack-allocated buffer to avoid heap allocations in monitoring code
char threadlist[256] = "Threads running:";
int threadlistLen = strlen(threadlist);
int running = 0; int running = 0;
for (int i = 0; i < MAX_THREADS; i++) { for (int i = 0; i < MAX_THREADS; i++) {
auto thread = concurrency::mainController.get(i); auto thread = concurrency::mainController.get(i);
if ((thread != nullptr) && (thread->enabled)) { if ((thread != nullptr) && (thread->enabled)) {
threadlist += vformat(" %s", thread->ThreadName.c_str()); // Use snprintf to safely append to stack buffer without heap allocation
int remaining = sizeof(threadlist) - threadlistLen - 1;
if (remaining > 0) {
int written = snprintf(threadlist + threadlistLen, remaining, " %s", thread->ThreadName.c_str());
if (written > 0 && written < remaining) {
threadlistLen += written;
}
}
running++; running++;
} }
} }
LOG_DEBUG(threadlist.c_str()); LOG_DEBUG(threadlist);
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(), LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false)); memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
lastheap = memGet.getFreeHeap(); lastheap = memGet.getFreeHeap();
@ -856,15 +865,19 @@ void Power::readPowerStatus()
sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]); sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]);
auto newHeap = memGet.getFreeHeap(); auto newHeap = memGet.getFreeHeap();
std::string heapTopic = // Use stack-allocated buffers to avoid heap allocations in monitoring code
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac); char heapTopic[128];
std::string heapString = std::to_string(newHeap); snprintf(heapTopic, sizeof(heapTopic), "%s/2/heap/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false); char heapString[16];
snprintf(heapString, sizeof(heapString), "%u", newHeap);
mqtt->pubSub.publish(heapTopic, heapString, false);
auto wifiRSSI = WiFi.RSSI(); auto wifiRSSI = WiFi.RSSI();
std::string wifiTopic = char wifiTopic[128];
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac); snprintf(wifiTopic, sizeof(wifiTopic), "%s/2/wifi/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
std::string wifiString = std::to_string(wifiRSSI); char wifiString[16];
mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false); snprintf(wifiString, sizeof(wifiString), "%d", wifiRSSI);
mqtt->pubSub.publish(wifiTopic, wifiString, false);
} }
#endif #endif

View File

@ -1128,6 +1128,15 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
#endif #endif
} }
TFTDisplay::~TFTDisplay()
{
// Clean up allocated line pixel buffer to prevent memory leak
if (linePixelBuffer != nullptr) {
free(linePixelBuffer);
linePixelBuffer = nullptr;
}
}
// Write the buffer to the display memory // Write the buffer to the display memory
void TFTDisplay::display(bool fromBlank) void TFTDisplay::display(bool fromBlank)
{ {

View File

@ -20,6 +20,9 @@ class TFTDisplay : public OLEDDisplay
*/ */
TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C); TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C);
// Destructor to clean up allocated memory
~TFTDisplay();
// Write the buffer to the display memory // Write the buffer to the display memory
virtual void display() override { display(false); }; virtual void display() override { display(false); };
virtual void display(bool fromBlank); virtual void display(bool fromBlank);

View File

@ -70,7 +70,7 @@ int32_t RotaryEncoderImpl::runOnce()
this->notifyObservers(&e); this->notifyObservers(&e);
} }
return 20; return 10;
} }
#endif #endif

View File

@ -74,10 +74,11 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
if (p->from != 0) { if (p->from != 0) {
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from); meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
if (origTx) { if (origTx) {
// Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from // Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly
// the destination // from the destination
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) || if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
(wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) { (p->hop_start != 0 && p->hop_start == p->hop_limit &&
wasSoleRelayer(ourRelayID, p->decoded.request_id, p->to))) {
if (origTx->next_hop != p->relay_node) { // Not already set if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node); LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
origTx->next_hop = p->relay_node; origTx->next_hop = p->relay_node;

View File

@ -294,7 +294,7 @@ void PacketHistory::insert(const PacketRecord &r)
/* Check if a certain node was a relayer of a packet in the history given an ID and sender /* Check if a certain node was a relayer of a packet in the history given an ID and sender
* @return true if node was indeed a relayer, false if not */ * @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender) bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole)
{ {
if (!initOk()) { if (!initOk()) {
LOG_ERROR("PacketHistory - wasRelayer: NOT INITIALIZED!"); LOG_ERROR("PacketHistory - wasRelayer: NOT INITIALIZED!");
@ -322,27 +322,42 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N
found->sender, found->id, found->next_hop, millis() - found->rxTimeMsec, found->relayed_by[0], found->relayed_by[1], found->sender, found->id, found->next_hop, millis() - found->rxTimeMsec, found->relayed_by[0], found->relayed_by[1],
found->relayed_by[2], relayer); found->relayed_by[2], relayer);
#endif #endif
return wasRelayer(relayer, *found); return wasRelayer(relayer, *found, wasSole);
} }
/* Check if a certain node was a relayer of a packet in the history given iterator /* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not */ * @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r) bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole)
{ {
for (uint8_t i = 0; i < NUM_RELAYERS; i++) { bool found = false;
bool other_present = false;
for (uint8_t i = 0; i < NUM_RELAYERS; ++i) {
if (r.relayed_by[i] == relayer) { if (r.relayed_by[i] == relayer) {
#if VERBOSE_PACKET_HISTORY found = true;
LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? YES", r.sender, r.id, } else if (r.relayed_by[i] != 0) {
r.relayed_by[0], r.relayed_by[1], r.relayed_by[2], relayer); other_present = true;
#endif
return true;
} }
} }
if (wasSole) {
*wasSole = (found && !other_present);
}
#if VERBOSE_PACKET_HISTORY #if VERBOSE_PACKET_HISTORY
LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? NO", r.sender, r.id, r.relayed_by[0], LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? NO", r.sender, r.id, r.relayed_by[0],
r.relayed_by[1], r.relayed_by[2], relayer); r.relayed_by[1], r.relayed_by[2], relayer);
#endif #endif
return false;
return found;
}
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
bool PacketHistory::wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
bool wasSole = false;
wasRelayer(relayer, id, sender, &wasSole);
return wasSole;
} }
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender // Remove a relayer from the list of relayers of a packet in the history given an ID and sender

View File

@ -34,8 +34,9 @@ class PacketHistory
void insert(const PacketRecord &r); // Insert or replace a packet record in the history void insert(const PacketRecord &r); // Insert or replace a packet record in the history
/* Check if a certain node was a relayer of a packet in the history given iterator /* Check if a certain node was a relayer of a packet in the history given iterator
* If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
* @return true if node was indeed a relayer, false if not */ * @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const PacketRecord &r); bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr);
PacketHistory(const PacketHistory &); // non construction-copyable PacketHistory(const PacketHistory &); // non construction-copyable
PacketHistory &operator=(const PacketHistory &); // non copyable PacketHistory &operator=(const PacketHistory &); // non copyable
@ -54,8 +55,12 @@ class PacketHistory
bool *weWereNextHop = nullptr); bool *weWereNextHop = nullptr);
/* Check if a certain node was a relayer of a packet in the history given an ID and sender /* Check if a certain node was a relayer of a packet in the history given an ID and sender
* If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
* @return true if node was indeed a relayer, false if not */ * @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender); bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole = nullptr);
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
bool wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender // Remove a relayer from the list of relayers of a packet in the history given an ID and sender
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);

View File

@ -64,7 +64,12 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
in areas not already covered by other routers, or to bridge around problematic terrain, in areas not already covered by other routers, or to bridge around problematic terrain,
but should not be given priority over other routers in order to avoid unnecessaraily but should not be given priority over other routers in order to avoid unnecessaraily
consuming hops. */ consuming hops. */
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11 meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11,
/* Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node. */
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE = 12
} meshtastic_Config_DeviceConfig_Role; } meshtastic_Config_DeviceConfig_Role;
/* Defines the device's behavior for how messages are rebroadcast */ /* Defines the device's behavior for how messages are rebroadcast */
@ -646,8 +651,8 @@ extern "C" {
/* Helper constants for enums */ /* Helper constants for enums */
#define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT #define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT
#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_ROUTER_LATE #define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_CLIENT_BASE
#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_ROUTER_LATE+1)) #define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_CLIENT_BASE+1))
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL #define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY #define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY

View File

@ -272,6 +272,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_HELTEC_MESH_SOLAR = 108, meshtastic_HardwareModel_HELTEC_MESH_SOLAR = 108,
/* Lilygo T-Echo Lite */ /* Lilygo T-Echo Lite */
meshtastic_HardwareModel_T_ECHO_LITE = 109, meshtastic_HardwareModel_T_ECHO_LITE = 109,
/* New Heltec LoRA32 with ESP32-S3 CPU */
meshtastic_HardwareModel_HELTEC_V4 = 110,
/* ------------------------------------------------------------------------------------------------------------------------------------------ /* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */ ------------------------------------------------------------------------------------------------------------------------------------------ */

View File

@ -342,6 +342,11 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
res->print(value->Stringify().c_str()); res->print(value->Stringify().c_str());
delete value; delete value;
// Clean up the fileList to prevent memory leak
for (auto *val : fileList) {
delete val;
}
} }
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
@ -610,33 +615,38 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
res->println("<pre>"); res->println("<pre>");
} }
// Helper lambda to create JSON array and clean up memory properly
auto createJSONArrayFromLog = [](uint32_t *logArray, int count) -> JSONValue * {
JSONArray tempArray;
for (int i = 0; i < count; i++) {
tempArray.push_back(new JSONValue((int)logArray[i]));
}
JSONValue *result = new JSONValue(tempArray);
// Clean up original array to prevent memory leak
for (auto *val : tempArray) {
delete val;
}
return result;
};
// data->airtime->tx_log // data->airtime->tx_log
JSONArray txLogValues;
uint32_t *logArray; uint32_t *logArray;
logArray = airTime->airtimeReport(TX_LOG); logArray = airTime->airtimeReport(TX_LOG);
for (int i = 0; i < airTime->getPeriodsToLog(); i++) { JSONValue *txLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
txLogValues.push_back(new JSONValue((int)logArray[i]));
}
// data->airtime->rx_log // data->airtime->rx_log
JSONArray rxLogValues;
logArray = airTime->airtimeReport(RX_LOG); logArray = airTime->airtimeReport(RX_LOG);
for (int i = 0; i < airTime->getPeriodsToLog(); i++) { JSONValue *rxLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
rxLogValues.push_back(new JSONValue((int)logArray[i]));
}
// data->airtime->rx_all_log // data->airtime->rx_all_log
JSONArray rxAllLogValues;
logArray = airTime->airtimeReport(RX_ALL_LOG); logArray = airTime->airtimeReport(RX_ALL_LOG);
for (int i = 0; i < airTime->getPeriodsToLog(); i++) { JSONValue *rxAllLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
rxAllLogValues.push_back(new JSONValue((int)logArray[i]));
}
// data->airtime // data->airtime
JSONObject jsonObjAirtime; JSONObject jsonObjAirtime;
jsonObjAirtime["tx_log"] = new JSONValue(txLogValues); jsonObjAirtime["tx_log"] = txLogJsonValue;
jsonObjAirtime["rx_log"] = new JSONValue(rxLogValues); jsonObjAirtime["rx_log"] = rxLogJsonValue;
jsonObjAirtime["rx_all_log"] = new JSONValue(rxAllLogValues); jsonObjAirtime["rx_all_log"] = rxAllLogJsonValue;
jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent()); jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent());
jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent()); jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent());
jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot())); jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot()));
@ -765,6 +775,11 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); res->print(value->Stringify().c_str());
delete value; delete value;
// Clean up the nodesArray to prevent memory leak
for (auto *val : nodesArray) {
delete val;
}
} }
/* /*
@ -955,5 +970,10 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); res->print(value->Stringify().c_str());
delete value; delete value;
// Clean up the networkObjs to prevent memory leak
for (auto *val : networkObjs) {
delete val;
}
} }
#endif #endif

View File

@ -431,15 +431,6 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq
#endif #endif
#ifdef T_LORA_PAGER
LOG_DEBUG("power down XL9555 io");
io.digitalWrite(EXPANDS_DRV_EN, LOW);
io.digitalWrite(EXPANDS_AMP_EN, LOW);
io.digitalWrite(EXPANDS_KB_EN, LOW);
io.digitalWrite(EXPANDS_SD_EN, LOW);
io.digitalWrite(EXPANDS_GPIO_EN, LOW);
#endif
auto res = esp_sleep_enable_gpio_wakeup(); auto res = esp_sleep_enable_gpio_wakeup();
if (res != ESP_OK) { if (res != ESP_OK) {
LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res); LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res);
@ -480,14 +471,6 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
gpio_wakeup_disable((gpio_num_t)RF95_IRQ); gpio_wakeup_disable((gpio_num_t)RF95_IRQ);
} }
#endif #endif
#ifdef T_LORA_PAGER
LOG_DEBUG("power up XL9555 io");
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
io.digitalWrite(EXPANDS_KB_EN, HIGH);
io.digitalWrite(EXPANDS_SD_EN, HIGH);
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
#endif
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here

View File

@ -0,0 +1,12 @@
; 9M2IBR APRS LoRa Tracker: ESP32-WROOM-32 + EBYTE E22-400M30S
; https://shopee.com.my/product/1095224/21692283917
[env:9m2ibr_aprs_lora_tracker]
extends = esp32_base
board = esp32doit-devkit-v1
board_level = extra
build_flags =
${esp32_base.build_flags}
-D PRIVATE_HW
-D EBYTE_E22
-D EBYTE_E22_900M30S ; Assume Tx power curve is identical to 900M30S as there is no documentation
-I variants/esp32/diy/9m2ibr_aprs_lora_tracker

View File

@ -0,0 +1,74 @@
/*
9M2IBR APRS LoRa Tracker: ESP32-WROOM-32 + EBYTE E22-400M30S
https://shopee.com.my/product/1095224/21692283917
Originally developed for LoRa_APRS_iGate and GPIO is similar to
https://github.com/richonguzman/LoRa_APRS_iGate/blob/main/variants/ESP32_DIY_1W_LoRa_Mesh_V1_2/board_pinout.h
*/
// OLED (may be different controllers depending on screen size)
#define I2C_SDA 21
#define I2C_SCL 22
#define HAS_SCREEN 1 // Generates randomized BLE pin
// GNSS: Ai-Thinker GP-02 BDS/GNSS module
#define GPS_RX_PIN 16
#define GPS_TX_PIN 17
// Button
#define BUTTON_PIN 15 // Right side button - if not available, set device.button_gpio to 0 from Meshtastic client
// LEDs
#define LED_PIN 13 // Tx LED
#define USER_LED 2 // Rx LED
// Buzzer
#define PIN_BUZZER 33
// Battery sense
#define BATTERY_PIN 35
#define ADC_MULTIPLIER 2.01 // 100k + 100k, and add 1% tolerance
#define ADC_CHANNEL ADC1_GPIO35_CHANNEL
#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION
// SPI
#define LORA_SCK 18
#define LORA_MISO 19
#define LORA_MOSI 23
// LoRa
#define LORA_CS 5
#define LORA_DIO0 26 // a No connect on the SX1262/SX1268 module
#define LORA_RESET 27 // RST for SX1276, and for SX1262/SX1268
#define LORA_DIO1 12 // IRQ for SX1262/SX1268
#define LORA_DIO2 RADIOLIB_NC // BUSY for SX1262/SX1268
#define LORA_DIO3 // NC, but used as TCXO supply by E22 module
#define LORA_RXEN 32 // RF switch RX (and E22 LNA) control by ESP32 GPIO
#define LORA_TXEN 25 // RF switch TX (and E22 PA) control by ESP32 GPIO
// RX/TX for RFM95/SX127x
#define RF95_RXEN LORA_RXEN
#define RF95_TXEN LORA_TXEN
// #define RF95_TCXO <GPIO#>
// common pinouts for SX126X modules
#define SX126X_CS 5
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_DIO2
#define SX126X_RESET LORA_RESET
#define SX126X_RXEN LORA_RXEN
#define SX126X_TXEN LORA_TXEN
// Support alternative modules if soldered in place of E22
#define USE_RF95 // RFM95/SX127x
#define USE_SX1262
#define USE_SX1268
#define USE_LLCC68
// E22 TCXO support
#ifdef EBYTE_E22
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
#endif

View File

@ -1,6 +1,7 @@
[env:heltec-wireless-bridge] [env:heltec-wireless-bridge]
;build_type = debug ; to make it possible to step through our jtag debugger ;build_type = debug ; to make it possible to step through our jtag debugger
extends = esp32_base extends = esp32_base
board_level = extra
board = heltec_wifi_lora_32 board = heltec_wifi_lora_32
build_flags = build_flags =
${esp32_base.build_flags} ${esp32_base.build_flags}

View File

@ -1,5 +1,6 @@
[env:trackerd] [env:trackerd]
extends = esp32_base extends = esp32_base
board_level = extra
board = pico32 board = pico32
board_build.f_flash = 80000000L board_build.f_flash = 80000000L

View File

@ -26,7 +26,7 @@ lib_deps = ${esp32s3_base.lib_deps}
lewisxhe/SensorLib@0.3.1 lewisxhe/SensorLib@0.3.1
https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
https://github.com/mverch67/RotaryEncoder https://github.com/mverch67/RotaryEncoder/archive/25a59d5745a6645536f921427d80b08e78f886d4.zip
[env:tlora-pager-tft] [env:tlora-pager-tft]
board_level = extra board_level = extra

View File

@ -1,6 +1,7 @@
; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 ; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921
[env:gat562_mesh_trial_tracker] [env:gat562_mesh_trial_tracker]
extends = nrf52840_base extends = nrf52840_base
board_level = extra
board = gat562_mesh_trial_tracker board = gat562_mesh_trial_tracker
board_check = true board_check = true
build_flags = ${nrf52840_base.build_flags} build_flags = ${nrf52840_base.build_flags}

View File

@ -4,6 +4,7 @@
[env:meshlink] [env:meshlink]
extends = nrf52840_base extends = nrf52840_base
board = meshlink board = meshlink
board_level = extra
;board_check = true ;board_check = true
build_flags = ${nrf52840_base.build_flags} build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/meshlink -I variants/nrf52840/meshlink

View File

@ -4,6 +4,7 @@
[env:meshlink_eink] [env:meshlink_eink]
extends = nrf52840_base extends = nrf52840_base
board = meshlink board = meshlink
board_level = extra
;board_check = true ;board_check = true
build_flags = ${nrf52840_base.build_flags} build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/meshlink_eink -I variants/nrf52840/meshlink_eink

View File

@ -1,6 +1,7 @@
[env:catsniffer] [env:catsniffer]
extends = rp2040_base extends = rp2040_base
board = rpipico board = rpipico
board_level = extra
upload_protocol = picotool upload_protocol = picotool
build_flags = build_flags =
${rp2040_base.build_flags} ${rp2040_base.build_flags}