Compare commits

..
Author SHA1 Message Date
Ben Meadors 7e6a74f06c Remove unused Adafruit_SSD1306 library dependency 2026-05-13 07:00:33 -05:00
226 changed files with 846 additions and 7216 deletions
+3 -7
View File
@@ -8,21 +8,17 @@
"features": {
"ghcr.io/devcontainers/features/python:1": {
"installTools": true,
"version": "3.13"
"version": "3.14"
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.cpptools",
"Jason2866.esp-decoder",
"pioarduino.pioarduino-ide",
"platformio.platformio-ide",
"Trunk.io"
],
"unwantedRecommendations": [
"ms-azuretools.vscode-docker",
"platformio.platformio-ide"
],
"unwantedRecommendations": ["ms-azuretools.vscode-docker"],
"settings": {
"extensions.ignoreRecommendations": true
}
-4
View File
@@ -47,10 +47,6 @@ data/boot/logo.*
managed_components/*
arduino-lib-builder*
dependencies.lock
# JLink / RTT debug artifacts (nRF SoCs)
flash.jlink
rtt_*.txt
idf_component.yml
CMakeLists.txt
/sdkconfig.*
+5 -5
View File
@@ -4,19 +4,19 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.10.0
ref: v1.9.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.529
- checkov@3.2.528
- renovate@43.150.0
- prettier@3.8.3
- trufflehog@3.95.3
- trufflehog@3.95.2
- yamllint@1.38.0
- bandit@1.9.4
- trivy@0.70.0
- taplo@0.10.0
- ruff@0.15.13
- ruff@0.15.12
- isort@8.0.1
- markdownlint@0.48.0
- oxipng@10.1.1
@@ -26,7 +26,7 @@ lint:
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@26.5.1
- black@26.3.1
- git-diff-check
- gitleaks@8.30.1
- clang-format@16.0.3
+4 -4
View File
@@ -1,10 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"Jason2866.esp-decoder",
"pioarduino.pioarduino-ide"
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack",
"platformio.platformio-ide"
"ms-vscode.cpptools-extension-pack"
]
}
+1 -1
View File
@@ -38,4 +38,4 @@ cp bin/device-install.* $OUTDIR/
cp bin/device-update.* $OUTDIR/
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true
-18
View File
@@ -1,18 +0,0 @@
# Station G3 motherboard with a Raspberry Pi Zero 2W as the MCU daughterboard.
# Verify spidev / I2C device paths for your OS — they may differ.
Meta:
name: Station G3
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262
IRQ: 22 # BCM pin — wiki spec
Reset: 16 # BCM pin — wiki spec
Busy: 24 # BCM pin — wiki spec
# CS: 8 # BCM 8 = SPI0 CE0 (default); uncomment only to override
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
spidev: spidev0.0
# SX126X_MAX_POWER: 19 # matches Station G2 firmware cap; raise carefully per PA jumper mode
+4 -8
View File
@@ -293,12 +293,9 @@ if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
board_arch = infer_architecture(env.BoardConfig())
should_skip_manifest = board_arch is None
# Most platforms can generate the manifest as part of the default 'buildprog' target.
# Typically this passes success/failure properly.
mtjson_deps = ["buildprog"]
if platform.name == "espressif32":
# On ESP32, we need to explicitly depend upon the binary to prevent fake-success upon failure.
mtjson_deps = ["$BUILD_DIR/${PROGNAME}.bin"]
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
mtjson_deps = [] if should_skip_manifest else ["buildprog"]
if not should_skip_manifest and platform.name == "espressif32":
# Build littlefs image as part of mtjson target
# Equivalent to `pio run -t buildfs`
target_lfs = env.DataToBin(
@@ -312,8 +309,7 @@ if should_skip_manifest:
env.AddCustomTarget(
name="mtjson",
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
dependencies=[],
dependencies=mtjson_deps,
actions=[skip_manifest],
title="Meshtastic Manifest (skipped)",
description="mtjson generation is skipped for native environments",
+1 -1
View File
@@ -7,7 +7,7 @@
"extra_flags": [
"-D CDEBYTE_EORA_S3",
"-D ARDUINO_USB_CDC_ON_BOOT=1",
"-D ARDUINO_USB_MODE=1",
"-D ARDUINO_USB_MODE=0",
"-D ARDUINO_RUNNING_CORE=1",
"-D ARDUINO_EVENT_RUNNING_CORE=1",
"-D BOARD_HAS_PSRAM"
+1 -1
View File
@@ -6,7 +6,7 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DBOARD_HAS_PSRAM"
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -9,7 +9,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -9,7 +9,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -9,7 +9,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DHELTEC_WIRELESS_TRACKER",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -7,7 +7,7 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
-26
View File
@@ -1,26 +0,0 @@
{
"build": {
"cpu": "cortex-m33",
"f_cpu": "128000000L",
"mcu": "nrf54l15",
"zephyr": {
"variant": "nrf54l15dk/nrf54l15/cpuapp"
}
},
"connectivity": ["bluetooth"],
"debug": {
"default_tools": ["jlink"],
"jlink_device": "nRF54L15_M33",
"svd_path": "nrf54l15.svd"
},
"frameworks": ["zephyr"],
"name": "Nordic nRF54L15-DK (PCA10156)",
"upload": {
"maximum_ram_size": 262144,
"maximum_size": 1572864,
"protocol": "jlink",
"protocols": ["jlink"]
},
"url": "https://www.nordicsemi.com/Products/nRF54L15",
"vendor": "Nordic Semiconductor"
}
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0"
],
-41
View File
@@ -1,41 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "station-g3"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "BQ Station G3",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "",
"vendor": "BQ Consulting"
}
+1 -1
View File
@@ -9,7 +9,7 @@
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_1W",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -8,7 +8,7 @@
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_S3_CORE",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -7,7 +7,7 @@
"extra_flags": [
"-DLILYGO_T3S3_V1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DBOARD_HAS_PSRAM"
+1 -1
View File
@@ -10,7 +10,7 @@
"-DBOARD_HAS_PSRAM",
"-DUNPHONE_SPIN=9",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
-7
View File
@@ -1,7 +0,0 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x640000,
app1, app, ota_1, 0x650000,0x640000,
spiffs, data, spiffs, 0xc90000,0x360000,
coredump, data, coredump,0xFF0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x640000
5 app1 app ota_1 0x650000 0x640000
6 spiffs data spiffs 0xc90000 0x360000
7 coredump data coredump 0xFF0000 0x10000
-7
View File
@@ -1,7 +0,0 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x330000,
app1, app, ota_1, 0x340000,0x330000,
spiffs, data, spiffs, 0x670000,0x180000,
coredump, data, coredump,0x7F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x330000
5 app1 app ota_1 0x340000 0x330000
6 spiffs data spiffs 0x670000 0x180000
7 coredump data coredump 0x7F0000 0x10000
+14 -3
View File
@@ -70,6 +70,17 @@ def esp32_create_combined_bin(source, target, env):
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
# Enable Newlib Nano formatting to save space
# ...but allow printf float support (compromise)
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
esp32_kind = env.GetProjectOption("custom_esp32_kind")
if esp32_kind == "esp32":
# Free up some IRAM by removing auxiliary SPI flash chip drivers.
# Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
env.Append(
LINKFLAGS=[
"-Wl,--wrap=esp_flash_chip_gd",
"-Wl,--wrap=esp_flash_chip_issi",
"-Wl,--wrap=esp_flash_chip_winbond",
]
)
else:
# For newer ESP32 targets, using newlib nano works better.
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
-23
View File
@@ -1,23 +0,0 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
# force linker response file instead of command line arguments
Import("env")
def wrap_with_tempfile(command_key):
command = env.get(command_key)
if not command or not isinstance(command, str):
return
if "TEMPFILE(" in command:
return
env.Replace(**{command_key: "${TEMPFILE('%s')}" % command})
# Force SCons to spill long commands into response files on this target.
env.Replace(MAXLINELENGTH=8192)
for key in ("LINKCOM", "CXXLINKCOM", "SHLINKCOM", "SHCXXLINKCOM"):
wrap_with_tempfile(key)
-140
View File
@@ -1,140 +0,0 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
#
# post:extra_scripts/nrf54l15_linker.py
#
# Fix for Zephyr two-pass link on nRF54L15:
# platformio-build.py registers env.Depends("$PROG_PATH", final_ld_script) but
# the SCons dependency chain is broken (final_ld_script Command never runs).
# This script adds a PreAction on the final firmware binary that runs the gcc
# preprocessing command directly (extracted from build.ninja) to generate
# zephyr/linker.cmd before the link step.
#
# PlatformIO bundles an old Ninja that can't handle multi-output depslog rules,
# so we parse the COMMAND line from build.ninja and run just the gcc -E part,
# skipping the cmake_transform_depfile step (only needed for Ninja deps tracking).
import os
import re
import subprocess
Import("env")
if env.get("PIOENV") != "nrf54l15dk":
pass # Only for the nrf54l15dk environment
else:
def _extract_gcc_command(ninja_build):
"""Parse build.ninja to find the gcc -E command that generates linker.cmd.
The rule format depends on the host:
Windows (CMake's RunCMake wraps every command):
COMMAND = cmd.exe /C "cd /D DIR && arm-none-eabi-gcc.exe ... -o linker.cmd && cmake.exe -E cmake_transform_depfile ..."
POSIX (Linux/macOS — no wrapper):
COMMAND = cd DIR && arm-none-eabi-gcc ... -o linker.cmd && cmake -E cmake_transform_depfile ...
Returns (gcc_cmd_string, cwd_path) or raises RuntimeError.
"""
in_rule = False
with open(ninja_build, "r", encoding="utf-8", errors="replace") as f:
for line in f:
# Detect start of the linker.cmd custom command rule
if not in_rule:
if "build zephyr/linker.cmd" in line and "CUSTOM_COMMAND" in line:
in_rule = True
continue
stripped = line.strip()
if not stripped.startswith("COMMAND = "):
continue
command_val = stripped[len("COMMAND = ") :]
# On Windows the value is wrapped in `cmd.exe /C "..."` — strip
# the wrapper. On POSIX hosts the inner sequence is the value
# itself (no quoting layer).
m = re.search(r'/C\s+"(.*)"\s*$', command_val)
inner = m.group(1) if m else command_val
parts = inner.split(" && ")
cwd = None
gcc_cmd = None
for part in parts:
part = part.strip()
if part.startswith("cd /D "): # Windows form
cwd = part[len("cd /D ") :]
elif part.startswith("cd "): # POSIX form
cwd = part[len("cd ") :]
elif "arm-none-eabi-gcc" in part:
gcc_cmd = part
if not gcc_cmd:
raise RuntimeError(
"nRF54L15 linker fix: arm-none-eabi-gcc command not found in:\n%s"
% inner[:400]
)
return gcc_cmd, cwd
raise RuntimeError(
"nRF54L15 linker fix: 'build zephyr/linker.cmd' rule not found in build.ninja"
)
def _generate_linker_cmd(target, source, env):
"""Generate zephyr/linker.cmd via direct gcc invocation before the final link."""
build_dir = env.subst("$BUILD_DIR")
zephyr_dir = os.path.join(build_dir, "zephyr")
linker_cmd = os.path.join(zephyr_dir, "linker.cmd")
if os.path.exists(linker_cmd):
return # Already present — nothing to do
ninja_build = os.path.join(build_dir, "build.ninja")
if not os.path.exists(ninja_build):
raise RuntimeError(
"nRF54L15 linker fix: build.ninja not found at %s\n"
"Run a full build first so CMake generates the Ninja files."
% ninja_build
)
gcc_cmd, cwd = _extract_gcc_command(ninja_build)
run_cwd = cwd if cwd else zephyr_dir
print(
"==> nRF54L15: Generating zephyr/linker.cmd (LINKER_ZEPHYR_FINAL) via GCC"
)
# gcc_cmd comes verbatim from our own build.ninja (never user input) and
# contains Windows-style paths with spaces that cannot be safely argv-split
# with shlex, so we run it via the platform shell. nosec/nosemgrep below
# acknowledge this deliberate, scoped use of shell=True.
result = subprocess.run( # nosec B602
gcc_cmd,
shell=True, # nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true
cwd=run_cwd,
capture_output=True,
text=True,
)
if result.returncode != 0:
print("GCC stdout:", result.stdout[:2000])
print("GCC stderr:", result.stderr[:2000])
raise RuntimeError(
"nRF54L15 linker fix: GCC failed to generate linker.cmd (rc=%d)"
% result.returncode
)
if not os.path.exists(linker_cmd):
raise RuntimeError(
"nRF54L15 linker fix: GCC returned 0 but linker.cmd was not created at %s"
% linker_cmd
)
print("==> linker.cmd generated successfully")
# Use PIOMAINPROG (set by ZephyrBuildProgram) to get the exact SCons node
prog = env.get("PIOMAINPROG")
if prog:
env.AddPreAction(prog, _generate_linker_cmd)
else:
print(
"[nrf54l15_linker] WARNING: PIOMAINPROG not set, falling back to $PROG_PATH"
)
env.AddPreAction(env.subst("$PROG_PATH"), _generate_linker_cmd)
+2 -5
View File
@@ -2,7 +2,7 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = heltec-v3
default_envs = tbeam
extra_configs =
variants/*/*.ini
@@ -17,7 +17,6 @@ test_build_src = true
extra_scripts =
pre:bin/platformio-pre.py
bin/platformio-custom.py
post:extra_scripts/nrf54l15_linker.py
; note: we add src to our include search path so that lmic_project_config can override
; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile
; of code is a heap corruption bug!
@@ -139,9 +138,7 @@ lib_deps =
# renovate: datasource=github-tags depName=Adafruit GFX packageName=adafruit/Adafruit-GFX-Library
https://github.com/adafruit/Adafruit-GFX-Library/archive/refs/tags/1.12.6.zip
# renovate: datasource=github-tags depName=NeoPixel packageName=adafruit/Adafruit_NeoPixel
https://github.com/adafruit/Adafruit_NeoPixel/archive/1.15.5.zip
# renovate: datasource=github-tags depName=Adafruit SSD1306 packageName=adafruit/Adafruit_SSD1306
https://github.com/adafruit/Adafruit_SSD1306/archive/refs/tags/2.5.16.zip
https://github.com/adafruit/Adafruit_NeoPixel/archive/refs/tags/1.15.4.zip
# renovate: datasource=github-tags depName=Adafruit BMP280 packageName=adafruit/Adafruit_BMP280_Library
https://github.com/adafruit/Adafruit_BMP280_Library/archive/refs/tags/3.0.0.zip
# renovate: datasource=github-tags depName=Adafruit BMP085 packageName=adafruit/Adafruit-BMP085-Library
+3 -2
View File
@@ -158,8 +158,9 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
#include <RAK13800_W5100S.h>
#endif // HAS_ETHERNET
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
#if HAS_WIFI
+1 -1
View File
@@ -277,7 +277,7 @@ void fsInit()
*/
void setupSDCard()
{
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) && !defined(HAS_SD_MMC)
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
concurrency::LockGuard g(spiLock);
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {
-8
View File
@@ -48,14 +48,6 @@ using namespace STM32_LittleFS_Namespace;
using namespace Adafruit_LittleFS_Namespace;
#endif
#if defined(ARCH_NRF54L15)
// nRF54L15 — Zephyr LittleFS on 36 KB storage_partition (internal RRAM)
#include "InternalFileSystem.h"
#define FSCom InternalFS
#define FSBegin() FSCom.begin()
using namespace Adafruit_LittleFS_Namespace;
#endif
void fsInit();
void fsListFiles();
bool copyFile(const char *from, const char *to);
+84 -136
View File
@@ -24,13 +24,6 @@
#include "meshUtils.h"
#include "power/PowerHAL.h"
#include "sleep.h"
#ifdef ARCH_ESP32
// #include <driver/adc.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_err.h>
#endif
#if defined(ARCH_PORTDUINO)
#include "api/WiFiServerAPI.h"
@@ -70,8 +63,9 @@
#include <WiFi.h>
#endif
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
#endif
@@ -83,86 +77,21 @@
#if defined(BATTERY_PIN) && defined(ARCH_ESP32)
#ifndef BAT_MEASURE_ADC_UNIT // ADC1 is default
static const adc_channel_t adc_channel = ADC_CHANNEL;
static const adc1_channel_t adc_channel = ADC_CHANNEL;
static const adc_unit_t unit = ADC_UNIT_1;
#else // ADC2
static const adc_channel_t adc_channel = ADC_CHANNEL;
#else // ADC2
static const adc2_channel_t adc_channel = ADC_CHANNEL;
static const adc_unit_t unit = ADC_UNIT_2;
RTC_NOINIT_ATTR uint64_t RTC_reg_b;
#endif // BAT_MEASURE_ADC_UNIT
static adc_oneshot_unit_handle_t adc_handle = nullptr;
static adc_cali_handle_t adc_cali_handle = nullptr;
static bool adc_calibrated = false;
esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t));
#ifndef ADC_ATTENUATION
static const adc_atten_t atten = ADC_ATTEN_DB_12;
#else
static const adc_atten_t atten = ADC_ATTENUATION;
#endif
#ifdef ADC_BITWIDTH
static const adc_bitwidth_t adc_width = ADC_BITWIDTH;
#else
static const adc_bitwidth_t adc_width = ADC_BITWIDTH_DEFAULT;
#endif
static int adcBitWidthToBits(adc_bitwidth_t width)
{
switch (width) {
case ADC_BITWIDTH_9:
return 9;
case ADC_BITWIDTH_10:
return 10;
case ADC_BITWIDTH_11:
return 11;
case ADC_BITWIDTH_12:
return 12;
#ifdef ADC_BITWIDTH_13
case ADC_BITWIDTH_13:
return 13;
#endif
default:
return 12;
}
}
static bool initAdcCalibration()
{
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = adc_width,
};
esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle);
if (ret == ESP_OK) {
LOG_INFO("ADC calibration: curve fitting enabled");
return true;
}
if (ret != ESP_ERR_NOT_SUPPORTED) {
LOG_WARN("ADC calibration: curve fitting failed: %s", esp_err_to_name(ret));
}
#endif
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
adc_cali_line_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = adc_width,
.default_vref = DEFAULT_VREF,
};
esp_err_t ret = adc_cali_create_scheme_line_fitting(&cali_config, &adc_cali_handle);
if (ret == ESP_OK) {
LOG_INFO("ADC calibration: line fitting enabled");
return true;
}
if (ret != ESP_ERR_NOT_SUPPORTED) {
LOG_WARN("ADC calibration: line fitting failed: %s", esp_err_to_name(ret));
}
#endif
LOG_INFO("ADC calibration not supported; using approximate scaling");
return false;
}
#endif // BATTERY_PIN && ARCH_ESP32
#ifdef EXT_PWR_DETECT
@@ -438,20 +367,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
scaled *= operativeAdcMultiplier;
#elif defined(ARCH_ESP32) // ADC block for espressif platforms
raw = espAdcRead();
int voltage_mv = 0;
if (adc_calibrated && adc_cali_handle) {
if (adc_cali_raw_to_voltage(adc_cali_handle, raw, &voltage_mv) != ESP_OK) {
LOG_WARN("ADC calibration read failed; using raw value");
voltage_mv = 0;
}
}
if (voltage_mv == 0) {
// Fallback approximate conversion without calibration
const int bits = adcBitWidthToBits(adc_width);
const float max_code = powf(2.0f, bits) - 1.0f;
voltage_mv = (int)((raw / max_code) * DEFAULT_VREF);
}
scaled = voltage_mv * operativeAdcMultiplier;
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
scaled *= operativeAdcMultiplier;
#else // block for all other platforms
#ifdef ARCH_NRF52
concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock);
@@ -493,22 +410,51 @@ class AnalogBatteryLevel : public HasBatteryLevel
uint32_t raw = 0;
uint8_t raw_c = 0; // raw reading counter
if (!adc_handle) {
LOG_ERROR("ADC oneshot handle not initialized");
return 0;
}
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
int val = 0;
esp_err_t err = adc_oneshot_read(adc_handle, adc_channel, &val);
if (err == ESP_OK) {
raw += val;
int val_ = adc1_get_raw(adc_channel);
if (val_ >= 0) { // save only valid readings
raw += val_;
raw_c++;
}
// delayMicroseconds(100);
}
#else // ADC2
#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3
// ADC2 wifi bug workaround not required, breaks compile
// On ESP32S3, ADC2 can take turns with Wifi (?)
int32_t adc_buf;
esp_err_t read_result;
// Multiple samples
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
adc_buf = 0;
read_result = -1;
read_result = adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
if (read_result == ESP_OK) {
raw += adc_buf;
raw_c++; // Count valid samples
} else {
LOG_DEBUG("ADC read failed: %s", esp_err_to_name(err));
LOG_DEBUG("An attempt to sample ADC2 failed");
}
}
#else // Other ESP32
int32_t adc_buf = 0;
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
// ADC2 wifi bug workaround, see
// https://github.com/espressif/arduino-esp32/issues/102
WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, RTC_reg_b);
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
raw += adc_buf;
raw_c++;
}
#endif // BAT_MEASURE_ADC_UNIT
#endif // End BAT_MEASURE_ADC_UNIT
return (raw / (raw_c < 1 ? 1 : raw_c));
}
#endif
@@ -720,31 +666,42 @@ bool Power::analogInit()
#ifdef ARCH_STM32WL
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
#elif defined(ARCH_ESP32) // ESP32 needs special analog stuff
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = unit,
};
if (!adc_handle) {
esp_err_t err = adc_oneshot_new_unit(&init_config, &adc_handle);
if (err != ESP_OK) {
LOG_ERROR("ADC oneshot init failed: %s", esp_err_to_name(err));
return false;
}
#ifndef ADC_WIDTH // max resolution by default
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
#else
static const adc_bits_width_t width = ADC_WIDTH;
#endif
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
adc1_config_width(width);
adc1_config_channel_atten(adc_channel, atten);
#else // ADC2
adc2_config_channel_atten(adc_channel, atten);
#ifndef CONFIG_IDF_TARGET_ESP32S3
// ADC2 wifi bug workaround
// Not required with ESP32S3, breaks compile
RTC_reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG);
#endif
#endif
// calibrate ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs);
// show ADC characterization base
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
LOG_INFO("ADC config based on Two Point values stored in eFuse");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
LOG_INFO("ADC config based on reference voltage stored in eFuse");
}
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = atten,
.bitwidth = adc_width,
};
esp_err_t err = adc_oneshot_config_channel(adc_handle, adc_channel, &chan_cfg);
if (err != ESP_OK) {
LOG_ERROR("ADC channel config failed: %s", esp_err_to_name(err));
return false;
#ifdef CONFIG_IDF_TARGET_ESP32S3
// ESP32S3
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
LOG_INFO("ADC config based on Two Point values and fitting curve "
"coefficients stored in eFuse");
}
adc_calibrated = initAdcCalibration();
#endif // ARCH_ESP32
#endif
else {
LOG_INFO("ADC config based on default reference voltage");
}
#endif // ARCH_ESP32
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
@@ -947,16 +904,7 @@ void Power::readPowerStatus()
// Notify any status instances that are observing us
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
// Log battery-presence transitions once; skip OptUnknown so we don't lie before the first probe.
static OptionalBool prevHasBattery = OptUnknown;
if (hasBattery != OptUnknown && hasBattery != prevHasBattery) {
LOG_INFO("Power: battery hardware %s", hasBattery == OptTrue ? "detected" : "absent (USB-only)");
prevHasBattery = hasBattery;
}
// Periodic telemetry only emits when a battery is actually present (otherwise values are constant -1/0).
if (hasBattery == OptTrue && !Throttle::isWithinTimespanMs(lastLogTime, 50 * 1000)) {
if (millis() > lastLogTime + 50 * 1000) {
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
lastLogTime = millis();
-4
View File
@@ -225,8 +225,6 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_
isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected();
#elif defined(ARCH_NRF52)
isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected();
#elif defined(ARCH_NRF54L15)
isBleConnected = nrf54l15Bluetooth != nullptr && nrf54l15Bluetooth->isConnected();
#endif
if (isBleConnected) {
auto thread = concurrency::OSThread::currentThread;
@@ -243,8 +241,6 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_
nimbleBluetooth->sendLog(buffer.get(), size);
#elif defined(ARCH_NRF52)
nrf52Bluetooth->sendLog(buffer.get(), size);
#elif defined(ARCH_NRF54L15)
nrf54l15Bluetooth->sendLog(buffer.get(), size);
#endif
}
}
-4
View File
@@ -561,9 +561,5 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define HAS_SCREEN 0
#endif
#ifndef USE_ETHERNET_DEFAULT
#define USE_ETHERNET_DEFAULT 0
#endif
#include "DebugConfiguration.h"
#include "RF95Configuration.h"
+3 -5
View File
@@ -1304,7 +1304,7 @@ GnssModel_t GPS::probe(int serialSpeed)
{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352},
{"UC6580", "UC6580", GNSS_MODEL_UC6580}
{"UC6580", "UC6580", GNSS_MODEL_UC6580},
// as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now.
/*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/};
GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed);
@@ -1331,10 +1331,8 @@ GnssModel_t GPS::probe(int serialSpeed)
case 1: {
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580},
{"UM600", "UM600", GNSS_MODEL_UC6580},
{"CM121", "CM121", GNSS_MODEL_CM121},
{"CC1167Q", "CC1167Q", GNSS_MODEL_CM121}};
std::vector<ChipInfo> unicore = {
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
currentDelay = 20;
currentStep = 2;
+12 -18
View File
@@ -230,7 +230,6 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
#endif
// Store the message and set the expiration timestamp
strncpy(NotificationRenderer::alertBannerMessage, banner_overlay_options.message, 255);
NotificationRenderer::parseBannerMessageWithFonts(NotificationRenderer::alertBannerMessage);
NotificationRenderer::alertBannerMessage[255] = '\0'; // Ensure null termination
NotificationRenderer::alertBannerUntil =
(banner_overlay_options.durationMs == 0) ? 0 : millis() + banner_overlay_options.durationMs;
@@ -240,9 +239,9 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
NotificationRenderer::alertBannerCallback = banner_overlay_options.bannerCallback;
NotificationRenderer::curSelected = banner_overlay_options.InitialSelected;
NotificationRenderer::pauseBanner = false;
NotificationRenderer::current_notification_type = banner_overlay_options.notificationType;
NotificationRenderer::current_notification_type = notificationTypeEnum::selection_picker;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, 2);
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
updateUiFrame(ui);
}
@@ -264,7 +263,7 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
NotificationRenderer::current_notification_type = notificationTypeEnum::node_picker;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, 2);
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
updateUiFrame(ui);
}
@@ -288,7 +287,7 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
NotificationRenderer::currentNumber = 0;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, 2);
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
updateUiFrame(ui);
}
@@ -311,7 +310,7 @@ void Screen::showTextInput(const char *header, const char *initialText, uint32_t
// Set the overlay using the same pattern as other notification types
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, 2);
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
updateUiFrame(ui);
}
@@ -425,11 +424,6 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#if defined(OLED_Y_OFFSET_PAGES)
// Panels whose active window does not start at GDDRAM row 0 (e.g. 72x40
// modules on pages 3..7) need a fixed vertical page shift on every write.
static_cast<SSD1306Wire *>(dispdev)->setYOffset(OLED_Y_OFFSET_PAGES);
#endif
#elif defined(USE_SPISSD1306)
dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48);
if (!dispdev->init()) {
@@ -701,7 +695,7 @@ void Screen::setup()
static OverlayCallback overlays[] = {
graphics::UIRenderer::drawNavigationBar // Custom indicator icons for each frame
};
ui->setOverlays(overlays, 1);
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
// Enable UTF-8 to display mapping
dispdev->setFontTableLookupFunction(customFontTableLookup);
@@ -915,7 +909,7 @@ int32_t Screen::runOnce()
#ifndef DISABLE_WELCOME_UNSET
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
menuHandler::LoraRegionPicker();
#else
menuHandler::OnboardMessage();
@@ -1152,7 +1146,7 @@ void Screen::setFrames(FrameFocus focus)
#if defined(DISPLAY_CLOCK_FRAME)
if (!hiddenFrames.clock) {
fsi.positions.clock = numframes;
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
#else
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
@@ -1318,7 +1312,7 @@ void Screen::setFrames(FrameFocus focus)
// Add overlays: frame icons and alert banner)
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, 2);
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
prevFrame = -1; // Force drawFavoriteNode to pick a new node (because our list just changed)
@@ -1611,7 +1605,7 @@ void Screen::showFrame(FrameDirection direction)
void Screen::setFastFramerate()
{
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
dispdev->clear();
#if GRAPHICS_TFT_COLORING_ENABLED
prepareFrameColorRegions();
@@ -1693,7 +1687,7 @@ int Screen::handleInputEvent(const InputEvent *event)
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
NotificationRenderer::inEvent = *event;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, 2);
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP
updateUiFrame(ui);
return 0;
@@ -1708,7 +1702,7 @@ int Screen::handleInputEvent(const InputEvent *event)
if (NotificationRenderer::isOverlayBannerShowing()) {
NotificationRenderer::inEvent = *event;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, 2);
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP
updateUiFrame(ui);
+1 -11
View File
@@ -88,16 +88,6 @@
#endif
#endif
// nRF52 flash optimization: re-route FONT_LARGE_LOCAL to the 16pt glyph so
// the display-tier dispatch below picks up 16pt everywhere it would have used
// 24pt. Drops the ~9.6 KB ArialMT_Plain_24 table from the linked binary.
// Set MESHTASTIC_LARGE_FONT_24PT=1 in build_flags to opt out per variant
// (undefined or 0 keeps the optimization on).
#if defined(ARCH_NRF52) && (!defined(MESHTASTIC_LARGE_FONT_24PT) || MESHTASTIC_LARGE_FONT_24PT == 0)
#undef FONT_LARGE_LOCAL
#define FONT_LARGE_LOCAL FONT_MEDIUM_LOCAL
#endif
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR)) && \
@@ -106,7 +96,7 @@
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#elif defined(OLED_TINY)
#elif defined(M5STACK_UNITC6L)
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13
#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13
+1 -7
View File
@@ -31,12 +31,6 @@ ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenw
return ScreenResolution::UltraLow;
}
#ifdef DISPLAY_FORCE_SMALL_FONTS
if (screenwidth <= 160 && screenheight <= 80) {
return ScreenResolution::Low;
}
#endif
// Standard OLED screens
if (screenwidth > 128 && screenheight <= 64) {
return ScreenResolution::Low;
@@ -246,7 +240,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
#if !defined(OLED_TINY)
#if !defined(M5STACK_UNITC6L)
// === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
batteryX += 1;
+2 -2
View File
@@ -460,7 +460,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
#if !defined(OLED_TINY)
#if !defined(M5STACK_UNITC6L)
// === Fifth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
@@ -592,7 +592,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label);
#if !defined(OLED_TINY)
#if !defined(M5STACK_UNITC6L)
// Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
+1 -1
View File
@@ -555,7 +555,7 @@ void menuHandler::TZPicker()
void menuHandler::clockMenu()
{
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
#else
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
+4 -4
View File
@@ -26,7 +26,7 @@ extern bool haveGlyphs(const char *str);
// Global screen instance
extern graphics::Screen *screen;
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
static uint32_t lastSwitchTime = 0;
#endif
namespace graphics
@@ -788,7 +788,7 @@ void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state
unsigned long now = millis();
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
@@ -824,7 +824,7 @@ void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *st
unsigned long now = millis();
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
@@ -894,7 +894,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
double lat = DegD(ourSelfPos.latitude_i);
double lon = DegD(ourSelfPos.longitude_i);
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
display->clear();
uint32_t now = millis();
if (now - lastSwitchTime >= 2000) {
+25 -174
View File
@@ -66,110 +66,6 @@ uint32_t pow_of_10(uint32_t n)
return ret;
}
char graphics::NotificationRenderer::alertBannerLines[MAX_LINES + 1][64] = {};
uint8_t graphics::NotificationRenderer::alertBannerLineCount = 0;
graphics::NotificationRenderer::BannerFont graphics::NotificationRenderer::alertBannerLineFonts[MAX_LINES + 1] = {};
static inline graphics::NotificationRenderer::BannerFont parseFontTagPrefix(const char *&p)
{
// Tags must be at the start of the line:
// [S] small, [M] medium, [L] large
if (p && p[0] == '[' && p[2] == ']' && p[1] != '\0') {
char t = p[1];
if (t == 'S') {
p += 3;
return graphics::NotificationRenderer::BANNER_FONT_SMALL;
}
if (t == 'M') {
p += 3;
return graphics::NotificationRenderer::BANNER_FONT_MEDIUM;
}
if (t == 'L') {
p += 3;
return graphics::NotificationRenderer::BANNER_FONT_LARGE;
}
}
return graphics::NotificationRenderer::BANNER_FONT_DEFAULT;
}
static inline const uint8_t *fontForBannerLine(graphics::NotificationRenderer::BannerFont f)
{
switch (f) {
case graphics::NotificationRenderer::BANNER_FONT_SMALL:
return FONT_SMALL;
case graphics::NotificationRenderer::BANNER_FONT_MEDIUM:
return FONT_MEDIUM;
case graphics::NotificationRenderer::BANNER_FONT_LARGE:
return FONT_LARGE;
case graphics::NotificationRenderer::BANNER_FONT_DEFAULT:
default:
return FONT_SMALL;
}
}
static inline uint8_t effectiveLineHeightForBannerLine(graphics::NotificationRenderer::BannerFont f)
{
uint8_t height = FONT_HEIGHT_SMALL;
switch (f) {
case graphics::NotificationRenderer::BANNER_FONT_MEDIUM:
height = FONT_HEIGHT_MEDIUM;
break;
case graphics::NotificationRenderer::BANNER_FONT_LARGE:
height = FONT_HEIGHT_LARGE;
break;
case graphics::NotificationRenderer::BANNER_FONT_SMALL:
case graphics::NotificationRenderer::BANNER_FONT_DEFAULT:
default:
height = FONT_HEIGHT_SMALL;
break;
}
return (height > 3) ? (height - 3) : height;
}
void graphics::NotificationRenderer::parseBannerMessageWithFonts(const char *message)
{
alertBannerLineCount = 0;
for (uint8_t i = 0; i < (MAX_LINES + 1); i++) {
alertBannerLines[i][0] = '\0';
alertBannerLineFonts[i] = BANNER_FONT_DEFAULT;
}
if (!message || !message[0]) {
return;
}
const char *p = message;
while (*p && alertBannerLineCount < (MAX_LINES + 1)) {
const char *lineStart = p;
while (*p && *p != '\n') {
p++;
}
char tmp[64] = {0};
size_t len = (size_t)(p - lineStart);
if (len > (sizeof(tmp) - 1)) {
len = sizeof(tmp) - 1;
}
memcpy(tmp, lineStart, len);
tmp[len] = '\0';
// Tag at start
const char *tp = tmp;
BannerFont f = parseFontTagPrefix(tp);
alertBannerLineFonts[alertBannerLineCount] = f;
// Store stripped text
strncpy(alertBannerLines[alertBannerLineCount], tp, sizeof(alertBannerLines[0]) - 1);
alertBannerLines[alertBannerLineCount][sizeof(alertBannerLines[0]) - 1] = '\0';
alertBannerLineCount++;
if (*p == '\n') {
p++;
}
}
}
// Used on boot when a certificate is being created
void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@@ -479,37 +375,23 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
const char *lineStarts[MAX_LINES + 1] = {0};
uint16_t lineCount = 0;
char lineBuffer[40] = {0};
bool useTaggedTextBanner =
(current_notification_type == notificationTypeEnum::text_banner && alertBannerOptions == 0 && alertBannerLineCount > 0);
if (useTaggedTextBanner) {
lineCount = std::min<uint8_t>(alertBannerLineCount, MAX_LINES);
for (uint16_t i = 0; i < lineCount; i++) {
lineStarts[i] = alertBannerLines[i];
lineLengths[i] = strlen(lineStarts[i]);
display->setFont(fontForBannerLine(alertBannerLineFonts[i]));
lineWidths[i] = display->getStringWidth(lineStarts[i], lineLengths[i], true);
if (lineWidths[i] > maxWidth)
maxWidth = lineWidths[i];
}
} else {
char *alertEnd = alertBannerMessage + strnlen(alertBannerMessage, sizeof(alertBannerMessage));
lineStarts[lineCount] = alertBannerMessage;
// Parse lines
char *alertEnd = alertBannerMessage + strnlen(alertBannerMessage, sizeof(alertBannerMessage));
lineStarts[lineCount] = alertBannerMessage;
while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) {
lineStarts[lineCount + 1] = std::find((char *)lineStarts[lineCount], alertEnd, '\n');
lineLengths[lineCount] = lineStarts[lineCount + 1] - lineStarts[lineCount];
if (lineStarts[lineCount + 1][0] == '\n')
lineStarts[lineCount + 1] += 1;
lineWidths[lineCount] = display->getStringWidth(lineStarts[lineCount], lineLengths[lineCount], true);
if (lineWidths[lineCount] > maxWidth)
maxWidth = lineWidths[lineCount];
lineCount++;
}
while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) {
lineStarts[lineCount + 1] = std::find((char *)lineStarts[lineCount], alertEnd, '\n');
lineLengths[lineCount] = lineStarts[lineCount + 1] - lineStarts[lineCount];
if (lineStarts[lineCount + 1][0] == '\n')
lineStarts[lineCount + 1] += 1;
lineWidths[lineCount] = display->getStringWidth(lineStarts[lineCount], lineLengths[lineCount], true);
if (lineWidths[lineCount] > maxWidth)
maxWidth = lineWidths[lineCount];
lineCount++;
}
// Measure option widths
display->setFont(FONT_SMALL);
for (int i = 0; i < alertBannerOptions; i++) {
optionWidths[i] = display->getStringWidth(optionsArrayPtr[i], strlen(optionsArrayPtr[i]), true);
if (optionWidths[i] > maxWidth)
@@ -623,10 +505,6 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
bool needs_bell = false;
uint16_t lineWidths[totalLines] = {0};
uint16_t lineLengths[totalLines] = {0};
BannerFont lineFonts[totalLines] = {};
uint8_t lineEffectiveHeights[totalLines] = {0};
const char *renderLines[totalLines] = {0};
bool useTaggedBannerFonts = (current_notification_type == notificationTypeEnum::text_banner && alertBannerOptions == 0);
if (maxWidth != 0)
is_picker = true;
@@ -639,22 +517,11 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
uint16_t widestLineWithBars = 0;
while (lines[lineCount] != nullptr) {
const char *renderText = lines[lineCount];
BannerFont lineFont = BANNER_FONT_DEFAULT;
if (useTaggedBannerFonts && lineCount < alertBannerLineCount) {
renderText = alertBannerLines[lineCount];
lineFont = alertBannerLineFonts[lineCount];
}
renderLines[lineCount] = renderText;
lineFonts[lineCount] = lineFont;
lineEffectiveHeights[lineCount] = effectiveLineHeightForBannerLine(lineFont);
display->setFont(fontForBannerLine(lineFont));
auto newlinePointer = strchr(renderText, '\n');
auto newlinePointer = strchr(lines[lineCount], '\n');
if (newlinePointer)
lineLengths[lineCount] = (newlinePointer - renderText);
else
lineLengths[lineCount] = strlen(renderText);
lineLengths[lineCount] = (newlinePointer - lines[lineCount]); // Check for newlines first
else // if the newline wasn't found, then pull string length from strlen
lineLengths[lineCount] = strlen(lines[lineCount]);
if (current_notification_type == notificationTypeEnum::node_picker) {
char measureBuffer[64] = {0};
@@ -666,7 +533,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
// Consider extra width for signal bars on lines that contain "Signal:"
uint16_t potentialWidth = lineWidths[lineCount];
if (graphics::bannerSignalBars >= 0 && strncmp(renderText, "Signal:", 7) == 0) {
if (graphics::bannerSignalBars >= 0 && strncmp(lines[lineCount], "Signal:", 7) == 0) {
const int totalBars = 5;
const int barWidth = 3;
const int barSpacing = 2;
@@ -702,21 +569,8 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
uint16_t screenHeight = display->height();
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
uint8_t visibleTotalLines = 0;
uint16_t contentHeight = 0;
const uint16_t availableHeight = (screenHeight > (vPadding * 2)) ? (screenHeight - vPadding * 2) : 0;
for (uint8_t i = 0; i < lineCount; i++) {
uint8_t thisLineHeight = lineEffectiveHeights[i] ? lineEffectiveHeights[i] : effectiveLineHeight;
if (contentHeight + thisLineHeight > availableHeight) {
break;
}
contentHeight += thisLineHeight;
visibleTotalLines++;
}
if (visibleTotalLines == 0 && lineCount > 0) {
visibleTotalLines = 1;
contentHeight = lineEffectiveHeights[0] ? lineEffectiveHeights[0] : effectiveLineHeight;
}
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
uint16_t boxHeight = contentHeight + vPadding * 2;
if (visibleTotalLines == 1) {
boxHeight += (currentResolution == ScreenResolution::High) ? 4 : 3;
@@ -728,7 +582,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
}
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
boxHeight += (currentResolution == ScreenResolution::High) ? 2 : 1;
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
if (visibleTotalLines == 1) {
boxTop += 25;
}
@@ -762,18 +616,15 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
// Draw Content
int16_t lineY = boxTop + vPadding;
for (int i = 0; i < visibleTotalLines; i++) {
display->setFont(fontForBannerLine(lineFonts[i]));
int16_t thisLineHeight = lineEffectiveHeights[i] ? lineEffectiveHeights[i] : effectiveLineHeight;
for (int i = 0; i < lineCount; i++) {
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
if (needs_bell && i == 0) {
int fontHeight = thisLineHeight + 3;
int bellY = lineY + (fontHeight - 8) / 2;
int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2;
display->drawXbm(textX - 10, bellY, 8, 8, bell_alert);
display->drawXbm(textX + lineWidths[i] + 2, bellY, 8, 8, bell_alert);
}
char lineBuffer[lineLengths[i] + 1];
strncpy(lineBuffer, renderLines[i], lineLengths[i]);
strncpy(lineBuffer, lines[i], lineLengths[i]);
lineBuffer[lineLengths[i]] = '\0';
// Determine if this is a pop-up or a pick list
if (alertBannerOptions > 0 && i == 0) {
@@ -807,7 +658,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->drawString(textX, lineY - yOffset, lineBuffer);
}
display->setColor(WHITE);
lineY += (thisLineHeight - 2 - background_yOffset);
lineY += (effectiveLineHeight - 2 - background_yOffset);
} else {
// Pop-up
// If this is the Signal line, center text + bars as one group
@@ -865,7 +716,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->drawString(textX, lineY, lineBuffer);
}
}
lineY += thisLineHeight;
lineY += (effectiveLineHeight);
}
}
-6
View File
@@ -31,12 +31,6 @@ class NotificationRenderer
static bool pauseBanner;
enum BannerFont : uint8_t { BANNER_FONT_DEFAULT = 0, BANNER_FONT_SMALL, BANNER_FONT_MEDIUM, BANNER_FONT_LARGE };
static char alertBannerLines[MAX_LINES + 1][64]; // parsed text per line
static uint8_t alertBannerLineCount;
static BannerFont alertBannerLineFonts[MAX_LINES + 1];
static void parseBannerMessageWithFonts(const char *message);
static void resetBanner();
static void showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs);
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
+6 -6
View File
@@ -25,7 +25,7 @@
// External variables
extern graphics::Screen *screen;
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
static uint32_t lastSwitchTime = 0;
#endif
namespace graphics
@@ -753,7 +753,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
if (!node || node->num == nodeDB->getNodeNum() || !nodeInfoLiteIsFavorite(node))
return;
display->clear();
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
uint32_t now = millis();
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
{
@@ -959,7 +959,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
if (seenStr[0]) {
display->drawString(x, getTextPositions(display)[line++], seenStr);
}
#if !defined(OLED_TINY)
#if !defined(M5STACK_UNITC6L)
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
meshtastic_DeviceMetrics nodeMetrics;
@@ -1172,7 +1172,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
}
#endif
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
line += 1;
// === Node Identity ===
@@ -1456,7 +1456,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
// needs to be drawn relative to x and y
// draw centered icon left to right and centered above the one line of app text
#if defined(OLED_TINY)
#if defined(M5STACK_UNITC6L)
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
if (gBootSplashBoldPass) {
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2 + 1, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
@@ -1666,7 +1666,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
}
display->drawString(x, textPos[line++], altitudeLine);
}
#if !defined(OLED_TINY)
#if !defined(M5STACK_UNITC6L)
// === Draw Compass ===
if (validHeading || statusLine1) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
+1 -1
View File
@@ -318,7 +318,7 @@ const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
#define connection_icon_height 5
const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
#ifdef OLED_TINY
#ifdef M5STACK_UNITC6L
#include "img/icon_small.xbm"
#else
#include "img/icon.xbm"
+4 -5
View File
@@ -53,11 +53,10 @@ void InkHUD::MessageStore::saveToFlash()
f.write(reinterpret_cast<const uint8_t *>(&m.timestamp), sizeof(m.timestamp)); // Write timestamp. 4 bytes
f.write(reinterpret_cast<const uint8_t *>(&m.sender), sizeof(m.sender)); // Write sender NodeId. 4 Bytes
f.write(reinterpret_cast<const uint8_t *>(&m.channelIndex), sizeof(m.channelIndex)); // Write channel index. 1 Byte
f.write(reinterpret_cast<const uint8_t *>(m.text.c_str()),
min((size_t)MAX_MESSAGE_SIZE, m.text.size())); // Write message text
f.write('\0'); // Append null term
LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast<uint32_t>(i),
min((size_t)MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str());
f.write(reinterpret_cast<const uint8_t *>(m.text.c_str()), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text
f.write('\0'); // Append null term
LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast<uint32_t>(i), min(MAX_MESSAGE_SIZE, m.text.size()),
m.text.c_str());
}
// Release firmware's SPI lock, because SafeFile::close needs it
-2
View File
@@ -13,8 +13,6 @@
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
// REVISIT esp_adc_cal.h
// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h"
#include <esp_adc_cal.h>
#include <soc/adc_channel.h>
+2 -2
View File
@@ -68,7 +68,7 @@ TLoraPagerKeyboard::TLoraPagerKeyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX),
next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0)
{
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
#else
ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
@@ -108,7 +108,7 @@ void TLoraPagerKeyboard::setBacklight(bool on)
uint32_t _brightness = 0;
if (on)
_brightness = brightness;
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcWrite(KB_BL_PIN, _brightness);
#else
ledcWrite(LEDC_BACKLIGHT_CHANNEL, _brightness);
-20
View File
@@ -2,9 +2,6 @@
#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
#include "input/InputBroker.h"
#endif
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
@@ -62,12 +59,6 @@ NimbleBluetooth *nimbleBluetooth = nullptr;
NRF52Bluetooth *nrf52Bluetooth = nullptr;
#endif
#ifdef ARCH_NRF54L15
void nrf54l15Setup();
void nrf54l15Loop();
NRF54L15Bluetooth *nrf54l15Bluetooth = nullptr;
#endif
#if HAS_WIFI || defined(USE_WS5500) || defined(USE_CH390D)
#include "mesh/api/WiFiServerAPI.h"
#include "mesh/wifi/WiFiAPClient.h"
@@ -705,9 +696,6 @@ void setup()
#ifdef ARCH_NRF52
nrf52Setup();
#endif
#ifdef ARCH_NRF54L15
nrf54l15Setup();
#endif
#ifdef ARCH_RP2040
rp2040Setup();
@@ -753,11 +741,6 @@ void setup()
}
}
#endif
#ifdef OLED_GEOMETRY_OVERRIDE
// Per-variant geometry (e.g. 72x40 micro-OLEDs). Takes precedence over the
// default GEOMETRY_128_64 set at the top of setup().
screen_geometry = OLED_GEOMETRY_OVERRIDE;
#endif
#endif
#if !MESHTASTIC_EXCLUDE_I2C
@@ -1143,9 +1126,6 @@ void loop()
#endif
#ifdef ARCH_NRF52
nrf52Loop();
#endif
#ifdef ARCH_NRF54L15
nrf54l15Loop();
#endif
power->powerCommandsCheck();
-4
View File
@@ -19,10 +19,6 @@ extern NimbleBluetooth *nimbleBluetooth;
#include "NRF52Bluetooth.h"
extern NRF52Bluetooth *nrf52Bluetooth;
#endif
#ifdef ARCH_NRF54L15
#include "NRF54L15Bluetooth.h"
extern NRF54L15Bluetooth *nrf54l15Bluetooth;
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#include "detect/ScanI2CTwoWire.h"
#endif
-17
View File
@@ -93,23 +93,6 @@ void Channels::initDefaultLoraConfig()
#ifdef USERPREFS_LORACONFIG_CHANNEL_NUM
loraConfig.channel_num = USERPREFS_LORACONFIG_CHANNEL_NUM;
#endif
// Apply any hardcoded USERPREFS overrides for custom modem config (e.g. region-locked boards)
#ifdef USERPREFS_LORACONFIG_USE_PRESET
loraConfig.use_preset = USERPREFS_LORACONFIG_USE_PRESET;
#endif
#ifdef USERPREFS_LORACONFIG_BANDWIDTH
loraConfig.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH;
#endif
#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR
loraConfig.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR;
#endif
#ifdef USERPREFS_LORACONFIG_CODING_RATE
loraConfig.coding_rate = USERPREFS_LORACONFIG_CODING_RATE;
#endif
#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY
loraConfig.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY;
#endif
}
bool Channels::ensureLicensedOperation()
+3 -7
View File
@@ -334,9 +334,7 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p)
if (toPhoneQueue.enqueue(p, 0) == false) {
LOG_CRIT("Failed to queue a packet into toPhoneQueue!");
releaseToPool(p);
fromNum++; // notify observers so phone can resync
return;
abort();
}
fromNum++;
}
@@ -353,8 +351,7 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage
if (toPhoneMqttProxyQueue.enqueue(m, 0) == false) {
LOG_CRIT("Failed to queue a packet into toPhoneMqttProxyQueue!");
releaseMqttClientProxyMessageToPool(m);
return;
abort();
}
fromNum++;
}
@@ -386,8 +383,7 @@ void MeshService::sendClientNotification(meshtastic_ClientNotification *n)
if (toPhoneClientNotificationQueue.enqueue(n, 0) == false) {
LOG_CRIT("Failed to queue a notification into toPhoneClientNotificationQueue!");
releaseClientNotificationToPool(n);
return;
abort();
}
fromNum++;
}
+3 -61
View File
@@ -393,13 +393,6 @@ NodeDB::NodeDB()
} else {
LOG_WARN("Failed to read unique id from efuse");
}
#elif defined(ARCH_NRF54L15)
// nRF54L15: DEVICEID is under FICR->INFO sub-struct (not top-level as on nRF52)
uint64_t device_id_start = ((uint64_t)NRF_FICR->INFO.DEVICEID[1] << 32) | NRF_FICR->INFO.DEVICEID[0];
uint64_t device_id_end = ((uint64_t)NRF_FICR->DEVICEADDR[1] << 32) | NRF_FICR->DEVICEADDR[0];
memcpy(myNodeInfo.device_id.bytes, &device_id_start, sizeof(device_id_start));
memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
myNodeInfo.device_id.size = 16;
#elif defined(ARCH_NRF52)
// Nordic applies a FIPS compliant Random ID to each chip at the factory
// We concatenate the device address to the Random ID to create a unique ID for now
@@ -833,23 +826,6 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.lora.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET;
#else
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
#endif
#ifdef USERPREFS_LORACONFIG_USE_PRESET
config.lora.use_preset = USERPREFS_LORACONFIG_USE_PRESET;
#else
config.lora.use_preset = true;
#endif
#ifdef USERPREFS_LORACONFIG_BANDWIDTH
config.lora.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH;
#endif
#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR
config.lora.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR;
#endif
#ifdef USERPREFS_LORACONFIG_CODING_RATE
config.lora.coding_rate = USERPREFS_LORACONFIG_CODING_RATE;
#endif
#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY
config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY;
#endif
config.lora.hop_limit = HOP_RELIABLE;
#ifdef USERPREFS_CONFIG_LORA_IGNORE_MQTT
@@ -927,7 +903,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
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) || \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(ELECROW_ThinkNode_M9)) && \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT)) && \
HAS_TFT
// switch BT off by default; use TFT programming mode or hotkey to enable
config.bluetooth.enabled = false;
@@ -985,10 +961,6 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.network.wifi_enabled = USERPREFS_NETWORK_WIFI_ENABLED;
#endif
#if USE_ETHERNET_DEFAULT
config.network.eth_enabled = true;
#endif
#ifdef USERPREFS_NETWORK_WIFI_SSID
strncpy(config.network.wifi_ssid, USERPREFS_NETWORK_WIFI_SSID, sizeof(config.network.wifi_ssid));
#endif
@@ -1071,8 +1043,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.has_store_forward = true;
moduleConfig.has_telemetry = true;
moduleConfig.has_external_notification = true;
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) || \
defined(NEOPIXEL_STATUS_NOTIFICATION_PIN)
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION)
moduleConfig.external_notification.enabled = true;
#endif
#if defined(PIN_BUZZER)
@@ -1093,7 +1064,7 @@ void NodeDB::installDefaultModuleConfig()
#endif
#if defined(PIN_VIBRATION)
moduleConfig.external_notification.nag_timeout = 2;
#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) || defined(NEOPIXEL_STATUS_NOTIFICATION_PIN)
#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION)
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
#endif
@@ -1246,14 +1217,7 @@ void NodeDB::initModuleConfigIntervals()
#else
moduleConfig.telemetry.device_update_interval = MAX_INTERVAL;
#endif
#ifdef USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL
moduleConfig.telemetry.environment_update_interval = USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL;
#else
moduleConfig.telemetry.environment_update_interval = 0;
#endif
#ifdef USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED
moduleConfig.telemetry.environment_measurement_enabled = USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED;
#endif
moduleConfig.telemetry.air_quality_interval = 0;
moduleConfig.telemetry.power_update_interval = 0;
moduleConfig.telemetry.health_update_interval = 0;
@@ -1762,28 +1726,6 @@ void NodeDB::loadFromDisk()
config.lora.tx_enabled = false;
#endif
// Always-apply LoRa overrides: applied after loading saved config so they
// take effect even when NVS already has a valid config (e.g. region-locked
// dev boards with no BLE/serial to set the region at runtime).
#ifdef USERPREFS_CONFIG_LORA_REGION
config.lora.region = USERPREFS_CONFIG_LORA_REGION;
#endif
#ifdef USERPREFS_LORACONFIG_USE_PRESET
config.lora.use_preset = USERPREFS_LORACONFIG_USE_PRESET;
#endif
#ifdef USERPREFS_LORACONFIG_BANDWIDTH
config.lora.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH;
#endif
#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR
config.lora.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR;
#endif
#ifdef USERPREFS_LORACONFIG_CODING_RATE
config.lora.coding_rate = USERPREFS_LORACONFIG_CODING_RATE;
#endif
#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY
config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY;
#endif
if (backupSecurity.private_key.size > 0) {
LOG_DEBUG("Restoring backup of security config");
config.security = backupSecurity;
-8
View File
@@ -71,15 +71,7 @@ void PhoneAPI::handleStartConfig()
}
pauseBluetoothLogging = true;
spiLock->lock();
#if defined(MESHTASTIC_EXCLUDE_FILES_MANIFEST)
// Skip the recursive FS walk. Used by platforms whose Zephyr LittleFS
// backend can't safely traverse a deep tree (e.g. nRF54L15) and platforms
// that don't support OTA browsing — the manifest is only consumed by
// companion apps for those flows.
filesManifest.clear();
#else
filesManifest = getFiles("/", 10);
#endif
spiLock->unlock();
LOG_DEBUG("Got %d files in manifest", filesManifest.size());
-75
View File
@@ -1,75 +0,0 @@
#include "PositionPrecision.h"
#include "Channels.h"
#include "mesh-pb-constants.h"
#include <Arduino.h>
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex)
{
const meshtastic_Channel &channel = channels.getByIndex(channelIndex);
if (channel.settings.has_module_settings) {
return channel.settings.module_settings.position_precision;
} else if (channel.role == meshtastic_Channel_Role_PRIMARY) {
return 32;
} else {
return 0;
}
}
static int32_t truncateCoordinate(int32_t coordinate, uint32_t precision)
{
uint32_t coordinateBits = static_cast<uint32_t>(coordinate);
uint32_t truncated = coordinateBits & (UINT32_MAX << (32 - precision));
// Use the middle of the possible location, not the low edge of the bucket.
truncated += (1UL << (31 - precision));
return static_cast<int32_t>(truncated);
}
void applyPositionPrecision(meshtastic_Position &position, uint32_t precision)
{
if (precision == 0) {
uint32_t time = position.time;
position = meshtastic_Position_init_default;
position.time = time;
return;
}
uint32_t effectivePrecision = precision > 32 ? 32 : precision;
position.precision_bits = effectivePrecision;
if (effectivePrecision < 32) {
position.latitude_i = truncateCoordinate(position.latitude_i, effectivePrecision);
position.longitude_i = truncateCoordinate(position.longitude_i, effectivePrecision);
}
}
bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision)
{
if (packet.which_payload_variant != meshtastic_MeshPacket_decoded_tag ||
packet.decoded.portnum != meshtastic_PortNum_POSITION_APP) {
return true;
}
meshtastic_Position position = meshtastic_Position_init_default;
if (!pb_decode_from_bytes(packet.decoded.payload.bytes, packet.decoded.payload.size, &meshtastic_Position_msg, &position)) {
return false;
}
applyPositionPrecision(position, precision);
packet.decoded.payload.size = pb_encode_to_bytes(packet.decoded.payload.bytes, sizeof(packet.decoded.payload.bytes),
&meshtastic_Position_msg, &position);
return true;
}
bool applyPositionPrecisionForChannel(meshtastic_MeshPacket &packet, uint8_t channelIndex)
{
if (packet.which_payload_variant != meshtastic_MeshPacket_decoded_tag ||
packet.decoded.portnum != meshtastic_PortNum_POSITION_APP) {
return true;
}
return applyPositionPrecision(packet, getPositionPrecisionForChannel(channelIndex));
}
-9
View File
@@ -1,9 +0,0 @@
#pragma once
#include "meshtastic/mesh.pb.h"
#include <stdint.h>
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex);
void applyPositionPrecision(meshtastic_Position &position, uint32_t precision);
bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision);
bool applyPositionPrecisionForChannel(meshtastic_MeshPacket &packet, uint8_t channelIndex);
+10 -17
View File
@@ -46,6 +46,16 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c
#endif
}
RadioLibInterface::~RadioLibInterface()
{
// If the static `instance` pointer still references us, clear it.
// A later successful init() may have replaced `instance` with a newer
// interface — don't clobber that case.
if (instance == this) {
instance = nullptr;
}
}
#ifdef ARCH_ESP32
// ESP32 doesn't use that flag
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
@@ -424,9 +434,6 @@ void RadioLibInterface::completeSending()
// that can take a long time
auto p = sendingPacket;
sendingPacket = NULL;
#ifdef LED_LORA
digitalWrite(LED_LORA, LED_STATE_OFF);
#endif
if (p) {
// Packet has been sent, count it toward our TX airtime utilization.
@@ -548,9 +555,6 @@ void RadioLibInterface::pollMissedIrqs()
if (isReceiving) {
checkRxDoneIrqFlag();
}
if (sendingPacket) {
checkTxDoneIrqFlag();
}
}
void RadioLibInterface::resetAGC()
@@ -566,14 +570,6 @@ void RadioLibInterface::checkRxDoneIrqFlag()
}
}
void RadioLibInterface::checkTxDoneIrqFlag()
{
if (iface->checkIrq(RADIOLIB_IRQ_TX_DONE)) {
LOG_WARN("caught missed TX_DONE");
notify(ISR_TX, true);
}
}
void RadioLibInterface::configHardwareForSend()
{
powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn);
@@ -615,9 +611,6 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
enableInterrupt(isrTxLevel0);
lastTxStart = millis();
printPacket("Started Tx", txp);
#ifdef LED_LORA
digitalWrite(LED_LORA, LED_STATE_ON);
#endif
}
return res == RADIOLIB_ERR_NONE;
+7 -8
View File
@@ -104,13 +104,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
*/
static RadioLibInterface *instance;
/** Clear instance on destruction so stale pointer checks in loop() are safe */
virtual ~RadioLibInterface()
{
if (instance == this)
instance = nullptr;
}
/**
* Glue functions called from ISR land
*/
@@ -143,6 +136,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy, PhysicalLayer *iface = NULL);
/**
* Clear the static `instance` pointer if it still points at us, so callers
* that check `RadioLibInterface::instance != nullptr` don't dereference a
* freed object after a failed init() + unique_ptr reset.
*/
virtual ~RadioLibInterface();
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
/**
@@ -293,5 +293,4 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override;
void checkRxDoneIrqFlag();
void checkTxDoneIrqFlag();
};
-6
View File
@@ -4,7 +4,6 @@
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PositionPrecision.h"
#include "RTC.h"
#include "configuration.h"
@@ -368,11 +367,6 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
}
fixPriority(p); // Before encryption, fix the priority if it's unset
if (!applyPositionPrecisionForChannel(*p, p->channel)) {
LOG_ERROR("Dropping malformed position packet before send");
packetPool.release(p);
return meshtastic_Routing_Error_BAD_REQUEST;
}
// If the packet is not yet encrypted, do so now
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
+3 -2
View File
@@ -3,8 +3,9 @@
#include "ServerAPI.h"
#include <WiFi.h>
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
/**
+4 -41
View File
@@ -163,41 +163,6 @@ typedef struct _meshtastic_LockdownAuth {
connection-level admin authorization, and reboot the device into
the locked state. Always honoured regardless of current lock state. */
bool lock_now;
/* Optional per-boot uptime cap on the unlocked session, in seconds.
0 = unlimited (token-only enforcement, suitable for unattended
tower / infrastructure nodes).
When non-zero, the firmware arms an uptime timer at unlock. On
each expiry, while there is still boot-count budget, the firmware
decrements the on-flash boot count in place, revokes per-
connection admin auth (clients must re-authenticate to see
content), re-engages the screen lock, and re-arms the timer
without rebooting. Mesh routing keeps running across session
boundaries; only when the boot-count budget reaches zero does
the device hard-lock and reboot.
Total exposure ceiling = ((resolved boot count) + 1) * max_session_seconds.
The +1 accounts for the initial passphrase-unlocked session
itself, since boots_remaining is the number of subsequent
session rolls (each consuming one boot from the rollback ledger).
The resolved boot count is the value the firmware writes into the
token at unlock time: the client-supplied boots_remaining when
non-zero, otherwise the firmware default (TOKEN_DEFAULT_BOOTS).
Note that boots_remaining == 0 in this message means "use firmware
default", NOT "zero boots" — a client computing the ceiling for
display should mirror that resolution rather than multiplying the
raw request value.
The cap is persisted in the token, so it survives token-based
auto-unlock across reboots. Explicit operator Lock Now still
deletes the token and forces passphrase re-entry.
Uses millis() (CPU uptime), not wall-clock time, so the cap is
immune to GPS spoofing, RTC backup-battery removal, and Faraday
cage isolation none of those move the uptime counter. The only
way to reset the session clock is a reboot, which costs a boot
from the on-flash, HMAC-bound counter. */
uint32_t max_session_seconds;
} meshtastic_LockdownAuth;
/* Parameters for setting up Meshtastic for ameteur radio usage */
@@ -521,7 +486,7 @@ extern "C" {
#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
#define meshtastic_AdminMessage_OTAEvent_init_default {_meshtastic_OTAMode_MIN, {0, {0}}}
#define meshtastic_LockdownAuth_init_default {{0, {0}}, 0, 0, 0, 0}
#define meshtastic_LockdownAuth_init_default {{0, {0}}, 0, 0, 0}
#define meshtastic_HamParameters_init_default {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
@@ -534,7 +499,7 @@ extern "C" {
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}}
#define meshtastic_LockdownAuth_init_zero {{0, {0}}, 0, 0, 0, 0}
#define meshtastic_LockdownAuth_init_zero {{0, {0}}, 0, 0, 0}
#define meshtastic_HamParameters_init_zero {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
@@ -556,7 +521,6 @@ extern "C" {
#define meshtastic_LockdownAuth_boots_remaining_tag 2
#define meshtastic_LockdownAuth_valid_until_epoch_tag 3
#define meshtastic_LockdownAuth_lock_now_tag 4
#define meshtastic_LockdownAuth_max_session_seconds_tag 5
#define meshtastic_HamParameters_call_sign_tag 1
#define meshtastic_HamParameters_tx_power_tag 2
#define meshtastic_HamParameters_frequency_tag 3
@@ -753,8 +717,7 @@ X(a, STATIC, SINGULAR, BYTES, ota_hash, 2)
X(a, STATIC, SINGULAR, BYTES, passphrase, 1) \
X(a, STATIC, SINGULAR, UINT32, boots_remaining, 2) \
X(a, STATIC, SINGULAR, UINT32, valid_until_epoch, 3) \
X(a, STATIC, SINGULAR, BOOL, lock_now, 4) \
X(a, STATIC, SINGULAR, UINT32, max_session_seconds, 5)
X(a, STATIC, SINGULAR, BOOL, lock_now, 4)
#define meshtastic_LockdownAuth_CALLBACK NULL
#define meshtastic_LockdownAuth_DEFAULT NULL
@@ -869,7 +832,7 @@ extern const pb_msgdesc_t meshtastic_SHTXX_config_msg;
#define meshtastic_AdminMessage_size 511
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25
#define meshtastic_LockdownAuth_size 54
#define meshtastic_LockdownAuth_size 48
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
#define meshtastic_SCD30_config_size 27
#define meshtastic_SCD4X_config_size 29
+3 -5
View File
@@ -198,9 +198,7 @@ typedef enum _meshtastic_Config_DisplayConfig_OledType {
/* Can not be auto detected but set by proto. Used for 128x64 screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3,
/* Can not be auto detected but set by proto. Used for 128x128 screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4,
/* Can not be auto detected but set by proto. Used for 64x128 rotated screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED = 5
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4
} meshtastic_Config_DisplayConfig_OledType;
typedef enum _meshtastic_Config_DisplayConfig_DisplayMode {
@@ -722,8 +720,8 @@ extern "C" {
#define _meshtastic_Config_DisplayConfig_DisplayUnits_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayUnits)(meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL+1))
#define _meshtastic_Config_DisplayConfig_OledType_MIN meshtastic_Config_DisplayConfig_OledType_OLED_AUTO
#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED
#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED+1))
#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128
#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128+1))
#define _meshtastic_Config_DisplayConfig_DisplayMode_MIN meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT
#define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR
@@ -448,7 +448,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2432
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1944
#define meshtastic_DeviceState_size 1737
#define meshtastic_NodeEnvironmentEntry_size 170
#define meshtastic_NodeInfoLite_size 105
#define meshtastic_NodePositionEntry_size 42
+12 -31
View File
@@ -321,10 +321,6 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_HELTEC_MESH_NODE_T1 = 133,
/* B&Q Consulting Station G3: TBD */
meshtastic_HardwareModel_STATION_G3 = 134,
/* Lilygo T-Impulse-Plus */
meshtastic_HardwareModel_T_IMPULSE_PLUS = 135,
/* Lilygo T-Echo Card */
meshtastic_HardwareModel_T_ECHO_CARD = 136,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -824,7 +820,6 @@ typedef struct _meshtastic_Routing {
} meshtastic_Routing;
typedef PB_BYTES_ARRAY_T(233) meshtastic_Data_payload_t;
typedef PB_BYTES_ARRAY_T(64) meshtastic_Data_xeddsa_signature_t;
/* (Formerly called SubPacket)
The payload portion fo a packet, this is the actual bytes that are sent
inside a radio packet (because from/to are broken out by the comms library) */
@@ -858,8 +853,6 @@ typedef struct _meshtastic_Data {
/* Bitfield for extra flags. First use is to indicate that user approves the packet being uploaded to MQTT. */
bool has_bitfield;
uint8_t bitfield;
/* XEdDSA signature for the payload */
meshtastic_Data_xeddsa_signature_t xeddsa_signature;
} meshtastic_Data;
typedef PB_BYTES_ARRAY_T(32) meshtastic_KeyVerification_hash1_t;
@@ -1064,8 +1057,6 @@ typedef struct _meshtastic_MeshPacket {
uint32_t tx_after;
/* Indicates which transport mechanism this packet arrived over */
meshtastic_MeshPacket_TransportMechanism transport_mechanism;
/* Indicates whether the packet has a valid signature */
bool xeddsa_signed;
} meshtastic_MeshPacket;
/* The bluetooth to device link:
@@ -1122,10 +1113,6 @@ typedef struct _meshtastic_NodeInfo {
/* True if node has been muted
Persistes between NodeDB internal clean ups */
bool is_muted;
/* True if node is signing its packets via XEdDSA
Persists between NodeDB internal clean ups
LSB 1 of the bitfield */
bool has_xeddsa_signed;
} meshtastic_NodeInfo;
typedef PB_BYTES_ARRAY_T(16) meshtastic_MyNodeInfo_device_id_t;
@@ -1595,15 +1582,15 @@ extern "C" {
#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
#define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}}
#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0, {0, {0}}}
#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0}
#define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}}
#define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0}
#define meshtastic_RemoteShell_init_default {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0}
#define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0}
#define meshtastic_StatusMessage_init_default {""}
#define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN, 0}
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0}
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_default {0, 0, 0, 0}
@@ -1630,15 +1617,15 @@ extern "C" {
#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
#define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}}
#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0, {0, {0}}}
#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0}
#define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}}
#define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0}
#define meshtastic_RemoteShell_init_zero {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0}
#define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0}
#define meshtastic_StatusMessage_init_zero {""}
#define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN, 0}
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0}
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_zero {0, 0, 0, 0}
@@ -1711,7 +1698,6 @@ extern "C" {
#define meshtastic_Data_reply_id_tag 7
#define meshtastic_Data_emoji_tag 8
#define meshtastic_Data_bitfield_tag 9
#define meshtastic_Data_xeddsa_signature_tag 10
#define meshtastic_KeyVerification_nonce_tag 1
#define meshtastic_KeyVerification_hash1_tag 2
#define meshtastic_KeyVerification_hash2_tag 3
@@ -1769,7 +1755,6 @@ extern "C" {
#define meshtastic_MeshPacket_relay_node_tag 19
#define meshtastic_MeshPacket_tx_after_tag 20
#define meshtastic_MeshPacket_transport_mechanism_tag 21
#define meshtastic_MeshPacket_xeddsa_signed_tag 22
#define meshtastic_NodeInfo_num_tag 1
#define meshtastic_NodeInfo_user_tag 2
#define meshtastic_NodeInfo_position_tag 3
@@ -1783,7 +1768,6 @@ extern "C" {
#define meshtastic_NodeInfo_is_ignored_tag 11
#define meshtastic_NodeInfo_is_key_manually_verified_tag 12
#define meshtastic_NodeInfo_is_muted_tag 13
#define meshtastic_NodeInfo_has_xeddsa_signed_tag 14
#define meshtastic_MyNodeInfo_my_node_num_tag 1
#define meshtastic_MyNodeInfo_reboot_count_tag 8
#define meshtastic_MyNodeInfo_min_app_version_tag 11
@@ -1950,8 +1934,7 @@ X(a, STATIC, SINGULAR, FIXED32, source, 5) \
X(a, STATIC, SINGULAR, FIXED32, request_id, 6) \
X(a, STATIC, SINGULAR, FIXED32, reply_id, 7) \
X(a, STATIC, SINGULAR, FIXED32, emoji, 8) \
X(a, STATIC, OPTIONAL, UINT32, bitfield, 9) \
X(a, STATIC, SINGULAR, BYTES, xeddsa_signature, 10)
X(a, STATIC, OPTIONAL, UINT32, bitfield, 9)
#define meshtastic_Data_CALLBACK NULL
#define meshtastic_Data_DEFAULT NULL
@@ -2036,8 +2019,7 @@ X(a, STATIC, SINGULAR, BOOL, pki_encrypted, 17) \
X(a, STATIC, SINGULAR, UINT32, next_hop, 18) \
X(a, STATIC, SINGULAR, UINT32, relay_node, 19) \
X(a, STATIC, SINGULAR, UINT32, tx_after, 20) \
X(a, STATIC, SINGULAR, UENUM, transport_mechanism, 21) \
X(a, STATIC, SINGULAR, BOOL, xeddsa_signed, 22)
X(a, STATIC, SINGULAR, UENUM, transport_mechanism, 21)
#define meshtastic_MeshPacket_CALLBACK NULL
#define meshtastic_MeshPacket_DEFAULT NULL
#define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data
@@ -2055,8 +2037,7 @@ X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \
X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) \
X(a, STATIC, SINGULAR, BOOL, is_ignored, 11) \
X(a, STATIC, SINGULAR, BOOL, is_key_manually_verified, 12) \
X(a, STATIC, SINGULAR, BOOL, is_muted, 13) \
X(a, STATIC, SINGULAR, BOOL, has_xeddsa_signed, 14)
X(a, STATIC, SINGULAR, BOOL, is_muted, 13)
#define meshtastic_NodeInfo_CALLBACK NULL
#define meshtastic_NodeInfo_DEFAULT NULL
#define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User
@@ -2358,7 +2339,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_ChunkedPayload_size 245
#define meshtastic_ClientNotification_size 482
#define meshtastic_Compressed_size 239
#define meshtastic_Data_size 335
#define meshtastic_Data_size 269
#define meshtastic_DeviceMetadata_size 54
#define meshtastic_DuplicatedPublicKey_size 0
#define meshtastic_FileInfo_size 236
@@ -2371,12 +2352,12 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_LockdownStatus_size 53
#define meshtastic_LogRecord_size 426
#define meshtastic_LowEntropyKey_size 0
#define meshtastic_MeshPacket_size 450
#define meshtastic_MeshPacket_size 381
#define meshtastic_MqttClientProxyMessage_size 501
#define meshtastic_MyNodeInfo_size 83
#define meshtastic_NeighborInfo_size 258
#define meshtastic_Neighbor_size 22
#define meshtastic_NodeInfo_size 327
#define meshtastic_NodeInfo_size 325
#define meshtastic_NodeRemoteHardwarePin_size 29
#define meshtastic_Position_size 144
#define meshtastic_QueueStatus_size 23
@@ -144,7 +144,7 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig {
(the default official mqtt.meshtastic.org server can handle encrypted packets)
Decrypted packets may be useful for external systems that want to consume meshtastic packets */
bool encryption_enabled;
/* Deprecated: JSON packet support on MQTT was removed, and this field is ignored. */
/* Whether to send / consume json packets on MQTT */
bool json_enabled;
/* If true, we attempt to establish a secure connection using TLS */
bool tls_enabled;
+3 -2
View File
@@ -13,8 +13,9 @@
#include <WebServer.h>
#include <WiFi.h>
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
#ifdef ARCH_ESP32
+3 -2
View File
@@ -12,8 +12,9 @@
#include <AsyncUDP.h>
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
#define UDP_MULTICAST_DEFAUL_PORT 4403 // Default port for UDP multicast is same as TCP api server
+3 -2
View File
@@ -10,8 +10,9 @@
#include "target_specific.h"
#include <WiFi.h>
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
#if HAS_ETHERNET && defined(USE_CH390D)
+3 -2
View File
@@ -9,8 +9,9 @@
#include <WiFi.h>
#endif
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
extern bool needReconnect;
+2 -10
View File
@@ -262,7 +262,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
break;
}
case meshtastic_AdminMessage_ota_request_tag: {
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
#if defined(ARCH_ESP32)
LOG_INFO("OTA Requested");
if (r->ota_request.ota_hash.size != 32) {
@@ -666,7 +666,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
bool requiresReboot = true;
switch (c.which_payload_variant) {
case meshtastic_Config_device_tag: {
case meshtastic_Config_device_tag:
LOG_INFO("Set config: Device");
config.has_device = true;
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && \
@@ -720,7 +720,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
}
#endif
break;
} // case meshtastic_Config_device_tag
case meshtastic_Config_position_tag:
LOG_INFO("Set config: Position");
config.has_position = true;
@@ -1346,10 +1345,6 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
if (config.bluetooth.enabled && nrf52Bluetooth) {
conn.bluetooth.is_connected = nrf52Bluetooth->isConnected();
}
#elif defined(ARCH_NRF54L15)
if (config.bluetooth.enabled && nrf54l15Bluetooth) {
conn.bluetooth.is_connected = nrf54l15Bluetooth->isConnected();
}
#endif
#endif
conn.has_serial = true; // No serial-less devices
@@ -1610,9 +1605,6 @@ void disableBluetooth()
#elif defined(ARCH_NRF52)
if (nrf52Bluetooth)
nrf52Bluetooth->shutdown();
#elif defined(ARCH_NRF54L15)
if (nrf54l15Bluetooth)
nrf54l15Bluetooth->shutdown();
#endif
#endif
}
@@ -206,10 +206,6 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
#ifdef PCA_LED_NOTIFICATION
io.digitalWrite(PCA_LED_NOTIFICATION, on);
#endif
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
notificationPixel.setPixelColor(0, on ? NEOPIXEL_STATUS_NOTIFICATION_COLOR : 0);
notificationPixel.show();
#endif
break;
}
@@ -328,12 +324,6 @@ ExternalNotificationModule::ExternalNotificationModule()
LOG_INFO("Use Pin %i in digital mode", output);
pinMode(output, OUTPUT);
}
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
LOG_INFO("Use WS2812 on GPIO %d as notification LED", NEOPIXEL_STATUS_NOTIFICATION_PIN);
notificationPixel.begin();
notificationPixel.clear();
notificationPixel.show();
#endif
setExternalState(0, false);
externalTurnedOn[0] = 0;
if (moduleConfig.external_notification.output_vibra) {
-17
View File
@@ -10,19 +10,6 @@
extern AmbientLightingThread *ambientLightingThread;
#endif
// Drive a single WS2812 as the notification LED (M1/M2-style LED_NOTIFICATION
// but addressable). A variant defines NEOPIXEL_STATUS_NOTIFICATION_PIN to
// enable. Colour defaults to green but can be overridden.
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
#include <Adafruit_NeoPixel.h>
#ifndef NEOPIXEL_STATUS_TYPE
#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800)
#endif
#ifndef NEOPIXEL_STATUS_NOTIFICATION_COLOR
#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x00FF00 // green
#endif
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6)
#include <NonBlockingRtttl.h>
#else
@@ -51,10 +38,6 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
CallbackObserver<ExternalNotificationModule, const InputEvent *>(this, &ExternalNotificationModule::handleInputEvent);
uint32_t output = 0;
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
Adafruit_NeoPixel notificationPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_NOTIFICATION_PIN, NEOPIXEL_STATUS_TYPE);
#endif
public:
ExternalNotificationModule();
+34 -14
View File
@@ -4,7 +4,6 @@
#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PositionPrecision.h"
#include "RTC.h"
#include "Router.h"
#include "TransmitHistory.h"
@@ -108,7 +107,13 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
}
nodeDB->updatePosition(getFrom(&mp), p);
precision = getPositionPrecisionForChannel(mp.channel);
if (channels.getByIndex(mp.channel).settings.has_module_settings) {
precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision;
} else if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) {
precision = 32;
} else {
precision = 0;
}
return false; // Let others look at this message also if they want
}
@@ -116,12 +121,15 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
void PositionModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p)
{
// Phone position packets need to be truncated to the channel precision
if (isFromUs(&mp)) {
if (precision == 0)
LOG_DEBUG("Strip phone position due to channel precision 0");
else if (precision < 32)
LOG_DEBUG("Truncate phone position to channel precision %i", precision);
applyPositionPrecision(*p, precision);
if (isFromUs(&mp) && (precision < 32 && precision > 0)) {
LOG_DEBUG("Truncate phone position to channel precision %i", precision);
p->latitude_i = p->latitude_i & (UINT32_MAX << (32 - precision));
p->longitude_i = p->longitude_i & (UINT32_MAX << (32 - precision));
// We want the imprecise position to be the middle of the possible location, not
p->latitude_i += (1 << (31 - precision));
p->longitude_i += (1 << (31 - precision));
mp.decoded.payload.size =
pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_Position_msg, p);
}
@@ -198,11 +206,20 @@ meshtastic_MeshPacket *PositionModule::allocPositionPacket()
// lat/lon are unconditionally included - IF AVAILABLE!
LOG_DEBUG("Send location with precision %i", precision);
p.latitude_i = localPosition.latitude_i;
p.longitude_i = localPosition.longitude_i;
if (precision < 32 && precision > 0) {
p.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - precision));
p.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - precision));
// We want the imprecise position to be the middle of the possible location, not
p.latitude_i += (1 << (31 - precision));
p.longitude_i += (1 << (31 - precision));
} else {
p.latitude_i = localPosition.latitude_i;
p.longitude_i = localPosition.longitude_i;
}
p.precision_bits = precision;
p.has_latitude_i = true;
p.has_longitude_i = true;
applyPositionPrecision(p, precision);
// Always use NTP / GPS time if available
if (getValidTime(RTCQualityNTP) > 0) {
p.time = getValidTime(RTCQualityNTP);
@@ -333,7 +350,8 @@ void PositionModule::sendOurPosition()
// If we changed channels, ask everyone else for their latest info
LOG_INFO("Send pos@%x:6 to mesh (wantReplies=%d)", localPosition.timestamp, requestReplies);
for (uint8_t channelNum = 0; channelNum < 8; channelNum++) {
if (getPositionPrecisionForChannel(channelNum) != 0) {
if (channels.getByIndex(channelNum).settings.has_module_settings &&
channels.getByIndex(channelNum).settings.module_settings.position_precision != 0) {
sendOurPosition(NODENUM_BROADCAST, requestReplies, channelNum);
return;
}
@@ -351,8 +369,10 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha
if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal)
service->cancelSending(prevPacketId);
// Set the class precision value for this particular packet.
precision = getPositionPrecisionForChannel(channel);
// Set's the class precision value for this particular packet
if (channels.getByIndex(channel).settings.has_module_settings) {
precision = channels.getByIndex(channel).settings.module_settings.position_precision;
}
meshtastic_MeshPacket *p = allocPositionPacket();
if (p == nullptr) {
-33
View File
@@ -17,29 +17,8 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
if (inputBroker)
inputObserver.observe(inputBroker);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
powerPixel.begin();
powerPixel.clear();
powerPixel.show();
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
pairingPixel.begin();
pairingPixel.clear();
pairingPixel.show();
#endif
}
// Helper: write a 1-pixel NeoPixel strand to `color` when stateOn, else clear.
// Kept as a static inline here (rather than a member) so it compiles out
// completely when no NeoPixel status pins are defined.
#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN)
static inline void writeStatusPixel(Adafruit_NeoPixel &pixel, uint32_t color, bool stateOn)
{
pixel.setPixelColor(0, stateOn ? color : 0);
pixel.show();
}
#endif
int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
{
switch (arg->getStatusType()) {
@@ -197,12 +176,6 @@ int32_t StatusLEDModule::runOnce()
#ifdef LED_PAIRING
digitalWrite(LED_PAIRING, PAIRING_LED_state);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, CHARGE_LED_state == LED_STATE_ON);
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, PAIRING_LED_state == LED_STATE_ON);
#endif
#ifdef RGB_LED_POWER
if (!config.device.led_heartbeat_disabled) {
@@ -252,12 +225,6 @@ void StatusLEDModule::setPowerLED(bool LEDon)
#ifdef LED_PAIRING
digitalWrite(LED_PAIRING, ledState);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, LEDon);
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, LEDon);
#endif
#ifdef Battery_LED_1
digitalWrite(Battery_LED_1, ledState);
-26
View File
@@ -13,25 +13,6 @@
#include "input/InputBroker.h"
#endif
// WS2812/NeoPixel status-LED support. A variant may define
// NEOPIXEL_STATUS_POWER_PIN (required to enable the power/charge pixel)
// NEOPIXEL_STATUS_POWER_COLOR (optional, default red 0xFF0000)
// NEOPIXEL_STATUS_PAIRING_PIN / _COLOR (default blue 0x0000FF)
// Each pixel is a standalone 1-LED strand on its own GPIO — this mirrors how
// boards like the LilyGo T-Echo-Card expose three independent WS2812s.
#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN)
#include <Adafruit_NeoPixel.h>
#ifndef NEOPIXEL_STATUS_TYPE
#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800)
#endif
#ifndef NEOPIXEL_STATUS_POWER_COLOR
#define NEOPIXEL_STATUS_POWER_COLOR 0xFF0000 // red
#endif
#ifndef NEOPIXEL_STATUS_PAIRING_COLOR
#define NEOPIXEL_STATUS_PAIRING_COLOR 0x0000FF // blue
#endif
#endif
class StatusLEDModule : private concurrency::OSThread
{
bool slowTrack = false;
@@ -46,13 +27,6 @@ class StatusLEDModule : private concurrency::OSThread
void setPowerLED(bool);
#ifdef NEOPIXEL_STATUS_POWER_PIN
Adafruit_NeoPixel powerPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_POWER_PIN, NEOPIXEL_STATUS_TYPE);
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
Adafruit_NeoPixel pairingPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_PAIRING_PIN, NEOPIXEL_STATUS_TYPE);
#endif
protected:
unsigned int my_interval = 1000; // interval in millisconds
virtual int32_t runOnce() override;
+2 -2
View File
@@ -90,8 +90,8 @@ int32_t PaxcounterModule::runOnce()
configuration.blecounter = 1;
configuration.blescantime = 0; // infinite
configuration.wificounter = 1;
// configuration.wifi_channel_map = WIFI_CHANNEL_ALL;
// configuration.wifi_channel_switch_interval = 50;
configuration.wifi_channel_map = WIFI_CHANNEL_ALL;
configuration.wifi_channel_switch_interval = 50;
configuration.wifi_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.wifi_threshold, -80);
configuration.ble_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.ble_threshold, -80);
libpax_update_config(&configuration);
+6 -2
View File
@@ -19,8 +19,12 @@
#include "mesh/wifi/WiFiAPClient.h"
#include <WiFi.h>
#endif
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#elif HAS_ETHERNET && defined(USE_CH390D)
#include "ESP32_CH390.h"
#define ETH CH390
#endif // HAS_ETHERNET
#include "Default.h"
#if !defined(ARCH_NRF52) || NRF52_USE_JSON
+244 -164
View File
@@ -10,18 +10,23 @@
#include "mesh/PhoneAPI.h"
#include "mesh/mesh-pb-constants.h"
#include "sleep.h"
#include <BLE2904.h>
#include <BLEAdvertising.h>
#include <BLEDevice.h>
#include <BLESecurity.h>
#include <BLEUtils.h>
#include <NimBLEDevice.h>
#include <atomic>
#include <mutex>
#ifdef NIMBLE_TWO
#include "NimBLEAdvertising.h"
#include "NimBLEExtAdvertising.h"
#include "PowerStatus.h"
#endif
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
#include "host/ble_store.h"
#else
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
namespace
{
@@ -29,6 +34,7 @@ constexpr uint16_t kPreferredBleMtu = 517;
constexpr uint16_t kPreferredBleTxOctets = 251;
constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8;
} // namespace
#endif
// Debugging options: careful, they slow things down quite a bit!
// #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration
@@ -38,10 +44,10 @@ constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8;
#define NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE 3
#define NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE 3
BLECharacteristic *fromNumCharacteristic;
BLECharacteristic *BatteryCharacteristic;
BLECharacteristic *logRadioCharacteristic;
BLEServer *bleServer;
NimBLECharacteristic *fromNumCharacteristic;
NimBLECharacteristic *BatteryCharacteristic;
NimBLECharacteristic *logRadioCharacteristic;
NimBLEServer *bleServer;
static bool passkeyShowing;
static std::atomic<uint16_t> nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection"
@@ -112,8 +118,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
- Yes, we have to do some copy operations on pop because of this, but it's worth it to avoid cross-task memory management.
NOTIFY IS BROKEN:
- Adding BLECharacteristic::PROPERTY_NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards
compatible.
- Adding NIMBLE_PROPERTY::NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards compatible.
ZERO-SIZE READS:
- Returning a zero-size read from onRead breaks some clients during the config phase. So we have to block onRead until we
@@ -134,7 +139,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
std::mutex fromPhoneMutex;
std::atomic<size_t> fromPhoneQueueSize{0};
// We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks.
std::array<BLEValue, NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE> fromPhoneQueue{};
std::array<NimBLEAttValue, NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE> fromPhoneQueue{};
/* Packets to phone (BLE onRead callback) */
std::mutex toPhoneMutex;
@@ -296,7 +301,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
LOG_DEBUG("NimbleBluetooth: handling ToRadio packet, fromPhoneQueueSize=%u", fromPhoneQueueSize.load());
// Pop the front of fromPhoneQueue, holding the mutex only briefly while we pop.
BLEValue val;
NimBLEAttValue val;
{ // scope for fromPhoneMutex mutex
std::lock_guard<std::mutex> guard(fromPhoneMutex);
val = fromPhoneQueue[0];
@@ -311,7 +316,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
fromPhoneQueueSize--;
}
handleToRadio(val.getData(), val.getLength());
handleToRadio(val.data(), val.length());
}
}
@@ -323,7 +328,9 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
PhoneAPI::onNowHasData(fromRadioNum);
#ifdef DEBUG_NIMBLE_NOTIFY
int currentNotifyCount = notifyCount.fetch_add(1);
uint8_t cc = bleServer->getConnectedCount();
// This logging slows things down when there are lots of packets going to the phone, like initial connection:
LOG_DEBUG("BLE notify(%d) fromNum: %d connections: %d", currentNotifyCount, fromRadioNum, cc);
@@ -333,7 +340,13 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
put_le32(val, fromRadioNum);
fromNumCharacteristic->setValue(val, sizeof(val));
#ifdef NIMBLE_TWO
// NOTE: I don't have any NIMBLE_TWO devices, but this line makes me suspicious, and I suspect it needs to just be
// notify().
fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE);
#else
fromNumCharacteristic->notify();
#endif
}
/// Check the current underlying physical link to see if the client is currently connected
@@ -396,9 +409,14 @@ static BluetoothPhoneAPI *bluetoothPhoneAPI;
// Last ToRadio value received from the phone
static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
class NimbleBluetoothToRadioCallback : public BLECharacteristicCallbacks
class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
{
void onWrite(BLECharacteristic *pCharacteristic) override
#ifdef NIMBLE_TWO
virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
#else
virtual void onWrite(NimBLECharacteristic *pCharacteristic) override
#endif
{
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
// Assumption: onWrite is serialized by NimBLE, so we don't need to lock here against multiple concurrent onWrite calls.
@@ -410,17 +428,15 @@ class NimbleBluetoothToRadioCallback : public BLECharacteristicCallbacks
LOG_DEBUG("BLE onWrite(%d): start millis=%d", currentWriteCount, startMillis);
#endif
// Create a BLEValue and populate it with the received data
BLEValue val;
val.setValue(pCharacteristic->getData(), pCharacteristic->getLength());
auto val = pCharacteristic->getValue();
if (memcmp(lastToRadio, val.getData(), val.getLength()) != 0) {
if (memcmp(lastToRadio, val.data(), val.length()) != 0) {
if (bluetoothPhoneAPI->fromPhoneQueueSize < NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE) {
// Note: the comparison above is safe without a mutex because we are the only method that *increases*
// fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *decreases* in the main task meanwhile.)
memcpy(lastToRadio, val.getData(), val.getLength());
memcpy(lastToRadio, val.data(), val.length());
{ // scope for fromPhoneMutex mutexv, pCharacteristic->getLen
{ // scope for fromPhoneMutex mutex
// Append to fromPhoneQueue, protected by fromPhoneMutex. Hold the mutex as briefly as possible.
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->fromPhoneMutex);
bluetoothPhoneAPI->fromPhoneQueue.at(bluetoothPhoneAPI->fromPhoneQueueSize) = val;
@@ -434,21 +450,24 @@ class NimbleBluetoothToRadioCallback : public BLECharacteristicCallbacks
#ifdef DEBUG_NIMBLE_ON_WRITE_TIMING
int finishMillis = millis();
LOG_DEBUG("BLE onWrite(%d): append to fromPhoneQueue took %u ms. numBytes=%d", currentWriteCount,
finishMillis - startMillis, val.getLength());
finishMillis - startMillis, val.length());
#endif
} else {
LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount,
val.getLength());
LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount, val.length());
}
} else {
LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.getLength());
LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.length());
}
}
};
class NimbleBluetoothFromRadioCallback : public BLECharacteristicCallbacks
class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
{
void onRead(BLECharacteristic *pCharacteristic) override
#ifdef NIMBLE_TWO
virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
#else
virtual void onRead(NimBLECharacteristic *pCharacteristic) override
#endif
{
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
@@ -554,15 +573,34 @@ class NimbleBluetoothFromRadioCallback : public BLECharacteristicCallbacks
}
};
class NimbleBluetoothSecurityCallback : public BLESecurityCallbacks
class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
{
void onPassKeyNotify(uint32_t passkey) override
#ifdef NIMBLE_TWO
public:
NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; }
private:
NimbleBluetooth *ble;
virtual uint32_t onPassKeyDisplay() override
#else
virtual uint32_t onPassKeyRequest() override
#endif
{
LOG_INFO("*** Enter passkey %06u on the peer side ***", passkey);
uint32_t passkey = config.bluetooth.fixed_pin;
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) {
LOG_INFO("Use random passkey");
// This is the passkey to be entered on peer - we pick a number >100,000 to ensure 6 digits
passkey = random(100000, 999999);
}
LOG_INFO("*** Enter passkey %d on the peer side ***", passkey);
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
meshtastic::BluetoothStatus newStatus(std::to_string(passkey));
bluetoothStatus->updateStatus(&newStatus);
#if HAS_SCREEN
#if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (screen) {
screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
char btPIN[16] = "888888";
@@ -572,7 +610,7 @@ class NimbleBluetoothSecurityCallback : public BLESecurityCallbacks
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, y_offset + y, "Bluetooth");
#if !defined(OLED_TINY)
#if !defined(M5STACK_UNITC6L)
display->setFont(FONT_SMALL);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5;
display->drawString(x_offset + x, y_offset + y, "Enter this code");
@@ -592,8 +630,15 @@ class NimbleBluetoothSecurityCallback : public BLESecurityCallbacks
}
#endif
passkeyShowing = true;
return passkey;
}
void onAuthenticationComplete(ble_gap_conn_desc *desc) override
#ifdef NIMBLE_TWO
virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo) override
#else
virtual void onAuthenticationComplete(ble_gap_conn_desc *desc) override
#endif
{
LOG_INFO("BLE authentication complete");
@@ -601,47 +646,58 @@ class NimbleBluetoothSecurityCallback : public BLESecurityCallbacks
bluetoothStatus->updateStatus(&newStatus);
clearPairingDisplay();
// Store the connection handle for future use
#ifdef NIMBLE_TWO
nimbleBluetoothConnHandle = connInfo.getConnHandle();
#else
nimbleBluetoothConnHandle = desc->conn_handle;
#endif
}
};
class NimbleBluetoothServerCallback : public BLEServerCallbacks
{
public:
explicit NimbleBluetoothServerCallback(NimbleBluetooth *ble) : ble(ble) {}
private:
NimbleBluetooth *ble;
void onConnect(BLEServer *pServer, struct ble_gap_conn_desc *desc)
#ifdef NIMBLE_TWO
virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override
{
BLEAddress peer_addr(desc->peer_id_addr);
LOG_INFO("BLE incoming connection %s", peer_addr.toString().c_str());
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
const uint16_t connHandle = desc->conn_handle;
const uint16_t connHandle = connInfo.getConnHandle();
#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6))
int phyResult =
ble_gap_set_prefered_le_phy(connHandle, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_CODED_ANY);
if (phyResult == 0) {
LOG_INFO("BLE conn %u requested 2M PHY", connHandle);
} else {
LOG_WARN("Failed to prefer 2M PHY for conn %u, rc=%d", connHandle, phyResult);
}
#endif
// With Google Pixel 8 Android devices, this causes ESP32 device crash
// when phone reconnects. Disable this to make progress on the
// Arduino v3 migration while we investigate the Android compatibility
// issue.
#if 0
int dataLenResult = ble_gap_set_data_len(connHandle, kPreferredBleTxOctets, kPreferredBleTxTimeUs);
if (dataLenResult == 0) {
LOG_INFO("BLE conn %u requested data length %u bytes", connHandle, kPreferredBleTxOctets);
} else {
LOG_WARN("Failed to raise data length for conn %u, rc=%d", connHandle, dataLenResult);
}
#endif
LOG_INFO("BLE conn %u peer MTU %u (target %u)", connHandle, pServer->getPeerMTU(connHandle), kPreferredBleMtu);
LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu);
pServer->updateConnParams(connHandle, 6, 12, 0, 200);
}
#endif
void onDisconnect(BLEServer *pServer, struct ble_gap_conn_desc *desc)
#ifdef NIMBLE_TWO
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override
{
LOG_INFO("BLE disconnected");
LOG_INFO("BLE disconnect reason: %d", reason);
#else
virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) override
{
LOG_INFO("BLE disconnect");
#endif
#ifdef NIMBLE_TWO
if (ble->isDeInit)
return;
#else
if (nimbleBluetooth && nimbleBluetooth->isDeInit)
return;
#endif
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
@@ -666,51 +722,43 @@ class NimbleBluetoothServerCallback : public BLEServerCallbacks
bluetoothPhoneAPI->writeCount = 0;
}
// Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
memset(lastToRadio, 0, sizeof(lastToRadio));
nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE;
nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection"
#ifdef NIMBLE_TWO
// Restart Advertising
ble->startAdvertising();
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
if (!pAdvertising->start(0)) {
if (pAdvertising->isAdvertising()) {
LOG_DEBUG("BLE advertising already running");
} else {
LOG_ERROR("BLE failed to restart advertising");
}
}
#endif
}
};
static NimbleBluetoothToRadioCallback *toRadioCallbacks;
static NimbleBluetoothFromRadioCallback *fromRadioCallbacks;
void NimbleBluetooth::startAdvertising()
{
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->stop();
pAdvertising->reset();
pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
// if (powerStatus->getHasBattery() == 1) {
// pAdvertising->addServiceUUID(BLEUUID((uint16_t)0x180f));
// }
BLEAdvertisementData scan = BLEAdvertisementData();
scan.setName(getDeviceName());
pAdvertising->setScanResponseData(scan);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMaxPreferred(0x12);
if (!pAdvertising->start(0)) {
LOG_ERROR("BLE failed to start advertising");
} else {
LOG_DEBUG("BLE Advertising started");
}
}
void NimbleBluetooth::shutdown()
{
// No measurable power saving for ESP32 during light-sleep(?)
#ifndef ARCH_ESP32
// Shutdown bluetooth for minimum power draw
LOG_INFO("Disable bluetooth");
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->stop();
#endif
}
// Proper shutdown for ESP32. Needs reboot to reverse.
void NimbleBluetooth::deinit()
{
#ifdef ARCH_ESP32
@@ -720,19 +768,21 @@ void NimbleBluetooth::deinit()
#ifdef BLE_LED
digitalWrite(BLE_LED, LED_STATE_OFF);
#endif
BLEDevice::deinit(true);
#ifndef NIMBLE_TWO
NimBLEDevice::deinit();
#endif
#endif
}
// Has initial setup been completed
bool NimbleBluetooth::isActive()
{
return bleServer != nullptr;
return bleServer;
}
bool NimbleBluetooth::isConnected()
{
return bleServer && bleServer->getConnectedCount() > 0;
return bleServer->getConnectedCount() > 0;
}
int NimbleBluetooth::getRssi()
@@ -745,9 +795,9 @@ int NimbleBluetooth::getRssi()
uint16_t connHandle = nimbleBluetoothConnHandle.load();
if (connHandle == BLE_HS_CONN_HANDLE_NONE) {
const auto peers = bleServer->getPeerDevices(true);
const auto peers = bleServer->getPeerDevices();
if (!peers.empty()) {
connHandle = peers.begin()->first;
connHandle = peers.front();
nimbleBluetoothConnHandle = connHandle;
}
}
@@ -775,84 +825,74 @@ void NimbleBluetooth::setup()
LOG_INFO("Init the NimBLE bluetooth module");
BLEDevice::init(getDeviceName());
BLEDevice::setPower(ESP_PWR_LVL_P9);
NimBLEDevice::init(getDeviceName());
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
int mtuResult = BLEDevice::setMTU(kPreferredBleMtu);
#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6))
int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu);
if (mtuResult == 0) {
LOG_INFO("BLE MTU request set to %u", kPreferredBleMtu);
} else {
LOG_WARN("Unable to request MTU %u, rc=%d", kPreferredBleMtu, mtuResult);
}
BLESecurity *pSecurity = new BLESecurity();
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
// Set IO capability to DisplayOnly for MITM authentication
pSecurity->setCapability(ESP_IO_CAP_OUT);
// Set the passkey
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) {
LOG_INFO("Use random passkey");
pSecurity->setPassKey(false); // generate a random passkey
} else {
LOG_INFO("Use fixed passkey");
pSecurity->setPassKey(true, config.bluetooth.fixed_pin);
}
// Enable authorization requirements:
// - bonding: true (for persistent storage of the keys)
// - MITM: true (enables Man-In-The-Middle protection for password prompts)
// - secure connection: true (enables secure connection for encryption)
pSecurity->setAuthenticationMode(true, true, true);
int phyResult = ble_gap_set_prefered_default_le_phy(BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK);
if (phyResult == 0) {
LOG_INFO("BLE default PHY preference set to 2M");
} else {
// No IO capability for no PIN mode
pSecurity->setCapability(ESP_IO_CAP_NONE);
// No PIN mode: no MITM protection
pSecurity->setAuthenticationMode(true, false, false);
}
// Set the security callbacks
BLEDevice::setSecurityCallbacks(new NimbleBluetoothSecurityCallback());
bleServer = BLEDevice::createServer();
// BLEDevice::createServer calls ble_svc_gap_init, which resets the device
// name to default, so set it again.
int nameRc = ble_svc_gap_device_name_set(BLEDevice::getDeviceName().c_str());
if (nameRc != 0) {
LOG_ERROR("ble_svc_gap_device_name_set: rc=%d %s", nameRc, BLEUtils::returnCodeToString(nameRc));
LOG_WARN("Failed to prefer 2M PHY by default, rc=%d", phyResult);
}
bleServer->setCallbacks(new NimbleBluetoothServerCallback(this));
int dataLenResult = ble_gap_write_sugg_def_data_len(kPreferredBleTxOctets, kPreferredBleTxTimeUs);
if (dataLenResult == 0) {
LOG_INFO("BLE suggested data length set to %u bytes", kPreferredBleTxOctets);
} else {
LOG_WARN("Failed to raise suggested data length (%u/%u), rc=%d", kPreferredBleTxOctets, kPreferredBleTxTimeUs,
dataLenResult);
}
#endif
if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC);
NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
}
bleServer = NimBLEDevice::createServer();
#ifdef NIMBLE_TWO
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this);
#else
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback();
#endif
bleServer->setCallbacks(serverCallbacks, true);
setupService();
startAdvertising();
}
void NimbleBluetooth::setupService()
{
BLEService *bleService = bleServer->createService(MESH_SERVICE_UUID);
BLECharacteristic *ToRadioCharacteristic;
BLECharacteristic *FromRadioCharacteristic;
NimBLEService *bleService = bleServer->createService(MESH_SERVICE_UUID);
NimBLECharacteristic *ToRadioCharacteristic;
NimBLECharacteristic *FromRadioCharacteristic;
// Define the characteristics that the app is looking for
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE);
ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE);
// Allow notifications so phones can stream FromRadio without polling.
FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ);
fromNumCharacteristic =
bleService->createCharacteristic(FROMNUM_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ);
logRadioCharacteristic = bleService->createCharacteristic(LOGRADIO_UUID, BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_READ);
FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ);
fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ);
logRadioCharacteristic =
bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U);
} else {
ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_WRITE_AUTHEN |
BLECharacteristic::PROPERTY_WRITE_ENC);
FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_READ_AUTHEN |
BLECharacteristic::PROPERTY_READ_ENC);
fromNumCharacteristic = bleService->createCharacteristic(
FROMNUM_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_ENC);
ToRadioCharacteristic = bleService->createCharacteristic(
TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC);
FromRadioCharacteristic = bleService->createCharacteristic(
FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC);
fromNumCharacteristic =
bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC);
logRadioCharacteristic = bleService->createCharacteristic(
LOGRADIO_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_ENC);
LOGRADIO_UUID,
NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC, 512U);
}
bluetoothPhoneAPI = new BluetoothPhoneAPI();
@@ -865,31 +905,76 @@ void NimbleBluetooth::setupService()
bleService->start();
// Setup the battery service
BLEService *batteryService = bleServer->createService(BLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
BLE2904 *batteryLevelDescriptor = new BLE2904();
batteryLevelDescriptor->setFormat(BLE2904::FORMAT_UINT8);
NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic)
(uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1);
#ifdef NIMBLE_TWO
NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904();
#else
NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904);
#endif
batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
batteryLevelDescriptor->setNamespace(1);
batteryLevelDescriptor->setUnit(0x27ad);
BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic)
(uint16_t)0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
BatteryCharacteristic->addDescriptor(batteryLevelDescriptor);
batteryService->start();
}
void NimbleBluetooth::startAdvertising()
{
#ifdef NIMBLE_TWO
NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
NimBLEExtAdvertisement legacyAdvertising;
legacyAdvertising.setLegacyAdvertising(true);
legacyAdvertising.setScannable(true);
legacyAdvertising.setConnectable(true);
legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN);
if (powerStatus->getHasBattery() == 1) {
legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f));
}
legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID));
legacyAdvertising.setMinInterval(500);
legacyAdvertising.setMaxInterval(1000);
NimBLEExtAdvertisement legacyScanResponse;
legacyScanResponse.setLegacyAdvertising(true);
legacyScanResponse.setConnectable(true);
legacyScanResponse.setName(getDeviceName());
if (!pAdvertising->setInstanceData(0, legacyAdvertising)) {
LOG_ERROR("BLE failed to set legacyAdvertising");
} else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) {
LOG_ERROR("BLE failed to set legacyScanResponse");
} else if (!pAdvertising->start(0, 0, 0)) {
LOG_ERROR("BLE failed to start legacyAdvertising");
}
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
pAdvertising->start(0);
#endif
}
/// Given a level between 0-100, update the BLE attribute
void updateBatteryLevel(uint8_t level)
{
if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) {
BatteryCharacteristic->setValue(&level, 1);
#ifdef NIMBLE_TWO
BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE);
#else
BatteryCharacteristic->notify();
#endif
}
}
void NimbleBluetooth::clearBonds()
{
LOG_INFO("Clearing bluetooth bonds!");
ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_SEC, nullptr);
ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CCCD, nullptr);
NimBLEDevice::deleteAllBonds();
}
void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
@@ -897,16 +982,11 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
if (!bleServer || !isConnected() || length > 512) {
return;
}
logRadioCharacteristic->setValue(logMessage, length);
logRadioCharacteristic->notify();
#ifdef NIMBLE_TWO
logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE);
#else
logRadioCharacteristic->notify(logMessage, length, true);
#endif
}
void clearNVS()
{
ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_SEC, nullptr);
ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CCCD, nullptr);
#ifdef ARCH_ESP32
ESP.restart();
#endif
}
#endif
+5
View File
@@ -12,11 +12,16 @@ class NimbleBluetooth : BluetoothApi
bool isConnected();
int getRssi();
void sendLog(const uint8_t *logMessage, size_t length);
#if defined(NIMBLE_TWO)
void startAdvertising();
#endif
bool isDeInit = false;
private:
void setupService();
#if !defined(NIMBLE_TWO)
void startAdvertising();
#endif
};
void setBluetoothEnable(bool enable);
-42
View File
@@ -1,42 +0,0 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_attr.h"
#ifdef ESP32_FORCE_IRAM_MEMSET
/*
* T-Beam/classic ESP32 boot workaround
* ------------------------------------
* During early flash operations the ESP32 disables cache, but some IRAM flash
* code paths still reach libc memcpy/memset. If those resolve to flash-resident
* implementations, startup can panic with cache-disabled access errors.
*
* We wrap memcpy/memset for the T-Beam environment. Fast path uses the
* normal libc routines when cache is enabled; slow path uses IRAM-safe byte
* loops when cache is disabled.
*/
extern void *__real_memcpy(void *dst, const void *src, size_t n);
static inline bool IRAM_ATTR cache_is_enabled(void)
{
return (*(volatile uint32_t *)0x3FF00040u) & (1u << 3);
}
extern void *IRAM_ATTR __wrap_memcpy(void *dst, const void *src, size_t n)
{
if (cache_is_enabled()) {
return __real_memcpy(dst, src, n);
}
uint8_t *d = (uint8_t *)dst;
const uint8_t *s = (const uint8_t *)src;
while (n--) {
*d++ = *s++;
}
return dst;
}
#endif
-42
View File
@@ -1,42 +0,0 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_attr.h"
#ifdef ESP32_FORCE_IRAM_MEMSET
/*
* T-Beam/classic ESP32 boot workaround
* ------------------------------------
* During early flash operations the ESP32 disables cache, but some IRAM flash
* code paths still reach libc memcpy/memset. If those resolve to flash-resident
* implementations, startup can panic with cache-disabled access errors.
*
* We wrap memcpy/memset for the T-Beam environment. Fast path uses the
* normal libc routines when cache is enabled; slow path uses IRAM-safe byte
* loops when cache is disabled.
*/
extern void *__real_memset(void *dst, int c, size_t n);
static inline bool IRAM_ATTR cache_is_enabled(void)
{
return (*(volatile uint32_t *)0x3FF00040u) & (1u << 3);
}
extern void *IRAM_ATTR __wrap_memset(void *dst, int c, size_t n)
{
if (cache_is_enabled()) {
return __real_memset(dst, c, n);
}
uint8_t *ptr = (uint8_t *)dst;
const uint8_t fill = (uint8_t)c;
while (n--) {
*ptr++ = fill;
}
return dst;
}
#endif
@@ -1,11 +0,0 @@
/* Arduino fix: catch esp_event's orphaned .text.handler_execute section and align to 4 bytes */
SECTIONS
{
.text.handler_execute ALIGN(4) :
{
KEEP(*(.text.handler_execute))
KEEP(*(.text.handler_execute.*))
. = ALIGN(4);
}
}
INSERT AFTER .flash.text;
-2
View File
@@ -158,8 +158,6 @@
#define HW_VENDOR meshtastic_HardwareModel_CHATTER_2
#elif defined(STATION_G2)
#define HW_VENDOR meshtastic_HardwareModel_STATION_G2
#elif defined(STATION_G3)
#define HW_VENDOR meshtastic_HardwareModel_STATION_G3
#elif defined(UNPHONE)
#define HW_VENDOR meshtastic_HardwareModel_UNPHONE
#elif defined(WIPHONE)
+6 -21
View File
@@ -165,30 +165,17 @@ void esp32Setup()
// #define APP_WATCHDOG_SECS 45
#define APP_WATCHDOG_SECS 90
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
const esp_task_wdt_config_t wdt_config = {
.timeout_ms = APP_WATCHDOG_SECS * 1000,
.idle_core_mask = (1U << CONFIG_FREERTOS_NUMBER_OF_CORES) - 1U,
.trigger_panic = true,
};
res = esp_task_wdt_init(&wdt_config);
if (res == ESP_ERR_INVALID_STATE) {
LOG_WARN("Task watchdog already initialized, reconfiguring existing instance");
res = esp_task_wdt_reconfigure(&wdt_config);
}
#ifdef CONFIG_IDF_TARGET_ESP32C6
esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t));
wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000;
wdt_config->trigger_panic = true;
res = esp_task_wdt_init(wdt_config);
assert(res == ESP_OK);
#else
res = esp_task_wdt_init(APP_WATCHDOG_SECS, true);
if (res == ESP_ERR_INVALID_STATE) {
LOG_WARN("Task watchdog already initialized, reusing existing instance");
res = ESP_OK;
}
assert(res == ESP_OK);
#endif
res = esp_task_wdt_status(NULL);
if (res == ESP_ERR_NOT_FOUND) {
res = esp_task_wdt_add(NULL);
}
res = esp_task_wdt_add(NULL);
assert(res == ESP_OK);
#if HAS_32768HZ
@@ -271,10 +258,8 @@ void cpuDeepSleep(uint32_t msecToWake)
#endif
variant_shutdown();
#if SOC_PM_SUPPORT_RTC_PERIPH_PD
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
@@ -3,63 +3,14 @@
#ifdef M5STACK_CARDPUTER_ADV
#include "AudioBoard.h"
#include <Wire.h>
DriverPins PinsAudioBoardES8311;
AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311);
// PI4IOE5V6408 on the optional Cap LoRa-1262 (and Cap LoRa868).
#define PI4IO_ADDR 0x43
#define PI4IO_REG_IO_DIR 0x03
#define PI4IO_REG_OUT_SET 0x05
#define PI4IO_REG_OUT_H_IM 0x07
static TwoWire *findLoraCapBus()
{
TwoWire *candidates[] = {&Wire1, &Wire};
for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) {
candidates[i]->beginTransmission(PI4IO_ADDR);
if (candidates[i]->endTransmission() == 0) {
return candidates[i];
}
}
return nullptr;
}
static bool pi4ioWrite(TwoWire *bus, uint8_t reg, uint8_t val)
{
bus->beginTransmission(PI4IO_ADDR);
bus->write(reg);
bus->write(val);
uint8_t status = bus->endTransmission();
if (status != 0) {
LOG_DEBUG("PI4IO write reg=0x%02x val=0x%02x failed, I2C status=%u", reg, val, status);
return false;
}
return true;
}
static void initLoraCap()
{
TwoWire *bus = findLoraCapBus();
if (!bus) {
LOG_ERROR("Cap LoRa-1262 not found");
return;
}
bool ok = pi4ioWrite(bus, PI4IO_REG_IO_DIR, 0b00000001);
ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000000);
ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_SET, 0b00000001);
if (!ok) {
LOG_ERROR("Antenna switch init failed");
}
}
// M5stack Cardputer ADV specific init
void lateInitVariant()
{
initLoraCap();
// AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug);
// I2C: function, scl, sda
PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire);
+27 -5
View File
@@ -386,12 +386,34 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
meshtastic::BluetoothStatus newStatus(textkey);
bluetoothStatus->updateStatus(&newStatus);
#if HAS_SCREEN && !defined(MESHTASTIC_EXCLUDE_SCREEN)
#if HAS_SCREEN && \
!defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (screen) {
std::string configuredPasskeyText = std::to_string(configuredPasskey);
std::string ble_message =
"Bluetooth\nPIN\n[M]" + configuredPasskeyText.substr(0, 3) + " " + configuredPasskeyText.substr(3, 6);
screen->showSimpleBanner(ble_message.c_str(), 30000);
screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
char btPIN[16] = "888888";
snprintf(btPIN, sizeof(btPIN), "%06u", configuredPasskey);
int x_offset = display->width() / 2;
int y_offset = display->height() <= 80 ? 0 : 12;
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, y_offset + y, "Bluetooth");
display->setFont(FONT_SMALL);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5;
display->drawString(x_offset + x, y_offset + y, "Enter this code");
display->setFont(FONT_LARGE);
String displayPin(btPIN);
String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5;
display->drawString(x_offset + x, y_offset + y, pin);
display->setFont(FONT_SMALL);
String deviceName = "Name: ";
deviceName.concat(getDeviceName());
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5;
display->drawString(x_offset + x, y_offset + y, deviceName);
});
}
#endif
passkeyShowing = true;
-824
View File
@@ -1,824 +0,0 @@
/**
* Arduino.h Zephyr compatibility shim for nRF54L15
*
* Provides the Arduino API surface expected by Meshtastic, backed by
* Zephyr primitives. Only the subset actually used by Meshtastic is
* implemented; the rest compiles as no-ops / stubs for now.
*
* Phase 2: compile only. Real GPIO / SPI / Wire implementations follow
* in Phase 3 once the build is clean.
*/
#pragma once
#ifndef Arduino_h
#define Arduino_h
// ── C standard headers ───────────────────────────────────────────────────────
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h> /* strcasecmp, strncasecmp */
// ── Zephyr kernel ────────────────────────────────────────────────────────────
#include <zephyr/kernel.h>
#include <zephyr/sys/reboot.h>
// ── Basic Arduino types ──────────────────────────────────────────────────────
typedef bool boolean;
typedef uint8_t byte;
typedef uint16_t word;
// ── Pin / digital constants ──────────────────────────────────────────────────
#define INPUT 0u
#define OUTPUT 1u
#define INPUT_PULLUP 2u
#define INPUT_PULLDOWN 3u
#define OUTPUT_OPENDRAIN 4u
#define HIGH 1u
#define LOW 0u
#define CHANGE 1
#define FALLING 2
#define RISING 3
#ifndef LED_BUILTIN
#define LED_BUILTIN -1
#endif
// ── Math / trig constants ────────────────────────────────────────────────────
#ifndef PI
#define PI 3.14159265358979323846
#endif
#define HALF_PI 1.57079632679489661923
#define TWO_PI 6.28318530717958647693
#define DEG_TO_RAD 0.01745329251994329576
#define RAD_TO_DEG 57.2957795130823208767
#define EULER 2.71828182845904523536
// ── Bit utilities ────────────────────────────────────────────────────────────
#define bitRead(v, b) (((v) >> (b)) & 1)
#define bitSet(v, b) ((v) |= (1UL << (b)))
#define bitClear(v, b) ((v) &= ~(1UL << (b)))
#define bitToggle(v, b) ((v) ^= (1UL << (b)))
#define bitWrite(v, b, x) ((x) ? bitSet(v, b) : bitClear(v, b))
#define bit(b) (1UL << (b))
#define lowByte(w) ((uint8_t)((w)&0xff))
#define highByte(w) ((uint8_t)((w) >> 8))
// word(h,l) — only define if not already defined (conflicts with typedef above)
#undef word
#define word(h, l) ((uint16_t)(((h) << 8) | (l)))
// ── UART config constants ─────────────────────────────────────────────────────
#define SERIAL_8N1 0x800001cu
#define SERIAL_8N2 0x8000001eu
#define SERIAL_8E1 0x8000001eu
#define SERIAL_7E1 0x8000001cu
// ── Integer order ────────────────────────────────────────────────────────────
// Adafruit BusIO's SPIDevice.h has `typedef BitOrder BusIOBitOrder;` which
// requires BitOrder to be a *type*, not a macro. Mirror the ArduinoCore-API
// enum definition rather than #defines.
enum BitOrder : uint8_t {
LSBFIRST = 0,
MSBFIRST = 1,
};
// ── pgmspace compatibility (no-ops on Cortex-M) ──────────────────────────────
#define PROGMEM
#define PSTR(s) (s)
#define F(s) (s)
#define pgm_read_byte(addr) (*((const uint8_t *)(addr)))
#define pgm_read_word(addr) (*((const uint16_t *)(addr)))
#define pgm_read_dword(addr) (*((const uint32_t *)(addr)))
#define pgm_read_float(addr) (*((const float *)(addr)))
#define pgm_read_ptr(addr) (*((const void **)(addr)))
#define strlen_P(s) strlen(s)
#define strcpy_P(d, s) strcpy(d, s)
#define strncpy_P(d, s, n) strncpy(d, s, n)
#define strcmp_P(a, b) strcmp(a, b)
#define memcpy_P(d, s, n) memcpy(d, s, n)
#define sprintf_P sprintf
typedef const char *PGM_P;
typedef const char *PGM_VOID_P;
// ── Arduino numeric base constants (used by Print, RadioLib, etc.) ───────────
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
// ── ulong / uint typedef (used by RadioLibInterface, etc.) ───────────────────
typedef unsigned long ulong;
typedef unsigned int uint;
// ── Interrupt stubs ──────────────────────────────────────────────────────────
static inline void interrupts() {}
static inline void noInterrupts() {}
#define digitalPinToInterrupt(p) (p)
// ── portMAX_DELAY — freertosinc.h also defines this; let it win ──────────────
// We intentionally do NOT define portMAX_DELAY here. freertosinc.h defines
// it for the FreeRTOS / Meshtastic threading layer and must not be overridden.
// ── Timing & system functions — declared with C linkage ──────────────────────
// buzz.cpp and others forward-declare delay() as extern "C"; keep linkage
// consistent by wrapping in extern "C" here.
#ifdef __cplusplus
extern "C" {
#endif
void NVIC_SystemReset(void);
uint32_t millis(void);
uint32_t micros(void);
void delay(uint32_t ms);
void delayMicroseconds(uint32_t us);
void yield(void);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
#include <cctype>
#include <cstdarg>
// ── C++ STL — include BEFORE defining any min/max helpers ───────────────────
// Include algorithm first so its min/max templates are in scope.
// We must NOT define min/max as function-like macros: the C++ STL uses
// 3-argument versions (min(a,b,comp)) that the preprocessor would treat as
// calling a 2-arg macro with 3 args.
#include <algorithm>
// Bring 2-arg std::min / std::max into the global namespace as unqualified
// names so that Arduino code calling min(a,b) continues to compile.
// (Arduino convention; kept minimal to avoid surprises.)
#undef min
#undef max
using std::max;
using std::min;
// ── Arduino math helpers (macros safe for mixed-type / C calls) ──────────────
#ifndef abs
#define abs(x) ((x) >= 0 ? (x) : -(x))
#endif
#define constrain(x, l, h) ((x) < (l) ? (l) : ((x) > (h) ? (h) : (x)))
#define round(x) ((x) >= 0 ? (long)((x) + 0.5) : (long)((x)-0.5))
#define radians(d) ((d)*DEG_TO_RAD)
#define degrees(r) ((r)*RAD_TO_DEG)
#define sq(x) ((x) * (x))
// ── Random ───────────────────────────────────────────────────────────────────
static inline void randomSeed(unsigned long seed)
{
srand((unsigned int)seed);
}
static inline long random(void)
{
return (long)rand();
}
static inline long random(long bound)
{
return bound > 0 ? (rand() % bound) : 0;
}
static inline long random(long lo, long hi)
{
return hi > lo ? lo + rand() % (hi - lo) : lo;
}
// ── GPIO — real Zephyr implementation (Phase 3) ──────────────────────────────
// Implemented in nrf54l15_arduino.cpp using Zephyr GPIO/SPI APIs.
// Pin numbering: P0.n = n, P1.n = 16+n, P2.n = 32+n
void pinMode(uint32_t pin, uint32_t mode);
void digitalWrite(uint32_t pin, uint32_t value);
int digitalRead(uint32_t pin);
static inline void digitalToggle(uint32_t pin)
{
digitalWrite(pin, !digitalRead(pin));
}
static inline uint32_t analogRead(uint32_t)
{
return 0;
}
static inline void analogWrite(uint32_t, uint32_t) {}
static inline void analogReadResolution(int) {}
static inline void analogWriteResolution(int) {}
// ── __WFI — provided by CMSIS core_cm33.h; do NOT redefine here ─────────────
// ── __FlashStringHelper — Arduino PROGMEM string class (no-op on Cortex-M) ──
class __FlashStringHelper;
// ── attachInterrupt / detachInterrupt — real Zephyr GPIO interrupt impl ──────
typedef void (*voidFuncPtr)(void);
void attachInterrupt(uint32_t pin, voidFuncPtr cb, int mode);
void detachInterrupt(uint32_t pin);
// ── Forward declaration of String (needed by Print / Stream) ─────────────────
class String;
// ── Print base class ─────────────────────────────────────────────────────────
class Print
{
public:
virtual size_t write(uint8_t c) = 0;
virtual size_t write(const uint8_t *buf, size_t n)
{
size_t written = 0;
while (n--)
written += write(*buf++);
return written;
}
size_t write(const char *s) { return s ? write((const uint8_t *)s, strlen(s)) : 0; }
size_t write(const char *s, size_t n) { return write((const uint8_t *)s, n); }
size_t print(const char *s) { return s ? write((const uint8_t *)s, strlen(s)) : 0; }
int printf(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
size_t print(char c) { return write((uint8_t)c); }
size_t print(const String &s);
size_t print(unsigned char n, int base = 10);
size_t print(int n, int base = 10);
size_t print(long n, int base = 10);
size_t print(unsigned int n, int base = 10);
size_t print(unsigned long n, int base = 10);
size_t print(float n, int digits = 2);
size_t print(double n, int digits = 2);
size_t print(bool b) { return print(b ? "true" : "false"); }
size_t println() { return write((uint8_t)'\n'); }
size_t println(const char *s)
{
size_t r = print(s);
return r + println();
}
size_t println(char c)
{
size_t r = print(c);
return r + println();
}
size_t println(const String &s);
size_t println(int n, int base = 10)
{
size_t r = print(n, base);
return r + println();
}
size_t println(long n, int base = 10)
{
size_t r = print(n, base);
return r + println();
}
size_t println(unsigned long n, int base = 10)
{
size_t r = print(n, base);
return r + println();
}
size_t println(unsigned int n, int base = 10)
{
size_t r = print(n, base);
return r + println();
}
size_t println(float n, int d = 2)
{
size_t r = print(n, d);
return r + println();
}
size_t println(double n, int d = 2)
{
size_t r = print(n, d);
return r + println();
}
size_t println(bool b)
{
size_t r = print(b);
return r + println();
}
virtual void flush() {}
};
// ── Stream base class ────────────────────────────────────────────────────────
class Stream : public Print
{
public:
virtual int available() = 0;
virtual int read() = 0;
virtual int peek() = 0;
virtual void setTimeout(unsigned long) {}
virtual bool find(const char *) { return false; }
String readString();
String readStringUntil(char terminator);
};
// ── Minimal Arduino String class (backed by a char buffer) ───────────────────
class String
{
public:
String() : _buf(nullptr), _len(0), _cap(0) {}
// Implicit conversion is part of the Arduino String contract, used pervasively across the codebase.
// cppcheck-suppress noExplicitConstructor
String(const char *cstr) : _buf(nullptr), _len(0), _cap(0)
{
if (cstr)
assign(cstr, strlen(cstr));
}
// cppcheck-suppress noExplicitConstructor
String(const String &s) : _buf(nullptr), _len(0), _cap(0) { assign(s._buf ? s._buf : "", s._len); }
// cppcheck-suppress noExplicitConstructor
String(char c) : _buf(nullptr), _len(0), _cap(0)
{
const char tmp[2] = {c, 0};
assign(tmp, 1);
}
// cppcheck-suppress noExplicitConstructor
String(int n) : _buf(nullptr), _len(0), _cap(0)
{
char tmp[16];
snprintf(tmp, 16, "%d", n);
assign(tmp, strlen(tmp));
}
// cppcheck-suppress noExplicitConstructor
String(unsigned int n) : _buf(nullptr), _len(0), _cap(0)
{
char tmp[16];
snprintf(tmp, 16, "%u", n);
assign(tmp, strlen(tmp));
}
// cppcheck-suppress noExplicitConstructor
String(long n) : _buf(nullptr), _len(0), _cap(0)
{
char tmp[24];
snprintf(tmp, 24, "%ld", n);
assign(tmp, strlen(tmp));
}
// cppcheck-suppress noExplicitConstructor
String(unsigned long n) : _buf(nullptr), _len(0), _cap(0)
{
char tmp[24];
snprintf(tmp, 24, "%lu", n);
assign(tmp, strlen(tmp));
}
// cppcheck-suppress noExplicitConstructor
String(float n, int d = 2) : _buf(nullptr), _len(0), _cap(0)
{
char tmp[32];
snprintf(tmp, 32, "%.*f", d, n);
assign(tmp, strlen(tmp));
}
// cppcheck-suppress noExplicitConstructor
String(double n, int d = 2) : _buf(nullptr), _len(0), _cap(0)
{
char tmp[32];
snprintf(tmp, 32, "%.*f", d, (double)n);
assign(tmp, strlen(tmp));
}
~String() { free(_buf); }
String &operator=(const String &s)
{
assign(s._buf ? s._buf : "", s._len);
return *this;
}
String &operator=(const char *s)
{
assign(s ? s : "", s ? strlen(s) : 0);
return *this;
}
String &operator=(char c)
{
const char tmp[2] = {c, 0};
assign(tmp, 1);
return *this;
}
String &operator+=(const String &s)
{
concat(s._buf ? s._buf : "", s._len);
return *this;
}
String &operator+=(const char *s)
{
if (s)
concat(s, strlen(s));
return *this;
}
String &operator+=(char c)
{
concat(&c, 1);
return *this;
}
String &operator+=(int n) { return *this += String(n); }
String &operator+=(unsigned long n) { return *this += String(n); }
String operator+(const String &rhs) const
{
String r(*this);
r += rhs;
return r;
}
String operator+(const char *rhs) const
{
String r(*this);
r += rhs;
return r;
}
String operator+(char rhs) const
{
String r(*this);
r += rhs;
return r;
}
bool operator==(const String &s) const { return _len == s._len && (_len == 0 || strcmp(_buf, s._buf) == 0); }
bool operator==(const char *s) const { return s && strcmp(c_str(), s) == 0; }
bool operator!=(const String &s) const { return !(*this == s); }
bool operator!=(const char *s) const { return !(*this == s); }
bool operator<(const String &s) const { return strcmp(c_str(), s.c_str()) < 0; }
bool operator>(const String &s) const { return strcmp(c_str(), s.c_str()) > 0; }
char operator[](unsigned int i) const { return (_buf && i < _len) ? _buf[i] : 0; }
char &operator[](unsigned int i)
{
static char dummy = 0;
return (_buf && i < _len) ? _buf[i] : dummy;
}
const char *c_str() const { return _buf ? _buf : ""; }
unsigned int length() const { return _len; }
bool isEmpty() const { return _len == 0; }
bool equals(const String &s) const { return *this == s; }
bool equals(const char *s) const { return *this == s; }
bool equalsIgnoreCase(const String &s) const
{
if (_len != s._len)
return false;
for (unsigned i = 0; i < _len; i++)
if (std::tolower(_buf[i]) != std::tolower(s._buf[i]))
return false;
return true;
}
bool startsWith(const String &pfx) const
{
if (pfx._len > _len)
return false;
return strncmp(c_str(), pfx.c_str(), pfx._len) == 0;
}
bool startsWith(const char *pfx) const
{
if (!pfx)
return false;
size_t pl = strlen(pfx);
return pl <= _len && strncmp(c_str(), pfx, pl) == 0;
}
bool endsWith(const String &sfx) const
{
if (sfx._len > _len)
return false;
return strcmp(c_str() + _len - sfx._len, sfx.c_str()) == 0;
}
int indexOf(char c, unsigned from = 0) const
{
if (!_buf)
return -1;
const char *p = strchr(_buf + from, c);
return p ? (int)(p - _buf) : -1;
}
int indexOf(const String &s, unsigned from = 0) const
{
if (!_buf)
return -1;
const char *p = strstr(_buf + from, s.c_str());
return p ? (int)(p - _buf) : -1;
}
int lastIndexOf(char c) const
{
if (!_buf)
return -1;
const char *p = strrchr(_buf, c);
return p ? (int)(p - _buf) : -1;
}
String substring(unsigned beginIndex) const
{
if (!_buf || beginIndex >= _len)
return String();
return String(_buf + beginIndex);
}
String substring(unsigned beginIndex, unsigned endIndex) const
{
if (!_buf || beginIndex >= _len)
return String();
if (endIndex > _len)
endIndex = _len;
if (endIndex <= beginIndex)
return String();
String r;
r.assign(_buf + beginIndex, endIndex - beginIndex);
return r;
}
void toUpperCase()
{
if (_buf)
for (unsigned i = 0; i < _len; i++)
_buf[i] = (char)std::toupper(_buf[i]);
}
void toLowerCase()
{
if (_buf)
for (unsigned i = 0; i < _len; i++)
_buf[i] = (char)std::tolower(_buf[i]);
}
void trim()
{
if (!_buf || _len == 0)
return;
unsigned s = 0;
while (s < _len && std::isspace(_buf[s]))
s++;
unsigned e = _len;
while (e > s && std::isspace(_buf[e - 1]))
e--;
if (s > 0 || e < _len) {
memmove(_buf, _buf + s, e - s);
_len = e - s;
_buf[_len] = 0;
}
}
void replace(char from, char to)
{
if (_buf)
for (unsigned i = 0; i < _len; i++)
if (_buf[i] == from)
_buf[i] = to;
}
void replace(const String &from, const String &to);
bool remove(unsigned index, unsigned count = 1)
{
if (!_buf || index >= _len)
return false;
if (index + count > _len)
count = _len - index;
memmove(_buf + index, _buf + index + count, _len - index - count + 1);
_len -= count;
return true;
}
void clear()
{
_len = 0;
if (_buf)
_buf[0] = 0;
}
char charAt(unsigned i) const { return (*this)[i]; }
void setCharAt(unsigned i, char c)
{
if (_buf && i < _len)
_buf[i] = c;
}
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const
{
if (!buf || bufsize == 0)
return;
unsigned int avail = (_buf && _len > index) ? (_len - index) : 0;
unsigned int copy = avail < bufsize - 1 ? avail : bufsize - 1;
if (copy > 0)
memcpy(buf, _buf + index, copy);
buf[copy] = '\0';
}
void concat(const String &s) { *this += s; }
void concat(const char *s) { *this += s; }
long toInt() const { return _buf ? atol(_buf) : 0; }
float toFloat() const { return _buf ? (float)atof(_buf) : 0.0f; }
double toDouble() const { return _buf ? atof(_buf) : 0.0; }
private:
char *_buf;
unsigned int _len;
unsigned int _cap;
void assign(const char *s, unsigned int n)
{
if (n >= _cap)
reserve(n + 1);
if (_buf) {
memcpy(_buf, s, n);
_buf[n] = 0;
_len = n;
}
}
void concat(const char *s, unsigned int n)
{
if (!s || n == 0)
return;
unsigned newlen = _len + n;
if (newlen >= _cap)
reserve(newlen + 1);
if (_buf) {
memcpy(_buf + _len, s, n);
_len = newlen;
_buf[_len] = 0;
}
}
void reserve(unsigned int n)
{
char *b = (char *)realloc(_buf, n);
if (b) {
_buf = b;
_cap = n;
}
}
};
inline String operator+(const char *lhs, const String &rhs)
{
return String(lhs) + rhs;
}
inline String operator+(char lhs, const String &rhs)
{
return String(lhs) + rhs;
}
// ── Print inline definitions that need String ────────────────────────────────
inline size_t Print::print(const String &s)
{
return write((const uint8_t *)s.c_str(), s.length());
}
inline size_t Print::println(const String &s)
{
size_t r = print(s);
return r + println();
}
// ── Stream inline definitions that need String ───────────────────────────────
inline String Stream::readString()
{
return String();
}
inline String Stream::readStringUntil(char)
{
return String();
}
// ── HardwareSerial ───────────────────────────────────────────────────────────
class HardwareSerial : public Stream
{
public:
void begin(unsigned long) {}
void begin(unsigned long, uint16_t) {}
void end() {}
void setPins(int rx, int tx) {}
void setPinout(int tx, int rx) {}
void setFIFOSize(size_t) {}
void setRxBufferSize(size_t) {}
void begin(unsigned long baud, uint32_t config, int8_t rx = -1, int8_t tx = -1, bool invert = false) {}
int available() override { return 0; }
int read() override { return -1; }
int peek() override { return -1; }
size_t write(uint8_t c) override;
size_t write(const uint8_t *buf, size_t n) override;
using Print::write; // un-hide base class write(const char*)
size_t readBytes(uint8_t *buf, size_t len) { return 0; }
size_t readBytes(char *buf, size_t len) { return 0; }
operator bool() const { return true; }
void flush() override {}
String readString() { return String(); }
String readStringUntil(char) { return String(); }
};
// Uart — nRF52 BSP alias for HardwareSerial (used by GPS.h when ARCH_NRF52)
typedef HardwareSerial Uart;
extern HardwareSerial Serial;
extern HardwareSerial Serial1;
extern HardwareSerial Serial2;
// ── map() utility ────────────────────────────────────────────────────────────
static inline long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
// ── shiftIn / shiftOut stubs ─────────────────────────────────────────────────
static inline uint8_t shiftIn(uint8_t, uint8_t, uint8_t)
{
return 0;
}
static inline void shiftOut(uint8_t, uint8_t, uint8_t, uint8_t) {}
// ── tone / noTone stubs ──────────────────────────────────────────────────────
static inline void tone(uint8_t, unsigned int, unsigned long = 0) {}
static inline void noTone(uint8_t) {}
// ── pulseIn stub ─────────────────────────────────────────────────────────────
static inline unsigned long pulseIn(uint8_t, uint8_t, unsigned long = 1000000UL)
{
return 0;
}
// ── strdup / stpcpy — POSIX extensions not in Zephyr newlib ─────────────────
#ifndef strdup
static inline char *strdup(const char *s)
{
size_t n = strlen(s) + 1;
char *d = (char *)malloc(n);
if (d)
memcpy(d, s, n);
return d;
}
#endif
#ifndef stpcpy
static inline char *stpcpy(char *dst, const char *src)
{
while ((*dst++ = *src++) != '\0') {
}
return dst - 1;
}
#endif
// ── strnstr — BSD extension not in Zephyr libc; defined in meshUtils.cpp ─────
// Declare here so callers (GPS.cpp etc.) don't need ARCH_PORTDUINO.
#ifndef STRNSTR
#define STRNSTR
char *strnstr(const char *s, const char *find, size_t slen);
#endif
// ── strlcpy — BSD extension; implementation in nrf54l15_arduino.cpp ──────────
#ifndef HAVE_STRLCPY
#define HAVE_STRLCPY
#ifdef __cplusplus
extern "C" {
#endif
size_t strlcpy(char *dst, const char *src, size_t size);
#ifdef __cplusplus
}
#endif
#endif
// ── setenv / getenv / tzset — Zephyr stubs for timezone support ──────────────
#include <stdlib.h>
static inline int setenv(const char *, const char *, int)
{
return 0;
}
static inline void tzset(void) {}
// ── dbgHeapFree / dbgHeapTotal — nRF52 BSP heap diagnostics ─────────────────
// Used by memGet.cpp when ARCH_NRF52 is defined. Return 0 for Phase 2.
static inline uint32_t dbgHeapFree(void)
{
return 0;
}
static inline uint32_t dbgHeapTotal(void)
{
return 0;
}
// ── WCharacter helpers ───────────────────────────────────────────────────────
static inline bool isAlpha(char c)
{
return std::isalpha((unsigned char)c) != 0;
}
static inline bool isAlphaNumeric(char c)
{
return std::isalnum((unsigned char)c) != 0;
}
static inline bool isDigit(char c)
{
return std::isdigit((unsigned char)c) != 0;
}
static inline bool isSpace(char c)
{
return std::isspace((unsigned char)c) != 0;
}
static inline bool isUpperCase(char c)
{
return std::isupper((unsigned char)c) != 0;
}
static inline bool isLowerCase(char c)
{
return std::islower((unsigned char)c) != 0;
}
static inline char toUpperCase(char c)
{
return (char)std::toupper((unsigned char)c);
}
static inline char toLowerCase(char c)
{
return (char)std::tolower((unsigned char)c);
}
#else /* C only */
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef abs
#define abs(x) ((x) >= 0 ? (x) : -(x))
#endif
#define constrain(x, l, h) ((x) < (l) ? (l) : ((x) > (h) ? (h) : (x)))
#define round(x) ((x) >= 0 ? (long)((x) + 0.5) : (long)((x)-0.5))
#endif /* __cplusplus */
#endif /* Arduino_h */
-34
View File
@@ -1,34 +0,0 @@
// IPAddress.h — stub for nRF54L15/Zephyr
// MQTT.cpp includes this for IPv4 address representation.
// Phase 2: compile-only stub.
#pragma once
#include <stdint.h>
class IPAddress
{
public:
IPAddress() : _addr(0) {}
explicit IPAddress(uint32_t addr) : _addr(addr) {}
IPAddress(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
: _addr(((uint32_t)a) | ((uint32_t)b << 8) | ((uint32_t)c << 16) | ((uint32_t)d << 24))
{
}
uint8_t operator[](int i) const { return reinterpret_cast<const uint8_t *>(&_addr)[i]; }
operator uint32_t() const { return _addr; }
bool operator==(const IPAddress &o) const { return _addr == o._addr; }
bool operator!=(const IPAddress &o) const { return _addr != o._addr; }
bool fromString(const char *addr)
{
unsigned a, b, c, d;
if (sscanf(addr, "%u.%u.%u.%u", &a, &b, &c, &d) == 4 && a <= 255 && b <= 255 && c <= 255 && d <= 255) {
_addr = a | (b << 8) | (c << 16) | (d << 24);
return true;
}
return false;
}
private:
uint32_t _addr;
};
@@ -1,274 +0,0 @@
// InternalFileSystem.cpp — Zephyr LittleFS backend for nRF54L15
//
// Implements Adafruit_LittleFS_Namespace used by FSCommon.h/cpp.
// Storage: 36 KB storage_partition in nRF54L15 internal RRAM (defined in
// zephyr/dts/nordic/nrf54l15_partition.dtsi, included by the board DTS).
#include "InternalFileSystem.h"
#include "configuration.h"
#include <zephyr/fs/fs.h>
#include <zephyr/fs/littlefs.h>
#include <zephyr/storage/flash_map.h>
using namespace Adafruit_LittleFS_Namespace;
// ── LittleFS mount ────────────────────────────────────────────────────────
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(nrf54l15_lfs_data);
static struct fs_mount_t _lfs_mnt = {
.type = FS_LITTLEFS,
.mnt_point = NRF54L15_FS_MOUNT,
.fs_data = &nrf54l15_lfs_data,
.storage_dev = (void *)(uintptr_t)FIXED_PARTITION_ID(storage_partition),
.flags = 0,
};
// ── Global singleton ──────────────────────────────────────────────────────
Adafruit_LittleFS_Namespace::InternalFileSystem InternalFS;
// ── Path helpers ──────────────────────────────────────────────────────────
void InternalFileSystem::toabs(const char *rel, char *abs, size_t abssz)
{
// Root "/" maps to the mount point itself (no trailing slash)
if (rel[0] == '/' && rel[1] == '\0') {
strncpy(abs, NRF54L15_FS_MOUNT, abssz - 1);
abs[abssz - 1] = '\0';
} else if (rel[0] == '/') {
snprintf(abs, abssz, "%s%s", NRF54L15_FS_MOUNT, rel);
} else {
snprintf(abs, abssz, "%s/%s", NRF54L15_FS_MOUNT, rel);
}
}
// Strip mount-point prefix to get the FS-root-relative path ("/prefs/...").
static void torel(const char *abs, char *rel, size_t relsz)
{
const char *mp = NRF54L15_FS_MOUNT;
size_t mplen = strlen(mp);
if (strncmp(abs, mp, mplen) == 0) {
const char *suffix = abs + mplen;
if (suffix[0] == '\0') {
strncpy(rel, "/", relsz);
} else {
strncpy(rel, suffix, relsz - 1);
rel[relsz - 1] = '\0';
}
} else {
strncpy(rel, abs, relsz - 1);
rel[relsz - 1] = '\0';
}
}
// ── InternalFileSystem methods ────────────────────────────────────────────
bool InternalFileSystem::begin()
{
if (_mounted)
return true;
int rc = fs_mount(&_lfs_mnt);
if (rc == 0) {
_mounted = true;
return true;
}
// Mount failed: attempt to format (creates a fresh LittleFS)
LOG_WARN("LittleFS mount failed (%d), formatting storage partition...", rc);
int fmt_rc = fs_mkfs(FS_LITTLEFS, (uintptr_t)FIXED_PARTITION_ID(storage_partition), NULL, 0);
if (fmt_rc != 0) {
LOG_ERROR("LittleFS format failed (%d)", fmt_rc);
return false;
}
rc = fs_mount(&_lfs_mnt);
if (rc == 0) {
_mounted = true;
return true;
}
LOG_ERROR("LittleFS mount failed after format (%d)", rc);
return false;
}
File InternalFileSystem::open(const char *path, const char *mode)
{
if (!_mounted)
return File();
char abs[NRF54L15_FS_PATHLEN];
toabs(path, abs, sizeof(abs));
auto s = std::make_shared<NRF54L15FileState>();
if (!s)
return File();
strncpy(s->fullpath, abs, sizeof(s->fullpath) - 1);
torel(abs, s->relpath, sizeof(s->relpath));
// Check whether the path is a directory
struct fs_dirent entry;
int stat_rc = fs_stat(abs, &entry);
if (stat_rc == 0 && entry.type == FS_DIR_ENTRY_DIR) {
s->is_dir = true;
if (fs_opendir(&s->dir, abs) == 0) {
s->valid = true;
return File(s);
}
return File();
}
// Open as a regular file
fs_mode_t flags;
if (strcmp(mode, FILE_O_WRITE) == 0) {
// Truncate on write — unlink first to ensure a clean start
fs_unlink(abs);
flags = FS_O_WRITE | FS_O_CREATE;
} else {
flags = FS_O_READ;
}
if (fs_open(&s->file, abs, flags) == 0) {
s->is_dir = false;
s->valid = true;
return File(s);
}
return File();
}
bool InternalFileSystem::exists(const char *path)
{
if (!_mounted)
return false;
char abs[NRF54L15_FS_PATHLEN];
toabs(path, abs, sizeof(abs));
struct fs_dirent entry;
return fs_stat(abs, &entry) == 0;
}
bool InternalFileSystem::remove(const char *path)
{
if (!_mounted)
return false;
char abs[NRF54L15_FS_PATHLEN];
toabs(path, abs, sizeof(abs));
return fs_unlink(abs) == 0;
}
bool InternalFileSystem::rename(const char *from, const char *to)
{
if (!_mounted)
return false;
char absfrom[NRF54L15_FS_PATHLEN], absto[NRF54L15_FS_PATHLEN];
toabs(from, absfrom, sizeof(absfrom));
toabs(to, absto, sizeof(absto));
return fs_rename(absfrom, absto) == 0;
}
bool InternalFileSystem::mkdir(const char *path)
{
if (!_mounted)
return false;
char abs[NRF54L15_FS_PATHLEN];
toabs(path, abs, sizeof(abs));
int rc = fs_mkdir(abs);
return rc == 0 || rc == -EEXIST;
}
bool InternalFileSystem::rmdir(const char *path)
{
if (!_mounted)
return false;
char abs[NRF54L15_FS_PATHLEN];
toabs(path, abs, sizeof(abs));
return fs_unlink(abs) == 0;
}
bool InternalFileSystem::rmdir_r(const char *path)
{
if (!_mounted)
return false;
char abs[NRF54L15_FS_PATHLEN];
toabs(path, abs, sizeof(abs));
struct fs_dir_t dir;
fs_dir_t_init(&dir);
if (fs_opendir(&dir, abs) != 0) {
// Not a directory — try to delete as file
return fs_unlink(abs) == 0;
}
struct fs_dirent entry;
char child[NRF54L15_FS_PATHLEN];
while (fs_readdir(&dir, &entry) == 0 && entry.name[0] != '\0') {
snprintf(child, sizeof(child), "%s/%s", abs, entry.name);
if (entry.type == FS_DIR_ENTRY_DIR) {
// Recurse: pass the absolute path stripped of mount prefix
char childrel[NRF54L15_FS_PATHLEN];
torel(child, childrel, sizeof(childrel));
rmdir_r(childrel);
} else {
fs_unlink(child);
}
}
fs_closedir(&dir);
return fs_unlink(abs) == 0;
}
bool InternalFileSystem::format()
{
if (_mounted) {
fs_unmount(&_lfs_mnt);
_mounted = false;
}
int rc = fs_mkfs(FS_LITTLEFS, (uintptr_t)FIXED_PARTITION_ID(storage_partition), NULL, 0);
if (rc != 0) {
LOG_ERROR("LittleFS format failed (%d)", rc);
return false;
}
return begin();
}
// ── File::openNextFile ────────────────────────────────────────────────────
// Defined here because it accesses Zephyr fs_readdir/fs_open APIs.
File File::openNextFile()
{
if (!_s || !_s->valid || !_s->is_dir)
return File();
struct fs_dirent entry;
if (fs_readdir(&_s->dir, &entry) != 0)
return File();
if (entry.name[0] == '\0')
return File(); // end of directory
char childabs[NRF54L15_FS_PATHLEN];
snprintf(childabs, sizeof(childabs), "%s/%s", _s->fullpath, entry.name);
auto s = std::make_shared<NRF54L15FileState>();
if (!s)
return File();
strncpy(s->fullpath, childabs, sizeof(s->fullpath) - 1);
torel(childabs, s->relpath, sizeof(s->relpath));
if (entry.type == FS_DIR_ENTRY_DIR) {
s->is_dir = true;
if (fs_opendir(&s->dir, childabs) == 0) {
s->valid = true;
return File(s);
}
} else {
s->is_dir = false;
if (fs_open(&s->file, childabs, FS_O_READ) == 0) {
s->valid = true;
return File(s);
}
}
return File();
}
-212
View File
@@ -1,212 +0,0 @@
// InternalFileSystem.h — Zephyr LittleFS backend for nRF54L15
//
// Implements the Adafruit InternalFileSystem API subset used by Meshtastic,
// backed by Zephyr's fs/littlefs on the storage_partition of the nRF54L15's
// internal RRAM. Partition size is taken from the DTS at compile time via
// FIXED_PARTITION_SIZE(storage_partition) — the DK overlay currently maps
// ~700 KB into slot1 (see zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay).
//
// Mount point: /lfs
// All paths passed to open/exists/mkdir etc. are relative to the FS root
// (e.g. "/prefs/config.proto") and are prepended with "/lfs" internally.
//
// File objects are copyable via std::shared_ptr<NRF54L15FileState>.
// The underlying Zephyr handle is closed when the last copy is destroyed.
#pragma once
#include <memory>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/fs/fs.h>
#include <zephyr/storage/flash_map.h>
#ifndef FILE_O_READ
#define FILE_O_READ "r"
#define FILE_O_WRITE "w"
#endif
#define NRF54L15_FS_MOUNT "/lfs"
#define NRF54L15_FS_PATHLEN 256
namespace Adafruit_LittleFS_Namespace
{
class InternalFileSystem; // forward
// ── Internal file/dir state ───────────────────────────────────────────────
struct NRF54L15FileState {
bool valid = false;
bool is_dir = false;
// Absolute Zephyr path, e.g. "/lfs/prefs/config.proto"
char fullpath[NRF54L15_FS_PATHLEN] = {0};
// Path from FS root, e.g. "/prefs/config.proto" (returned by name())
char relpath[NRF54L15_FS_PATHLEN] = {0};
struct fs_file_t file;
struct fs_dir_t dir;
NRF54L15FileState()
{
fs_file_t_init(&file);
fs_dir_t_init(&dir);
}
~NRF54L15FileState()
{
if (valid) {
if (is_dir)
fs_closedir(&dir);
else
fs_close(&file);
valid = false;
}
}
};
// ── File ─────────────────────────────────────────────────────────────────
class File
{
public:
File() = default;
explicit File(InternalFileSystem &) {} // nRF52 compat constructor
explicit operator bool() const { return _s && _s->valid; }
int read(void *buf, uint16_t nbyte)
{
if (!_s || !_s->valid || _s->is_dir)
return -1;
ssize_t n = fs_read(&_s->file, buf, nbyte);
return n < 0 ? -1 : (int)n;
}
int read()
{
uint8_t b;
return read(&b, 1) == 1 ? (int)b : -1;
}
size_t write(const uint8_t *buf, size_t len)
{
if (!_s || !_s->valid || _s->is_dir)
return 0;
ssize_t n = fs_write(&_s->file, buf, len);
return n < 0 ? 0 : (size_t)n;
}
size_t write(uint8_t b) { return write(&b, 1); }
void flush()
{
if (_s && _s->valid && !_s->is_dir)
fs_sync(&_s->file);
}
void close() { _s.reset(); }
size_t size()
{
if (!_s || !_s->valid || _s->is_dir)
return 0;
struct fs_dirent entry;
if (fs_stat(_s->fullpath, &entry) == 0)
return (size_t)entry.size;
return 0;
}
bool isDirectory() { return _s && _s->valid && _s->is_dir; }
// Returns path from FS root, e.g. "/prefs/config.proto"
const char *name() { return _s ? _s->relpath : ""; }
// Returns the next entry in a directory. Modifies the dir stream in _s.
File openNextFile();
void rewindDirectory()
{
if (!_s || !_s->valid || !_s->is_dir)
return;
// Zephyr has no rewinddir(); close + reopen the same handle. Skipping
// the close would leak the LittleFS dir state and the next openNextFile
// could return stale entries on some Zephyr versions.
fs_closedir(&_s->dir);
fs_dir_t_init(&_s->dir);
if (fs_opendir(&_s->dir, _s->fullpath) != 0) {
_s->valid = false;
}
}
bool seek(uint32_t pos)
{
if (!_s || !_s->valid || _s->is_dir)
return false;
return fs_seek(&_s->file, (off_t)pos, FS_SEEK_SET) == 0;
}
int available()
{
if (!_s || !_s->valid || _s->is_dir)
return 0;
off_t pos = fs_tell(&_s->file);
if (pos < 0)
return 0;
struct fs_dirent entry;
if (fs_stat(_s->fullpath, &entry) != 0)
return 0;
long rem = (long)entry.size - (long)pos;
return rem > 0 ? (int)rem : 0;
}
int peek() { return -1; }
// Internal: constructed by InternalFileSystem and openNextFile()
explicit File(std::shared_ptr<NRF54L15FileState> s) : _s(std::move(s)) {}
private:
std::shared_ptr<NRF54L15FileState> _s;
};
// ── InternalFileSystem ────────────────────────────────────────────────────
class InternalFileSystem
{
public:
bool begin();
File open(const char *path, const char *mode);
bool exists(const char *path);
bool remove(const char *path);
bool rename(const char *from, const char *to);
bool mkdir(const char *path);
bool rmdir(const char *path);
bool rmdir_r(const char *path); // recursive delete (used by FSCommon rmDir)
uint32_t usedBytes()
{
struct fs_statvfs st = {};
if (fs_statvfs(NRF54L15_FS_MOUNT, &st) != 0)
return 0;
// Zephyr returns block counts; convert to bytes. f_frsize is the
// fundamental fragment size (LittleFS reports it equal to the block
// size). used = (total - free) * frag_size.
if (st.f_blocks <= st.f_bfree)
return 0;
return (uint32_t)((st.f_blocks - st.f_bfree) * st.f_frsize);
}
uint32_t totalBytes() { return (uint32_t)FIXED_PARTITION_SIZE(storage_partition); }
bool format();
// Convert a FS-root-relative path to an absolute Zephyr path.
static void toabs(const char *rel, char *abs, size_t abssz);
private:
bool _mounted = false;
};
} // namespace Adafruit_LittleFS_Namespace
extern Adafruit_LittleFS_Namespace::InternalFileSystem InternalFS;
-18
View File
@@ -1,18 +0,0 @@
// NRF52Bluetooth.h — stub for nRF54L15/Zephyr
// main.h includes this when ARCH_NRF52 is defined.
// Bluetooth is excluded (MESHTASTIC_EXCLUDE_BLUETOOTH=1); this satisfies the
// include chain without pulling in the nRF52 Bluefruit SDK.
#pragma once
class NRF52Bluetooth
{
public:
void setup() {}
void shutdown() {}
void startDisabled() {}
void resumeAdvertising() {}
void clearBonds() {}
bool isConnected() { return false; }
int getRssi() { return 0; }
void sendLog(const uint8_t *, size_t) {}
};
-805
View File
@@ -1,805 +0,0 @@
// NRF54L15Bluetooth.cpp — Zephyr BLE GATT peripheral for Meshtastic nRF54L15
//
// GATT profile (identical UUIDs to the nRF52 / NimBLE implementations):
// Service: 6ba1b218-15a8-461f-9fa8-5dcae273eafd
// fromNum: ed9da18c-a800-4f66-a670-aa7547e34453 READ | NOTIFY
// fromRadio: 2c55e69e-4993-11ed-b878-0242ac120002 READ
// toRadio: f75c76d2-129e-4dad-a1dd-7866124401e7 WRITE
// logRadio: 5a3d6e49-06e6-4423-9944-e9de8cdf9547 READ | NOTIFY | INDICATE
//
// Threading model:
// - BT RX thread: connected_cb / disconnected_cb / GATT read_/write_
// callbacks
// - Meshtastic OSThread scheduler (cooperative, main thread):
// BleDeferredThread
// polls pendingToRadio and runs the zombie-connection watchdog every 100 ms
// - PhoneAPI::onNowHasData: sends fromNum notify synchronously from whichever
// thread pushed the packet (bt_gatt_notify is thread-safe in Zephyr)
// - active_conn protected by ble_mutex where needed
#include "NRF54L15Bluetooth.h"
#include "BluetoothCommon.h"
#include "BluetoothStatus.h"
#include "PowerFSM.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "main.h"
#include "mesh/PhoneAPI.h"
#include "mesh/mesh-pb-constants.h"
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/reboot.h>
// ── UUID definitions (little-endian per Bluetooth spec)
// ─────────────────────── Syntax: replace hyphens with commas, prefix 0x —
// matches BT_UUID_128_ENCODE doc.
#define MESH_SVC_UUID_VAL BT_UUID_128_ENCODE(0x6ba1b218, 0x15a8, 0x461f, 0x9fa8, 0x5dcae273eafd)
#define FROMNUM_UUID_VAL BT_UUID_128_ENCODE(0xed9da18c, 0xa800, 0x4f66, 0xa670, 0xaa7547e34453)
#define FROMRADIO_UUID_VAL BT_UUID_128_ENCODE(0x2c55e69e, 0x4993, 0x11ed, 0xb878, 0x0242ac120002)
#define TORADIO_UUID_VAL BT_UUID_128_ENCODE(0xf75c76d2, 0x129e, 0x4dad, 0xa1dd, 0x7866124401e7)
#define LOGRADIO_UUID_VAL BT_UUID_128_ENCODE(0x5a3d6e49, 0x06e6, 0x4423, 0x9944, 0xe9de8cdf9547)
static const struct bt_uuid_128 mesh_svc_uuid = BT_UUID_INIT_128(MESH_SVC_UUID_VAL);
static const struct bt_uuid_128 fromnum_uuid = BT_UUID_INIT_128(FROMNUM_UUID_VAL);
static const struct bt_uuid_128 fromradio_uuid = BT_UUID_INIT_128(FROMRADIO_UUID_VAL);
static const struct bt_uuid_128 toradio_uuid = BT_UUID_INIT_128(TORADIO_UUID_VAL);
static const struct bt_uuid_128 logradio_uuid = BT_UUID_INIT_128(LOGRADIO_UUID_VAL);
// ── Module state ─────────────────────────────────────────────────────────────
static struct bt_conn *active_conn = nullptr;
static K_MUTEX_DEFINE(ble_mutex);
// Take a reference to active_conn under ble_mutex. Returns nullptr if there is
// no active connection. Caller MUST bt_conn_unref() when done.
//
// Reading `active_conn` outside this lock races with disconnected_cb which can
// unref + null it on the BT RX thread — touching the freed pointer (even just
// to bt_conn_ref it) is a use-after-free.
static struct bt_conn *acquire_active_conn()
{
struct bt_conn *conn = nullptr;
k_mutex_lock(&ble_mutex, K_FOREVER);
if (active_conn) {
conn = bt_conn_ref(active_conn);
}
k_mutex_unlock(&ble_mutex);
return conn;
}
static bool bt_initialized = false; // bt_enable() called at most once
static bool ble_enabled = false; // set by setup(), cleared by shutdown()
// Forward declarations — BT_GATT_SERVICE_DEFINE(mesh_svc, ...) is below, but
// read_fromradio() (defined earlier) needs to reference the service to notify
// on fromNum after each non-empty read.
#define FROMNUM_ATTR_IDX 2
#define LOGRADIO_ATTR_IDX 9
extern const struct bt_gatt_service_static mesh_svc;
static void start_advertising(); // forward declaration (defined in advertising
// section below)
// Work item for advertising restart after disconnect.
//
// disconnected_cb runs on the BT RX thread (the same thread that processes
// HCI Command Complete events). Calling bt_le_adv_start() →
// bt_hci_cmd_send_sync() directly from that thread deadlocks: the thread blocks
// on k_sem_take waiting for Command Complete, but it is the very thread that
// would process it. After 10 s the host panics with "Controller unresponsive,
// opcode 0x2006 timeout".
//
// Fix: submit a k_work item. The system workqueue runs bt_adv_restart_work_fn
// on its own thread → no deadlock.
static struct k_work adv_restart_work;
static void adv_restart_work_fn(struct k_work *work)
{
if (ble_enabled) {
start_advertising();
}
}
// CCC state: 0=off, BT_GATT_CCC_NOTIFY=notify, BT_GATT_CCC_INDICATE=indicate
static uint16_t fromnum_ccc_val = 0;
static uint16_t logradio_ccc_val = 0;
// Scratch buffers — only one BLE operation at a time
static uint8_t fromRadioBytes[meshtastic_FromRadio_size];
static size_t fromRadioLen = 0;
static uint8_t toRadioBytes[meshtastic_ToRadio_size];
static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
static uint32_t fromNumValue = 0;
// Deferred ToRadio processing
//
// write_toradio() runs on the BT RX workqueue thread (6 KB stack). Calling
// phoneAPI->handleToRadio() directly triggers handleStartConfig →
// getFiles("/", 10) → nanopb encode, which overflows the stack on the exact
// "Client wants config" write. Instead we copy the payload into a pending
// buffer under a mutex and let BleDeferredThread (running on the Meshtastic
// OSThread scheduler, 24 KB stack) do the actual call outside the lock.
//
// The mutex makes the producer/consumer handoff race-free — producer may
// overwrite a pending buffer the consumer hasn't read yet (dropped packet),
// but partial reads / torn writes are impossible.
K_MUTEX_DEFINE(pendingToRadioMutex);
static uint8_t pendingToRadioBuf[MAX_TO_FROM_RADIO_SIZE];
static size_t pendingToRadioLen = 0;
static bool pendingToRadio = false;
// Zombie-connection watchdog state.
//
// The nRF54L15 Zephyr 4.2.1 SW-LL occasionally fails to forward an
// LE Disconnection Complete event to the host: when iOS tears down the link
// (either explicitly by the user or via supervision timeout), the LL layer
// drops the connection but disconnected_cb never fires, active_conn stays
// non-null and advertising never restarts — the device vanishes from scans
// until power cycle. Track the connected timestamp and the last time we
// observed ATT traffic; a long ATT idle on an "active" connection means we
// are zombied. A cold reboot is the only path that reliably recovers (any
// bt_hci_cmd_send_sync after this state, e.g. bt_le_adv_start or
// bt_conn_disconnect, hangs in k_sem_take and later panics with "Controller
// unresponsive, opcode 0x2006 timeout").
static uint32_t connect_time_ms = 0;
static uint32_t last_att_time_ms = 0;
// ── BluetoothPhoneAPI
// ─────────────────────────────────────────────────────────
class BluetoothPhoneAPI : public PhoneAPI
{
virtual void onNowHasData(uint32_t fromRadioNum) override;
virtual bool checkIsConnected() override;
public:
BluetoothPhoneAPI() { api_type = TYPE_BLE; }
};
static BluetoothPhoneAPI *phoneAPI = nullptr;
// ── CCC change callbacks
// ──────────────────────────────────────────────────────
static void fromnum_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
fromnum_ccc_val = value;
LOG_INFO("BLE fromNum CCC: %u", value);
}
static void logradio_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
logradio_ccc_val = value;
LOG_INFO("BLE logRadio CCC: %u", value);
}
// ── GATT attribute callbacks
// ──────────────────────────────────────────────────
static ssize_t read_fromnum(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset)
{
LOG_INFO("GATT read_fromnum: fromNum=%u offset=%u", fromNumValue, offset);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &fromNumValue, sizeof(fromNumValue));
}
static ssize_t read_fromradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset)
{
if (offset == 0) {
// First chunk: pull the next packet from the queue.
// Subsequent chunks (offset > 0) are ATT_READ_BLOB continuations of the
// same value and must reuse fromRadioBytes untouched.
fromRadioLen = phoneAPI ? phoneAPI->getFromRadio(fromRadioBytes) : 0;
LOG_DEBUG("GATT read_fromradio len=%u", (unsigned)fromRadioLen);
}
last_att_time_ms = k_uptime_get_32();
return bt_gatt_attr_read(conn, attr, buf, len, offset, fromRadioBytes, fromRadioLen);
}
static ssize_t read_logradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset)
{
// logRadio is write-only from the device side (notify/indicate).
// Return an empty read so GATT discovery doesn't fail with NOT_PERMITTED.
return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0);
}
static ssize_t write_toradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len,
uint16_t offset, uint8_t flags)
{
// Writes >MTU-3 arrive here with offset=0 and
// flags=BT_GATT_WRITE_FLAG_EXECUTE after Zephyr reassembles the ATT Prepare
// Write fragments (CONFIG_BT_ATT_PREPARE_COUNT>0). Single writes arrive with
// flags=0.
LOG_DEBUG("GATT write_toradio len=%u flags=0x%x", len, flags);
if (offset != 0) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
// Reject any write that won't fit in the dedup buffer (lastToRadio) or the
// pending handoff buffer (pendingToRadioBuf), both sized
// MAX_TO_FROM_RADIO_SIZE. Returning success while silently dropping a
// payload would let the phone believe a config write was applied.
if (len > sizeof(toRadioBytes) || len > MAX_TO_FROM_RADIO_SIZE) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
// Deduplicate — drop packet if identical to the last one we processed
if (memcmp(lastToRadio, buf, len) != 0) {
memcpy(lastToRadio, buf, len);
if (len < MAX_TO_FROM_RADIO_SIZE) {
memset(lastToRadio + len, 0, MAX_TO_FROM_RADIO_SIZE - len);
}
// Defer handleToRadio() to BleDeferredThread (24 KB stack).
// Running it here on bt_workq (6 KB) overflows during handleStartConfig.
// Always overwrite pending — we already dedup'd above via lastToRadio,
// so any new write here is genuinely new data that must be delivered.
k_mutex_lock(&pendingToRadioMutex, K_FOREVER);
memcpy(pendingToRadioBuf, buf, len);
pendingToRadioLen = len;
pendingToRadio = true;
k_mutex_unlock(&pendingToRadioMutex);
}
last_att_time_ms = k_uptime_get_32();
return (ssize_t)len;
}
// ── GATT service definition (static, linked at compile time)
// ──────────────────
//
// Attribute indices (0-based):
// [0] Primary Service declaration
// [1] fromNum characteristic declaration
// [2] fromNum value ← notify target (FROMNUM_ATTR_IDX)
// [3] fromNum CCC descriptor
// [4] fromRadio characteristic declaration
// [5] fromRadio value
// [6] toRadio characteristic declaration
// [7] toRadio value
// [8] logRadio characteristic declaration
// [9] logRadio value ← notify target (LOGRADIO_ATTR_IDX)
// [10] logRadio CCC descriptor
// All user characteristics require authenticated encryption (MITM passkey)
// before the client can read/write. This mirrors the nrf52
// SECMODE_ENC_WITH_MITM service permission. The stack returns "Insufficient
// Authentication" on the first access attempt, prompting the client to pair
// with the configured PIN.
#define MESH_PERM_READ (BT_GATT_PERM_READ | BT_GATT_PERM_READ_AUTHEN)
#define MESH_PERM_WRITE (BT_GATT_PERM_WRITE | BT_GATT_PERM_WRITE_AUTHEN)
BT_GATT_SERVICE_DEFINE(mesh_svc, BT_GATT_PRIMARY_SERVICE(&mesh_svc_uuid.uuid),
// fromNum: READ | NOTIFY — packet-counter triggers phone to read fromRadio
BT_GATT_CHARACTERISTIC(&fromnum_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, MESH_PERM_READ,
read_fromnum, NULL, &fromNumValue),
BT_GATT_CCC(fromnum_ccc_changed, MESH_PERM_READ | MESH_PERM_WRITE),
// fromRadio: READ — phone polls this after receiving a fromNum notification
BT_GATT_CHARACTERISTIC(&fromradio_uuid.uuid, BT_GATT_CHRC_READ, MESH_PERM_READ, read_fromradio, NULL,
NULL),
// toRadio: WRITE — phone sends protobuf packets to the device
BT_GATT_CHARACTERISTIC(&toradio_uuid.uuid, BT_GATT_CHRC_WRITE, MESH_PERM_WRITE, NULL, write_toradio, NULL),
// logRadio: READ | NOTIFY | INDICATE — log stream to phone when connected
BT_GATT_CHARACTERISTIC(&logradio_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE, MESH_PERM_READ,
read_logradio, NULL, NULL),
BT_GATT_CCC(logradio_ccc_changed, MESH_PERM_READ | MESH_PERM_WRITE), );
// ── Advertising
// ───────────────────────────────────────────────────────────────
//
// Use legacy advertising (bt_le_adv_start / HCI 0x2006 path).
//
// History: we previously used bt_le_ext_adv_create (true extended advertising)
// because bt_le_adv_start() with CONFIG_BT_EXT_ADV=y was translated internally
// to the extended HCI path with LEGACY-bit (0x2036), which produced
// non-connectable PDUs on the nRF54L15 SW-LL. The true extended path
// (0x203x, AUX_ADV_IND) was connectable but caused two problems:
// 1. iOS CoreBluetooth does not reliably complete GATT after connecting via
// extended advertising (zero ATT PDUs observed in all test sessions).
// 2. After each connection the controller auto-stops the advertising set, and
// the subsequent bt_le_ext_adv_delete() sends LE Remove Advertising Set
// (0x203c) which times out → kernel oops at hci_core.c:506.
//
// With CONFIG_BT_EXT_ADV=n the host uses pure legacy HCI commands — the same
// path Nordic NCS uses in all nRF54L15 examples (peripheral_uart,
// peripheral_lbs) and which is universally iOS-compatible. The legacy data
// payload is 31 bytes:
// FLAGS (3B) + UUID128 (18B) = 21B in adv; NAME in scan-response (17B).
static void start_advertising()
{
// IMPORTANT: BT_DATA_BYTES() uses C99 compound literals that GCC C++ treats
// as temporaries; with -Os the compiler may elide writes, leaving stack
// uninitialized. Use static const arrays for stable data (flags, UUID)
// and a runtime pointer for the dynamic device name.
static const uint8_t adv_flags_val[] = {BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR};
static const uint8_t adv_uuid128_val[] = {MESH_SVC_UUID_VAL};
const char *name = bt_get_name();
size_t full_name_len = strlen(name);
// Legacy scan-response payload is 31 bytes total. Each AD entry costs 2
// bytes (length + type), leaving 29 bytes for the name. With
// CONFIG_BT_DEVICE_NAME_MAX=32 the name can exceed that — truncate and
// mark as SHORTENED so bt_le_adv_start() doesn't reject the payload.
constexpr size_t LEGACY_SCAN_RSP_NAME_MAX = 31 - 2;
bool name_shortened = full_name_len > LEGACY_SCAN_RSP_NAME_MAX;
uint8_t name_len = (uint8_t)(name_shortened ? LEGACY_SCAN_RSP_NAME_MAX : full_name_len);
// Primary advertising data: FLAGS + Meshtastic service UUID128 (21 bytes
// total)
struct bt_data ad[] = {
{BT_DATA_FLAGS, sizeof(adv_flags_val), adv_flags_val},
{BT_DATA_UUID128_ALL, sizeof(adv_uuid128_val), adv_uuid128_val},
};
// Scan response: device name (discovered after scan request)
struct bt_data sd[] = {
{(uint8_t)(name_shortened ? BT_DATA_NAME_SHORTENED : BT_DATA_NAME_COMPLETE), name_len, (const uint8_t *)name},
};
// BT_LE_ADV_OPT_CONN = connectable legacy ADV_IND + stops after first
// connection (replaces deprecated
// CONNECTABLE|ONE_TIME in Zephyr 4.2.1;
// BT_LE_ADV_OPT_CONN = BIT(0)|BIT(1))
// BT_LE_ADV_OPT_USE_IDENTITY = use static random identity address (stable
// across reboots) Advertising restart after disconnect is via
// adv_restart_work (system workqueue) so calling bt_le_adv_start() from the
// BT RX thread context is avoided.
int err = bt_le_adv_start(BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_USE_IDENTITY, BT_GAP_ADV_FAST_INT_MIN_2,
BT_GAP_ADV_FAST_INT_MAX_2, NULL),
ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err == -EALREADY) {
return;
}
if (err) {
LOG_WARN("BLE adv start failed: %d", err);
} else {
LOG_INFO("BLE advertising as '%s'", bt_get_name());
}
}
static void stop_advertising()
{
bt_le_adv_stop();
}
// ── Connection callbacks
// ──────────────────────────────────────────────────────
static void connected_cb(struct bt_conn *conn, uint8_t err)
{
if (err) {
LOG_WARN("BLE connection failed, err=0x%02x", err);
return;
}
k_mutex_lock(&ble_mutex, K_FOREVER);
active_conn = bt_conn_ref(conn);
k_mutex_unlock(&ble_mutex);
memset(lastToRadio, 0, sizeof(lastToRadio));
connect_time_ms = k_uptime_get_32();
last_att_time_ms = connect_time_ms;
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_INFO("BLE connected: %s", addr);
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newStatus);
// nRF54L15-DK has no screen — cannot display a PIN to the user.
// Requesting BT_SECURITY_L2 causes the OS to show a pairing dialog that
// the user dismisses, triggering disconnect + advertising restart failure.
// Skip security negotiation; the Meshtastic app works over plain GATT.
// (Security can be re-enabled once a display or NFC OOB path is available.)
}
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
LOG_INFO("BLE disconnected, reason=0x%02x", reason);
k_mutex_lock(&ble_mutex, K_FOREVER);
if (active_conn) {
bt_conn_unref(active_conn);
active_conn = nullptr;
}
k_mutex_unlock(&ble_mutex);
fromnum_ccc_val = 0;
logradio_ccc_val = 0;
connect_time_ms = 0;
last_att_time_ms = 0;
if (phoneAPI) {
phoneAPI->close();
}
memset(lastToRadio, 0, sizeof(lastToRadio));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
// Schedule advertising restart via work queue — NOT from this callback
// directly. disconnected_cb runs on the BT RX thread; calling
// bt_le_adv_start() here would deadlock (see adv_restart_work comment above).
if (ble_enabled) {
k_work_submit(&adv_restart_work);
}
}
#if defined(CONFIG_BT_SMP)
static void security_changed_cb(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
if (err == BT_SECURITY_ERR_PIN_OR_KEY_MISSING) {
// Phone has a stale bond (device was wiped/reflashed). Unpair the stale
// entry so the phone re-pairs cleanly on the next connection attempt.
LOG_WARN("BLE stale bond detected (key missing) — unpairing");
bt_unpair(BT_ID_DEFAULT, bt_conn_get_dst(conn));
bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
} else if (err) {
LOG_WARN("BLE security change failed: level=%d err=%d", (int)level, (int)err);
} else {
LOG_INFO("BLE security level %d established", (int)level);
}
}
#endif /* CONFIG_BT_SMP */
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected_cb,
.disconnected = disconnected_cb,
#if defined(CONFIG_BT_SMP)
.security_changed = security_changed_cb,
#endif
};
// ── Pairing / auth callbacks
// ──────────────────────────────────────────────────
#if defined(CONFIG_BT_SMP)
static uint32_t configuredPasskey;
static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
{
char passkey_str[7];
snprintf(passkey_str, sizeof(passkey_str), "%06u", passkey);
configuredPasskey = passkey;
LOG_INFO("BLE pairing PIN: %s", passkey_str);
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
std::string textkey(passkey_str);
meshtastic::BluetoothStatus pairingStatus(textkey);
bluetoothStatus->updateStatus(&pairingStatus);
}
static void auth_cancel(struct bt_conn *conn)
{
LOG_WARN("BLE pairing cancelled");
}
static struct bt_conn_auth_cb auth_cb = {
.passkey_display = auth_passkey_display,
.passkey_entry = NULL,
.cancel = auth_cancel,
};
static void pairing_complete_cb(struct bt_conn *conn, bool bonded)
{
LOG_INFO("BLE pairing complete, bonded=%d", (int)bonded);
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newStatus);
}
static void pairing_failed_cb(struct bt_conn *conn, enum bt_security_err reason)
{
LOG_WARN("BLE pairing failed, reason=%d", (int)reason);
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
}
static struct bt_conn_auth_info_cb auth_info_cb = {
.pairing_complete = pairing_complete_cb,
.pairing_failed = pairing_failed_cb,
};
#endif /* CONFIG_BT_SMP */
// ── BluetoothPhoneAPI methods
// ─────────────────────────────────────────────────
void BluetoothPhoneAPI::onNowHasData(uint32_t fromRadioNum)
{
PhoneAPI::onNowHasData(fromRadioNum);
fromNumValue = fromRadioNum;
if (!(fromnum_ccc_val & BT_GATT_CCC_NOTIFY))
return;
// active_conn may be torn down on another thread while we're dispatching
// this notify — acquire under ble_mutex so disconnected_cb can't free the
// conn between the null check and bt_conn_ref.
struct bt_conn *conn = acquire_active_conn();
if (!conn)
return;
bt_gatt_notify(conn, &mesh_svc.attrs[FROMNUM_ATTR_IDX], &fromNumValue, sizeof(fromNumValue));
bt_conn_unref(conn);
}
bool BluetoothPhoneAPI::checkIsConnected()
{
return active_conn != nullptr;
}
// ── Deferred ToRadio processor + zombie-connection watchdog ──────────────────
//
// write_toradio() runs on the BT RX workqueue thread (CONFIG_BT_RX_STACK_SIZE)
// and cannot execute phoneAPI->handleToRadio() directly: handleStartConfig
// recurses through nanopb encode + state machine init and overflows the RX
// stack. This thread runs on the Meshtastic OSThread scheduler (24 KB stack),
// picks up the pending ToRadio buffer flagged by write_toradio(), and calls
// handleToRadio() with plenty of headroom.
//
// Real-time fromNum notifications are sent synchronously from
// BluetoothPhoneAPI::onNowHasData() (called by PhoneAPI when new data is
// queued).
//
// Zombie-connection detection has two tiers:
//
// (1) Liveness probe. After IDLE_BEFORE_PROBE_MS without ATT traffic, send
// a bt_gatt_notify to fromNum every PROBE_INTERVAL_MS. If the
// controller replies -ENOTCONN the LL link is definitely dead but the
// host didn't forward LE Disconnection Complete → reboot. We avoid
// probing during normal activity so iOS isn't woken up unnecessarily
// (each probe wakes iOS → triggers a zero-byte FromRadio drain).
//
// (2) Hard watchdog. Absolute HARD_WATCHDOG_MS ceiling on ATT idle as a
// fallback if probes somehow don't detect the zombie.
class BleDeferredThread : public concurrency::OSThread
{
static constexpr uint32_t IDLE_BEFORE_PROBE_MS = 30000; // 30 s: start probing
static constexpr uint32_t PROBE_INTERVAL_MS = 5000; // 5 s: between probes
static constexpr uint32_t HARD_WATCHDOG_MS = 60000; // 1 min: last resort
uint32_t last_probe_ms = 0;
public:
BleDeferredThread() : concurrency::OSThread("BleDeferred") {}
protected:
int32_t runOnce() override
{
// Snapshot the pending ToRadio buffer under the mutex, then release
// the lock before calling into handleToRadio (which can be slow and
// must not block the BT RX thread producer).
uint8_t buf[MAX_TO_FROM_RADIO_SIZE];
size_t n = 0;
bool have_pending = false;
k_mutex_lock(&pendingToRadioMutex, K_FOREVER);
if (pendingToRadio) {
memcpy(buf, pendingToRadioBuf, pendingToRadioLen);
n = pendingToRadioLen;
pendingToRadio = false;
have_pending = true;
}
k_mutex_unlock(&pendingToRadioMutex);
if (have_pending && phoneAPI) {
phoneAPI->handleToRadio(buf, n);
}
// Take a reference to active_conn so it can't be freed underneath us
// if disconnected_cb fires on another thread while we're dispatching.
struct bt_conn *conn = acquire_active_conn();
if (!conn || connect_time_ms == 0) {
if (conn)
bt_conn_unref(conn);
last_probe_ms = 0;
return 100;
}
uint32_t now = k_uptime_get_32();
uint32_t att_idle = now - last_att_time_ms;
// Liveness probe — only when ATT has been quiet for a while.
if (att_idle > IDLE_BEFORE_PROBE_MS && (now - last_probe_ms) >= PROBE_INTERVAL_MS &&
(fromnum_ccc_val & BT_GATT_CCC_NOTIFY)) {
last_probe_ms = now;
int err = bt_gatt_notify(conn, &mesh_svc.attrs[FROMNUM_ATTR_IDX], &fromNumValue, sizeof(fromNumValue));
if (err == -ENOTCONN) {
LOG_WARN("BLE zombie (probe ENOTCONN); rebooting");
bt_conn_unref(conn);
k_sleep(K_MSEC(50)); // flush log
sys_reboot(SYS_REBOOT_COLD);
}
}
bt_conn_unref(conn);
// Hard ceiling — last-resort reboot if probes miss the zombie.
if (att_idle > HARD_WATCHDOG_MS && (now - connect_time_ms) > HARD_WATCHDOG_MS) {
LOG_WARN("BLE zombie (hard watchdog %us); rebooting", HARD_WATCHDOG_MS / 1000);
k_sleep(K_MSEC(50));
sys_reboot(SYS_REBOOT_COLD);
}
return 100;
}
};
static BleDeferredThread *bleDeferredThread = nullptr;
// ── BT stack pre-initializer (call from main thread before OSThreads start) ──
//
// bt_enable() requires substantially more stack than a Meshtastic OSThread
// (PowerFSMThread) provides — calling it there causes a stack overflow.
// Call this from nrf54l15Setup() (main Zephyr thread, CONFIG_MAIN_STACK_SIZE)
// so that by the time NRF54L15Bluetooth::setup() runs from PowerFSMThread,
// bt_initialized is already true and bt_enable() is skipped.
void nrf54l15_bt_preinit()
{
if (!bt_initialized) {
int err = bt_enable(NULL);
if (err) {
LOG_ERROR("BLE pre-init failed: %d", err);
return;
}
bt_initialized = true;
LOG_INFO("BLE stack pre-initialized on main thread");
// Phase 7: load bonding keys from LittleFS (/lfs/bt_settings).
// LittleFS is already mounted by fsInit() before nrf54l15Setup() runs.
// On first boot the file doesn't exist — settings_load() returns 0 (OK).
// On subsequent boots, previously bonded peers are restored so the
// phone can reconnect without re-pairing.
err = settings_load();
if (err) {
LOG_WARN("settings_load failed: %d (OK on first boot)", err);
} else {
LOG_INFO("BT settings loaded from /lfs/bt_settings");
}
}
}
// ── NRF54L15Bluetooth public methods ─────────────────────────────────────────
// Shared init: idempotent setup of work item, OSThread, auth callbacks,
// bt_enable, and device name. Leaves advertising control to the caller.
static bool nrf54l15_bt_init_common()
{
k_work_init(&adv_restart_work, adv_restart_work_fn);
if (!bleDeferredThread) {
bleDeferredThread = new BleDeferredThread();
}
if (!phoneAPI) {
phoneAPI = new BluetoothPhoneAPI();
}
#if defined(CONFIG_BT_SMP)
// NO_PIN is unsupported on this platform: the mesh GATT permissions are
// declared with BT_GATT_PERM_*_AUTHEN, prj.conf sets
// CONFIG_BT_SMP_ENFORCE_MITM=y, and the build pulls in the SMP/passkey path.
// If a user requested NO_PIN we'd register no auth callbacks → no display
// path for the passkey → every GATT access returns BT_ATT_ERR_AUTHENTICATION
// and the link is unusable. Fall back to RANDOM_PIN behavior with a warning
// instead of leaving BLE silently broken.
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
LOG_WARN("BLE: NO_PIN not supported on nRF54L15-DK (MITM-only build); "
"treating as RANDOM_PIN");
}
bt_conn_auth_cb_register(&auth_cb);
bt_conn_auth_info_cb_register(&auth_info_cb);
// FIXED_PIN — register the configured passkey so the mobile app prompts
// the user for that specific number instead of a random display-only PIN.
// RANDOM_PIN (and clamped NO_PIN) keeps the default behavior: Zephyr
// generates a fresh passkey on each pairing attempt and fires
// auth_passkey_display with it.
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN) {
configuredPasskey = config.bluetooth.fixed_pin;
int rc = bt_passkey_set(configuredPasskey);
if (rc) {
LOG_WARN("bt_passkey_set(%u) failed: %d", configuredPasskey, rc);
} else {
LOG_INFO("BLE fixed PIN: %06u", configuredPasskey);
}
} else {
bt_passkey_set(BT_PASSKEY_INVALID); // random per-pair
}
#endif /* CONFIG_BT_SMP */
if (!bt_initialized) {
int err = bt_enable(NULL);
if (err) {
LOG_ERROR("BLE enable failed: %d", err);
return false;
}
bt_initialized = true;
LOG_INFO("BLE stack enabled");
}
bt_set_name(getDeviceName());
return true;
}
void NRF54L15Bluetooth::setup()
{
LOG_INFO("NRF54L15Bluetooth::setup()");
if (!nrf54l15_bt_init_common()) {
return;
}
ble_enabled = true;
start_advertising();
}
void NRF54L15Bluetooth::shutdown()
{
LOG_INFO("NRF54L15Bluetooth::shutdown()");
ble_enabled = false;
stop_advertising();
struct bt_conn *conn = acquire_active_conn();
if (conn) {
bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
bt_conn_unref(conn);
}
}
void NRF54L15Bluetooth::startDisabled()
{
// Initialize BT stack but leave advertising off until resumeAdvertising().
if (!nrf54l15_bt_init_common()) {
return;
}
ble_enabled = false;
LOG_INFO("BLE initialized, advertising stopped (startDisabled)");
}
void NRF54L15Bluetooth::resumeAdvertising()
{
ble_enabled = true;
start_advertising();
}
void NRF54L15Bluetooth::clearBonds()
{
LOG_INFO("BLE clear bonds");
bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
}
bool NRF54L15Bluetooth::isConnected()
{
return active_conn != nullptr;
}
int NRF54L15Bluetooth::getRssi()
{
return 0; // TODO: Zephyr has no direct bt_conn_get_rssi; use HCI RSSI read
// command
}
void NRF54L15Bluetooth::sendLog(const uint8_t *logMessage, size_t length)
{
if (length > 512 || logradio_ccc_val == 0) {
return;
}
// Acquire a reference under ble_mutex so disconnected_cb can't free the
// connection between the null check and bt_gatt_notify.
struct bt_conn *conn = acquire_active_conn();
if (!conn) {
return;
}
// Send as notify regardless of whether client subscribed to NOTIFY or
// INDICATE — bt_gatt_indicate() requires a params struct with a callback;
// notify is simpler and the app accepts both. Change to indicate if
// compatibility issues arise.
bt_gatt_notify(conn, &mesh_svc.attrs[LOGRADIO_ATTR_IDX], logMessage, (uint16_t)length);
bt_conn_unref(conn);
}
-29
View File
@@ -1,29 +0,0 @@
// NRF54L15Bluetooth.h — Zephyr BLE backend for nRF54L15
//
// Implements the same interface as NRF52Bluetooth (same method names and
// signatures) so main.cpp and AdminModule can use nrf52Bluetooth pointer
// without knowing the underlying implementation.
//
// GATT profile is identical to the nRF52 implementation:
// Service: MESH_SERVICE_UUID
// toRadio: TORADIO_UUID (WRITE)
// fromRadio: FROMRADIO_UUID (READ)
// fromNum: FROMNUM_UUID (READ | NOTIFY)
// logRadio: LOGRADIO_UUID (READ | NOTIFY | INDICATE)
#pragma once
#include "BluetoothCommon.h"
class NRF54L15Bluetooth : public BluetoothApi
{
public:
void setup();
void shutdown();
void startDisabled();
void resumeAdvertising();
void clearBonds();
bool isConnected();
int getRssi();
void sendLog(const uint8_t *logMessage, size_t length);
};
-17
View File
@@ -1,17 +0,0 @@
// Nrf52SaadcLock.h — stub for nRF54L15/Zephyr
// Power.cpp includes this when ARCH_NRF52 is defined.
// Phase 2: compile-only stub.
#pragma once
#ifdef ARCH_NRF52
#include "concurrency/Lock.h"
namespace concurrency
{
/** Shared mutex for SAADC configuration and reads (VDD + battery analog path).
* On nRF54L15 ADC is handled differently; this is a compile-only stub. */
extern Lock *nrf52SaadcLock;
} // namespace concurrency
#endif
-4
View File
@@ -1,4 +0,0 @@
// Print.h — shim for nRF54L15/Zephyr
// Meshtastic includes <Print.h> separately; redirect to our Arduino.h shim.
#pragma once
#include "Arduino.h"
-62
View File
@@ -1,62 +0,0 @@
/**
* SPI.h Arduino SPI shim for Zephyr/nRF54L15
*
* Provides the Arduino SPIClass interface backed by Zephyr's SPI API. The
* backing controller is SPIM00 (HP domain, 3.0 V); the implementation in
* nrf54l15_arduino.cpp binds to DEVICE_DT_GET(DT_NODELABEL(spi00)) and the
* bus is configured in zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay.
* RadioLib uses ArduinoHal which calls transfer() byte-by-byte.
*
* CS pin is handled by RadioLib via digitalWrite() hardware CS is not used.
*/
#pragma once
#include "Arduino.h"
#include <stdint.h>
#define SPI_MODE0 0
#define SPI_MODE1 1
#define SPI_MODE2 2
#define SPI_MODE3 3
struct SPISettings {
uint32_t clock;
uint8_t bitOrder;
uint8_t dataMode;
// Arduino API allows `SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0))` — implicit form is intentional.
// cppcheck-suppress noExplicitConstructor
SPISettings(uint32_t clock = 4000000, uint8_t bitOrder = MSBFIRST, uint8_t dataMode = SPI_MODE0)
: clock(clock), bitOrder(bitOrder), dataMode(dataMode)
{
}
};
class SPIClass
{
public:
void begin() {}
void begin(uint8_t sck, uint8_t miso, uint8_t mosi, uint8_t ss = 0xFF) {}
void end() {}
void beginTransaction(SPISettings) {}
void endTransaction() {}
void setBitOrder(uint8_t order) {}
void setDataMode(uint8_t mode) {}
void setClockDivider(uint8_t div) {}
void setFrequency(uint32_t freq) {}
// Real Zephyr SPI implementation — defined in nrf54l15_arduino.cpp
uint8_t transfer(uint8_t data);
uint16_t transfer16(uint16_t data);
void transfer(void *buf, size_t count);
void transferBytes(const uint8_t *tx, uint8_t *rx, uint32_t count);
uint8_t transfer(uint8_t tx, uint8_t *rx, uint32_t count)
{
transferBytes(&tx, rx, count);
return rx ? rx[0] : 0;
}
};
extern SPIClass SPI;
extern SPIClass SPI1;
-5
View File
@@ -1,5 +0,0 @@
// Stream.h — shim for nRF54L15/Zephyr
// StreamAPI.h and other Meshtastic headers include <Stream.h>.
// Redirect to our Arduino.h shim which defines the Stream base class.
#pragma once
#include "Arduino.h"
-4
View File
@@ -1,4 +0,0 @@
// Tone.h — shim for nRF54L15/Zephyr
// Tone functions are stubbed in Arduino.h; this header satisfies direct includes.
#pragma once
#include "Arduino.h"

Some files were not shown because too many files have changed in this diff Show More