Compare commits

..

20 Commits

Author SHA1 Message Date
Jonathan Bennett
90e2083d2c clean up merge 2025-12-10 15:27:09 -06:00
Jonathan Bennett
af1d4e08ff Merge branch 'develop' into refactor-nodedb 2025-12-10 11:15:18 -06:00
Jason P
2032ff1c32 Create new screen colors for BaseUI (#8921)
* Create new colors for BaseUI

* Update Ice color
2025-12-10 11:09:37 -06:00
Alex Samorukov
5910cc2e26 Use PSRAM to reduce heap usage percentage on ESP32 with PSRAM (#8891)
* Use PSRAM for malloc > 256bytes to get more heap memory

* Use dynamic allocator on boards with PSRAM to free more heap

* Apply suggestion from @Copilot

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

* Move heap_caps_malloc_extmem_enable() to the top of the init

* Update src/main.cpp

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

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-10 06:23:23 -06:00
Austin
aa72e397f2 PIO: Fix closedcube lib reference (#8920)
Fixes ClosedCube reinstalling on every build
2025-12-09 16:40:37 -06:00
Austin
c55bea8460 ARCtastic (#8904) -- Do It Live!
Actions Runner Controller

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-12-09 15:11:07 -06:00
Austin
aa605fc4a2 Actions: Fix release manifest formating (#8918) 2025-12-09 14:27:13 -06:00
Igor Danilov
d75680a2dd Fix #8915 [Bug]: Exception Decoder does not recognize the backtrace (#8917) 2025-12-09 12:24:41 -06:00
Ben Meadors
decd58cd5c Merge pull request #8913 from meshtastic/revert-8858-nrf52-power-saving-1
Revert "Cut NRF52 bluetooth power usage by 300% - testers needed!"
2025-12-09 08:02:29 -06:00
Ben Meadors
e691bd9732 Revert "Cut NRF52 bluetooth power usage by 300% - testers needed! (#8858)"
This reverts commit ae8d3fbb3d.
2025-12-09 08:02:04 -06:00
Ben Meadors
6bad81f8dd Merge pull request #8911 from vidplace7/fix-chmod
Fix apply device-install permissions
2025-12-09 06:50:19 -06:00
Austin Lane
69b9977fc1 Fix apply device-install permissions
device-install.sh doesn't exist for non-esp32 targets
2025-12-09 07:48:30 -05:00
Jonathan Bennett
d772cd4431 Retain getMeshNode virtual marker for tests 2025-10-17 12:16:59 -05:00
Jonathan Bennett
b7b8071056 Merge branch 'develop' into refactor-nodedb 2025-10-17 12:15:36 -05:00
Jonathan Bennett
4a3d28f06b Add FUNCTION_START and FUNCTION_END to public NodeDB functions 2025-10-17 00:36:58 -05:00
Jonathan Bennett
0f4210d2e8 Merge branch 'develop' into refactor-nodedb 2025-10-16 14:40:23 -05:00
Jonathan Bennett
dc6e109329 Finish adding NodeDB wrapper classes 2025-10-16 14:37:33 -05:00
Jonathan Bennett
10141b2562 InkHUD: don't try to access meshNodes directly 2025-10-16 12:08:00 -05:00
Jonathan Bennett
770085ddf7 Begin refactoring nodedb.cpp with public wrappers 2025-10-16 09:44:35 -05:00
Jonathan Bennett
833f950edc Move loadProto and saveProto into FSCommon 2025-10-16 09:36:54 -05:00
33 changed files with 431 additions and 332 deletions

View File

@@ -2,4 +2,5 @@
self-hosted-runner: self-hosted-runner:
# Labels of self-hosted runner in array of strings. # Labels of self-hosted runner in array of strings.
labels: labels:
- arctastic
- test-runner - test-runner

View File

@@ -18,7 +18,8 @@ permissions: read-all
jobs: jobs:
pio-build: pio-build:
name: build-${{ inputs.platform }} name: build-${{ inputs.platform }}
runs-on: ubuntu-24.04 # Use 'arctastic' self-hosted runner pool when building in the main repo
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
outputs: outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }} artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps: steps:

View File

@@ -139,8 +139,8 @@ jobs:
- name: Device scripts permissions - name: Device scripts permissions
run: | run: |
chmod +x ./output/device-install.sh chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh chmod +x ./output/device-update.sh || true
- name: Zip firmware - name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output

View File

@@ -207,8 +207,8 @@ jobs:
- name: Device scripts permissions - name: Device scripts permissions
run: | run: |
chmod +x ./output/device-install.sh chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh chmod +x ./output/device-update.sh || true
- name: Zip firmware - name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
@@ -280,9 +280,9 @@ jobs:
- name: Generate Release manifest - name: Generate Release manifest
run: | run: |
jq -n --arg ver "${{ needs.version.outputs.long }}" --arg targets "${{ toJson(needs.setup.outputs.all) }}" '{ jq -n --arg ver "${{ needs.version.outputs.long }}" --argjson targets ${{ toJson(needs.setup.outputs.all) }} '{
"version": $ver, "version": $ver,
"targets": ($targets | fromjson) "targets": $targets
}' > firmware-${{ needs.version.outputs.long }}.json }' > firmware-${{ needs.version.outputs.long }}.json
- name: Save Release manifest artifact - name: Save Release manifest artifact
@@ -338,8 +338,8 @@ jobs:
- name: Device scripts permissions - name: Device scripts permissions
run: | run: |
chmod +x ./output/device-install.sh chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh chmod +x ./output/device-update.sh || true
- name: Zip firmware - name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output

View File

@@ -188,8 +188,8 @@ jobs:
- name: Device scripts permissions - name: Device scripts permissions
run: | run: |
chmod +x ./output/device-install.sh chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh chmod +x ./output/device-update.sh || true
- name: Zip firmware - name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
@@ -303,8 +303,8 @@ jobs:
- name: Device scripts permissions - name: Device scripts permissions
run: | run: |
chmod +x ./output/device-install.sh chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh chmod +x ./output/device-update.sh || true
- name: Zip firmware - name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output

View File

@@ -102,7 +102,7 @@ jobs:
PIP_DISABLE_PIP_VERSION_CHECK: 1 PIP_DISABLE_PIP_VERSION_CHECK: 1
- name: Create Bumps pull request - name: Create Bumps pull request
uses: peter-evans/create-pull-request@v8 uses: peter-evans/create-pull-request@v7
with: with:
base: ${{ github.event.repository.default_branch }} base: ${{ github.event.repository.default_branch }}
branch: create-pull-request/bump-version branch: create-pull-request/bump-version

View File

@@ -31,7 +31,7 @@ jobs:
./bin/regen-protos.sh ./bin/regen-protos.sh
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@v8 uses: peter-evans/create-pull-request@v7
with: with:
branch: create-pull-request/update-protobufs branch: create-pull-request/update-protobufs
labels: submodules labels: submodules

View File

@@ -9,24 +9,24 @@ plugins:
lint: lint:
enabled: enabled:
- checkov@3.2.495 - checkov@3.2.495
- renovate@42.42.2 - renovate@42.30.4
- prettier@3.7.4 - prettier@3.7.4
- trufflehog@3.92.1 - trufflehog@3.91.2
- yamllint@1.37.1 - yamllint@1.37.1
- bandit@1.9.2 - bandit@1.9.2
- trivy@0.68.1 - trivy@0.67.2
- taplo@0.10.0 - taplo@0.10.0
- ruff@0.14.8 - ruff@0.14.7
- isort@7.0.0 - isort@7.0.0
- markdownlint@0.46.0 - markdownlint@0.46.0
- oxipng@10.0.0 - oxipng@9.1.5
- svgo@4.0.0 - svgo@4.0.0
- actionlint@1.7.9 - actionlint@1.7.9
- flake8@7.3.0 - flake8@7.3.0
- hadolint@2.14.0 - hadolint@2.14.0
- shfmt@3.6.0 - shfmt@3.6.0
- shellcheck@0.11.0 - shellcheck@0.11.0
- black@25.12.0 - black@25.11.0
- git-diff-check - git-diff-check
- gitleaks@8.30.0 - gitleaks@8.30.0
- clang-format@16.0.3 - clang-format@16.0.3

View File

@@ -75,7 +75,7 @@ TOOLS = {
} }
BACKTRACE_REGEX = re.compile( BACKTRACE_REGEX = re.compile(
r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b" r"\b(0x4[0-9a-fA-F]{7,8}):0x[0-9a-fA-F]{8}\b"
) )
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$") EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
COUNTER_REGEX = re.compile( COUNTER_REGEX = re.compile(
@@ -89,7 +89,7 @@ POINTER_REGEX = re.compile(
STACK_BEGIN = ">>>stack>>>" STACK_BEGIN = ">>>stack>>>"
STACK_END = "<<<stack<<<" STACK_END = "<<<stack<<<"
STACK_REGEX = re.compile( STACK_REGEX = re.compile(
"^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$" r"^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$"
) )
StackLine = namedtuple("StackLine", ["offset", "content"]) StackLine = namedtuple("StackLine", ["offset", "content"])
@@ -223,7 +223,7 @@ class AddressResolver(object):
if match is None: if match is None:
if last is not None and line.startswith("(inlined by)"): if last is not None and line.startswith("(inlined by)"):
line = line[12:].strip() line = line[12:].strip()
self._address_map[last] += "\n \-> inlined by: " + line self._address_map[last] += "\n \\-> inlined by: " + line
continue continue
if match.group("result") == "?? ??:0": if match.group("result") == "?? ??:0":

View File

@@ -10,12 +10,6 @@ Import("env")
platform = env.PioPlatform() platform = env.PioPlatform()
sys.path.append(join(platform.get_package_dir("tool-esptoolpy"))) 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 import esptool

View File

@@ -123,7 +123,7 @@ lib_deps =
[device-ui_base] [device-ui_base]
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/14dc991a4b57066e0a83599be1a87ff08e5e5308.zip https://github.com/meshtastic/device-ui/archive/4fb5f24787caa841b58dbf623a52c4c5861d6722.zip
; Common libs for environmental measurements in telemetry module ; Common libs for environmental measurements in telemetry module
[environmental_base] [environmental_base]
@@ -207,7 +207,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library # renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6 sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001 # renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
ClosedCube OPT3001@1.1.2 closedcube/ClosedCube OPT3001@1.1.2
# renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2 # renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2
boschsensortec/bsec2@1.10.2610 boschsensortec/bsec2@1.10.2610
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library # renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library

View File

@@ -10,7 +10,10 @@
*/ */
#include "FSCommon.h" #include "FSCommon.h"
#include "SPILock.h" #include "SPILock.h"
#include "SafeFile.h"
#include "configuration.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 // 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) #if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
@@ -335,4 +338,63 @@ void setupSDCard()
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024))); LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024))); LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
#endif #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;
} }

