mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-25 04:00:30 +00:00
Compare commits
16 Commits
refactor-n
...
jp-bennett
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cfd6f4508 | ||
|
|
bf32f17f28 | ||
|
|
68250dc937 | ||
|
|
bcfe069997 | ||
|
|
4fc96bdf83 | ||
|
|
467c042bf7 | ||
|
|
cc4c41167c | ||
|
|
fff2bbf4a0 | ||
|
|
fba92229a6 | ||
|
|
ff0a4ea320 | ||
|
|
83b603827c | ||
|
|
ee80ec7b68 | ||
|
|
ec0dfb7337 | ||
|
|
817f3b9ec8 | ||
|
|
0726bb4b56 | ||
|
|
6b11991be0 |
8
.github/workflows/build_firmware.yml
vendored
8
.github/workflows/build_firmware.yml
vendored
@@ -56,16 +56,18 @@ jobs:
|
||||
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
|
||||
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
|
||||
|
||||
- name: Echo manifest from release/firmware-*.mt.json to job summary
|
||||
if: ${{ always() }}
|
||||
- name: Job summary
|
||||
env:
|
||||
PIO_ENV: ${{ inputs.pio_env }}
|
||||
run: |
|
||||
echo "## Manifest: \`$PIO_ENV\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## $PIO_ENV" >> $GITHUB_STEP_SUMMARY
|
||||
echo "<details><summary><strong>Manifest</strong></summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo '' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
cat release/firmware-*.mt.json >> $GITHUB_STEP_SUMMARY
|
||||
echo '' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
|
||||
17
.github/workflows/main_matrix.yml
vendored
17
.github/workflows/main_matrix.yml
vendored
@@ -77,16 +77,21 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
check: ${{ fromJson(needs.setup.outputs.check) }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
# Use 'arctastic' self-hosted runner pool when checking in the main repo
|
||||
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
- name: Check ${{ matrix.check.board }}
|
||||
run: bin/check-all.sh ${{ matrix.check.board }}
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
pio_platform: ${{ matrix.check.platform }}
|
||||
pio_env: ${{ matrix.check.board }}
|
||||
pio_target: check
|
||||
|
||||
build:
|
||||
needs: [setup, version]
|
||||
|
||||
2
.github/workflows/release_channels.yml
vendored
2
.github/workflows/release_channels.yml
vendored
@@ -102,7 +102,7 @@ jobs:
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
||||
|
||||
- name: Create Bumps pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
base: ${{ github.event.repository.default_branch }}
|
||||
branch: create-pull-request/bump-version
|
||||
|
||||
2
.github/workflows/update_protobufs.yml
vendored
2
.github/workflows/update_protobufs.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
./bin/regen-protos.sh
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
branch: create-pull-request/update-protobufs
|
||||
labels: submodules
|
||||
|
||||
@@ -9,24 +9,24 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.495
|
||||
- renovate@42.30.4
|
||||
- renovate@42.42.2
|
||||
- prettier@3.7.4
|
||||
- trufflehog@3.91.2
|
||||
- trufflehog@3.92.1
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.9.2
|
||||
- trivy@0.67.2
|
||||
- trivy@0.68.1
|
||||
- taplo@0.10.0
|
||||
- ruff@0.14.7
|
||||
- ruff@0.14.8
|
||||
- isort@7.0.0
|
||||
- markdownlint@0.46.0
|
||||
- oxipng@9.1.5
|
||||
- oxipng@10.0.0
|
||||
- svgo@4.0.0
|
||||
- actionlint@1.7.9
|
||||
- flake8@7.3.0
|
||||
- hadolint@2.14.0
|
||||
- shfmt@3.6.0
|
||||
- shellcheck@0.11.0
|
||||
- black@25.11.0
|
||||
- black@25.12.0
|
||||
- git-diff-check
|
||||
- gitleaks@8.30.0
|
||||
- clang-format@16.0.3
|
||||
|
||||
@@ -22,7 +22,7 @@ export APP_VERSION=$VERSION
|
||||
|
||||
basename=firmware-$1-$VERSION
|
||||
|
||||
pio run --environment $1 # -v
|
||||
pio run --environment $1 -t mtjson # -v
|
||||
|
||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||
|
||||
@@ -32,20 +32,10 @@ cp $BUILDDIR/$basename.factory.bin $OUTDIR/$basename.factory.bin
|
||||
echo "Copying ESP32 update bin file"
|
||||
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
|
||||
|
||||
echo "Building Filesystem for ESP32 targets"
|
||||
# If you want to build the webui, uncomment the following lines
|
||||
# pio run --environment $1 -t buildfs
|
||||
# cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
|
||||
# # Remove webserver files from the filesystem and rebuild
|
||||
# ls -l data/static # Diagnostic list of files
|
||||
# rm -rf data/static
|
||||
pio run --environment $1 -t buildfs --disable-auto-clean
|
||||
echo "Copying Filesystem for ESP32 targets"
|
||||
cp $BUILDDIR/littlefs-$1-$VERSION.bin $OUTDIR/littlefs-$1-$VERSION.bin
|
||||
cp bin/device-install.* $OUTDIR/
|
||||
cp bin/device-update.* $OUTDIR/
|
||||
|
||||
# Generate the manifest file
|
||||
echo "Generating Meshtastic manifest"
|
||||
TIMEFORMAT="Generated manifest in %E seconds"
|
||||
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
|
||||
echo "Copying manifest"
|
||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||
|
||||
@@ -22,7 +22,7 @@ export APP_VERSION=$VERSION
|
||||
|
||||
basename=firmware-$1-$VERSION
|
||||
|
||||
pio run --environment $1 # -v
|
||||
pio run --environment $1 -t mtjson # -v
|
||||
|
||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||
|
||||
@@ -47,8 +47,5 @@ if (echo $1 | grep -q "rak4631"); then
|
||||
cp $SRCHEX $OUTDIR/
|
||||
fi
|
||||
|
||||
# Generate the manifest file
|
||||
echo "Generating Meshtastic manifest"
|
||||
TIMEFORMAT="Generated manifest in %E seconds"
|
||||
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
|
||||
echo "Copying manifest"
|
||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||
|
||||
@@ -22,15 +22,12 @@ export APP_VERSION=$VERSION
|
||||
|
||||
basename=firmware-$1-$VERSION
|
||||
|
||||
pio run --environment $1 # -v
|
||||
pio run --environment $1 -t mtjson # -v
|
||||
|
||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||
|
||||
echo "Copying uf2 file"
|
||||
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
|
||||
|
||||
# Generate the manifest file
|
||||
echo "Generating Meshtastic manifest"
|
||||
TIMEFORMAT="Generated manifest in %E seconds"
|
||||
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
|
||||
echo "Copying manifest"
|
||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||
|
||||
@@ -22,15 +22,12 @@ export APP_VERSION=$VERSION
|
||||
|
||||
basename=firmware-$1-$VERSION
|
||||
|
||||
pio run --environment $1 # -v
|
||||
pio run --environment $1 -t mtjson # -v
|
||||
|
||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||
|
||||
echo "Copying STM32 bin file"
|
||||
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
|
||||
|
||||
# Generate the manifest file
|
||||
echo "Generating Meshtastic manifest"
|
||||
TIMEFORMAT="Generated manifest in %E seconds"
|
||||
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
|
||||
echo "Copying manifest"
|
||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||
|
||||
@@ -159,20 +159,22 @@ def load_boot_logo(source, target, env):
|
||||
|
||||
# Load the boot logo on TFT builds
|
||||
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
|
||||
env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo)
|
||||
env.AddPreAction(f"$BUILD_DIR/{lfsbin}", load_boot_logo)
|
||||
|
||||
# Rename (mv) littlefs.bin to include the PROGNAME
|
||||
# This ensures the littlefs.bin is named consistently with the firmware
|
||||
env.AddPostAction('$BUILD_DIR/littlefs.bin', env.VerboseAction(
|
||||
f'mv $BUILD_DIR/littlefs.bin $BUILD_DIR/{lfsbin}',
|
||||
f'Renaming littlefs.bin to {lfsbin}'
|
||||
))
|
||||
mtjson_deps = ["buildprog"]
|
||||
if platform.name == "espressif32":
|
||||
# Build littlefs image as part of mtjson target
|
||||
# Equivalent to `pio run -t buildfs`
|
||||
target_lfs = env.DataToBin(
|
||||
join("$BUILD_DIR", "${ESP32_FS_IMAGE_NAME}"), "$PROJECT_DATA_DIR"
|
||||
)
|
||||
mtjson_deps.append(target_lfs)
|
||||
|
||||
env.AddCustomTarget(
|
||||
name="mtjson",
|
||||
dependencies=None,
|
||||
dependencies=mtjson_deps,
|
||||
actions=[manifest_gather],
|
||||
title="Meshtastic Manifest",
|
||||
description="Generating Meshtastic manifest JSON + Checksums",
|
||||
always_build=True,
|
||||
always_build=False,
|
||||
)
|
||||
|
||||
@@ -11,6 +11,9 @@ else:
|
||||
prefsLoc = env["PROJECT_DIR"] + "/version.properties"
|
||||
verObj = readProps(prefsLoc)
|
||||
env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}")
|
||||
env.Replace(ESP32_FS_IMAGE_NAME=f"littlefs-{env.get('PIOENV')}-{verObj['long']}")
|
||||
|
||||
# Print the new program name for verification
|
||||
print(f"PROGNAME: {env.get('PROGNAME')}")
|
||||
if platform.name == "espressif32":
|
||||
print(f"ESP32_FS_IMAGE_NAME: {env.get('ESP32_FS_IMAGE_NAME')}")
|
||||
|
||||
@@ -10,6 +10,12 @@ Import("env")
|
||||
platform = env.PioPlatform()
|
||||
|
||||
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
|
||||
# IntelHex workaround, remove after fixed upstream
|
||||
# https://github.com/platformio/platform-espressif32/issues/1632
|
||||
try:
|
||||
import intelhex
|
||||
except ImportError:
|
||||
env.Execute("$PYTHONEXE -m pip install intelhex")
|
||||
import esptool
|
||||
|
||||
|
||||
|
||||
@@ -10,10 +10,7 @@
|
||||
*/
|
||||
#include "FSCommon.h"
|
||||
#include "SPILock.h"
|
||||
#include "SafeFile.h"
|
||||
#include "configuration.h"
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
// Software SPI is used by MUI so disable SD card here until it's also implemented
|
||||
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
||||
@@ -338,63 +335,4 @@ void setupSDCard()
|
||||
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
|
||||
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Load a protobuf from a file, return LoadFileResult */
|
||||
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct)
|
||||
{
|
||||
LoadFileResult state = LoadFileResult::OTHER_FAILURE;
|
||||
#ifdef FSCom
|
||||
concurrency::LockGuard g(spiLock);
|
||||
|
||||
auto f = FSCom.open(filename, FILE_O_READ);
|
||||
|
||||
if (f) {
|
||||
LOG_INFO("Load %s", filename);
|
||||
pb_istream_t stream = {&readcb, &f, protoSize};
|
||||
if (fields != &meshtastic_NodeDatabase_msg) // contains a vector object
|
||||
memset(dest_struct, 0, objSize);
|
||||
if (!pb_decode(&stream, fields, dest_struct)) {
|
||||
LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream));
|
||||
state = LoadFileResult::DECODE_FAILED;
|
||||
} else {
|
||||
LOG_INFO("Loaded %s successfully", filename);
|
||||
state = LoadFileResult::LOAD_SUCCESS;
|
||||
}
|
||||
f.close();
|
||||
} else {
|
||||
LOG_ERROR("Could not open / read %s", filename);
|
||||
}
|
||||
#else
|
||||
LOG_ERROR("ERROR: Filesystem not implemented");
|
||||
state = LoadFileResult::NO_FILESYSTEM;
|
||||
#endif
|
||||
return state;
|
||||
}
|
||||
|
||||
/** Save a protobuf from a file, return true for success */
|
||||
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool fullAtomic)
|
||||
{
|
||||
bool okay = false;
|
||||
#ifdef FSCom
|
||||
auto f = SafeFile(filename, fullAtomic);
|
||||
|
||||
LOG_INFO("Save %s", filename);
|
||||
pb_ostream_t stream = {&writecb, static_cast<Print *>(&f), protoSize};
|
||||
|
||||
if (!pb_encode(&stream, fields, dest_struct)) {
|
||||
LOG_ERROR("Error: can't encode protobuf %s", PB_GET_ERROR(&stream));
|
||||
} else {
|
||||
okay = true;
|
||||
}
|
||||
|
||||
bool writeSucceeded = f.close();
|
||||
|
||||
if (!okay || !writeSucceeded) {
|
||||
LOG_ERROR("Can't write prefs!");
|
||||
}
|
||||
#else
|
||||
LOG_ERROR("ERROR: Filesystem not implemented");
|
||||
#endif
|
||||
return okay;
|
||||
}
|
||||
@@ -3,19 +3,6 @@
|
||||
#include "configuration.h"
|
||||
#include <vector>
|
||||
|
||||
enum LoadFileResult {
|
||||
// Successfully opened the file
|
||||
LOAD_SUCCESS = 1,
|
||||
// File does not exist
|
||||
NOT_FOUND = 2,
|
||||
// Device does not have a filesystem
|
||||
NO_FILESYSTEM = 3,
|
||||
// File exists, but could not decode protobufs
|
||||
DECODE_FAILED = 4,
|
||||
// File exists, but open failed for some reason
|
||||
OTHER_FAILURE = 5
|
||||
};
|
||||
|
||||
// Cross platform filesystem API
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
@@ -68,8 +55,4 @@ bool renameFile(const char *pathFrom, const char *pathTo);
|
||||
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
|
||||
void listDir(const char *dirname, uint8_t levels, bool del = false);
|
||||
void rmDir(const char *dirname);
|
||||
void setupSDCard();
|
||||
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct);
|
||||
|
||||
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
||||
bool fullAtomic = true);
|
||||
void setupSDCard();
|
||||
@@ -1133,13 +1133,10 @@ int32_t GPS::runOnce()
|
||||
// if gps_update_interval is <=10s, GPS never goes off, so we treat that differently
|
||||
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
|
||||
|
||||
// 1. Got a time for the first time
|
||||
// Got a time for the first time
|
||||
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
||||
gotTime = true;
|
||||
}
|
||||
|
||||
// 2. Got a lock for the first time, or 3. Got a lock after turning back on
|
||||
// Got a lock for the first time, or Got a lock after turning back on
|
||||
bool gotLoc = lookForLocation();
|
||||
if (gotLoc) {
|
||||
#ifdef GPS_DEBUG
|
||||
@@ -1147,6 +1144,10 @@ int32_t GPS::runOnce()
|
||||
LOG_DEBUG("hasValidLocation RISING EDGE");
|
||||
}
|
||||
#endif
|
||||
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
||||
gotTime = true;
|
||||
}
|
||||
|
||||
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) {
|
||||
hasValidLocation = true;
|
||||
shouldPublish = true;
|
||||
|
||||
@@ -532,8 +532,10 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
||||
const int labelX = x;
|
||||
int barsOffset = (isHighResolution) ? 24 : 0;
|
||||
#ifdef USE_EINK
|
||||
#ifndef T_DECK_PRO
|
||||
barsOffset -= 12;
|
||||
#endif
|
||||
#endif
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
const int barX = x + 45 + barsOffset;
|
||||
#else
|
||||
@@ -574,7 +576,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
||||
#endif
|
||||
// Value string
|
||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
|
||||
display->drawString(SCREEN_WIDTH, getTextPositions(display)[line], combinedStr);
|
||||
};
|
||||
|
||||
// === Memory values ===
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "ClockRenderer.h"
|
||||
#include "FSCommon.h"
|
||||
#include "GPS.h"
|
||||
#include "MenuHandler.h"
|
||||
#include "MeshRadio.h"
|
||||
@@ -1790,7 +1789,7 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
|
||||
void menuHandler::saveUIConfig()
|
||||
{
|
||||
saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
|
||||
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
@@ -62,12 +62,22 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
||||
{
|
||||
// Fill a collection with pointers to each node in db
|
||||
std::vector<meshtastic_NodeInfoLite *> ordered;
|
||||
for (int i = 1; i < maxCards(); i++) {
|
||||
auto mn = nodeDB->getMeshNodeByIndex(i);
|
||||
for (auto mn = nodeDB->meshNodes->begin(); mn != nodeDB->meshNodes->end(); ++mn) {
|
||||
// Only copy if valid, and not our own node
|
||||
if (mn->num != 0 && mn->num != nodeDB->getNodeNum())
|
||||
ordered.push_back(&*mn);
|
||||
}
|
||||
|
||||
// Sort the collection by age
|
||||
std::sort(ordered.begin(), ordered.end(), [](meshtastic_NodeInfoLite *top, meshtastic_NodeInfoLite *bottom) -> bool {
|
||||
return (top->last_heard > bottom->last_heard);
|
||||
});
|
||||
|
||||
// Keep the most recent entries only
|
||||
// Just enough to fill the screen
|
||||
if (ordered.size() > maxCards())
|
||||
ordered.resize(maxCards());
|
||||
|
||||
// Create card info for these (stale) node observations
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
for (meshtastic_NodeInfoLite *node : ordered) {
|
||||
|
||||
@@ -53,9 +53,9 @@ void CannedMessageStore::load()
|
||||
|
||||
// Attempt to load the bulk canned message data from flash
|
||||
meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig;
|
||||
LoadFileResult result = loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size,
|
||||
sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg,
|
||||
&cannedMessageModuleConfig);
|
||||
LoadFileResult result = nodeDB->loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size,
|
||||
sizeof(meshtastic_CannedMessageModuleConfig),
|
||||
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
|
||||
|
||||
// Abort if nothing to load
|
||||
if (result != LoadFileResult::LOAD_SUCCESS || strlen(cannedMessageModuleConfig.messages) == 0)
|
||||
@@ -129,8 +129,8 @@ void CannedMessageStore::handleSet(const meshtastic_AdminMessage *request)
|
||||
#endif
|
||||
|
||||
// Write to flash
|
||||
saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, &meshtastic_CannedMessageModuleConfig_msg,
|
||||
&cannedMessageModuleConfig);
|
||||
nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
|
||||
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
|
||||
|
||||
// Reload from flash, to update the canned messages in RAM
|
||||
// (This is a lazy way to handle it)
|
||||
|
||||
@@ -225,4 +225,4 @@ class MeshModule
|
||||
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
|
||||
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
|
||||
*/
|
||||
void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
|
||||
void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "SPILock.h"
|
||||
#include "SafeFile.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "error.h"
|
||||
#include "main.h"
|
||||
@@ -313,7 +314,7 @@ NodeDB::NodeDB()
|
||||
LOG_DEBUG("Number of Device Reboots: %d", myNodeInfo.reboot_count);
|
||||
#endif
|
||||
|
||||
_resetRadioConfig(); // If bogus settings got saved, then fix them
|
||||
resetRadioConfig(); // If bogus settings got saved, then fix them
|
||||
// nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
|
||||
|
||||
// Uncomment below to always enable UDP broadcasts
|
||||
@@ -424,13 +425,13 @@ NodeDB::NodeDB()
|
||||
config.has_position = true;
|
||||
info->has_position = true;
|
||||
info->position = TypeConversions::ConvertToPositionLite(fixedGPS);
|
||||
nodeDB->_setLocalPosition(fixedGPS);
|
||||
nodeDB->setLocalPosition(fixedGPS);
|
||||
config.position.fixed_position = true;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
sortMeshDB();
|
||||
_saveToDisk(saveWhat);
|
||||
saveToDisk(saveWhat);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,7 +460,7 @@ bool isBroadcast(uint32_t dest)
|
||||
return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA;
|
||||
}
|
||||
|
||||
void NodeDB::_resetRadioConfig(bool is_fresh_install)
|
||||
void NodeDB::resetRadioConfig(bool is_fresh_install)
|
||||
{
|
||||
if (is_fresh_install) {
|
||||
radioGeneration++;
|
||||
@@ -479,7 +480,6 @@ void NodeDB::_resetRadioConfig(bool is_fresh_install)
|
||||
|
||||
bool NodeDB::factoryReset(bool eraseBleBonds)
|
||||
{
|
||||
FUNCTION_START("factoryReset");
|
||||
LOG_INFO("Perform factory reset!");
|
||||
// first, remove the "/prefs" (this removes most prefs)
|
||||
spiLock->lock();
|
||||
@@ -498,7 +498,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
|
||||
installDefaultModuleConfig();
|
||||
installDefaultChannels();
|
||||
// third, write everything to disk
|
||||
_saveToDisk();
|
||||
saveToDisk();
|
||||
if (eraseBleBonds) {
|
||||
LOG_INFO("Erase BLE bonds");
|
||||
#ifdef ARCH_ESP32
|
||||
@@ -513,7 +513,6 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
|
||||
Bluefruit.Central.clearBonds();
|
||||
#endif
|
||||
}
|
||||
FUNCTION_END;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -650,7 +649,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
|
||||
config.security.serial_enabled = true;
|
||||
config.security.admin_channel_enabled = false;
|
||||
_resetRadioConfig(true); // This also triggers NodeInfo/Position requests since we're fresh
|
||||
resetRadioConfig(true); // This also triggers NodeInfo/Position requests since we're fresh
|
||||
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
|
||||
|
||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
|
||||
@@ -747,7 +746,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
|
||||
#ifdef USERPREFS_CONFIG_DEVICE_ROLE
|
||||
// Apply role-specific defaults when role is set via user preferences
|
||||
_installRoleDefaults(config.device.role);
|
||||
installRoleDefaults(config.device.role);
|
||||
#endif
|
||||
|
||||
initConfigIntervals();
|
||||
@@ -806,11 +805,15 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.output_ms = 500;
|
||||
moduleConfig.external_notification.nag_timeout = 2;
|
||||
#endif
|
||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312)
|
||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE)
|
||||
// Default to RAK led pin 2 (blue)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output = PIN_LED2;
|
||||
#if defined(MUZI_BASE)
|
||||
moduleConfig.external_notification.active = false;
|
||||
#else
|
||||
moduleConfig.external_notification.active = true;
|
||||
#endif
|
||||
moduleConfig.external_notification.alert_message = true;
|
||||
moduleConfig.external_notification.output_ms = 1000;
|
||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||
@@ -905,7 +908,7 @@ void NodeDB::installDefaultModuleConfig()
|
||||
initModuleConfigIntervals();
|
||||
}
|
||||
|
||||
void NodeDB::_installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
|
||||
void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
|
||||
{
|
||||
if (role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
|
||||
initConfigIntervals();
|
||||
@@ -991,7 +994,6 @@ void NodeDB::installDefaultChannels()
|
||||
|
||||
void NodeDB::resetNodes(bool keepFavorites)
|
||||
{
|
||||
FUNCTION_START("resetNodes");
|
||||
if (!config.position.fixed_position)
|
||||
clearLocalPosition();
|
||||
numMeshNodes = 1;
|
||||
@@ -1015,12 +1017,10 @@ void NodeDB::resetNodes(bool keepFavorites)
|
||||
saveDeviceStateToDisk();
|
||||
if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
|
||||
neighborInfoModule->resetNeighbors();
|
||||
FUNCTION_END;
|
||||
}
|
||||
|
||||
void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||
{
|
||||
FUNCTION_START("removeNodeByNum");
|
||||
int newPos = 0, removed = 0;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (meshNodes->at(i).num != nodeNum)
|
||||
@@ -1033,17 +1033,16 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||
meshtastic_NodeInfoLite());
|
||||
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
||||
saveNodeDatabaseToDisk();
|
||||
FUNCTION_END;
|
||||
}
|
||||
|
||||
void NodeDB::_clearLocalPosition()
|
||||
void NodeDB::clearLocalPosition()
|
||||
{
|
||||
meshtastic_NodeInfoLite *node = _getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum());
|
||||
node->position.latitude_i = 0;
|
||||
node->position.longitude_i = 0;
|
||||
node->position.altitude = 0;
|
||||
node->position.time = 0;
|
||||
_setLocalPosition(meshtastic_Position_init_default);
|
||||
setLocalPosition(meshtastic_Position_init_default);
|
||||
}
|
||||
|
||||
void NodeDB::cleanupMeshDB()
|
||||
@@ -1119,7 +1118,7 @@ void NodeDB::pickNewNodeNum()
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *found;
|
||||
while (((found = _getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
||||
while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
||||
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
||||
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
||||
if (found)
|
||||
@@ -1133,6 +1132,39 @@ void NodeDB::pickNewNodeNum()
|
||||
myNodeInfo.my_node_num = nodeNum;
|
||||
}
|
||||
|
||||
/** Load a protobuf from a file, return LoadFileResult */
|
||||
LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
|
||||
void *dest_struct)
|
||||
{
|
||||
LoadFileResult state = LoadFileResult::OTHER_FAILURE;
|
||||
#ifdef FSCom
|
||||
concurrency::LockGuard g(spiLock);
|
||||
|
||||
auto f = FSCom.open(filename, FILE_O_READ);
|
||||
|
||||
if (f) {
|
||||
LOG_INFO("Load %s", filename);
|
||||
pb_istream_t stream = {&readcb, &f, protoSize};
|
||||
if (fields != &meshtastic_NodeDatabase_msg) // contains a vector object
|
||||
memset(dest_struct, 0, objSize);
|
||||
if (!pb_decode(&stream, fields, dest_struct)) {
|
||||
LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream));
|
||||
state = LoadFileResult::DECODE_FAILED;
|
||||
} else {
|
||||
LOG_INFO("Loaded %s successfully", filename);
|
||||
state = LoadFileResult::LOAD_SUCCESS;
|
||||
}
|
||||
f.close();
|
||||
} else {
|
||||
LOG_ERROR("Could not open / read %s", filename);
|
||||
}
|
||||
#else
|
||||
LOG_ERROR("ERROR: Filesystem not implemented");
|
||||
state = LoadFileResult::NO_FILESYSTEM;
|
||||
#endif
|
||||
return state;
|
||||
}
|
||||
|
||||
void NodeDB::loadFromDisk()
|
||||
{
|
||||
// Mark the current device state as completely unusable, so that if we fail reading the entire file from
|
||||
@@ -1232,7 +1264,7 @@ void NodeDB::loadFromDisk()
|
||||
if (backupSecurity.private_key.size > 0) {
|
||||
LOG_DEBUG("Restoring backup of security config");
|
||||
config.security = backupSecurity;
|
||||
_saveToDisk(SEGMENT_CONFIG);
|
||||
saveToDisk(SEGMENT_CONFIG);
|
||||
}
|
||||
|
||||
// Make sure we load hard coded admin keys even when the configuration file has none.
|
||||
@@ -1283,7 +1315,7 @@ void NodeDB::loadFromDisk()
|
||||
if (numAdminKeys > 0) {
|
||||
LOG_INFO("Saving %d hard coded admin keys.", numAdminKeys);
|
||||
config.security.admin_key_count = numAdminKeys;
|
||||
_saveToDisk(SEGMENT_CONFIG);
|
||||
saveToDisk(SEGMENT_CONFIG);
|
||||
}
|
||||
|
||||
state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
|
||||
@@ -1335,7 +1367,7 @@ void NodeDB::loadFromDisk()
|
||||
if (moduleConfig.paxcounter.paxcounter_update_interval == 900)
|
||||
moduleConfig.paxcounter.paxcounter_update_interval = 0;
|
||||
|
||||
_saveToDisk(SEGMENT_MODULECONFIG);
|
||||
saveToDisk(SEGMENT_MODULECONFIG);
|
||||
}
|
||||
#if ARCH_PORTDUINO
|
||||
// set any config overrides
|
||||
@@ -1346,6 +1378,34 @@ void NodeDB::loadFromDisk()
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Save a protobuf from a file, return true for success */
|
||||
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
||||
bool fullAtomic)
|
||||
{
|
||||
bool okay = false;
|
||||
#ifdef FSCom
|
||||
auto f = SafeFile(filename, fullAtomic);
|
||||
|
||||
LOG_INFO("Save %s", filename);
|
||||
pb_ostream_t stream = {&writecb, static_cast<Print *>(&f), protoSize};
|
||||
|
||||
if (!pb_encode(&stream, fields, dest_struct)) {
|
||||
LOG_ERROR("Error: can't encode protobuf %s", PB_GET_ERROR(&stream));
|
||||
} else {
|
||||
okay = true;
|
||||
}
|
||||
|
||||
bool writeSucceeded = f.close();
|
||||
|
||||
if (!okay || !writeSucceeded) {
|
||||
LOG_ERROR("Can't write prefs!");
|
||||
}
|
||||
#else
|
||||
LOG_ERROR("ERROR: Filesystem not implemented");
|
||||
#endif
|
||||
return okay;
|
||||
}
|
||||
|
||||
bool NodeDB::saveChannelsToDisk()
|
||||
{
|
||||
#ifdef FSCom
|
||||
@@ -1434,7 +1494,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool NodeDB::_saveToDisk(int saveWhat)
|
||||
bool NodeDB::saveToDisk(int saveWhat)
|
||||
{
|
||||
LOG_DEBUG("Save to disk %d", saveWhat);
|
||||
bool success = saveToDiskNoRetry(saveWhat);
|
||||
@@ -1458,12 +1518,10 @@ bool NodeDB::_saveToDisk(int saveWhat)
|
||||
|
||||
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
||||
{
|
||||
FUNCTION_START("readNextMeshNode");
|
||||
meshtastic_NodeInfoLite *retVal = nullptr;
|
||||
if (readIndex < numMeshNodes)
|
||||
retVal = &meshNodes->at(readIndex++);
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
return &meshNodes->at(readIndex++);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
||||
@@ -1491,7 +1549,7 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
|
||||
|
||||
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
|
||||
|
||||
size_t NodeDB::_getNumOnlineMeshNodes(bool localOnly)
|
||||
size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||
{
|
||||
size_t numseen = 0;
|
||||
|
||||
@@ -1513,10 +1571,8 @@ size_t NodeDB::_getNumOnlineMeshNodes(bool localOnly)
|
||||
*/
|
||||
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
|
||||
{
|
||||
FUNCTION_START("updatePosition");
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||
if (!info) {
|
||||
FUNCTION_END;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1525,7 +1581,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
||||
LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d", p.timestamp, p.time, p.latitude_i, p.longitude_i,
|
||||
p.altitude);
|
||||
|
||||
_setLocalPosition(p);
|
||||
setLocalPosition(p);
|
||||
info->position = TypeConversions::ConvertToPositionLite(p);
|
||||
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) {
|
||||
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
|
||||
@@ -1552,8 +1608,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
||||
}
|
||||
info->has_position = true;
|
||||
updateGUIforNode = info;
|
||||
_notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
FUNCTION_END;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
|
||||
/** Update telemetry info for this node based on received metrics
|
||||
@@ -1561,11 +1616,9 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
||||
*/
|
||||
void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src)
|
||||
{
|
||||
FUNCTION_START("updatePosition");
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||
// Environment metrics should never go to NodeDb but we'll safegaurd anyway
|
||||
if (!info || t.which_variant != meshtastic_Telemetry_device_metrics_tag) {
|
||||
FUNCTION_END;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1578,8 +1631,7 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
||||
info->device_metrics = t.variant.device_metrics;
|
||||
info->has_device_metrics = true;
|
||||
updateGUIforNode = info;
|
||||
_notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
FUNCTION_END;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1587,10 +1639,8 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
||||
*/
|
||||
void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
{
|
||||
FUNCTION_START("addFromContact");
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
|
||||
if (!info || !contact.has_user) {
|
||||
FUNCTION_END;
|
||||
return;
|
||||
}
|
||||
// If the local node has this node marked as manually verified
|
||||
@@ -1599,7 +1649,6 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
|
||||
if (contact.user.public_key.size != info->user.public_key.size ||
|
||||
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
|
||||
FUNCTION_END;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1643,26 +1692,22 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
// Mark the node's key as manually verified to indicate trustworthiness.
|
||||
updateGUIforNode = info;
|
||||
sortMeshDB();
|
||||
_notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
saveNodeDatabaseToDisk();
|
||||
FUNCTION_END;
|
||||
}
|
||||
|
||||
/** Update user info and channel for this node based on received user data
|
||||
*/
|
||||
bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex)
|
||||
{
|
||||
FUNCTION_START("updateUser");
|
||||
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||
if (!info) {
|
||||
FUNCTION_END;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
if (p.public_key.size == 32 && nodeId != getNodeNum()) {
|
||||
if (p.public_key.size == 32 && nodeId != nodeDB->getNodeNum()) {
|
||||
printBytes("Incoming Pubkey: ", p.public_key.bytes, 32);
|
||||
|
||||
// Alert the user if a remote node is advertising public key that matches our own
|
||||
@@ -1679,7 +1724,6 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
sprintf(cn->message, warning, p.long_name);
|
||||
service->sendClientNotification(cn);
|
||||
}
|
||||
FUNCTION_END;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1687,7 +1731,6 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
// if the key doesn't match, don't update nodeDB at all.
|
||||
if (p.public_key.size != 32 || (memcmp(p.public_key.bytes, info->user.public_key.bytes, 32) != 0)) {
|
||||
LOG_WARN("Public Key mismatch, dropping NodeInfo");
|
||||
FUNCTION_END;
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Public Key set for node, not updating!");
|
||||
@@ -1715,19 +1758,19 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
|
||||
if (changed) {
|
||||
updateGUIforNode = info;
|
||||
_notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
|
||||
// We just changed something about a User,
|
||||
// store our DB unless we just did so less than a minute ago
|
||||
|
||||
if (!Throttle::isWithinTimespanMs(lastNodeDbSave, ONE_MINUTE_MS)) {
|
||||
_saveToDisk(SEGMENT_NODEDATABASE);
|
||||
saveToDisk(SEGMENT_NODEDATABASE);
|
||||
lastNodeDbSave = millis();
|
||||
} else {
|
||||
LOG_DEBUG("Defer NodeDB saveToDisk for now");
|
||||
}
|
||||
}
|
||||
FUNCTION_END;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -1735,121 +1778,107 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
FUNCTION_START("updateFrom");
|
||||
if (mp.from == getNodeNum()) {
|
||||
LOG_DEBUG("Ignore update from self");
|
||||
} else if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
|
||||
return;
|
||||
}
|
||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
|
||||
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
|
||||
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp));
|
||||
if (info) {
|
||||
if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard
|
||||
info->last_heard = mp.rx_time;
|
||||
|
||||
if (mp.rx_snr)
|
||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||
|
||||
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
|
||||
|
||||
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
|
||||
if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
|
||||
info->has_hops_away = true;
|
||||
info->hops_away = mp.hop_start - mp.hop_limit;
|
||||
}
|
||||
sortMeshDB();
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard
|
||||
info->last_heard = mp.rx_time;
|
||||
|
||||
if (mp.rx_snr)
|
||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||
|
||||
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
|
||||
|
||||
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
|
||||
if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
|
||||
info->has_hops_away = true;
|
||||
info->hops_away = mp.hop_start - mp.hop_limit;
|
||||
}
|
||||
sortMeshDB();
|
||||
}
|
||||
FUNCTION_END;
|
||||
}
|
||||
|
||||
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
||||
{
|
||||
FUNCTION_START("set_favorite");
|
||||
meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId);
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||
if (lite && lite->is_favorite != is_favorite) {
|
||||
lite->is_favorite = is_favorite;
|
||||
sortMeshDB();
|
||||
saveNodeDatabaseToDisk();
|
||||
}
|
||||
FUNCTION_END;
|
||||
}
|
||||
|
||||
// returns true if nodeId is_favorite; false if not or not found
|
||||
bool NodeDB::isFavorite(uint32_t nodeId)
|
||||
{
|
||||
FUNCTION_START("set_favorite");
|
||||
// NODENUM_BROADCAST will never be in the DB
|
||||
if (nodeId == NODENUM_BROADCAST) {
|
||||
FUNCTION_END;
|
||||
return false;
|
||||
}
|
||||
// returns true if nodeId is_favorite; false if not or not found
|
||||
|
||||
meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId);
|
||||
// NODENUM_BROADCAST will never be in the DB
|
||||
if (nodeId == NODENUM_BROADCAST)
|
||||
return false;
|
||||
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||
|
||||
if (lite) {
|
||||
FUNCTION_END;
|
||||
return lite->is_favorite;
|
||||
}
|
||||
FUNCTION_END;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
||||
{
|
||||
FUNCTION_START("isFromOrToFavoritedNode");
|
||||
// This method is logically equivalent to:
|
||||
// return isFavorite(p.from) || isFavorite(p.to);
|
||||
// but is more efficient by:
|
||||
// 1. doing only one pass through the database, instead of two
|
||||
// 2. exiting early when a favorite is found, or if both from and to have been seen
|
||||
|
||||
if (p.to == NODENUM_BROADCAST)
|
||||
return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from
|
||||
|
||||
meshtastic_NodeInfoLite *lite = NULL;
|
||||
|
||||
bool seenFrom = false;
|
||||
bool seenTo = false;
|
||||
|
||||
if (p.to == NODENUM_BROADCAST)
|
||||
seenTo = true;
|
||||
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
lite = &meshNodes->at(i);
|
||||
|
||||
if (!seenFrom && lite->num == p.from) {
|
||||
if (lite->is_favorite) {
|
||||
FUNCTION_END;
|
||||
if (lite->num == p.from) {
|
||||
if (lite->is_favorite)
|
||||
return true;
|
||||
}
|
||||
|
||||
seenFrom = true;
|
||||
}
|
||||
|
||||
if (!seenTo && lite->num == p.to) {
|
||||
if (lite->is_favorite) {
|
||||
FUNCTION_END;
|
||||
if (lite->num == p.to) {
|
||||
if (lite->is_favorite)
|
||||
return true;
|
||||
}
|
||||
|
||||
seenTo = true;
|
||||
}
|
||||
|
||||
if (seenFrom && seenTo) {
|
||||
FUNCTION_END;
|
||||
if (seenFrom && seenTo)
|
||||
return false; // we've seen both, and neither is a favorite, so we can stop searching early
|
||||
}
|
||||
|
||||
// Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
|
||||
// all favorited nodes first.
|
||||
}
|
||||
FUNCTION_END;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void NodeDB::pause_sort(bool paused)
|
||||
{
|
||||
// Including the mutex macro for completeness, but it's possible it isn't appropriate here
|
||||
FUNCTION_START("pause_sort");
|
||||
sortingIsPaused = paused;
|
||||
FUNCTION_END;
|
||||
}
|
||||
|
||||
void NodeDB::sortMeshDB()
|
||||
@@ -1884,13 +1913,10 @@ void NodeDB::sortMeshDB()
|
||||
|
||||
uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
||||
{
|
||||
FUNCTION_START("getMeshNodeChannel");
|
||||
const meshtastic_NodeInfoLite *info = _getMeshNode(n);
|
||||
const meshtastic_NodeInfoLite *info = getMeshNode(n);
|
||||
if (!info) {
|
||||
FUNCTION_END;
|
||||
return 0; // defaults to PRIMARY
|
||||
}
|
||||
FUNCTION_END;
|
||||
return info->channel;
|
||||
}
|
||||
|
||||
@@ -1903,7 +1929,7 @@ std::string NodeDB::getNodeId() const
|
||||
|
||||
/// Find a node in our DB, return null for missing
|
||||
/// NOTE: This function might be called from an ISR
|
||||
meshtastic_NodeInfoLite *NodeDB::_getMeshNode(NodeNum n)
|
||||
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
||||
{
|
||||
for (int i = 0; i < numMeshNodes; i++)
|
||||
if (meshNodes->at(i).num == n)
|
||||
@@ -1913,7 +1939,7 @@ meshtastic_NodeInfoLite *NodeDB::_getMeshNode(NodeNum n)
|
||||
}
|
||||
|
||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||
bool NodeDB::_isFull()
|
||||
bool NodeDB::isFull()
|
||||
{
|
||||
return (numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP);
|
||||
}
|
||||
@@ -1921,7 +1947,7 @@ bool NodeDB::_isFull()
|
||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
{
|
||||
meshtastic_NodeInfoLite *lite = _getMeshNode(n);
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(n);
|
||||
|
||||
if (!lite) {
|
||||
if (isFull()) {
|
||||
@@ -1976,25 +2002,18 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
/// valid lat/lon
|
||||
bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n)
|
||||
{
|
||||
FUNCTION_START("hasValidPosition");
|
||||
auto retVal = n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0);
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0);
|
||||
}
|
||||
|
||||
/// If we have a node / user and they report is_licensed = true
|
||||
/// we consider them licensed
|
||||
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
|
||||
{
|
||||
FUNCTION_START("getLicenseStatus");
|
||||
meshtastic_NodeInfoLite *info = _getMeshNode(nodeNum);
|
||||
meshtastic_NodeInfoLite *info = getMeshNode(nodeNum);
|
||||
if (!info || !info->has_user) {
|
||||
FUNCTION_END;
|
||||
return UserLicenseStatus::NotKnown;
|
||||
}
|
||||
auto retVal = info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
|
||||
}
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||
@@ -2016,7 +2035,6 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
|
||||
|
||||
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
|
||||
{
|
||||
FUNCTION_START("backupPreferences");
|
||||
bool success = false;
|
||||
lastBackupAttempt = millis();
|
||||
#ifdef FSCom
|
||||
@@ -2050,13 +2068,11 @@ bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
|
||||
// TODO: After more mainline SD card support
|
||||
}
|
||||
#endif
|
||||
FUNCTION_END;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat)
|
||||
{
|
||||
FUNCTION_START("backupPreferences");
|
||||
bool success = false;
|
||||
#ifdef FSCom
|
||||
if (location == meshtastic_AdminMessage_BackupLocation_FLASH) {
|
||||
@@ -2064,7 +2080,6 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
||||
if (!FSCom.exists(backupFileName)) {
|
||||
spiLock->unlock();
|
||||
LOG_WARN("Could not restore. No backup file found");
|
||||
FUNCTION_END;
|
||||
return false;
|
||||
} else {
|
||||
spiLock->unlock();
|
||||
@@ -2090,7 +2105,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
||||
LOG_DEBUG("Restored channels");
|
||||
}
|
||||
|
||||
success = _saveToDisk(restoreWhat);
|
||||
success = saveToDisk(restoreWhat);
|
||||
if (success) {
|
||||
LOG_INFO("Restored preferences from backup");
|
||||
} else {
|
||||
@@ -2102,7 +2117,6 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
||||
} else if (location == meshtastic_AdminMessage_BackupLocation_SD) {
|
||||
// TODO: After more mainline SD card support
|
||||
}
|
||||
FUNCTION_END;
|
||||
return success;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -18,13 +18,6 @@
|
||||
#include "PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
#define FUNCTION_START(FUNCTION_NAME) \
|
||||
if (fakeMutex) \
|
||||
LOG_ERROR("Concurrency violation in " FUNCTION_NAME); \
|
||||
fakeMutex = true;
|
||||
|
||||
#define FUNCTION_END fakeMutex = false;
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||
// E3B0C442 is the blank hash
|
||||
static const uint8_t LOW_ENTROPY_HASHES[][32] = {
|
||||
@@ -117,6 +110,19 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
|
||||
/// Given a packet, return how many seconds in the past (vs now) it was received
|
||||
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
||||
|
||||
enum LoadFileResult {
|
||||
// Successfully opened the file
|
||||
LOAD_SUCCESS = 1,
|
||||
// File does not exist
|
||||
NOT_FOUND = 2,
|
||||
// Device does not have a filesystem
|
||||
NO_FILESYSTEM = 3,
|
||||
// File exists, but could not decode protobufs
|
||||
DECODE_FAILED = 4,
|
||||
// File exists, but open failed for some reason
|
||||
OTHER_FAILURE = 5
|
||||
};
|
||||
|
||||
enum UserLicenseStatus { NotKnown, NotLicensed, Licensed };
|
||||
|
||||
class NodeDB
|
||||
@@ -129,6 +135,7 @@ class NodeDB
|
||||
// Note: these two references just point into our static array we serialize to/from disk
|
||||
|
||||
public:
|
||||
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||
@@ -144,26 +151,17 @@ class NodeDB
|
||||
/// write to flash
|
||||
/// @return true if the save was successful
|
||||
bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS |
|
||||
SEGMENT_NODEDATABASE)
|
||||
{
|
||||
FUNCTION_START("saveToDisk");
|
||||
auto retVal = _saveToDisk(saveWhat);
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
}
|
||||
SEGMENT_NODEDATABASE);
|
||||
|
||||
/** Reinit radio config if needed, because either:
|
||||
* a) sometimes a buggy android app might send us bogus settings or
|
||||
* b) the client set factory_reset
|
||||
*
|
||||
* @param factory_reset if true, reset all settings to factory defaults
|
||||
* @param is_fresh_install set to true after a fresh install, to trigger NodeInfo/Position requests
|
||||
* @return true if the config was completely reset, in that case, we should send it back to the client
|
||||
*/
|
||||
void resetRadioConfig(bool is_fresh_install = false)
|
||||
{
|
||||
FUNCTION_START("resetRadioConfig");
|
||||
_resetRadioConfig(is_fresh_install);
|
||||
FUNCTION_END;
|
||||
}
|
||||
void resetRadioConfig(bool is_fresh_install = false);
|
||||
|
||||
/// given a subpacket sniffed from the network, update our DB state
|
||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||
@@ -210,13 +208,7 @@ class NodeDB
|
||||
std::string getNodeId() const;
|
||||
|
||||
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
|
||||
uint8_t getLastByteOfNodeNum(NodeNum num)
|
||||
{
|
||||
FUNCTION_START("getLastByteOfNodeNum");
|
||||
auto retVal = (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF);
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
}
|
||||
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
|
||||
|
||||
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
||||
// bool handleWantNodeNum(NodeNum n);
|
||||
@@ -235,104 +227,79 @@ class NodeDB
|
||||
/* Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
||||
* @param localOnly if true, ignore nodes heard via MQTT
|
||||
*/
|
||||
size_t getNumOnlineMeshNodes(bool localOnly = false)
|
||||
{
|
||||
FUNCTION_START("getNumOnlineMeshNodes");
|
||||
auto retVal = _getNumOnlineMeshNodes(localOnly);
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
}
|
||||
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
||||
|
||||
void resetNodes(bool keepFavorites = false), removeNodeByNum(NodeNum nodeNum);
|
||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
|
||||
removeNodeByNum(NodeNum nodeNum);
|
||||
|
||||
bool factoryReset(bool eraseBleBonds = false);
|
||||
|
||||
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
|
||||
{
|
||||
FUNCTION_START("installRoleDefaults");
|
||||
_installRoleDefaults(role);
|
||||
FUNCTION_END;
|
||||
}
|
||||
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
|
||||
void *dest_struct);
|
||||
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
||||
bool fullAtomic = true);
|
||||
|
||||
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
|
||||
|
||||
const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex);
|
||||
|
||||
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
||||
{
|
||||
FUNCTION_START("getMeshNodeByIndex");
|
||||
meshtastic_NodeInfoLite *retValue = nullptr;
|
||||
if (x < numMeshNodes)
|
||||
retValue = &meshNodes->at(x);
|
||||
FUNCTION_END;
|
||||
return retValue;
|
||||
assert(x < numMeshNodes);
|
||||
return &meshNodes->at(x);
|
||||
}
|
||||
|
||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n)
|
||||
{
|
||||
FUNCTION_START("getMeshNode");
|
||||
auto retVal = _getMeshNode(n);
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
size_t getNumMeshNodes()
|
||||
{
|
||||
FUNCTION_START("getNumMeshNodes");
|
||||
auto retVal = numMeshNodes;
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
}
|
||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
||||
size_t getNumMeshNodes() { return numMeshNodes; }
|
||||
|
||||
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
||||
|
||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||
bool isFull()
|
||||
size_t getMaxNodesAllocatedSize()
|
||||
{
|
||||
FUNCTION_START("isFull");
|
||||
auto retVal = _isFull();
|
||||
FUNCTION_END;
|
||||
return retVal;
|
||||
meshtastic_NodeDatabase emptyNodeDatabase;
|
||||
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
|
||||
size_t nodeDatabaseSize;
|
||||
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
|
||||
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size);
|
||||
}
|
||||
|
||||
void clearLocalPosition()
|
||||
{
|
||||
FUNCTION_START("clearLocalPosition");
|
||||
_clearLocalPosition();
|
||||
FUNCTION_END;
|
||||
}
|
||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||
bool isFull();
|
||||
|
||||
void clearLocalPosition();
|
||||
|
||||
void setLocalPosition(meshtastic_Position position, bool timeOnly = false)
|
||||
{
|
||||
FUNCTION_START("setLocalPosition");
|
||||
_setLocalPosition(position, timeOnly);
|
||||
FUNCTION_END;
|
||||
if (timeOnly) {
|
||||
LOG_DEBUG("Set local position time only: time=%u timestamp=%u", position.time, position.timestamp);
|
||||
localPosition.time = position.time;
|
||||
localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time;
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i,
|
||||
position.time, position.timestamp);
|
||||
localPosition = position;
|
||||
}
|
||||
|
||||
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
|
||||
#endif
|
||||
|
||||
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
|
||||
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
||||
int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
||||
|
||||
/// Notify observers of changes to the DB
|
||||
void notifyObservers(bool forceUpdate = false)
|
||||
{
|
||||
FUNCTION_START("notifyObservers");
|
||||
_notifyObservers(forceUpdate);
|
||||
FUNCTION_END;
|
||||
}
|
||||
|
||||
private:
|
||||
bool fakeMutex = false;
|
||||
|
||||
/// Notify observers of changes to the DB
|
||||
void _notifyObservers(bool forceUpdate = false)
|
||||
{
|
||||
// Notify observers of the current node state
|
||||
const meshtastic::NodeStatus status = meshtastic::NodeStatus(_getNumOnlineMeshNodes(), numMeshNodes, forceUpdate);
|
||||
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate);
|
||||
newStatus.notifyObservers(&status);
|
||||
}
|
||||
|
||||
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
||||
|
||||
private:
|
||||
bool duplicateWarned = false;
|
||||
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
||||
uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
|
||||
@@ -366,51 +333,6 @@ class NodeDB
|
||||
bool saveDeviceStateToDisk();
|
||||
bool saveNodeDatabaseToDisk();
|
||||
void sortMeshDB();
|
||||
|
||||
void initConfigIntervals(), initModuleConfigIntervals();
|
||||
|
||||
size_t getMaxNodesAllocatedSize()
|
||||
{
|
||||
meshtastic_NodeDatabase emptyNodeDatabase;
|
||||
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
|
||||
size_t nodeDatabaseSize;
|
||||
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
|
||||
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size);
|
||||
}
|
||||
|
||||
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
|
||||
|
||||
// wrapped private functions:
|
||||
|
||||
bool _saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS |
|
||||
SEGMENT_NODEDATABASE);
|
||||
void _resetRadioConfig(bool is_fresh_install = false);
|
||||
|
||||
/* Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
||||
* @param localOnly if true, ignore nodes heard via MQTT
|
||||
*/
|
||||
size_t _getNumOnlineMeshNodes(bool localOnly = false);
|
||||
|
||||
void _installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
|
||||
|
||||
meshtastic_NodeInfoLite *_getMeshNode(NodeNum n);
|
||||
|
||||
bool _isFull();
|
||||
|
||||
void _clearLocalPosition();
|
||||
|
||||
void _setLocalPosition(meshtastic_Position position, bool timeOnly = false)
|
||||
{
|
||||
if (timeOnly) {
|
||||
LOG_DEBUG("Set local position time only: time=%u timestamp=%u", position.time, position.timestamp);
|
||||
localPosition.time = position.time;
|
||||
localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time;
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i,
|
||||
position.time, position.timestamp);
|
||||
localPosition = position;
|
||||
}
|
||||
};
|
||||
|
||||
extern NodeDB *nodeDB;
|
||||
|
||||
@@ -150,7 +150,9 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
||||
PacketId nakId = (c && c->error_reason != meshtastic_Routing_Error_NONE) ? p->decoded.request_id : 0;
|
||||
|
||||
// We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records
|
||||
if (ackId || nakId) {
|
||||
if ((ackId || nakId) &&
|
||||
// Implicit ACKs from MQTT should not stop retransmissions
|
||||
!(isFromUs(p) && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MQTT)) {
|
||||
LOG_DEBUG("Received a %s for 0x%x, stopping retransmissions", ackId ? "ACK" : "NAK", ackId);
|
||||
if (ackId) {
|
||||
stopRetransmission(p->to, ackId);
|
||||
|
||||
@@ -1322,7 +1322,7 @@ void AdminModule::saveChanges(int saveWhat, bool shouldReboot)
|
||||
|
||||
void AdminModule::handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg)
|
||||
{
|
||||
saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uicfg);
|
||||
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uicfg);
|
||||
}
|
||||
|
||||
void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
|
||||
|
||||
@@ -2273,9 +2273,9 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
|
||||
|
||||
void CannedMessageModule::loadProtoForModule()
|
||||
{
|
||||
if (loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
|
||||
sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg,
|
||||
&cannedMessageModuleConfig) != LoadFileResult::LOAD_SUCCESS) {
|
||||
if (nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
|
||||
sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg,
|
||||
&cannedMessageModuleConfig) != LoadFileResult::LOAD_SUCCESS) {
|
||||
installDefaultCannedMessageModuleConfig();
|
||||
}
|
||||
}
|
||||
@@ -2295,8 +2295,8 @@ bool CannedMessageModule::saveProtoForModule()
|
||||
spiLock->unlock();
|
||||
#endif
|
||||
|
||||
okay &= saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
|
||||
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
|
||||
okay &= nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
|
||||
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
|
||||
|
||||
return okay;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* @date [Insert Date]
|
||||
*/
|
||||
#include "ExternalNotificationModule.h"
|
||||
#include "FSCommon.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
@@ -371,8 +370,8 @@ ExternalNotificationModule::ExternalNotificationModule()
|
||||
if (inputBroker) // put our callback in the inputObserver list
|
||||
inputObserver.observe(inputBroker);
|
||||
#endif
|
||||
if (loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), &meshtastic_RTTTLConfig_msg,
|
||||
&rtttlConfig) != LoadFileResult::LOAD_SUCCESS) {
|
||||
if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig),
|
||||
&meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) {
|
||||
memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone));
|
||||
// The default ringtone is always loaded from userPrefs.jsonc
|
||||
strncpy(rtttlConfig.ringtone, USERPREFS_RINGTONE_RTTTL, sizeof(rtttlConfig.ringtone));
|
||||
@@ -641,7 +640,7 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg)
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig);
|
||||
nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ void setupModules()
|
||||
}
|
||||
#endif // HAS_BUTTON
|
||||
#if ARCH_PORTDUINO
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") {
|
||||
seesawRotary = new SeesawRotary("SeesawRotary");
|
||||
if (!seesawRotary->init()) {
|
||||
delete seesawRotary;
|
||||
|
||||
@@ -45,8 +45,12 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
|
||||
{
|
||||
auto p = *pptr;
|
||||
|
||||
// If inbound message is a replay (or spoof!) of our own messages, we shouldn't process
|
||||
// (why use second-hand sources for our own data?)
|
||||
const auto transport = mp.transport_mechanism;
|
||||
if (isFromUs(&mp) && !IS_ONE_OF(transport, meshtastic_MeshPacket_TransportMechanism_TRANSPORT_INTERNAL,
|
||||
meshtastic_MeshPacket_TransportMechanism_TRANSPORT_API)) {
|
||||
LOG_WARN("Ignoring packet supposedly from us over external transport");
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME this can in fact happen with packets sent from EUD (src=RX_SRC_USER)
|
||||
// to set fixed location, EUD-GPS location or just the time (see also issue #900)
|
||||
@@ -472,19 +476,53 @@ void PositionModule::sendLostAndFoundText()
|
||||
delete[] message;
|
||||
}
|
||||
|
||||
// Helper: return imprecise (truncated + centered) lat/lon as int32 using current precision
|
||||
static inline void computeImpreciseLatLon(int32_t inLat, int32_t inLon, uint8_t precisionBits, int32_t &outLat, int32_t &outLon)
|
||||
{
|
||||
if (precisionBits > 0 && precisionBits < 32) {
|
||||
// Build mask for top 'precisionBits' bits of a 32-bit unsigned field
|
||||
const uint32_t mask = (precisionBits == 32) ? UINT32_MAX : (UINT32_MAX << (32 - precisionBits));
|
||||
// Note: latitude_i/longitude_i are stored as signed 32-bit in meshtastic code but
|
||||
// the bitmask logic used previously operated as unsigned—preserve that behavior by
|
||||
// casting to uint32_t for masking, then back to int32_t.
|
||||
uint32_t lat_u = static_cast<uint32_t>(inLat) & mask;
|
||||
uint32_t lon_u = static_cast<uint32_t>(inLon) & mask;
|
||||
|
||||
// Add the "center of cell" offset used elsewhere:
|
||||
// The code previously added (1 << (31 - precision)) to produce the middle of the possible location.
|
||||
uint32_t center_offset = (1u << (31 - precisionBits));
|
||||
lat_u += center_offset;
|
||||
lon_u += center_offset;
|
||||
|
||||
outLat = static_cast<int32_t>(lat_u);
|
||||
outLon = static_cast<int32_t>(lon_u);
|
||||
} else {
|
||||
// full precision: return input unchanged
|
||||
outLat = inLat;
|
||||
outLon = inLon;
|
||||
}
|
||||
}
|
||||
|
||||
struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition)
|
||||
{
|
||||
// The minimum distance to travel before we are able to send a new position packet.
|
||||
const uint32_t distanceTravelThreshold =
|
||||
Default::getConfiguredOrDefault(config.position.broadcast_smart_minimum_distance, 100);
|
||||
|
||||
// Determine the distance in meters between two points on the globe
|
||||
float distanceTraveledSinceLastSend = GeoCoord::latLongToMeter(
|
||||
lastGpsLatitude * 1e-7, lastGpsLongitude * 1e-7, currentPosition.latitude_i * 1e-7, currentPosition.longitude_i * 1e-7);
|
||||
int32_t lastLatImprecise, lastLonImprecise;
|
||||
int32_t currentLatImprecise, currentLonImprecise;
|
||||
|
||||
return SmartPosition{.distanceTraveled = abs(distanceTraveledSinceLastSend),
|
||||
computeImpreciseLatLon(lastGpsLatitude, lastGpsLongitude, precision, lastLatImprecise, lastLonImprecise);
|
||||
computeImpreciseLatLon(currentPosition.latitude_i, currentPosition.longitude_i, precision, currentLatImprecise,
|
||||
currentLonImprecise);
|
||||
|
||||
float distMeters = GeoCoord::latLongToMeter(lastLatImprecise * 1e-7, lastLonImprecise * 1e-7, currentLatImprecise * 1e-7,
|
||||
currentLonImprecise * 1e-7);
|
||||
|
||||
float distanceTraveled = fabsf(distMeters);
|
||||
|
||||
return SmartPosition{.distanceTraveled = distanceTraveled,
|
||||
.distanceThreshold = distanceTravelThreshold,
|
||||
.hasTraveledOverThreshold = abs(distanceTraveledSinceLastSend) >= distanceTravelThreshold};
|
||||
.hasTraveledOverThreshold = distanceTraveled >= distanceTravelThreshold};
|
||||
}
|
||||
|
||||
void PositionModule::handleNewPosition()
|
||||
|
||||
@@ -75,6 +75,12 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit
|
||||
return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *RoutingModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopLimit)
|
||||
{
|
||||
return MeshModule::allocAckNak(err, to, idFrom, chIndex, hopLimit);
|
||||
}
|
||||
|
||||
RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
|
||||
{
|
||||
isPromiscuous = true;
|
||||
|
||||
@@ -16,6 +16,9 @@ class RoutingModule : public ProtobufModule<meshtastic_Routing>
|
||||
virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
|
||||
bool ackWantsAck = false);
|
||||
|
||||
meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopLimit = 0);
|
||||
|
||||
// Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response
|
||||
uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit);
|
||||
|
||||
|
||||
@@ -87,10 +87,13 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
|
||||
// Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message.
|
||||
// We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node
|
||||
// receives it when we get our own packet back. Then we'll stop our retransmissions.
|
||||
if (isFromUs(e.packet))
|
||||
routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index);
|
||||
else
|
||||
if (isFromUs(e.packet)) {
|
||||
auto pAck = routingModule->allocAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index);
|
||||
pAck->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MQTT;
|
||||
router->sendLocal(pAck);
|
||||
} else {
|
||||
LOG_INFO("Ignore downlink message we originally sent");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isFromUs(e.packet)) {
|
||||
|
||||
@@ -337,7 +337,7 @@ void cpuDeepSleep(uint32_t msecToWake)
|
||||
#endif
|
||||
|
||||
#ifdef TTGO_T_ECHO
|
||||
// To power off the T-Echo, the display must be set
|
||||
// To power off the T-Echo, the display must be set
|
||||
// as an input pin; otherwise, there will be leakage current.
|
||||
pinMode(PIN_EINK_CS, INPUT);
|
||||
pinMode(PIN_EINK_DC, INPUT);
|
||||
|
||||
@@ -5,7 +5,7 @@ custom_esp32_kind = esp32
|
||||
custom_mtjson_part =
|
||||
platform =
|
||||
# renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32
|
||||
platformio/espressif32@6.11.0
|
||||
platformio/espressif32@6.12.0
|
||||
|
||||
extra_scripts =
|
||||
${env.extra_scripts}
|
||||
|
||||
Reference in New Issue
Block a user