View File

@@ -3,6 +3,19 @@
#include "configuration.h" #include "configuration.h"
#include <vector> #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 // Cross platform filesystem API
#if defined(ARCH_PORTDUINO) #if defined(ARCH_PORTDUINO)
@@ -55,4 +68,8 @@ bool renameFile(const char *pathFrom, const char *pathTo);
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels); std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
void listDir(const char *dirname, uint8_t levels, bool del = false); void listDir(const char *dirname, uint8_t levels, bool del = false);
void rmDir(const char *dirname); void rmDir(const char *dirname);
void setupSDCard(); 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);

View File

@@ -269,9 +269,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define TCA9535_ADDR 0x20 #define TCA9535_ADDR 0x20
#define TCA9555_ADDR 0x26 #define TCA9555_ADDR 0x26
// used for display brightness control
#define STC8H1K28_ADDR 0x30
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Touchscreen // Touchscreen
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

View File

@@ -61,7 +61,6 @@ class ScanI2C
NAU7802, NAU7802,
FT6336U, FT6336U,
STK8BAXX, STK8BAXX,
STC8H1K28,
ICM20948, ICM20948,
SCD4X, SCD4X,
MAX30102, MAX30102,

View File

@@ -234,8 +234,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#endif #endif
#ifdef HAS_LP5562 #ifdef HAS_LP5562
SCAN_SIMPLE_CASE(LP5562_ADDR, LP5562, "LP5562", (uint8_t)addr.address); SCAN_SIMPLE_CASE(LP5562_ADDR, LP5562, "LP5562", (uint8_t)addr.address);
#else
SCAN_SIMPLE_CASE(STC8H1K28_ADDR, LP5562, "STC8H1K28", (uint8_t)addr.address);
#endif #endif
case XPOWERS_AXP192_AXP2101_ADDRESS: case XPOWERS_AXP192_AXP2101_ADDRESS:
// Do we have the axp2101/192 or the TCA8418 // Do we have the axp2101/192 or the TCA8418

View File

@@ -1,6 +1,7 @@
#include "configuration.h" #include "configuration.h"
#if HAS_SCREEN #if HAS_SCREEN
#include "ClockRenderer.h" #include "ClockRenderer.h"
#include "FSCommon.h"
#include "GPS.h" #include "GPS.h"
#include "MenuHandler.h" #include "MenuHandler.h"
#include "MeshRadio.h" #include "MeshRadio.h"
@@ -1041,12 +1042,13 @@ void menuHandler::switchToMUIMenu()
void menuHandler::TFTColorPickerMenu(OLEDDisplay *display) void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
{ {
static const char *optionsArray[] = {"Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Teal", static const char *optionsArray[] = {
"Pink", "White"}; "Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Blue", "Teal", "Cyan", "Ice", "Pink",
"White", "Gray"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Select Screen Color"; bannerOptions.message = "Select Screen Color";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10; bannerOptions.optionsCount = 14;
bannerOptions.bannerCallback = [display](int selected) -> void { bannerOptions.bannerCallback = [display](int selected) -> void {
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \ #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \
HAS_TFT || defined(HACKADAY_COMMUNICATOR) HAS_TFT || defined(HACKADAY_COMMUNICATOR)
@@ -1082,20 +1084,40 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
TFT_MESH_g = 153; TFT_MESH_g = 153;
TFT_MESH_b = 255; TFT_MESH_b = 255;
} else if (selected == 7) { } else if (selected == 7) {
LOG_INFO("Setting color to Teal"); LOG_INFO("Setting color to Blue");
TFT_MESH_r = 64; TFT_MESH_r = 0;
TFT_MESH_g = 224; TFT_MESH_g = 0;
TFT_MESH_b = 208; TFT_MESH_b = 255;
} else if (selected == 8) { } else if (selected == 8) {
LOG_INFO("Setting color to Teal");
TFT_MESH_r = 16;
TFT_MESH_g = 102;
TFT_MESH_b = 102;
} else if (selected == 9) {
LOG_INFO("Setting color to Cyan");
TFT_MESH_r = 0;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else if (selected == 10) {
LOG_INFO("Setting color to Ice");
TFT_MESH_r = 173;
TFT_MESH_g = 216;
TFT_MESH_b = 230;
} else if (selected == 11) {
LOG_INFO("Setting color to Pink"); LOG_INFO("Setting color to Pink");
TFT_MESH_r = 255; TFT_MESH_r = 255;
TFT_MESH_g = 105; TFT_MESH_g = 105;
TFT_MESH_b = 180; TFT_MESH_b = 180;
} else if (selected == 9) { } else if (selected == 12) {
LOG_INFO("Setting color to White"); LOG_INFO("Setting color to White");
TFT_MESH_r = 255; TFT_MESH_r = 255;
TFT_MESH_g = 255; TFT_MESH_g = 255;
TFT_MESH_b = 255; TFT_MESH_b = 255;
} else if (selected == 13) {
LOG_INFO("Setting color to Gray");
TFT_MESH_r = 128;
TFT_MESH_g = 128;
TFT_MESH_b = 128;
} else { } else {
menuQueue = system_base_menu; menuQueue = system_base_menu;
screen->runNow(); screen->runNow();
@@ -1768,7 +1790,7 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
void menuHandler::saveUIConfig() void menuHandler::saveUIConfig()
{ {
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
} }
} // namespace graphics } // namespace graphics

View File

@@ -62,22 +62,12 @@ void InkHUD::HeardApplet::populateFromNodeDB()
{ {
// Fill a collection with pointers to each node in db // Fill a collection with pointers to each node in db
std::vector<meshtastic_NodeInfoLite *> ordered; std::vector<meshtastic_NodeInfoLite *> ordered;
for (auto mn = nodeDB->meshNodes->begin(); mn != nodeDB->meshNodes->end(); ++mn) { for (int i = 1; i < maxCards(); i++) {
// Only copy if valid, and not our own node auto mn = nodeDB->getMeshNodeByIndex(i);
if (mn->num != 0 && mn->num != nodeDB->getNodeNum()) if (mn->num != 0 && mn->num != nodeDB->getNodeNum())
ordered.push_back(&*mn); 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 // Create card info for these (stale) node observations
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
for (meshtastic_NodeInfoLite *node : ordered) { for (meshtastic_NodeInfoLite *node : ordered) {

View File

@@ -53,9 +53,9 @@ void CannedMessageStore::load()
// Attempt to load the bulk canned message data from flash // Attempt to load the bulk canned message data from flash
meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig; meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig;
LoadFileResult result = nodeDB->loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size, LoadFileResult result = loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size,
sizeof(meshtastic_CannedMessageModuleConfig), sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg,
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); &cannedMessageModuleConfig);
// Abort if nothing to load // Abort if nothing to load
if (result != LoadFileResult::LOAD_SUCCESS || strlen(cannedMessageModuleConfig.messages) == 0) if (result != LoadFileResult::LOAD_SUCCESS || strlen(cannedMessageModuleConfig.messages) == 0)
@@ -129,8 +129,8 @@ void CannedMessageStore::handleSet(const meshtastic_AdminMessage *request)
#endif #endif
// Write to flash // Write to flash
nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, &meshtastic_CannedMessageModuleConfig_msg,
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); &cannedMessageModuleConfig);
// Reload from flash, to update the canned messages in RAM // Reload from flash, to update the canned messages in RAM
// (This is a lazy way to handle it) // (This is a lazy way to handle it)

View File

@@ -439,6 +439,11 @@ void setup()
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n"); LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
// use PSRAM for malloc calls > 256 bytes
heap_caps_malloc_extmem_enable(256);
#endif
#if defined(DEBUG_MUTE) && defined(DEBUG_PORT) #if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n"); DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO)); DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));

View File

@@ -15,7 +15,6 @@
#include "RTC.h" #include "RTC.h"
#include "Router.h" #include "Router.h"
#include "SPILock.h" #include "SPILock.h"
#include "SafeFile.h"
#include "TypeConversions.h" #include "TypeConversions.h"
#include "error.h" #include "error.h"
#include "main.h" #include "main.h"
@@ -314,7 +313,7 @@ NodeDB::NodeDB()
LOG_DEBUG("Number of Device Reboots: %d", myNodeInfo.reboot_count); LOG_DEBUG("Number of Device Reboots: %d", myNodeInfo.reboot_count);
#endif #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); // 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 // Uncomment below to always enable UDP broadcasts
@@ -425,13 +424,13 @@ NodeDB::NodeDB()
config.has_position = true; config.has_position = true;
info->has_position = true; info->has_position = true;
info->position = TypeConversions::ConvertToPositionLite(fixedGPS); info->position = TypeConversions::ConvertToPositionLite(fixedGPS);
nodeDB->setLocalPosition(fixedGPS); nodeDB->_setLocalPosition(fixedGPS);
config.position.fixed_position = true; config.position.fixed_position = true;
#endif #endif
} }
#endif #endif
sortMeshDB(); sortMeshDB();
saveToDisk(saveWhat); _saveToDisk(saveWhat);
} }
/** /**
@@ -460,7 +459,7 @@ bool isBroadcast(uint32_t dest)
return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA; 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) { if (is_fresh_install) {
radioGeneration++; radioGeneration++;
@@ -480,6 +479,7 @@ void NodeDB::resetRadioConfig(bool is_fresh_install)
bool NodeDB::factoryReset(bool eraseBleBonds) bool NodeDB::factoryReset(bool eraseBleBonds)
{ {
FUNCTION_START("factoryReset");
LOG_INFO("Perform factory reset!"); LOG_INFO("Perform factory reset!");
// first, remove the "/prefs" (this removes most prefs) // first, remove the "/prefs" (this removes most prefs)
spiLock->lock(); spiLock->lock();
@@ -498,7 +498,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
installDefaultModuleConfig(); installDefaultModuleConfig();
installDefaultChannels(); installDefaultChannels();
// third, write everything to disk // third, write everything to disk
saveToDisk(); _saveToDisk();
if (eraseBleBonds) { if (eraseBleBonds) {
LOG_INFO("Erase BLE bonds"); LOG_INFO("Erase BLE bonds");
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@@ -513,6 +513,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
Bluefruit.Central.clearBonds(); Bluefruit.Central.clearBonds();
#endif #endif
} }
FUNCTION_END;
return true; return true;
} }
@@ -649,7 +650,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
config.security.serial_enabled = true; config.security.serial_enabled = true;
config.security.admin_channel_enabled = false; 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); 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) || \ #if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
@@ -746,7 +747,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#ifdef USERPREFS_CONFIG_DEVICE_ROLE #ifdef USERPREFS_CONFIG_DEVICE_ROLE
// Apply role-specific defaults when role is set via user preferences // Apply role-specific defaults when role is set via user preferences
installRoleDefaults(config.device.role); _installRoleDefaults(config.device.role);
#endif #endif
initConfigIntervals(); initConfigIntervals();
@@ -904,7 +905,7 @@ void NodeDB::installDefaultModuleConfig()
initModuleConfigIntervals(); initModuleConfigIntervals();
} }
void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) void NodeDB::_installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
{ {
if (role == meshtastic_Config_DeviceConfig_Role_ROUTER) { if (role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
initConfigIntervals(); initConfigIntervals();
@@ -990,6 +991,7 @@ void NodeDB::installDefaultChannels()
void NodeDB::resetNodes(bool keepFavorites) void NodeDB::resetNodes(bool keepFavorites)
{ {
FUNCTION_START("resetNodes");
if (!config.position.fixed_position) if (!config.position.fixed_position)
clearLocalPosition(); clearLocalPosition();
numMeshNodes = 1; numMeshNodes = 1;
@@ -1013,10 +1015,12 @@ void NodeDB::resetNodes(bool keepFavorites)
saveDeviceStateToDisk(); saveDeviceStateToDisk();
if (neighborInfoModule && moduleConfig.neighbor_info.enabled) if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
neighborInfoModule->resetNeighbors(); neighborInfoModule->resetNeighbors();
FUNCTION_END;
} }
void NodeDB::removeNodeByNum(NodeNum nodeNum) void NodeDB::removeNodeByNum(NodeNum nodeNum)
{ {
FUNCTION_START("removeNodeByNum");
int newPos = 0, removed = 0; int newPos = 0, removed = 0;
for (int i = 0; i < numMeshNodes; i++) { for (int i = 0; i < numMeshNodes; i++) {
if (meshNodes->at(i).num != nodeNum) if (meshNodes->at(i).num != nodeNum)
@@ -1029,16 +1033,17 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
meshtastic_NodeInfoLite()); meshtastic_NodeInfoLite());
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed); LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
saveNodeDatabaseToDisk(); 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.latitude_i = 0;
node->position.longitude_i = 0; node->position.longitude_i = 0;
node->position.altitude = 0; node->position.altitude = 0;
node->position.time = 0; node->position.time = 0;
setLocalPosition(meshtastic_Position_init_default); _setLocalPosition(meshtastic_Position_init_default);
} }
void NodeDB::cleanupMeshDB() void NodeDB::cleanupMeshDB()
@@ -1114,7 +1119,7 @@ void NodeDB::pickNewNodeNum()
} }
meshtastic_NodeInfoLite *found; 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 == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
if (found) if (found)
@@ -1128,39 +1133,6 @@ void NodeDB::pickNewNodeNum()
myNodeInfo.my_node_num = nodeNum; 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() void NodeDB::loadFromDisk()
{ {
// Mark the current device state as completely unusable, so that if we fail reading the entire file from // Mark the current device state as completely unusable, so that if we fail reading the entire file from
@@ -1260,7 +1232,7 @@ void NodeDB::loadFromDisk()
if (backupSecurity.private_key.size > 0) { if (backupSecurity.private_key.size > 0) {
LOG_DEBUG("Restoring backup of security config"); LOG_DEBUG("Restoring backup of security config");
config.security = backupSecurity; config.security = backupSecurity;
saveToDisk(SEGMENT_CONFIG); _saveToDisk(SEGMENT_CONFIG);
} }
// Make sure we load hard coded admin keys even when the configuration file has none. // Make sure we load hard coded admin keys even when the configuration file has none.
@@ -1311,7 +1283,7 @@ void NodeDB::loadFromDisk()
if (numAdminKeys > 0) { if (numAdminKeys > 0) {
LOG_INFO("Saving %d hard coded admin keys.", numAdminKeys); LOG_INFO("Saving %d hard coded admin keys.", numAdminKeys);
config.security.admin_key_count = numAdminKeys; config.security.admin_key_count = numAdminKeys;
saveToDisk(SEGMENT_CONFIG); _saveToDisk(SEGMENT_CONFIG);
} }
state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig), state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
@@ -1363,7 +1335,7 @@ void NodeDB::loadFromDisk()
if (moduleConfig.paxcounter.paxcounter_update_interval == 900) if (moduleConfig.paxcounter.paxcounter_update_interval == 900)
moduleConfig.paxcounter.paxcounter_update_interval = 0; moduleConfig.paxcounter.paxcounter_update_interval = 0;
saveToDisk(SEGMENT_MODULECONFIG); _saveToDisk(SEGMENT_MODULECONFIG);
} }
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
// set any config overrides // set any config overrides
@@ -1374,34 +1346,6 @@ void NodeDB::loadFromDisk()
#endif #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() bool NodeDB::saveChannelsToDisk()
{ {
#ifdef FSCom #ifdef FSCom
@@ -1490,7 +1434,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
return success; return success;
} }
bool NodeDB::saveToDisk(int saveWhat) bool NodeDB::_saveToDisk(int saveWhat)
{ {
LOG_DEBUG("Save to disk %d", saveWhat); LOG_DEBUG("Save to disk %d", saveWhat);
bool success = saveToDiskNoRetry(saveWhat); bool success = saveToDiskNoRetry(saveWhat);
@@ -1514,10 +1458,12 @@ bool NodeDB::saveToDisk(int saveWhat)
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex) const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
{ {
FUNCTION_START("readNextMeshNode");
meshtastic_NodeInfoLite *retVal = nullptr;
if (readIndex < numMeshNodes) if (readIndex < numMeshNodes)
return &meshNodes->at(readIndex++); retVal = &meshNodes->at(readIndex++);
else FUNCTION_END;
return NULL; return retVal;
} }
/// Given a node, return how many seconds in the past (vs now) that we last heard from it /// Given a node, return how many seconds in the past (vs now) that we last heard from it
@@ -1545,7 +1491,7 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline #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; size_t numseen = 0;
@@ -1567,8 +1513,10 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
*/ */
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src) void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
{ {
FUNCTION_START("updatePosition");
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
if (!info) { if (!info) {
FUNCTION_END;
return; return;
} }
@@ -1577,7 +1525,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, 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); p.altitude);
setLocalPosition(p); _setLocalPosition(p);
info->position = TypeConversions::ConvertToPositionLite(p); info->position = TypeConversions::ConvertToPositionLite(p);
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) { } 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 // FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
@@ -1604,7 +1552,8 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
} }
info->has_position = true; info->has_position = true;
updateGUIforNode = info; 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
FUNCTION_END;
} }
/** Update telemetry info for this node based on received metrics /** Update telemetry info for this node based on received metrics
@@ -1612,9 +1561,11 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
*/ */
void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src) void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src)
{ {
FUNCTION_START("updatePosition");
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
// Environment metrics should never go to NodeDb but we'll safegaurd anyway // Environment metrics should never go to NodeDb but we'll safegaurd anyway
if (!info || t.which_variant != meshtastic_Telemetry_device_metrics_tag) { if (!info || t.which_variant != meshtastic_Telemetry_device_metrics_tag) {
FUNCTION_END;
return; return;
} }
@@ -1627,7 +1578,8 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
info->device_metrics = t.variant.device_metrics; info->device_metrics = t.variant.device_metrics;
info->has_device_metrics = true; info->has_device_metrics = true;
updateGUIforNode = info; 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
FUNCTION_END;
} }
/** /**
@@ -1635,8 +1587,10 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
*/ */
void NodeDB::addFromContact(meshtastic_SharedContact contact) void NodeDB::addFromContact(meshtastic_SharedContact contact)
{ {
FUNCTION_START("addFromContact");
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
if (!info || !contact.has_user) { if (!info || !contact.has_user) {
FUNCTION_END;
return; return;
} }
// If the local node has this node marked as manually verified // If the local node has this node marked as manually verified
@@ -1645,6 +1599,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) { if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
if (contact.user.public_key.size != info->user.public_key.size || 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) { memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
FUNCTION_END;
return; return;
} }
} }
@@ -1688,22 +1643,26 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
// Mark the node's key as manually verified to indicate trustworthiness. // Mark the node's key as manually verified to indicate trustworthiness.
updateGUIforNode = info; updateGUIforNode = info;
sortMeshDB(); 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(); saveNodeDatabaseToDisk();
FUNCTION_END;
} }
/** Update user info and channel for this node based on received user data /** 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) bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex)
{ {
FUNCTION_START("updateUser");
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
if (!info) { if (!info) {
FUNCTION_END;
return false; return false;
} }
#if !(MESHTASTIC_EXCLUDE_PKI) #if !(MESHTASTIC_EXCLUDE_PKI)
if (p.public_key.size == 32 && nodeId != nodeDB->getNodeNum()) { if (p.public_key.size == 32 && nodeId != getNodeNum()) {
printBytes("Incoming Pubkey: ", p.public_key.bytes, 32); printBytes("Incoming Pubkey: ", p.public_key.bytes, 32);
// Alert the user if a remote node is advertising public key that matches our own // Alert the user if a remote node is advertising public key that matches our own
@@ -1720,6 +1679,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
sprintf(cn->message, warning, p.long_name); sprintf(cn->message, warning, p.long_name);
service->sendClientNotification(cn); service->sendClientNotification(cn);
} }
FUNCTION_END;
return false; return false;
} }
} }
@@ -1727,6 +1687,7 @@ 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 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)) { 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"); LOG_WARN("Public Key mismatch, dropping NodeInfo");
FUNCTION_END;
return false; return false;
} }
LOG_INFO("Public Key set for node, not updating!"); LOG_INFO("Public Key set for node, not updating!");
@@ -1754,19 +1715,19 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
if (changed) { if (changed) {
updateGUIforNode = info; 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, // We just changed something about a User,
// store our DB unless we just did so less than a minute ago // store our DB unless we just did so less than a minute ago
if (!Throttle::isWithinTimespanMs(lastNodeDbSave, ONE_MINUTE_MS)) { if (!Throttle::isWithinTimespanMs(lastNodeDbSave, ONE_MINUTE_MS)) {
saveToDisk(SEGMENT_NODEDATABASE); _saveToDisk(SEGMENT_NODEDATABASE);
lastNodeDbSave = millis(); lastNodeDbSave = millis();
} else { } else {
LOG_DEBUG("Defer NodeDB saveToDisk for now"); LOG_DEBUG("Defer NodeDB saveToDisk for now");
} }
} }
FUNCTION_END;
return changed; return changed;
} }
@@ -1774,107 +1735,121 @@ 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 /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
{ {
FUNCTION_START("updateFrom");
if (mp.from == getNodeNum()) { if (mp.from == getNodeNum()) {
LOG_DEBUG("Ignore update from self"); LOG_DEBUG("Ignore update from self");
return; } else if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
}
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); LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp)); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp));
if (!info) { 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();
} }
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) void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
{ {
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId); FUNCTION_START("set_favorite");
meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId);
if (lite && lite->is_favorite != is_favorite) { if (lite && lite->is_favorite != is_favorite) {
lite->is_favorite = is_favorite; lite->is_favorite = is_favorite;
sortMeshDB(); sortMeshDB();
saveNodeDatabaseToDisk(); saveNodeDatabaseToDisk();
} }
FUNCTION_END;
} }
// returns true if nodeId is_favorite; false if not or not found
bool NodeDB::isFavorite(uint32_t nodeId) bool NodeDB::isFavorite(uint32_t nodeId)
{ {
// returns true if nodeId is_favorite; false if not or not found FUNCTION_START("set_favorite");
// NODENUM_BROADCAST will never be in the DB // NODENUM_BROADCAST will never be in the DB
if (nodeId == NODENUM_BROADCAST) if (nodeId == NODENUM_BROADCAST) {
FUNCTION_END;
return false; return false;
}
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId); meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId);
if (lite) { if (lite) {
FUNCTION_END;
return lite->is_favorite; return lite->is_favorite;
} }
FUNCTION_END;
return false; return false;
} }
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p) bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
{ {
FUNCTION_START("isFromOrToFavoritedNode");
// This method is logically equivalent to: // This method is logically equivalent to:
// return isFavorite(p.from) || isFavorite(p.to); // return isFavorite(p.from) || isFavorite(p.to);
// but is more efficient by: // but is more efficient by:
// 1. doing only one pass through the database, instead of two // 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 // 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; meshtastic_NodeInfoLite *lite = NULL;
bool seenFrom = false; bool seenFrom = false;
bool seenTo = false; bool seenTo = false;
if (p.to == NODENUM_BROADCAST)
seenTo = true;
for (int i = 0; i < numMeshNodes; i++) { for (int i = 0; i < numMeshNodes; i++) {
lite = &meshNodes->at(i); lite = &meshNodes->at(i);
if (lite->num == p.from) { if (!seenFrom && lite->num == p.from) {
if (lite->is_favorite) if (lite->is_favorite) {
FUNCTION_END;
return true; return true;
}
seenFrom = true; seenFrom = true;
} }
if (lite->num == p.to) { if (!seenTo && lite->num == p.to) {
if (lite->is_favorite) if (lite->is_favorite) {
FUNCTION_END;
return true; return true;
}
seenTo = true; seenTo = true;
} }
if (seenFrom && seenTo) if (seenFrom && seenTo) {
FUNCTION_END;
return false; // we've seen both, and neither is a favorite, so we can stop searching early 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 // 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. // all favorited nodes first.
} }
FUNCTION_END;
return false; return false;
} }
void NodeDB::pause_sort(bool paused) 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; sortingIsPaused = paused;
FUNCTION_END;
} }
void NodeDB::sortMeshDB() void NodeDB::sortMeshDB()
@@ -1909,10 +1884,13 @@ void NodeDB::sortMeshDB()
uint8_t NodeDB::getMeshNodeChannel(NodeNum n) uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
{ {
const meshtastic_NodeInfoLite *info = getMeshNode(n); FUNCTION_START("getMeshNodeChannel");
const meshtastic_NodeInfoLite *info = _getMeshNode(n);
if (!info) { if (!info) {
FUNCTION_END;
return 0; // defaults to PRIMARY return 0; // defaults to PRIMARY
} }
FUNCTION_END;
return info->channel; return info->channel;
} }
@@ -1925,7 +1903,7 @@ std::string NodeDB::getNodeId() const
/// Find a node in our DB, return null for missing /// Find a node in our DB, return null for missing
/// NOTE: This function might be called from an ISR /// 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++) for (int i = 0; i < numMeshNodes; i++)
if (meshNodes->at(i).num == n) if (meshNodes->at(i).num == n)
@@ -1935,7 +1913,7 @@ meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
} }
// returns true if the maximum number of nodes is reached or we are running low on memory // 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); return (numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP);
} }
@@ -1943,7 +1921,7 @@ bool NodeDB::isFull()
/// Find a node in our DB, create an empty NodeInfo if missing /// Find a node in our DB, create an empty NodeInfo if missing
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
{ {
meshtastic_NodeInfoLite *lite = getMeshNode(n); meshtastic_NodeInfoLite *lite = _getMeshNode(n);
if (!lite) { if (!lite) {
if (isFull()) { if (isFull()) {
@@ -1998,18 +1976,25 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
/// valid lat/lon /// valid lat/lon
bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n) bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n)
{ {
return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0); FUNCTION_START("hasValidPosition");
auto retVal = n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0);
FUNCTION_END;
return retVal;
} }
/// If we have a node / user and they report is_licensed = true /// If we have a node / user and they report is_licensed = true
/// we consider them licensed /// we consider them licensed
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum) UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
{ {
meshtastic_NodeInfoLite *info = getMeshNode(nodeNum); FUNCTION_START("getLicenseStatus");
meshtastic_NodeInfoLite *info = _getMeshNode(nodeNum);
if (!info || !info->has_user) { if (!info || !info->has_user) {
FUNCTION_END;
return UserLicenseStatus::NotKnown; return UserLicenseStatus::NotKnown;
} }
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed; auto retVal = info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
FUNCTION_END;
return retVal;
} }
#if !defined(MESHTASTIC_EXCLUDE_PKI) #if !defined(MESHTASTIC_EXCLUDE_PKI)
@@ -2031,6 +2016,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location) bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
{ {
FUNCTION_START("backupPreferences");
bool success = false; bool success = false;
lastBackupAttempt = millis(); lastBackupAttempt = millis();
#ifdef FSCom #ifdef FSCom
@@ -2064,11 +2050,13 @@ bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
// TODO: After more mainline SD card support // TODO: After more mainline SD card support
} }
#endif #endif
FUNCTION_END;
return success; return success;
} }
bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat) bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat)
{ {
FUNCTION_START("backupPreferences");
bool success = false; bool success = false;
#ifdef FSCom #ifdef FSCom
if (location == meshtastic_AdminMessage_BackupLocation_FLASH) { if (location == meshtastic_AdminMessage_BackupLocation_FLASH) {
@@ -2076,6 +2064,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
if (!FSCom.exists(backupFileName)) { if (!FSCom.exists(backupFileName)) {
spiLock->unlock(); spiLock->unlock();
LOG_WARN("Could not restore. No backup file found"); LOG_WARN("Could not restore. No backup file found");
FUNCTION_END;
return false; return false;
} else { } else {
spiLock->unlock(); spiLock->unlock();
@@ -2101,7 +2090,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
LOG_DEBUG("Restored channels"); LOG_DEBUG("Restored channels");
} }
success = saveToDisk(restoreWhat); success = _saveToDisk(restoreWhat);
if (success) { if (success) {
LOG_INFO("Restored preferences from backup"); LOG_INFO("Restored preferences from backup");
} else { } else {
@@ -2113,6 +2102,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
} else if (location == meshtastic_AdminMessage_BackupLocation_SD) { } else if (location == meshtastic_AdminMessage_BackupLocation_SD) {
// TODO: After more mainline SD card support // TODO: After more mainline SD card support
} }
FUNCTION_END;
return success; return success;
#endif #endif
} }

View File

@@ -18,6 +18,13 @@
#include "PortduinoGlue.h" #include "PortduinoGlue.h"
#endif #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) #if !defined(MESHTASTIC_EXCLUDE_PKI)
// E3B0C442 is the blank hash // E3B0C442 is the blank hash
static const uint8_t LOW_ENTROPY_HASHES[][32] = { static const uint8_t LOW_ENTROPY_HASHES[][32] = {
@@ -110,19 +117,6 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
/// Given a packet, return how many seconds in the past (vs now) it was received /// Given a packet, return how many seconds in the past (vs now) it was received
uint32_t sinceReceived(const meshtastic_MeshPacket *p); 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 }; enum UserLicenseStatus { NotKnown, NotLicensed, Licensed };
class NodeDB class NodeDB
@@ -135,7 +129,6 @@ class NodeDB
// Note: these two references just point into our static array we serialize to/from disk // Note: these two references just point into our static array we serialize to/from disk
public: public:
std::vector<meshtastic_NodeInfoLite> *meshNodes;
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled 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 meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
Observable<const meshtastic::NodeStatus *> newStatus; Observable<const meshtastic::NodeStatus *> newStatus;
@@ -151,17 +144,26 @@ class NodeDB
/// write to flash /// write to flash
/// @return true if the save was successful /// @return true if the save was successful
bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS | bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS |
SEGMENT_NODEDATABASE); SEGMENT_NODEDATABASE)
{
FUNCTION_START("saveToDisk");
auto retVal = _saveToDisk(saveWhat);
FUNCTION_END;
return retVal;
}
/** Reinit radio config if needed, because either: /** Reinit radio config if needed, because either:
* a) sometimes a buggy android app might send us bogus settings or * a) sometimes a buggy android app might send us bogus settings or
* b) the client set factory_reset * 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 * @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); void resetRadioConfig(bool is_fresh_install = false)
{
FUNCTION_START("resetRadioConfig");
_resetRadioConfig(is_fresh_install);
FUNCTION_END;
}
/// given a subpacket sniffed from the network, update our DB state /// 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 /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
@@ -208,7 +210,13 @@ class NodeDB
std::string getNodeId() const; std::string getNodeId() const;
// @return last byte of a NodeNum, 0xFF if it ended at 0x00 // @return last byte of a NodeNum, 0xFF if it ended at 0x00
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); } uint8_t getLastByteOfNodeNum(NodeNum num)
{
FUNCTION_START("getLastByteOfNodeNum");
auto retVal = (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF);
FUNCTION_END;
return retVal;
}
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use /// 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); // bool handleWantNodeNum(NodeNum n);
@@ -227,79 +235,104 @@ class NodeDB
/* Return the number of nodes we've heard from recently (within the last 2 hrs?) /* Return the number of nodes we've heard from recently (within the last 2 hrs?)
* @param localOnly if true, ignore nodes heard via MQTT * @param localOnly if true, ignore nodes heard via MQTT
*/ */
size_t getNumOnlineMeshNodes(bool localOnly = false); size_t getNumOnlineMeshNodes(bool localOnly = false)
{
FUNCTION_START("getNumOnlineMeshNodes");
auto retVal = _getNumOnlineMeshNodes(localOnly);
FUNCTION_END;
return retVal;
}
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false), void resetNodes(bool keepFavorites = false), removeNodeByNum(NodeNum nodeNum);
removeNodeByNum(NodeNum nodeNum);
bool factoryReset(bool eraseBleBonds = false); bool factoryReset(bool eraseBleBonds = false);
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
void *dest_struct); {
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, FUNCTION_START("installRoleDefaults");
bool fullAtomic = true); _installRoleDefaults(role);
FUNCTION_END;
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); }
const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex); const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex);
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x) meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
{ {
assert(x < numMeshNodes); FUNCTION_START("getMeshNodeByIndex");
return &meshNodes->at(x); meshtastic_NodeInfoLite *retValue = nullptr;
if (x < numMeshNodes)
retValue = &meshNodes->at(x);
FUNCTION_END;
return retValue;
} }
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n); virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n)
size_t getNumMeshNodes() { return numMeshNodes; } {
FUNCTION_START("getMeshNode");
auto retVal = _getMeshNode(n);
FUNCTION_END;
return retVal;
}
size_t getNumMeshNodes()
{
FUNCTION_START("getNumMeshNodes");
auto retVal = numMeshNodes;
FUNCTION_END;
return retVal;
}
UserLicenseStatus getLicenseStatus(uint32_t nodeNum); UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
size_t getMaxNodesAllocatedSize() // returns true if the maximum number of nodes is reached or we are running low on memory
bool isFull()
{ {
meshtastic_NodeDatabase emptyNodeDatabase; FUNCTION_START("isFull");
emptyNodeDatabase.version = DEVICESTATE_CUR_VER; auto retVal = _isFull();
size_t nodeDatabaseSize; FUNCTION_END;
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase); return retVal;
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size);
} }
// returns true if the maximum number of nodes is reached or we are running low on memory void clearLocalPosition()
bool isFull(); {
FUNCTION_START("clearLocalPosition");
void clearLocalPosition(); _clearLocalPosition();
FUNCTION_END;
}
void setLocalPosition(meshtastic_Position position, bool timeOnly = false) void setLocalPosition(meshtastic_Position position, bool timeOnly = false)
{ {
if (timeOnly) { FUNCTION_START("setLocalPosition");
LOG_DEBUG("Set local position time only: time=%u timestamp=%u", position.time, position.timestamp); _setLocalPosition(position, timeOnly);
localPosition.time = position.time; FUNCTION_END;
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); 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 backupPreferences(meshtastic_AdminMessage_BackupLocation location);
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location, bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS); int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
/// Notify observers of changes to the DB
void notifyObservers(bool forceUpdate = false) void notifyObservers(bool forceUpdate = false)
{ {
// Notify observers of the current node state FUNCTION_START("notifyObservers");
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate); _notifyObservers(forceUpdate);
newStatus.notifyObservers(&status); FUNCTION_END;
} }
private: 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);
newStatus.notifyObservers(&status);
}
std::vector<meshtastic_NodeInfoLite> *meshNodes;
bool duplicateWarned = false; bool duplicateWarned = false;
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash 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 uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
@@ -333,6 +366,51 @@ class NodeDB
bool saveDeviceStateToDisk(); bool saveDeviceStateToDisk();
bool saveNodeDatabaseToDisk(); bool saveNodeDatabaseToDisk();
void sortMeshDB(); 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; extern NodeDB *nodeDB;

View File

@@ -37,8 +37,8 @@
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool; static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool; Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
#elif defined(ARCH_STM32WL) #elif defined(ARCH_STM32WL) || defined(BOARD_HAS_PSRAM)
// On STM32 there isn't enough heap left over for the rest of the firmware if we allocate this statically. // On STM32 and boards with PSRAM, there isn't enough heap left over for the rest of the firmware if we allocate this statically.
// For now, make it dynamic again. // For now, make it dynamic again.
#define MAX_PACKETS \ #define MAX_PACKETS \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \

View File

@@ -1322,7 +1322,7 @@ void AdminModule::saveChanges(int saveWhat, bool shouldReboot)
void AdminModule::handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg) void AdminModule::handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg)
{ {
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uicfg); saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uicfg);
} }
void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p) void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)

View File

@@ -2273,9 +2273,9 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
void CannedMessageModule::loadProtoForModule() void CannedMessageModule::loadProtoForModule()
{ {
if (nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, if (loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg,
&cannedMessageModuleConfig) != LoadFileResult::LOAD_SUCCESS) { &cannedMessageModuleConfig) != LoadFileResult::LOAD_SUCCESS) {
installDefaultCannedMessageModuleConfig(); installDefaultCannedMessageModuleConfig();
} }
} }
@@ -2295,8 +2295,8 @@ bool CannedMessageModule::saveProtoForModule()
spiLock->unlock(); spiLock->unlock();
#endif #endif
okay &= nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, okay &= saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
return okay; return okay;
} }

View File

@@ -14,6 +14,7 @@
* @date [Insert Date] * @date [Insert Date]
*/ */
#include "ExternalNotificationModule.h" #include "ExternalNotificationModule.h"
#include "FSCommon.h"
#include "MeshService.h" #include "MeshService.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "RTC.h" #include "RTC.h"
@@ -370,8 +371,8 @@ ExternalNotificationModule::ExternalNotificationModule()
if (inputBroker) // put our callback in the inputObserver list if (inputBroker) // put our callback in the inputObserver list
inputObserver.observe(inputBroker); inputObserver.observe(inputBroker);
#endif #endif
if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), if (loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), &meshtastic_RTTTLConfig_msg,
&meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) { &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) {
memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone));
// The default ringtone is always loaded from userPrefs.jsonc // The default ringtone is always loaded from userPrefs.jsonc
strncpy(rtttlConfig.ringtone, USERPREFS_RINGTONE_RTTTL, sizeof(rtttlConfig.ringtone)); strncpy(rtttlConfig.ringtone, USERPREFS_RINGTONE_RTTTL, sizeof(rtttlConfig.ringtone));
@@ -640,7 +641,7 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg)
} }
if (changed) { if (changed) {
nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig);
} }
} }

View File

@@ -64,16 +64,6 @@ void onConnect(uint16_t conn_handle)
connection->getPeerName(central_name, sizeof(central_name)); connection->getPeerName(central_name, sizeof(central_name));
LOG_INFO("BLE Connected to %s", central_name); LOG_INFO("BLE Connected to %s", central_name);
// negotiate connections params as soon as possible
ble_gap_conn_params_t newParams;
newParams.min_conn_interval = 24;
newParams.max_conn_interval = 40;
newParams.slave_latency = 5;
newParams.conn_sup_timeout = 400;
sd_ble_gap_conn_param_update(conn_handle, &newParams);
// Notify UI (or any other interested firmware components) // Notify UI (or any other interested firmware components)
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newStatus); bluetoothStatus->updateStatus(&newStatus);
@@ -129,7 +119,7 @@ void startAdv(void)
Bluefruit.Advertising.addService(meshBleService); Bluefruit.Advertising.addService(meshBleService);
/* Start Advertising /* Start Advertising
* - Enable auto advertising if disconnected * - Enable auto advertising if disconnected
* - Interval: fast mode = 20 ms, slow mode = 417,5 ms * - Interval: fast mode = 20 ms, slow mode = 152.5 ms
* - Timeout for fast mode is 30 seconds * - Timeout for fast mode is 30 seconds
* - Start(timeout) with timeout = 0 will advertise forever (until connected) * - Start(timeout) with timeout = 0 will advertise forever (until connected)
* *
@@ -137,7 +127,7 @@ void startAdv(void)
* https://developer.apple.com/library/content/qa/qa1931/_index.html * https://developer.apple.com/library/content/qa/qa1931/_index.html
*/ */
Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X
} }
@@ -282,24 +272,6 @@ void NRF52Bluetooth::setup()
// Set the connect/disconnect callback handlers // Set the connect/disconnect callback handlers
Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setConnectCallback(onConnect);
Bluefruit.Periph.setDisconnectCallback(onDisconnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect);
// Set slave latency to 5 to conserve power
// Despite name this does not impact data transfer
// https://docs.silabs.com/bluetooth/2.13/bluetooth-general-system-and-performance/optimizing-current-consumption-in-bluetooth-low-energy-devices
Bluefruit.Periph.setConnSlaveLatency(5);
// TODO: Adafruit defaul min, max interval seems to be (20,30) [in 1.25 ms units] -> (25.00, 31.25) milliseconds
// so using formula Interval Max * (Slave Latency + 1) ≤ 2 seconds
// max slave latency we can use is 30 (max available in BLE)
// and even double max inteval (see apple doc linked above for formulas)
// See Periph.SetConnInterval method
// Tweak this later for even more power savings once those changes are confirmed to work well.
// Changing min, max interval may slow BLE transfer a bit - bumping slave latency will most likely not.
#ifndef BLE_DFU_SECURE #ifndef BLE_DFU_SECURE
bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
bledfu.begin(); // Install the DFU helper bledfu.begin(); // Install the DFU helper
@@ -328,7 +300,7 @@ void NRF52Bluetooth::setup()
void NRF52Bluetooth::resumeAdvertising() void NRF52Bluetooth::resumeAdvertising()
{ {
Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); Bluefruit.Advertising.start(0);
} }

View File

@@ -5,7 +5,7 @@ custom_esp32_kind = esp32
custom_mtjson_part = custom_mtjson_part =
platform = platform =
# renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32 # renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32
platformio/espressif32@6.12.0 platformio/espressif32@6.11.0
extra_scripts = extra_scripts =
${env.extra_scripts} ${env.extra_scripts}

View File

@@ -128,6 +128,3 @@ build_flags =
${crowpanel_large_esp32s3_base.build_flags} ${crowpanel_large_esp32s3_base.build_flags}
-D VIEW_320x240 -D VIEW_320x240
-D DISPLAY_SIZE=800x480 ; landscape mode -D DISPLAY_SIZE=800x480 ; landscape mode
build_src_filter =
${esp32s3_base.build_src_filter}
+<../variants/esp32s3/elecrow_panel>

View File

@@ -1,21 +0,0 @@
// meshtastic/firmware/variants/elecrow_panel/variant.cpp
#include "variant.h"
#include "Arduino.h"
#include "Wire.h"
bool elecrow_v2 = false; // false = v1, true = v2
extern "C" {
void initVariant()
{
Wire.begin(I2C_SDA, I2C_SCL, 100000);
delay(50);
Wire.beginTransmission(0x30);
if (Wire.endTransmission() == 0) {
elecrow_v2 = true;
}
Wire.end();
}
}

View File

@@ -1,8 +1,6 @@
#define I2C_SDA 15 #define I2C_SDA 15
#define I2C_SCL 16 #define I2C_SCL 16
extern bool elecrow_v2; // false = v1, true = v2
#if CROW_SELECT == 1 #if CROW_SELECT == 1
#define WAKE_ON_TOUCH #define WAKE_ON_TOUCH
#define SCREEN_TOUCH_INT 47 #define SCREEN_TOUCH_INT 47
@@ -19,7 +17,7 @@ extern bool elecrow_v2; // false = v1, true = v2
#define DAC_I2S_DOUT 12 #define DAC_I2S_DOUT 12
#define DAC_I2S_MCLK 8 // don't use GPIO0 because it's assigned to LoRa or button #define DAC_I2S_MCLK 8 // don't use GPIO0 because it's assigned to LoRa or button
#else #else
#define PIN_BUZZER (elecrow_v2 ? 0 : 8) #define PIN_BUZZER 8
#endif #endif
// GPS via UART1 connector // GPS via UART1 connector
@@ -74,7 +72,7 @@ extern bool elecrow_v2; // false = v1, true = v2
#define SENSOR_POWER_ON LOW #define SENSOR_POWER_ON LOW
#else #else
// 4.3", 5.0", 7.0" // 4.3", 5.0", 7.0"
#define LORA_CS (elecrow_v2 ? 8 : 0) #define LORA_CS 0
#define LORA_SCK 5 #define LORA_SCK 5
#define LORA_MISO 4 #define LORA_MISO 4
#define LORA_MOSI 6 #define LORA_MOSI 6

View File

@@ -21,8 +21,8 @@
/** Master clock frequency */ /** Master clock frequency */
#define VARIANT_MCK (64000000ul) #define VARIANT_MCK (64000000ul)
//#define USE_LFXO // Board uses 32khz crystal for LF #define USE_LFXO // Board uses 32khz crystal for LF
#define USE_LFRC // Board uses RC for LF
/*---------------------------------------------------------------------------- /*----------------------------------------------------------------------------
* Headers * Headers
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/

View File

@@ -22,9 +22,7 @@
/** Master clock frequency */ /** Master clock frequency */
#define VARIANT_MCK (64000000ul) #define VARIANT_MCK (64000000ul)
//#define USE_LFXO // Board uses 32khz crystal for LF #define USE_LFXO // Board uses 32khz crystal for LF
#define USE_LFRC
/*---------------------------------------------------------------------------- /*----------------------------------------------------------------------------
* Headers * Headers