diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml
index c9664152e..31f0dd5a0 100644
--- a/.github/workflows/build_esp32.yml
+++ b/.github/workflows/build_esp32.yml
@@ -35,6 +35,7 @@ jobs:
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
- name: Build ESP32
run: bin/build-esp32.sh ${{ inputs.board }}
diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml
new file mode 100644
index 000000000..a30cf33f1
--- /dev/null
+++ b/.github/workflows/build_esp32_c3.yml
@@ -0,0 +1,62 @@
+name: Build ESP32-C3
+
+on:
+ workflow_call:
+ inputs:
+ board:
+ required: true
+ type: string
+
+permissions: read-all
+
+jobs:
+ build-esp32-c3:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Build base
+ id: base
+ uses: ./.github/actions/setup-base
+
+ - name: Pull web ui
+ uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ with:
+ repo: meshtastic/web
+ file: build.tar
+ target: build.tar
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Unpack web ui
+ run: |
+ tar -xf build.tar -C data/static
+ rm build.tar
+ - name: Remove debug flags for release
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ run: |
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
+ - name: Build ESP32
+ run: bin/build-esp32.sh ${{ inputs.board }}
+
+ - name: Pull OTA Firmware
+ uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ with:
+ repo: meshtastic/firmware-ota
+ file: firmware-c3.bin
+ target: release/bleota-c3.bin
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Get release version string
+ shell: bash
+ run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - name: Store binaries as an artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+ path: |
+ release/*.bin
+ release/*.elf
diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml
index 9611dd5b8..f603a6a31 100644
--- a/.github/workflows/build_esp32_s3.yml
+++ b/.github/workflows/build_esp32_s3.yml
@@ -34,6 +34,7 @@ jobs:
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
- name: Build ESP32
run: bin/build-esp32.sh ${{ inputs.board }}
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 5c1cf4c21..9ca0764b5 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -32,7 +32,7 @@ jobs:
- board: meshtastic-diy-v1
- board: rak4631
- board: t-echo
- - board: station-g1
+ - board: station-g2
- board: m5stack-coreink
- board: tbeam-s3-core
- board: tlora-t3s3-v1
@@ -67,7 +67,6 @@ jobs:
- board: tlora-v2-1-1_6-tcxo
- board: tlora-v2-1-1_8
- board: tbeam
- - board: heltec-ht62-esp32c3-sx1262
- board: heltec-v2_0
- board: heltec-v2_1
- board: tbeam0_7
@@ -93,16 +92,29 @@ jobs:
- board: heltec-wsl-v3
- board: heltec-wireless-tracker
- board: heltec-wireless-tracker-V1-0
- - board: heltec-wireless-paper
+ - board: heltec-wireless-paper-v1_0
+ - board: heltec-wireless-paper #v1.1
- board: tbeam-s3-core
- board: tlora-t3s3-v1
- board: t-watch-s3
- board: t-deck
- board: picomputer-s3
+ - board: station-g2
+ - board: unphone
uses: ./.github/workflows/build_esp32_s3.yml
with:
board: ${{ matrix.board }}
+ build-esp32-c3:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - board: heltec-ht62-esp32c3-sx1262
+ uses: ./.github/workflows/build_esp32_c3.yml
+ with:
+ board: ${{ matrix.board }}
+
build-nrf52:
strategy:
fail-fast: false
@@ -224,6 +236,7 @@ jobs:
[
build-esp32,
build-esp32-s3,
+ build-esp32-c3,
build-nrf52,
build-raspbian,
build-native,
@@ -249,7 +262,7 @@ jobs:
id: version
- name: Move files up
- run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
+ run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v3
diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml
index 2f9a99e58..dd4133dab 100644
--- a/.github/workflows/package_raspbian.yml
+++ b/.github/workflows/package_raspbian.yml
@@ -23,6 +23,14 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
+ - name: Pull web ui
+ uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ with:
+ repo: meshtastic/web
+ file: build.tar
+ target: build.tar
+ token: ${{ secrets.GITHUB_TOKEN }}
+
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
@@ -37,9 +45,12 @@ jobs:
- name: build .debpkg
run: |
+ mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
mkdir -p .debpkg/usr/lib/systemd/system/
+ tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
+ gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
chmod +x .debpkg/usr/sbin/meshtasticd
@@ -52,7 +63,7 @@ jobs:
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
arch: arm64
- depends: libyaml-cpp0.7
+ depends: libyaml-cpp0.7, openssl, libulfius2.7
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v3
diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml
index 6944d827e..30f9b3578 100644
--- a/.github/workflows/update_protobufs.yml
+++ b/.github/workflows/update_protobufs.yml
@@ -17,9 +17,9 @@ jobs:
- name: Download nanopb
run: |
- wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.7-linux-x86.tar.gz
- tar xvzf nanopb-0.4.7-linux-x86.tar.gz
- mv nanopb-0.4.7-linux-x86 nanopb-0.4.7
+ wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
+ tar xvzf nanopb-0.4.8-linux-x86.tar.gz
+ mv nanopb-0.4.8-linux-x86 nanopb-0.4.8
- name: Re-generate protocol buffers
run: |
diff --git a/.gitignore b/.gitignore
index 89f8ee065..0f2202f8d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,5 @@ venv/
release/
.vscode/extensions.json
/compile_commands.json
+src/mesh/raspihttp/certificate.pem
+src/mesh/raspihttp/private_key.pem
\ No newline at end of file
diff --git a/.trunk/.gitignore b/.trunk/.gitignore
index 1e2465290..15966d087 100644
--- a/.trunk/.gitignore
+++ b/.trunk/.gitignore
@@ -6,3 +6,4 @@
plugins
user_trunk.yaml
user.yaml
+tmp
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 81a35f8f1..0826b71d9 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -1,34 +1,36 @@
version: 0.1
cli:
- version: 1.17.2
+ version: 1.20.1
plugins:
sources:
- id: trunk
- ref: v1.3.0
+ ref: v1.4.4
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - bandit@1.7.5
- - checkov@3.1.9
- - terrascan@1.18.5
- - trivy@0.47.0
+ - trufflehog@3.68.5
+ - yamllint@1.35.1
+ - bandit@1.7.7
+ - checkov@3.2.32
+ - terrascan@1.19.1
+ - trivy@0.49.1
#- trufflehog@3.63.2-rc0
- taplo@0.8.1
- - ruff@0.1.6
- - isort@5.12.0
- - markdownlint@0.37.0
+ - ruff@0.3.1
+ - isort@5.13.2
+ - markdownlint@0.39.0
- oxipng@9.0.0
- - svgo@3.0.5
- - actionlint@1.6.26
- - flake8@6.1.0
+ - svgo@3.2.0
+ - actionlint@1.6.27
+ - flake8@7.0.0
- hadolint@2.12.0
- shfmt@3.6.0
- shellcheck@0.9.0
- - black@23.9.1
+ - black@24.2.0
- git-diff-check
- - gitleaks@8.18.1
+ - gitleaks@8.18.2
- clang-format@16.0.3
- - prettier@3.1.0
+ - prettier@3.2.5
runtimes:
enabled:
- python@3.10.8
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 03922dc72..e86d31c7d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,7 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "trunk.io",
- "trunk.enableWindows": true
+ "trunk.enableWindows": true,
+ "files.insertFinalNewline": false,
+ "files.trimFinalNewlines": false
}
diff --git a/Dockerfile b/Dockerfile
index 21e42ad87..fee6c62d4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM debian:bullseye-slim AS builder
+FROM debian:bookworm-slim AS builder
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC
@@ -11,31 +11,45 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Install build deps
USER root
-RUN apt-get update && \
- apt-get -y install wget python3 g++ zip python3-venv git vim ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev
-# create a non-priveleged user & group
+# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
+# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
+RUN apt-get update && apt-get install --no-install-recommends -y wget python3 python3-pip python3-wheel python3-venv g++ zip git \
+ ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev \
+ libulfius-dev liborcania-dev libssl-dev pkg-config && \
+ apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware
+
+RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh && chown mesh:mesh /tmp/firmware
+USER mesh
+
+WORKDIR /tmp/firmware
+RUN python3 -m venv /tmp/firmware
+RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14
+# trunk-ignore(terrascan/AC_DOCKER_00024): We would actually like these files to be owned by mesh tyvm
+COPY --chown=mesh:mesh . /tmp/firmware
+RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh
+RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd"
+
+
+##### PRODUCTION BUILD #############
+
+FROM debian:bookworm-slim
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=Etc/UTC
+
+# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
+# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
+RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libulfius2.7 liborcania2.3 libssl3 && \
+ apt-get clean && rm -rf /var/lib/apt/lists/*
+
RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
-
USER mesh
-RUN wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -qO /tmp/get-platformio.py && \
- chmod +x /tmp/get-platformio.py && \
- python3 /tmp/get-platformio.py && \
- git clone https://github.com/meshtastic/firmware --recurse-submodules /tmp/firmware && \
- cd /tmp/firmware && \
- chmod +x /tmp/firmware/bin/build-native.sh && \
- source ~/.platformio/penv/bin/activate && \
- ./bin/build-native.sh
-FROM frolvlad/alpine-glibc:glibc-2.31
-
-RUN apk --update add --no-cache g++ shadow && \
- groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
-
-COPY --from=builder /tmp/firmware/release/meshtasticd_linux_x86_64 /home/mesh/
-
-USER mesh
WORKDIR /home/mesh
-CMD sh -cx "./meshtasticd_linux_x86_64 --hwid '${HWID:-$RANDOM}'"
+COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/
-HEALTHCHECK NONE
\ No newline at end of file
+VOLUME /home/mesh/data
+
+CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ]
+
+HEALTHCHECK NONE
diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index bf84dd939..39935b849 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -4,7 +4,7 @@ extends = arduino_base
platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch.
build_src_filter =
- ${arduino_base.build_src_filter} - - - -
+ ${arduino_base.build_src_filter} - - - - -
upload_speed = 921600
debug_init_break = tbreak setup
diff --git a/arch/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini
index 0b196652a..71b2c1f3b 100644
--- a/arch/esp32/esp32s2.ini
+++ b/arch/esp32/esp32s2.ini
@@ -2,17 +2,20 @@
extends = esp32_base
build_src_filter =
- ${esp32_base.build_src_filter} -
+ ${esp32_base.build_src_filter} - - -
monitor_speed = 115200
build_flags =
${esp32_base.build_flags}
-DHAS_BLUETOOTH=0
+ -DMESHTASTIC_EXCLUDE_PAXCOUNTER
+ -DMESHTASTIC_EXCLUDE_BLUETOOTH
lib_ignore =
${esp32_base.lib_ignore}
NimBLE-Arduino
+ libpax
lib_deps = ${esp32_base.lib_deps}
- tanakamasayuki/EspUsbHost@^1.0.2
\ No newline at end of file
+ tanakamasayuki/EspUsbHost@^1.0.2
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index 04ca89a54..2505fe315 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -1,6 +1,6 @@
[nrf52_base]
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
-platform = platformio/nordicnrf52@^10.1.0
+platform = platformio/nordicnrf52@^10.4.0
extends = arduino_base
build_type = debug ; I'm debugging with ICE a lot now
@@ -11,7 +11,7 @@ build_flags =
-Isrc/platform/nrf52
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - -
lib_deps=
${arduino_base.lib_deps}
diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini
index 0dcc9afc2..53f06c9f3 100644
--- a/arch/portduino/portduino.ini
+++ b/arch/portduino/portduino.ini
@@ -1,6 +1,6 @@
; The Portduino based sim environment on top of any host OS, all hardware will be simulated
[portduino_base]
-platform = https://github.com/meshtastic/platform-native.git#a28dd5a9ccd5c48a9bede46037855ff83915d74b
+platform = https://github.com/meshtastic/platform-native.git#6fb39b6f94ece9c042141edb4afb91aca94dcaab
framework = arduino
build_src_filter =
@@ -12,6 +12,7 @@ build_src_filter =
-
-
-
+ +
-
-
-
@@ -33,4 +34,4 @@ build_flags =
-DPORTDUINO_LINUX_HARDWARE
-lbluetooth
-lgpiod
- -lyaml-cpp
\ No newline at end of file
+ -lyaml-cpp
diff --git a/arch/rp2040/rp2040.ini b/arch/rp2040/rp2040.ini
index 48fe0dae6..dd3a4d7ff 100644
--- a/arch/rp2040/rp2040.ini
+++ b/arch/rp2040/rp2040.ini
@@ -1,8 +1,8 @@
; Common settings for rp2040 Processor based targets
[rp2040_base]
-platform = https://github.com/maxgerhardt/platform-raspberrypi.git#612de5399d68b359053f1307ed223d400aea975c
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae81fcc73c34b1493ca9e261695e471bc0c2
extends = arduino_base
-platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.6.2
+platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.7.2
board_build.core = earlephilhower
board_build.filesystem_size = 0.5m
@@ -12,7 +12,7 @@ build_flags =
-D__PLAT_RP2040__
# -D _POSIX_THREADS
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - -
lib_ignore =
BluetoothOTA
diff --git a/arch/stm32/stm32wl5e.ini b/arch/stm32/stm32wl5e.ini
index 4483ff526..4d74ade8f 100644
--- a/arch/stm32/stm32wl5e.ini
+++ b/arch/stm32/stm32wl5e.ini
@@ -13,7 +13,7 @@ build_flags =
-DVECT_TAB_OFFSET=0x08000000
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - - - - - -
board_upload.offset_address = 0x08000000
upload_protocol = stlink
diff --git a/bin/build-native.sh b/bin/build-native.sh
index 7e9fcb632..9d31d091a 100755
--- a/bin/build-native.sh
+++ b/bin/build-native.sh
@@ -13,8 +13,8 @@ mkdir -p $OUTDIR/
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
-platformio pkg update
+platformio pkg update --environment native
pio run --environment native
-cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(arch)"
+cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR
diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml
index b5b105e4c..f729f1ac7 100644
--- a/bin/config-dist.yaml
+++ b/bin/config-dist.yaml
@@ -1,5 +1,6 @@
### Define your devices here using Broadcom pin numbering
### Uncomment the block that corresponds to your hardware
+### Including the "Module:" line!
---
Lora:
# Module: sx1262 # Waveshare SX126X XXXM
@@ -100,20 +101,29 @@ Display:
# Height: 240
Touchscreen:
+### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching.
+
# Module: STMPE610
# CS: 7
# IRQ: 24
-# Module: XPT2046
+# Module: XPT2046 # Waveshare 2.8inch
# CS: 7
# IRQ: 17
### Configure device for direct keyboard input
Input:
-# KeyboardDevice: /dev/input/event0
+# KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd
###
Logging:
LogLevel: info # debug, info, warn, error
+
+Webserver:
+# Port: 443 # Port for Webserver & Webservices
+# RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer
+
+General:
+ MaxNodes: 200
diff --git a/bin/device-install.bat b/bin/device-install.bat
index c7d8a10cf..6c880185e 100755
--- a/bin/device-install.bat
+++ b/bin/device-install.bat
@@ -31,9 +31,13 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
%PYTHON% -m esptool --baud 115200 erase_flash
%PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME%
- @REM Account for S3 board's different OTA partition
- IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% (
- %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
+ @REM Account for S3 and C3 board's different OTA partition
+ IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% IF x%FILENAME:station-g2=%==x%FILENAME% IF x%FILENAME:unphone=%==x%FILENAME% (
+ IF x%FILENAME:esp32c3=%==x%FILENAME% (
+ %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
+ ) else (
+ %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-c3.bin
+ )
) else (
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-s3.bin
)
diff --git a/bin/device-install.sh b/bin/device-install.sh
index 35d99286d..563a87af4 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -1,12 +1,12 @@
#!/bin/sh
-PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
+PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
set -e
# Usage info
show_help() {
-cat << EOF
+ cat <&2
- exit 1
- ;;
- esac
+ case "${opt}" in
+ h)
+ show_help
+ exit 0
+ ;;
+ p)
+ export ESPTOOL_PORT=${OPTARG}
+ ;;
+ P)
+ PYTHON=${OPTARG}
+ ;;
+ f)
+ FILENAME=${OPTARG}
+ ;;
+ *)
+ echo "Invalid flag."
+ show_help >&2
+ exit 1
+ ;;
+ esac
done
-shift "$((OPTIND-1))"
+shift "$((OPTIND - 1))"
[ -z "$FILENAME" -a -n "$1" ] && {
- FILENAME=$1
- shift
+ FILENAME=$1
+ shift
}
-if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then
+if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
- "$PYTHON" -m esptool erase_flash
- "$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
+ "$PYTHON" -m esptool erase_flash
+ "$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
# Account for S3 board's different OTA partition
- if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then
- "$PYTHON" -m esptool write_flash 0x260000 bleota.bin
+ if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ] && [ -n "${FILENAME##*"unphone"*}" ]; then
+ if [ -n "${FILENAME##*"esp32c3"*}" ]; then
+ "$PYTHON" -m esptool write_flash 0x260000 bleota.bin
+ else
+ "$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin
+ fi
else
- "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin
+ "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin
fi
- "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
+ "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
else
show_help
diff --git a/bin/native-install.sh b/bin/native-install.sh
index cc6d968f9..ba71c4f46 100755
--- a/bin/native-install.sh
+++ b/bin/native-install.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-cp "release/meshtasticd_linux_$(arch)" /usr/sbin/meshtasticd
+cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd
mkdir /etc/meshtasticd
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml
diff --git a/bin/regen-protos.bat b/bin/regen-protos.bat
index 1422f7914..f28ef0025 100644
--- a/bin/regen-protos.bat
+++ b/bin/regen-protos.bat
@@ -1 +1 @@
-cd protobufs && ..\nanopb-0.4.7\generator-bin\protoc.exe --experimental_allow_proto3_optional --nanopb_out=-v:..\src\mesh\generated -I=..\protobufs ..\protobufs\meshtastic\*.proto
+cd protobufs && ..\nanopb-0.4.8\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh
index ad771ab45..2e60784e3 100755
--- a/bin/regen-protos.sh
+++ b/bin/regen-protos.sh
@@ -2,19 +2,10 @@
set -e
-echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.7 to be located in the"
+echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.8 to be located in the"
echo "firmware root directory if the following step fails, you should download the correct"
-echo "prebuilt binaries for your computer into nanopb-0.4.7"
+echo "prebuilt binaries for your computer into nanopb-0.4.8"
# the nanopb tool seems to require that the .options file be in the current directory!
cd protobufs
-../nanopb-0.4.7/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated/ -I=../protobufs meshtastic/*.proto --experimental_allow_proto3_optional
-
-# cd ../src/mesh/generated/meshtastic
-# sed -i 's/#include "meshtastic/#include "./g' -- *
-
-# sed -i 's/meshtastic_//g' -- *
-
-#echo "Regenerating protobuf documentation - if you see an error message"
-#echo "you can ignore it unless doing a new protobuf release to github."
-#bin/regen-docs.sh
+../nanopb-0.4.8/generator-bin/protoc --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto
diff --git a/boards/CDEBYTE_EoRa-S3.json b/boards/CDEBYTE_EoRa-S3.json
new file mode 100644
index 000000000..9ecee3c9f
--- /dev/null
+++ b/boards/CDEBYTE_EoRa-S3.json
@@ -0,0 +1,38 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-D CDEBYTE_EORA_S3",
+ "-D ARDUINO_USB_CDC_ON_BOOT=1",
+ "-D ARDUINO_USB_MODE=0",
+ "-D ARDUINO_RUNNING_CORE=1",
+ "-D ARDUINO_EVENT_RUNNING_CORE=1",
+ "-D BOARD_HAS_PSRAM"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "dio",
+ "hwids": [["0x303A", "0x1001"]],
+ "mcu": "esp32s3",
+ "variant": "CDEBYTE_EoRa-S3"
+ },
+ "connectivity": ["wifi"],
+ "debug": {
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "CDEBYTE EoRa-S3",
+ "upload": {
+ "flash_size": "4MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 4194304,
+ "wait_for_upload_port": true,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://www.cdebyte.com/Module-Testkits-EoRaPI",
+ "vendor": "CDEBYTE"
+}
diff --git a/boards/ESP32-S3-WROOM-1-N4.json b/boards/ESP32-S3-WROOM-1-N4.json
new file mode 100644
index 000000000..3620a711d
--- /dev/null
+++ b/boards/ESP32-S3-WROOM-1-N4.json
@@ -0,0 +1,39 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-D ARDUINO_USB_CDC_ON_BOOT=0",
+ "-D ARDUINO_USB_MSC_ON_BOOT=0",
+ "-D ARDUINO_USB_DFU_ON_BOOT=0",
+ "-D ARDUINO_USB_MODE=0",
+ "-D ARDUINO_RUNNING_CORE=1",
+ "-D ARDUINO_EVENT_RUNNING_CORE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [["0x303A", "0x1001"]],
+ "mcu": "esp32s3",
+ "variant": "ESP32-S3-WROOM-1-N4"
+ },
+ "connectivity": ["wifi"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "ESP32-S3-WROOM-1-N4 (4 MB Flash, No PSRAM)",
+ "upload": {
+ "flash_size": "4MB",
+ "maximum_ram_size": 524288,
+ "maximum_size": 4194304,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf",
+ "vendor": "Espressif"
+}
diff --git a/boards/canaryone.json b/boards/canaryone.json
index d8f966a47..f64a4a7c7 100644
--- a/boards/canaryone.json
+++ b/boards/canaryone.json
@@ -7,7 +7,10 @@
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_CANARY -DNRF52840_XXAA",
"f_cpu": "64000000L",
- "hwids": [["0x239A", "0x4405"]],
+ "hwids": [
+ ["0x239A", "0x4405"],
+ ["0x239A", "0x009F"]
+ ],
"usb_product": "CanaryOne",
"mcu": "nrf52840",
"variant": "canaryone",
diff --git a/boards/station-g2.json b/boards/station-g2.json
new file mode 100755
index 000000000..871f067aa
--- /dev/null
+++ b/boards/station-g2.json
@@ -0,0 +1,41 @@
+{
+ "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=0",
+ "-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-g2"
+ },
+ "connectivity": ["wifi", "bluetooth", "lora"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "BQ Station G2",
+ "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": "https://wiki.uniteng.com/en/meshtastic/station-g2",
+ "vendor": "BQ Consulting"
+}
diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json
index 7bda2e5a0..4c82a2789 100644
--- a/boards/tbeam-s3-core.json
+++ b/boards/tbeam-s3-core.json
@@ -7,8 +7,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_S3_CORE",
- "-DARDUINO_USB_CDC_ON_BOOT=1",
- "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
diff --git a/platformio.ini b/platformio.ini
index 0033b6e46..a1082a84a 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -69,16 +69,17 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_PAGER
-DRADIOLIB_EXCLUDE_FSK4
-DRADIOLIB_EXCLUDE_APRS
+ -DRADIOLIB_EXCLUDE_LORAWAN
monitor_speed = 115200
lib_deps =
- jgromes/RadioLib@^6.4.0
+ jgromes/RadioLib@~6.5.0
https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306
mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
- https://github.com/meshtastic/TinyGPSPlus.git#2044b2c51e91ab4cd8cc93b15e40658cd808dd06
- https://github.com/meshtastic/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3
+ https://github.com/meshtastic/TinyGPSPlus.git#964f75a72cccd6b53cd74e4add1f7a42c6f7344d
+ https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
nanopb/Nanopb@^0.4.7
erriez/ErriezCRC32@^1.0.1
@@ -113,9 +114,10 @@ lib_deps =
; (not included in native / portduino)
[environmental_base]
lib_deps =
- adafruit/Adafruit BusIO@^1.11.4
+ adafruit/Adafruit BusIO@^1.15.0
adafruit/Adafruit Unified Sensor@^1.1.11
adafruit/Adafruit BMP280 Library@^2.6.8
+ adafruit/Adafruit BMP085 Library@^1.2.4
adafruit/Adafruit BME280 Library@^2.2.2
https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.5.2400
boschsensortec/BME68x Sensor Library@^1.1.40407
@@ -129,4 +131,5 @@ lib_deps =
adafruit/Adafruit PM25 AQI Sensor@^1.0.6
adafruit/Adafruit MPU6050@^2.2.4
adafruit/Adafruit LIS3DH@^1.2.4
- https://github.com/lewisxhe/BMA423_Library@^0.0.1
+ https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17
+ adafruit/Adafruit LSM6DS@^4.7.2
\ No newline at end of file
diff --git a/protobufs b/protobufs
index 24edea644..eade2c6be 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 24edea64429de4474c00d09990ef4c496614dc5d
+Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0
diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h
index 744f0ad64..fa5acdaae 100644
--- a/src/AccelerometerThread.h
+++ b/src/AccelerometerThread.h
@@ -5,18 +5,19 @@
#include "power.h"
#include
+#include
#include
#include
+#include
#include
-#include
-BMA423 bmaSensor;
+SensorBMA423 bmaSensor;
bool BMA_IRQ = false;
#define ACCELEROMETER_CHECK_INTERVAL_MS 100
#define ACCELEROMETER_CLICK_THRESHOLD 40
-uint16_t readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
+int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
{
Wire.beginTransmission(address);
Wire.write(reg);
@@ -29,7 +30,7 @@ uint16_t readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
return 0; // Pass
}
-uint16_t writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
+int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
{
Wire.beginTransmission(address);
Wire.write(reg);
@@ -72,24 +73,14 @@ class AccelerometerThread : public concurrency::OSThread
lis.setRange(LIS3DH_RANGE_2_G);
// Adjust threshold, higher numbers are less sensitive
lis.setClick(config.device.double_tap_as_button_press ? 2 : 1, ACCELEROMETER_CLICK_THRESHOLD);
- } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.begin(readRegister, writeRegister, delay)) {
+ } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 &&
+ bmaSensor.begin(accelerometer_found.address, &readRegister, &writeRegister)) {
LOG_DEBUG("BMA423 initializing\n");
- Acfg cfg;
- cfg.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
- cfg.range = BMA4_ACCEL_RANGE_2G;
- cfg.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
- cfg.perf_mode = BMA4_CONTINUOUS_MODE;
- bmaSensor.setAccelConfig(cfg);
- bmaSensor.enableAccel();
-
- struct bma4_int_pin_config pin_config;
- pin_config.edge_ctrl = BMA4_LEVEL_TRIGGER;
- pin_config.lvl = BMA4_ACTIVE_HIGH;
- pin_config.od = BMA4_PUSH_PULL;
- pin_config.output_en = BMA4_OUTPUT_ENABLE;
- pin_config.input_en = BMA4_INPUT_DISABLE;
- // The correct trigger interrupt needs to be configured as needed
- bmaSensor.setINTPinConfig(pin_config, BMA4_INTR1_MAP);
+ bmaSensor.configAccelerometer(bmaSensor.RANGE_2G, bmaSensor.ODR_100HZ, bmaSensor.BW_NORMAL_AVG4,
+ bmaSensor.PERF_CONTINUOUS_MODE);
+ bmaSensor.enableAccelerometer();
+ bmaSensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE,
+ BMA4_INPUT_DISABLE);
#ifdef BMA423_INT
pinMode(BMA4XX_INT, INPUT);
@@ -102,25 +93,31 @@ class AccelerometerThread : public concurrency::OSThread
RISING); // Select the interrupt mode according to the actual circuit
#endif
- struct bma423_axes_remap remap_data;
- remap_data.x_axis = 0;
- remap_data.x_axis_sign = 1;
- remap_data.y_axis = 1;
- remap_data.y_axis_sign = 0;
- remap_data.z_axis = 2;
- remap_data.z_axis_sign = 1;
+#ifdef T_WATCH_S3
// Need to raise the wrist function, need to set the correct axis
- bmaSensor.setRemapAxes(&remap_data);
- // sensor.enableFeature(BMA423_STEP_CNTR, true);
- bmaSensor.enableFeature(BMA423_TILT, true);
- bmaSensor.enableFeature(BMA423_WAKEUP, true);
- // sensor.resetStepCounter();
+ bmaSensor.setReampAxes(bmaSensor.REMAP_TOP_LAYER_RIGHT_CORNER);
+#else
+ bmaSensor.setReampAxes(bmaSensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER);
+#endif
+ // bmaSensor.enableFeature(bmaSensor.FEATURE_STEP_CNTR, true);
+ bmaSensor.enableFeature(bmaSensor.FEATURE_TILT, true);
+ bmaSensor.enableFeature(bmaSensor.FEATURE_WAKEUP, true);
+ // bmaSensor.resetPedometer();
// Turn on feature interrupt
- bmaSensor.enableStepCountInterrupt();
- bmaSensor.enableTiltInterrupt();
+ bmaSensor.enablePedometerIRQ();
+ bmaSensor.enableTiltIRQ();
// It corresponds to isDoubleClick interrupt
- bmaSensor.enableWakeupInterrupt();
+ bmaSensor.enableWakeupIRQ();
+ } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) {
+ LOG_DEBUG("LSM6DS3 initializing\n");
+ // Default threshold of 2G, less sensitive options are 4, 8 or 16G
+ lsm.setAccelRange(LSM6DS_ACCEL_RANGE_2_G);
+#ifndef LSM6DS3_WAKE_THRESH
+#define LSM6DS3_WAKE_THRESH 20
+#endif
+ lsm.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH);
+ // Duration is number of occurances needed to trigger, higher threshold is less sensitive
}
}
@@ -141,11 +138,14 @@ class AccelerometerThread : public concurrency::OSThread
buttonPress();
return 500;
}
- } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.getINT()) {
- if (bmaSensor.isTilt() || bmaSensor.isDoubleClick()) {
+ } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) {
+ if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) {
wakeScreen();
return 500;
}
+ } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) {
+ wakeScreen();
+ return 500;
}
return ACCELEROMETER_CHECK_INTERVAL_MS;
@@ -169,6 +169,7 @@ class AccelerometerThread : public concurrency::OSThread
ScanI2C::DeviceType acceleremoter_type;
Adafruit_MPU6050 mpu;
Adafruit_LIS3DH lis;
+ Adafruit_LSM6DS3TRC lsm;
};
} // namespace concurrency
\ No newline at end of file
diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp
index 84d433285..206bb7239 100644
--- a/src/ButtonThread.cpp
+++ b/src/ButtonThread.cpp
@@ -1,10 +1,12 @@
#include "ButtonThread.h"
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
+#endif
#include "MeshService.h"
#include "PowerFSM.h"
#include "RadioLibInterface.h"
#include "buzz.h"
-#include "graphics/Screen.h"
#include "main.h"
#include "modules/ExternalNotificationModule.h"
#include "power.h"
@@ -21,18 +23,24 @@
using namespace concurrency;
+ButtonThread *buttonThread; // Declared extern in header
volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE;
+#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
+OneButton ButtonThread::userButton; // Get reference to static member
+#endif
+
ButtonThread::ButtonThread() : OSThread("Button")
{
-#if defined(ARCH_PORTDUINO) || defined(BUTTON_PIN)
+#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
+
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
- userButton = OneButton(settingsMap[user], true, true);
+ this->userButton = OneButton(settingsMap[user], true, true);
LOG_DEBUG("Using GPIO%02d for button\n", settingsMap[user]);
}
#elif defined(BUTTON_PIN)
- int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
+ int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin
this->userButton = OneButton(pin, true, true);
LOG_DEBUG("Using GPIO%02d for button\n", pin);
#endif
@@ -41,31 +49,20 @@ ButtonThread::ButtonThread() : OSThread("Button")
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(pin, INPUT_PULLUP_SENSE);
#endif
+
+#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
userButton.attachClick(userButtonPressed);
userButton.setClickMs(250);
userButton.setPressMs(c_longPressTime);
userButton.setDebounceMs(1);
userButton.attachDoubleClick(userButtonDoublePressed);
- userButton.attachMultiClick(userButtonMultiPressed);
+ userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton
#ifndef T_DECK // T-Deck immediately wakes up after shutdown, so disable this function
userButton.attachLongPressStart(userButtonPressedLongStart);
userButton.attachLongPressStop(userButtonPressedLongStop);
#endif
-#if defined(ARCH_PORTDUINO)
- if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
- wakeOnIrq(settingsMap[user], FALLING);
-#else
- static OneButton *pBtn = &userButton; // only one instance of ButtonThread is created, so static is safe
- attachInterrupt(
- pin,
- []() {
- BaseType_t higherWake = 0;
- mainDelay.interruptFromISR(&higherWake);
- pBtn->tick();
- },
- CHANGE);
-#endif
#endif
+
#ifdef BUTTON_PIN_ALT
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
#ifdef INPUT_PULLUP_SENSE
@@ -79,13 +76,15 @@ ButtonThread::ButtonThread() : OSThread("Button")
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
- wakeOnIrq(BUTTON_PIN_ALT, FALLING);
#endif
#ifdef BUTTON_PIN_TOUCH
userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
- userButtonTouch.attachClick(touchPressed);
- wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
+ userButtonTouch.setPressMs(400);
+ userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click?
+#endif
+
+ attachButtonInterrupts();
#endif
}
@@ -136,25 +135,41 @@ int32_t ButtonThread::runOnce()
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_BUTTON("Double press!\n");
-#if defined(USE_EINK) && defined(PIN_EINK_EN)
- digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
-#endif
service.refreshLocalMeshNode();
service.sendNetworkPing(NODENUM_BROADCAST, true);
- if (screen)
+ if (screen) {
screen->print("Sent ad-hoc ping\n");
+ screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
+ }
break;
}
case BUTTON_EVENT_MULTI_PRESSED: {
- LOG_BUTTON("Multi press!\n");
- if (!config.device.disable_triple_click && (gps != nullptr)) {
- gps->toggleGpsMode();
- if (screen)
- screen->forceDisplay();
- }
+ LOG_BUTTON("Mulitipress! %hux\n", multipressClickCount);
+ switch (multipressClickCount) {
+#if HAS_GPS
+ // 3 clicks: toggle GPS
+ case 3:
+ if (!config.device.disable_triple_click && (gps != nullptr)) {
+ gps->toggleGpsMode();
+ if (screen)
+ screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
+ }
+ break;
+#endif
+#if defined(USE_EINK) && defined(PIN_EINK_EN) // i.e. T-Echo
+ // 4 clicks: toggle backlight
+ case 4:
+ digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
+ break;
+#endif
+ // No valid multipress action
+ default:
+ break;
+ } // end switch: click count
+
break;
- }
+ } // end multipress event
case BUTTON_EVENT_LONG_PRESSED: {
LOG_BUTTON("Long press!\n");
@@ -174,12 +189,24 @@ int32_t ButtonThread::runOnce()
power->shutdown();
break;
}
- case BUTTON_EVENT_TOUCH_PRESSED: {
+
+#ifdef BUTTON_PIN_TOUCH
+ case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
LOG_BUTTON("Touch press!\n");
- if (screen)
- screen->forceDisplay();
+ if (config.display.wake_on_tap_or_motion) {
+ if (screen) {
+ // Wake if asleep
+ if (powerFSM.getState() == &stateDARK)
+ powerFSM.trigger(EVENT_PRESS);
+
+ // Update display (legacy behaviour)
+ screen->forceDisplay();
+ }
+ }
break;
}
+#endif // BUTTON_PIN_TOUCH
+
default:
break;
}
@@ -189,6 +216,58 @@ int32_t ButtonThread::runOnce()
return 50;
}
+/*
+ * Attach (or re-attach) hardware interrupts for buttons
+ * Public method. Used outside class when waking from MCU sleep
+ */
+void ButtonThread::attachButtonInterrupts()
+{
+#if defined(ARCH_PORTDUINO)
+ if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
+ wakeOnIrq(settingsMap[user], FALLING);
+#elif defined(BUTTON_PIN)
+ // Interrupt for user button, during normal use. Improves responsiveness.
+ attachInterrupt(
+ config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN,
+ []() {
+ BaseType_t higherWake = 0;
+ mainDelay.interruptFromISR(&higherWake);
+ ButtonThread::userButton.tick();
+ },
+ CHANGE);
+#endif
+
+#ifdef BUTTON_PIN_ALT
+ wakeOnIrq(BUTTON_PIN_ALT, FALLING);
+#endif
+
+#ifdef BUTTON_PIN_TOUCH
+ wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
+#endif
+}
+
+/*
+ * Detach the "normal" button interrupts.
+ * Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep
+ */
+void ButtonThread::detachButtonInterrupts()
+{
+#if defined(ARCH_PORTDUINO)
+ if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
+ detachInterrupt(settingsMap[user]);
+#elif defined(BUTTON_PIN)
+ detachInterrupt(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
+#endif
+
+#ifdef BUTTON_PIN_ALT
+ detachInterrupt(BUTTON_PIN_ALT);
+#endif
+
+#ifdef BUTTON_PIN_TOUCH
+ detachInterrupt(BUTTON_PIN_TOUCH);
+#endif
+}
+
/**
* Watch a GPIO and if we get an IRQ, wake the main thread.
* Use to add wake on button press
@@ -204,6 +283,25 @@ void ButtonThread::wakeOnIrq(int irq, int mode)
FALLING);
}
+// Static callback
+void ButtonThread::userButtonMultiPressed(void *callerThread)
+{
+ // Grab click count from non-static button, while the info is still valid
+ ButtonThread *thread = (ButtonThread *)callerThread;
+ thread->storeClickCount();
+
+ // Then handle later, in the usual way
+ btnEvent = BUTTON_EVENT_MULTI_PRESSED;
+}
+
+// Non-static method, runs during callback. Grabs info while still valid
+void ButtonThread::storeClickCount()
+{
+#ifdef BUTTON_PIN
+ multipressClickCount = userButton.getNumberClicks();
+#endif
+}
+
void ButtonThread::userButtonPressedLongStart()
{
if (millis() > c_holdOffTime) {
diff --git a/src/ButtonThread.h b/src/ButtonThread.h
index 554c1f0c4..07c7ccff7 100644
--- a/src/ButtonThread.h
+++ b/src/ButtonThread.h
@@ -17,15 +17,18 @@ class ButtonThread : public concurrency::OSThread
BUTTON_EVENT_MULTI_PRESSED,
BUTTON_EVENT_LONG_PRESSED,
BUTTON_EVENT_LONG_RELEASED,
- BUTTON_EVENT_TOUCH_PRESSED
+ BUTTON_EVENT_TOUCH_LONG_PRESSED,
};
ButtonThread();
int32_t runOnce() override;
+ void attachButtonInterrupts();
+ void detachButtonInterrupts();
+ void storeClickCount();
private:
-#ifdef BUTTON_PIN
- OneButton userButton;
+#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
+ static OneButton userButton; // Static - accessed from an interrupt
#endif
#ifdef BUTTON_PIN_ALT
OneButton userButtonAlt;
@@ -33,20 +36,22 @@ class ButtonThread : public concurrency::OSThread
#ifdef BUTTON_PIN_TOUCH
OneButton userButtonTouch;
#endif
-#if defined(ARCH_PORTDUINO)
- OneButton userButton;
-#endif
// set during IRQ
static volatile ButtonEventType btnEvent;
+ // Store click count during callback, for later use
+ volatile int multipressClickCount = 0;
+
static void wakeOnIrq(int irq, int mode);
// IRQ callbacks
- static void touchPressed() { btnEvent = BUTTON_EVENT_TOUCH_PRESSED; }
static void userButtonPressed() { btnEvent = BUTTON_EVENT_PRESSED; }
static void userButtonDoublePressed() { btnEvent = BUTTON_EVENT_DOUBLE_PRESSED; }
- static void userButtonMultiPressed() { btnEvent = BUTTON_EVENT_MULTI_PRESSED; }
+ static void userButtonMultiPressed(void *callerThread); // Retrieve click count from non-static Onebutton while still valid
static void userButtonPressedLongStart();
static void userButtonPressedLongStop();
+ static void touchPressedLongStart() { btnEvent = BUTTON_EVENT_TOUCH_LONG_PRESSED; }
};
+
+extern ButtonThread *buttonThread;
diff --git a/src/GPSStatus.h b/src/GPSStatus.h
index bcfb5f2eb..1245d5e5d 100644
--- a/src/GPSStatus.h
+++ b/src/GPSStatus.h
@@ -4,8 +4,6 @@
#include "configuration.h"
#include
-extern NodeDB nodeDB;
-
namespace meshtastic
{
@@ -55,7 +53,7 @@ class GPSStatus : public Status
#ifdef GPS_EXTRAVERBOSE
LOG_WARN("Using fixed latitude\n");
#endif
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.latitude_i;
} else {
return p.latitude_i;
@@ -68,7 +66,7 @@ class GPSStatus : public Status
#ifdef GPS_EXTRAVERBOSE
LOG_WARN("Using fixed longitude\n");
#endif
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.longitude_i;
} else {
return p.longitude_i;
@@ -81,27 +79,18 @@ class GPSStatus : public Status
#ifdef GPS_EXTRAVERBOSE
LOG_WARN("Using fixed altitude\n");
#endif
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.altitude;
} else {
return p.altitude;
}
}
- uint32_t getDOP() const
- {
- return p.PDOP;
- }
+ uint32_t getDOP() const { return p.PDOP; }
- uint32_t getHeading() const
- {
- return p.ground_track;
- }
+ uint32_t getHeading() const { return p.ground_track; }
- uint32_t getNumSatellites() const
- {
- return p.sats_in_view;
- }
+ uint32_t getNumSatellites() const { return p.sats_in_view; }
bool matches(const GPSStatus *newStatus) const
{
@@ -149,4 +138,4 @@ class GPSStatus : public Status
} // namespace meshtastic
-extern meshtastic::GPSStatus *gpsStatus;
\ No newline at end of file
+extern meshtastic::GPSStatus *gpsStatus;
diff --git a/src/Power.cpp b/src/Power.cpp
index 8e44ddb98..d13fd6891 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -24,11 +24,13 @@
#include "nrfx_power.h"
#endif
-#ifdef DEBUG_HEAP_MQTT
+#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#include "target_specific.h"
+#if !MESTASTIC_EXCLUDE_WIFI
#include
#endif
+#endif
#ifndef DELAY_FOREVER
#define DELAY_FOREVER portMAX_DELAY
@@ -54,6 +56,19 @@ static const adc_atten_t atten = ADC_ATTENUATION;
#endif
#endif // BATTERY_PIN && ARCH_ESP32
+#ifdef EXT_CHRG_DETECT
+#ifndef EXT_CHRG_DETECT_MODE
+static const uint8_t ext_chrg_detect_mode = INPUT;
+#else
+static const uint8_t ext_chrg_detect_mode = EXT_CHRG_DETECT_MODE;
+#endif
+#ifndef EXT_CHRG_DETECT_VALUE
+static const uint8_t ext_chrg_detect_value = HIGH;
+#else
+static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
+#endif
+#endif
+
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
INA260Sensor ina260Sensor;
INA219Sensor ina219Sensor;
@@ -322,7 +337,14 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// Assume charging if we have a battery and external power is connected.
/// we can't be smart enough to say 'full'?
- virtual bool isCharging() override { return isBatteryConnect() && isVbusIn(); }
+ virtual bool isCharging() override
+ {
+#ifdef EXT_CHRG_DETECT
+ return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
+#else
+ return isBatteryConnect() && isVbusIn();
+#endif
+ }
private:
/// If we see a battery voltage higher than physics allows - assume charger is pumping
@@ -389,6 +411,9 @@ bool Power::analogInit()
#ifdef EXT_PWR_DETECT
pinMode(EXT_PWR_DETECT, INPUT);
#endif
+#ifdef EXT_CHRG_DETECT
+ pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
+#endif
#ifdef BATTERY_PIN
LOG_DEBUG("Using analog input %d for battery level\n", BATTERY_PIN);
@@ -473,19 +498,9 @@ bool Power::setup()
void Power::shutdown()
{
- screen->setOn(false);
-#if defined(USE_EINK) && defined(PIN_EINK_EN)
- digitalWrite(PIN_EINK_EN, LOW); // power off backlight first
-#endif
-
LOG_INFO("Shutting down\n");
-#ifdef HAS_PMU
- if (pmu_found == true) {
- PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF);
- PMU->shutdown();
- }
-#elif defined(ARCH_NRF52) || defined(ARCH_ESP32)
+#if defined(ARCH_NRF52) || defined(ARCH_ESP32)
#ifdef PIN_LED1
ledOff(PIN_LED1);
#endif
diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp
index bac3899bb..4f42b36b5 100644
--- a/src/PowerFSM.cpp
+++ b/src/PowerFSM.cpp
@@ -8,6 +8,7 @@
* actions to be taken upon entering or exiting each state.
*/
#include "PowerFSM.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "configuration.h"
@@ -16,6 +17,10 @@
#include "sleep.h"
#include "target_specific.h"
+#ifndef SLEEP_TIME
+#define SLEEP_TIME 30
+#endif
+
/// Should we behave as if we have AC power now?
static bool isPowered()
{
@@ -45,7 +50,7 @@ static void sdsEnter()
{
LOG_DEBUG("Enter state: SDS\n");
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
- doDeepSleep(getConfiguredOrDefaultMs(config.power.sds_secs), false);
+ doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false);
}
extern Power *power;
@@ -80,7 +85,7 @@ static void lsIdle()
// If some other service would stall sleep, don't let sleep happen yet
if (doPreflightSleep()) {
// Briefly come out of sleep long enough to blink the led once every few seconds
- uint32_t sleepTime = 30;
+ uint32_t sleepTime = SLEEP_TIME;
setLed(false); // Never leave led on while in light sleep
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
@@ -102,9 +107,7 @@ static void lsIdle()
break;
default:
- // We woke for some other reason (button press, device interrupt)
- // uint64_t status = esp_sleep_get_ext1_wakeup_status();
- LOG_INFO("wakeCause2 %d\n", wakeCause2);
+ // We woke for some other reason (button press, device IRQ interrupt)
#ifdef BUTTON_PIN
bool pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
@@ -182,10 +185,12 @@ static void powerEnter()
screen->setOn(true);
setBluetoothEnable(true);
// within enter() the function getState() returns the state we came from
- if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
+
+ // Mothballed: print change of power-state to device screen
+ /* if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
strcmp(powerFSM.getState()->name, "DARK") != 0) {
screen->print("Powered...\n");
- }
+ }*/
}
}
@@ -202,8 +207,10 @@ static void powerExit()
{
screen->setOn(true);
setBluetoothEnable(true);
- if (!isPowered())
- screen->print("Unpowered...\n");
+
+ // Mothballed: print change of power-state to device screen
+ /*if (!isPowered())
+ screen->print("Unpowered...\n");*/
}
static void onEnter()
@@ -342,13 +349,10 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
powerFSM.add_timed_transition(&stateON, &stateDARK,
- getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
"Screen-on timeout");
powerFSM.add_timed_transition(&statePOWER, &stateDARK,
- getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
- "Screen-on timeout");
- powerFSM.add_timed_transition(&stateDARK, &stateDARK,
- getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
"Screen-on timeout");
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
@@ -358,12 +362,26 @@ void PowerFSM_setup()
// modules
if ((isRouter || config.power.is_power_saving) && !isTrackerOrSensor) {
powerFSM.add_timed_transition(&stateNB, &stateLS,
- getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL,
+ Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL,
"Min wake timeout");
- powerFSM.add_timed_transition(&stateDARK, &stateLS,
- getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs),
- NULL, "Bluetooth timeout");
+
+ // If ESP32 and using power-saving, timer mover from DARK to light-sleep
+ // Also serves purpose of the old DARK to DARK transition(?) See https://github.com/meshtastic/firmware/issues/3517
+ powerFSM.add_timed_transition(
+ &stateDARK, &stateLS,
+ Default::getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL,
+ "Bluetooth timeout");
+ } else {
+ // If ESP32, but not using power-saving, check periodically if config has drifted out of stateDark
+ powerFSM.add_timed_transition(&stateDARK, &stateDARK,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs),
+ NULL, "Screen-on timeout");
}
+#else
+ // If not ESP32, light-sleep not used. Check periodically if config has drifted out of stateDark
+ powerFSM.add_timed_transition(&stateDARK, &stateDARK,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
+ "Screen-on timeout");
#endif
powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state
diff --git a/src/PowerFSMThread.h b/src/PowerFSMThread.h
index 584c955aa..fb640dd8b 100644
--- a/src/PowerFSMThread.h
+++ b/src/PowerFSMThread.h
@@ -1,3 +1,4 @@
+#include "Default.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "concurrency/OSThread.h"
@@ -28,7 +29,7 @@ class PowerFSMThread : public OSThread
timeLastPowered = millis();
} else if (config.power.on_battery_shutdown_after_secs > 0 && config.power.on_battery_shutdown_after_secs != UINT32_MAX &&
millis() > (timeLastPowered +
- getConfiguredOrDefaultMs(
+ Default::getConfiguredOrDefaultMs(
config.power.on_battery_shutdown_after_secs))) { // shutdown after 30 minutes unpowered
powerFSM.trigger(EVENT_SHUTDOWN);
}
diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp
index d3f39c377..e09e5fe30 100644
--- a/src/RedirectablePrint.cpp
+++ b/src/RedirectablePrint.cpp
@@ -99,7 +99,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
// If we are the first message on a report, include the header
if (!isContinuationMessage) {
- uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
@@ -182,11 +182,11 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len)
{
const char alphabet[17] = "0123456789abcdef";
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
- log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+\n");
+ log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n");
for (uint16_t i = 0; i < len; i += 16) {
if (i % 128 == 0)
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+\n");
char s[] = "| | | |\n";
uint8_t ix = 1, iy = 52;
for (uint8_t j = 0; j < 16; j++) {
@@ -208,7 +208,7 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
log(logLevel, ".");
log(logLevel, s);
}
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+\n");
}
std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index ed217c3ed..e17c8f99e 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -3,7 +3,11 @@
#include "PowerFSM.h"
#include "configuration.h"
+#ifdef RP2040_SLOW_CLOCK
+#define Port Serial2
+#else
#define Port Serial
+#endif
// Defaulting to the formerly removed phone_timeout_secs value of 15 minutes
#define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL
@@ -31,6 +35,10 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
canWrite = false; // We don't send packets to our port until it has talked to us first
// setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks
+#ifdef RP2040_SLOW_CLOCK
+ Port.setTX(SERIAL2_TX);
+ Port.setRX(SERIAL2_RX);
+#endif
Port.begin(SERIAL_BAUD);
#if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040)
time_t timeout = millis();
@@ -64,7 +72,7 @@ bool SerialConsole::checkIsConnected()
/**
* we override this to notice when we've received a protobuf over the serial
- * stream. Then we shunt off debug serial output.
+ * stream. Then we shut off debug serial output.
*/
bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
{
diff --git a/src/configuration.h b/src/configuration.h
index d8b0dba5f..701e07a32 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -74,6 +74,13 @@ along with this program. If not, see .
#define RTC_DATA_ATTR
#endif
+// -----------------------------------------------------------------------------
+// Regulatory overrides for producing regional builds
+// -----------------------------------------------------------------------------
+
+// Define if region should override user saved region
+// #define LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923
+
// -----------------------------------------------------------------------------
// Feature toggles
// -----------------------------------------------------------------------------
@@ -111,6 +118,7 @@ along with this program. If not, see .
#define MCP9808_ADDR 0x18
#define INA_ADDR 0x40
#define INA_ADDR_ALTERNATE 0x41
+#define INA_ADDR_WAVESHARE_UPS 0x43
#define INA3221_ADDR 0x42
#define QMC6310_ADDR 0x1C
#define QMI8658_ADDR 0x6B
@@ -127,6 +135,7 @@ along with this program. If not, see .
#define MPU6050_ADDR 0x68
#define LIS3DH_ADR 0x18
#define BMA423_ADDR 0x19
+#define LSM6DS3_ADDR 0x6A
// -----------------------------------------------------------------------------
// LED
@@ -136,9 +145,13 @@ along with this program. If not, see .
// -----------------------------------------------------------------------------
// Security
// -----------------------------------------------------------------------------
-
#define ATECC608B_ADDR 0x35
+// -----------------------------------------------------------------------------
+// IO Expander
+// -----------------------------------------------------------------------------
+#define TCA9555_ADDR 0x26
+
// -----------------------------------------------------------------------------
// GPS
// -----------------------------------------------------------------------------
@@ -160,19 +173,16 @@ along with this program. If not, see .
also enable HAS_ option not specifically disabled by variant.h */
#include "architecture.h"
+#ifndef DEFAULT_REBOOT_SECONDS
+#define DEFAULT_REBOOT_SECONDS 7
+#endif
+
+#ifndef DEFAULT_SHUTDOWN_SECONDS
+#define DEFAULT_SHUTDOWN_SECONDS 2
+#endif
+
/* Step #3: mop up with disabled values for HAS_ options not handled by the above two */
-// -----------------------------------------------------------------------------
-// GPS
-// -----------------------------------------------------------------------------
-
-#ifndef GPS_BAUDRATE
-#define GPS_BAUDRATE 9600
-#endif
-#ifndef GPS_THREAD_INTERVAL
-#define GPS_THREAD_INTERVAL 100
-#endif
-
#ifndef HAS_WIFI
#define HAS_WIFI 0
#endif
@@ -221,4 +231,65 @@ along with this program. If not, see .
#ifndef HW_VENDOR
#error HW_VENDOR must be defined
+#endif
+
+// -----------------------------------------------------------------------------
+// Global switches to turn off features for a minimized build
+// -----------------------------------------------------------------------------
+
+// #define MESHTASTIC_MINIMIZE_BUILD 1
+#ifdef MESHTASTIC_MINIMIZE_BUILD
+#define MESHTASTIC_EXCLUDE_MODULES 1
+#define MESHTASTIC_EXCLUDE_WIFI 1
+#define MESHTASTIC_EXCLUDE_BLUETOOTH 1
+#define MESHTASTIC_EXCLUDE_GPS 1
+#define MESHTASTIC_EXCLUDE_SCREEN 1
+#define MESHTASTIC_EXCLUDE_MQTT 1
+#endif
+
+// Turn off all optional modules
+#ifdef MESHTASTIC_EXCLUDE_MODULES
+#define MESHTASTIC_EXCLUDE_AUDIO 1
+#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
+#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
+#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
+#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1
+#define MESHTASTIC_EXCLUDE_POWER_TELEMETRY 1
+#define MESHTASTIC_EXCLUDE_RANGETEST 1
+#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1
+#define MESHTASTIC_EXCLUDE_STOREFORWARD 1
+#define MESHTASTIC_EXCLUDE_ATAK 1
+#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1
+#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
+#define MESHTASTIC_EXCLUDE_TRACEROUTE 1
+#define MESHTASTIC_EXCLUDE_WAYPOINT 1
+#define MESHTASTIC_EXCLUDE_INPUTBROKER 1
+#define MESHTASTIC_EXCLUDE_SERIAL 1
+#endif
+
+// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)
+#ifdef MESHTASTIC_EXCLUDE_WIFI
+#define MESHTASTIC_EXCLUDE_WEBSERVER 1
+#undef HAS_WIFI
+#define HAS_WIFI 0
+#endif
+
+// // Turn off Bluetooth
+#ifdef MESHTASTIC_EXCLUDE_BLUETOOTH
+#undef HAS_BLUETOOTH
+#define HAS_BLUETOOTH 0
+#endif
+
+// // Turn off GPS
+#ifdef MESHTASTIC_EXCLUDE_GPS
+#undef HAS_GPS
+#define HAS_GPS 0
+#undef MESHTASTIC_EXCLUDE_RANGETEST
+#define MESHTASTIC_EXCLUDE_RANGETEST 1
+#endif
+
+// Turn off Screen
+#ifdef MESHTASTIC_EXCLUDE_SCREEN
+#undef HAS_SCREEN
+#define HAS_SCREEN 0
#endif
\ No newline at end of file
diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h
new file mode 100644
index 000000000..eadd92e64
--- /dev/null
+++ b/src/detect/LoRaRadioType.h
@@ -0,0 +1,5 @@
+#pragma once
+
+enum LoRaRadioType { NO_RADIO, STM32WLx_RADIO, SIM_RADIO, RF95_RADIO, SX1262_RADIO, SX1268_RADIO, LLCC68_RADIO, SX1280_RADIO };
+
+extern LoRaRadioType radioType;
\ No newline at end of file
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index bf206c190..149bb95f0 100644
--- a/src/detect/ScanI2C.cpp
+++ b/src/detect/ScanI2C.cpp
@@ -36,8 +36,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
- ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423};
- return firstOfOrNONE(3, types);
+ ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3};
+ return firstOfOrNONE(4, types);
}
ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 2b4b8a735..c8fcfee10 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -23,6 +23,7 @@ class ScanI2C
BME_680,
BME_280,
BMP_280,
+ BMP_085,
INA260,
INA219,
INA3221,
@@ -37,6 +38,9 @@ class ScanI2C
MPU6050,
LIS3DH,
BMA423,
+ BQ24295,
+ LSM6DS3,
+ TCA9555,
#ifdef HAS_NCP5623
NCP5623,
#endif
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 990fb36ea..ba2820a77 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -183,8 +183,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
case ATECC608B_ADDR:
- type = ATECC608B;
- if (atecc.begin(addr.address) == true) {
+#ifdef RP2040_SLOW_CLOCK
+ if (atecc.begin(addr.address, Wire, Serial2) == true)
+#else
+ if (atecc.begin(addr.address) == true)
+#endif
+
+ {
LOG_INFO("ATECC608B initialized\n");
} else {
LOG_WARN("ATECC608B initialization failed\n");
@@ -242,6 +247,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
LOG_INFO("BME-280 sensor found at address 0x%x\n", (uint8_t)addr.address);
type = BME_280;
break;
+ case 0x55:
+ LOG_INFO("BMP-085 or BMP-180 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ type = BMP_085;
+ break;
default:
LOG_INFO("BMP-280 sensor found at address 0x%x\n", (uint8_t)addr.address);
type = BMP_280;
@@ -250,6 +259,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
case INA_ADDR:
case INA_ADDR_ALTERNATE:
+ case INA_ADDR_WAVESHARE_UPS:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue);
if (registerValue == 0x5449) {
@@ -261,8 +271,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
}
break;
case INA3221_ADDR:
- LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address);
- type = INA3221;
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
+ LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue);
+ if (registerValue == 0x5449) {
+ LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ type = INA3221;
+ } else { // Unknown device
+ LOG_INFO("No INA3221 found at address 0x%x\n", (uint8_t)addr.address);
+ }
break;
case MCP9808_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2);
@@ -283,12 +299,31 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n")
SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310 Highrate 3-Axis magnetic sensor found\n")
- SCAN_SIMPLE_CASE(QMI8658_ADDR, QMI8658, "QMI8658 Highrate 6-Axis inertial measurement sensor found\n")
+
+ case QMI8658_ADDR:
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID
+ if (registerValue == 0xC0) {
+ type = BQ24295;
+ LOG_INFO("BQ24295 PMU found\n");
+ break;
+ }
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID
+ if (registerValue == 0x6A) {
+ type = LSM6DS3;
+ LOG_INFO("LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
+ } else {
+ type = QMI8658;
+ LOG_INFO("QMI8658 Highrate 6-Axis inertial measurement sensor found\n");
+ }
+ break;
+
SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found\n")
SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n")
SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n");
SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n");
+ SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
+ SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n");
default:
LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address);
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 08ef116b2..17e35a4b3 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1,9 +1,14 @@
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_GPS
+#include "Default.h"
#include "GPS.h"
#include "NodeDB.h"
#include "RTC.h"
-#include "configuration.h"
+
#include "main.h" // pmu_found
#include "sleep.h"
+
+#include "cas.h"
#include "ubx.h"
#ifdef ARCH_PORTDUINO
@@ -48,6 +53,28 @@ void GPS::UBXChecksum(uint8_t *message, size_t length)
message[length - 1] = CK_B;
}
+// Calculate the checksum for a CAS packet
+void GPS::CASChecksum(uint8_t *message, size_t length)
+{
+ uint32_t cksum = ((uint32_t)message[5] << 24); // Message ID
+ cksum += ((uint32_t)message[4]) << 16; // Class
+ cksum += message[2]; // Payload Len
+
+ // Iterate over the payload as a series of uint32_t's and
+ // accumulate the cksum
+ uint32_t *payload = (uint32_t *)(message + 6);
+ for (size_t i = 0; i < (length - 10) / 4; i++) {
+ uint32_t p = payload[i];
+ cksum += p;
+ }
+
+ // Place the checksum values in the message
+ message[length - 4] = (cksum & 0xFF);
+ message[length - 3] = (cksum & (0xFF << 8)) >> 8;
+ message[length - 2] = (cksum & (0xFF << 16)) >> 16;
+ message[length - 1] = (cksum & (0xFF << 24)) >> 24;
+}
+
// Function to create a ublox packet for editing in memory
uint8_t GPS::makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
{
@@ -69,6 +96,41 @@ uint8_t GPS::makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_siz
return (payload_size + 8);
}
+// Function to create a CAS packet for editing in memory
+uint8_t GPS::makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
+{
+ // General CAS structure
+ // | H1 | H2 | payload_len | cls | msg | Payload ... | Checksum |
+ // Size: | 1 | 1 | 2 | 1 | 1 | payload_len | 4 |
+ // Pos: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ... | 6 + payload_len ... |
+ // |------|------|-------------|------|------|------|--------------|---------------------------|
+ // | 0xBA | 0xCE | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX ... | 0xXX | 0xXX | 0xXX | 0xXX |
+
+ // Construct the CAS packet
+ UBXscratch[0] = 0xBA; // header 1 (0xBA)
+ UBXscratch[1] = 0xCE; // header 2 (0xCE)
+ UBXscratch[2] = payload_size; // length 1
+ UBXscratch[3] = 0; // length 2
+ UBXscratch[4] = class_id; // class
+ UBXscratch[5] = msg_id; // id
+
+ UBXscratch[6 + payload_size] = 0x00; // Checksum
+ UBXscratch[7 + payload_size] = 0x00;
+ UBXscratch[8 + payload_size] = 0x00;
+ UBXscratch[9 + payload_size] = 0x00;
+
+ for (int i = 0; i < payload_size; i++) {
+ UBXscratch[6 + i] = pgm_read_byte(&msg[i]);
+ }
+ CASChecksum(UBXscratch, (payload_size + 10));
+
+#if defined(GPS_DEBUG) && defined(DEBUG_PORT)
+ LOG_DEBUG("Constructed CAS packet: \n");
+ DEBUG_PORT.hexDump(MESHTASTIC_LOG_LEVEL_DEBUG, UBXscratch, payload_size + 10);
+#endif
+ return (payload_size + 10);
+}
+
GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
{
uint8_t buffer[768] = {0};
@@ -78,6 +140,7 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
while (millis() < startTimeout) {
if (_serial_gps->available()) {
b = _serial_gps->read();
+
#ifdef GPS_DEBUG
LOG_DEBUG("%02X", (char *)buffer);
#endif
@@ -101,6 +164,67 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
return GNSS_RESPONSE_NONE;
}
+GPS_RESPONSE GPS::getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
+{
+ uint32_t startTime = millis();
+ uint8_t buffer[CAS_ACK_NACK_MSG_SIZE] = {0};
+ uint8_t bufferPos = 0;
+
+ // CAS-ACK-(N)ACK structure
+ // | H1 | H2 | Payload Len | cls | msg | Payload | Checksum (4) |
+ // | | | | | | Cls | Msg | Reserved | |
+ // |------|------|-------------|------|------|------|------|-------------|---------------------------|
+ // ACK-NACK| 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x00 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
+ // ACK-ACK | 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x01 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
+
+ while (millis() - startTime < waitMillis) {
+ if (_serial_gps->available()) {
+ buffer[bufferPos++] = _serial_gps->read();
+
+ // keep looking at the first two bytes of buffer until
+ // we have found the CAS frame header (0xBA, 0xCE), if not
+ // keep reading bytes until we find a frame header or we run
+ // out of time.
+ if ((bufferPos == 2) && !(buffer[0] == 0xBA && buffer[1] == 0xCE)) {
+ buffer[0] = buffer[1];
+ buffer[1] = 0;
+ bufferPos = 1;
+ }
+ }
+
+ // we have read all the bytes required for the Ack/Nack (14-bytes)
+ // and we must have found a frame to get this far
+ if (bufferPos == sizeof(buffer) - 1) {
+ uint8_t msg_cls = buffer[4]; // message class should be 0x05
+ uint8_t msg_msg_id = buffer[5]; // message id should be 0x00 or 0x01
+ uint8_t payload_cls = buffer[6]; // payload class id
+ uint8_t payload_msg = buffer[7]; // payload message id
+
+ // Check for an ACK-ACK for the specified class and message id
+ if ((msg_cls == 0x05) && (msg_msg_id == 0x01) && payload_cls == class_id && payload_msg == msg_id) {
+#ifdef GPS_DEBUG
+ LOG_INFO("Got ACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
+#endif
+ return GNSS_RESPONSE_OK;
+ }
+
+ // Check for an ACK-NACK for the specified class and message id
+ if ((msg_cls == 0x05) && (msg_msg_id == 0x00) && payload_cls == class_id && payload_msg == msg_id) {
+#ifdef GPS_DEBUG
+ LOG_WARN("Got NACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
+#endif
+ return GNSS_RESPONSE_NAK;
+ }
+
+ // This isn't the frame we are looking for, clear the buffer
+ // and try again until we run out of time.
+ memset(buffer, 0x0, sizeof(buffer));
+ bufferPos = 0;
+ }
+ }
+ return GNSS_RESPONSE_NONE;
+}
+
GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
{
uint8_t b;
@@ -255,19 +379,6 @@ bool GPS::setup()
if (!didSerialInit) {
#if !defined(GPS_UC6580)
-#if defined(RAK4630) && defined(PIN_3V3_EN)
- // If we are using the RAK4630 and we have no other peripherals on the I2C bus or module interest in 3V3_S,
- // then we can safely set en_gpio turn off power to 3V3 (IO2) to hard sleep the GPS
- if (rtc_found.port == ScanI2C::DeviceType::NONE && rgb_found.type == ScanI2C::DeviceType::NONE &&
- accelerometer_found.port == ScanI2C::DeviceType::NONE && !moduleConfig.detection_sensor.enabled &&
- !moduleConfig.telemetry.air_quality_enabled && !moduleConfig.telemetry.environment_measurement_enabled &&
- config.power.device_battery_ina_address == 0 && en_gpio == 0) {
- LOG_DEBUG("Since no problematic peripherals or interested modules were found, setting power save GPS_EN to pin %i\n",
- PIN_3V3_EN);
- en_gpio = PIN_3V3_EN;
- }
-#endif
-
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]);
gnssModel = probe(serialSpeeds[speedSelect]);
@@ -303,6 +414,53 @@ bool GPS::setup()
// Switch to Vehicle Mode, since SoftRF enables Aviation < 2g
_serial_gps->write("$PCAS11,3*1E\r\n");
delay(250);
+ } else if (gnssModel == GNSS_MODEL_MTK_L76B) {
+ // Waveshare Pico-GPS hat uses the L76B with 9600 baud
+ // Initialize the L76B Chip, use GPS + GLONASS
+ // See note in L76_Series_GNSS_Protocol_Specification, chapter 3.29
+ _serial_gps->write("$PMTK353,1,1,0,0,0*2B\r\n");
+ // Above command will reset the GPS and takes longer before it will accept new commands
+ delay(1000);
+ // only ask for RMC and GGA (GNRMC and GNGGA)
+ // See note in L76_Series_GNSS_Protocol_Specification, chapter 2.1
+ _serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
+ delay(250);
+ // Enable SBAS
+ _serial_gps->write("$PMTK301,2*2E\r\n");
+ delay(250);
+ // Enable PPS for 2D/3D fix only
+ _serial_gps->write("$PMTK285,3,100*3F\r\n");
+ delay(250);
+ // Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s)
+ _serial_gps->write("$PMTK886,1*29\r\n");
+ delay(250);
+ } else if (gnssModel == GNSS_MODEL_ATGM336H) {
+ // Set the intial configuration of the device - these _should_ work for most AT6558 devices
+ msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
+ LOG_WARN("ATGM336H - Could not set Configuration");
+ }
+
+ // Set the update frequence to 1Hz
+ msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
+ LOG_WARN("ATGM336H - Could not set Update Frequency");
+ }
+
+ // Set the NEMA output messages
+ // Ask for only RMC and GGA
+ uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA};
+ for (uint i = 0; i < sizeof(fields); i++) {
+ // Construct a CAS-CFG-MSG packet
+ uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00};
+ msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACKCas(0x06, 0x01, 250) != GNSS_RESPONSE_OK) {
+ LOG_WARN("ATGM336H - Could not enable NMEA MSG: %d\n", fields[i]);
+ }
+ }
} else if (gnssModel == GNSS_MODEL_UC6580) {
// The Unicore UC6580 can use a lot of sat systems, enable it to
// use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS
@@ -319,7 +477,6 @@ bool GPS::setup()
delay(250);
_serial_gps->write("$CFGMSG,6,1,0\r\n");
delay(250);
-
} else if (gnssModel == GNSS_MODEL_UBLOX) {
// Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command)
// We need set it because by default it is GPS only, and we want to use GLONASS too
@@ -458,7 +615,6 @@ bool GPS::setup()
LOG_WARN("Unable to enable NMEA 4.10.\n");
}
}
-
} else {
if (strncmp(info.hwVersion, "00040007", 8) == 0) { // This PSM mode is only for Neo-6
msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_ECO);
@@ -490,14 +646,6 @@ bool GPS::setup()
}
}
}
-
- msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to save GNSS module configuration.\n");
- } else {
- LOG_INFO("GNSS module configuration saved!\n");
- }
} else {
// LOG_INFO("u-blox M10 hardware found.\n");
delay(1000);
@@ -590,6 +738,13 @@ bool GPS::setup()
// BBR will survive a restart, and power off for a while, but modules with small backup
// batteries or super caps will not retain the config for a long power off time.
}
+ msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to save GNSS module configuration.\n");
+ } else {
+ LOG_INFO("GNSS module configuration saved!\n");
+ }
}
didSerialInit = true;
}
@@ -640,17 +795,27 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
return;
}
#endif
-#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76K and clones
+#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
if (on) {
- LOG_INFO("Waking GPS");
+ LOG_INFO("Waking GPS\n");
pinMode(PIN_GPS_STANDBY, OUTPUT);
+ // Some PCB's use an inverse logic due to a transistor driver
+ // Example for this is the Pico-Waveshare Lora+GPS HAT
+#ifdef PIN_GPS_STANDBY_INVERTED
+ digitalWrite(PIN_GPS_STANDBY, 0);
+#else
digitalWrite(PIN_GPS_STANDBY, 1);
+#endif
return;
} else {
- LOG_INFO("GPS entering sleep");
+ LOG_INFO("GPS entering sleep\n");
// notifyGPSSleep.notifyObservers(NULL);
pinMode(PIN_GPS_STANDBY, OUTPUT);
+#ifdef PIN_GPS_STANDBY_INVERTED
+ digitalWrite(PIN_GPS_STANDBY, 1);
+#else
digitalWrite(PIN_GPS_STANDBY, 0);
+#endif
return;
}
#endif
@@ -744,7 +909,7 @@ uint32_t GPS::getWakeTime() const
if (t == UINT32_MAX)
return t; // already maxint
- return getConfiguredOrDefaultMs(t, default_broadcast_interval_secs);
+ return Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs);
}
/** Get how long we should sleep between aqusition attempts in msecs
@@ -760,7 +925,7 @@ uint32_t GPS::getSleepTime() const
if (t == UINT32_MAX)
return t; // already maxint
- return t * 1000;
+ return Default::getConfiguredOrDefaultMs(t, default_gps_update_interval);
}
void GPS::publishUpdate()
@@ -800,7 +965,7 @@ int32_t GPS::runOnce()
LOG_WARN("GPS FactoryReset requested\n");
if (gps->factoryReset()) { // If we don't succeed try again next time
devicestate.did_gps_reset = true;
- nodeDB.saveToDisk(SEGMENT_DEVICESTATE);
+ nodeDB->saveToDisk(SEGMENT_DEVICESTATE);
}
}
GPSInitFinished = true;
@@ -820,7 +985,7 @@ int32_t GPS::runOnce()
if (devicestate.did_gps_reset && (millis() - lastWakeStartMsec > 60000) && !hasFlow()) {
LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n");
devicestate.did_gps_reset = false;
- nodeDB.saveDeviceStateToDisk();
+ nodeDB->saveDeviceStateToDisk();
return disable(); // Stop the GPS thread as it can do nothing useful until next reboot.
}
}
@@ -931,10 +1096,18 @@ GnssModel_t GPS::probe(int serialSpeed)
uint8_t buffer[768] = {0};
delay(100);
- // Close all NMEA sentences , Only valid for MTK platform
+ // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
delay(20);
+ // Get version information
+ clearBuffer();
+ _serial_gps->write("$PCAS06,1*1A\r\n");
+ if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) {
+ LOG_INFO("ATGM336H GNSS init succeeded, using ATGM336H Module\n");
+ return GNSS_MODEL_ATGM336H;
+ }
+
// Get version information
clearBuffer();
_serial_gps->write("$PCAS06,0*1B\r\n");
@@ -943,6 +1116,18 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_MTK;
}
+ // Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS)
+ _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
+ delay(20);
+
+ // Get version information
+ clearBuffer();
+ _serial_gps->write("$PMTK605*31\r\n");
+ if (getACK("Quectel-L76B", 500) == GNSS_RESPONSE_OK) {
+ LOG_INFO("L76B GNSS init succeeded, using L76B GNSS Module\n");
+ return GNSS_MODEL_MTK_L76B;
+ }
+
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
UBXChecksum(cfg_rate, sizeof(cfg_rate));
clearBuffer();
@@ -1124,7 +1309,6 @@ GPS *GPS::createGps()
LOG_DEBUG("Using GPIO%d for GPS RX\n", new_gps->rx_gpio);
LOG_DEBUG("Using GPIO%d for GPS TX\n", new_gps->tx_gpio);
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
-
#else
_serial_gps->begin(GPS_BAUDRATE);
#endif
@@ -1183,7 +1367,21 @@ bool GPS::factoryReset()
// byte _message_CFG_RST_COLDSTART[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x00, 0x00, 0xC6, 0x8B};
// _serial_gps->write(_message_CFG_RST_COLDSTART, sizeof(_message_CFG_RST_COLDSTART));
// delay(1000);
+ } else if (gnssModel == GNSS_MODEL_MTK) {
+ // send the CAS10 to perform a factory restart of the device (and other device that support PCAS statements)
+ LOG_INFO("GNSS Factory Reset via PCAS10,3\n");
+ _serial_gps->write("$PCAS10,3*1F\r\n");
+ delay(100);
+ } else if (gnssModel == GNSS_MODEL_ATGM336H) {
+ LOG_INFO("Factory Reset via CAS-CFG-RST\n");
+ uint8_t msglen = makeCASPacket(0x06, 0x02, sizeof(_message_CAS_CFG_RST_FACTORY), _message_CAS_CFG_RST_FACTORY);
+ _serial_gps->write(UBXscratch, msglen);
+ delay(100);
} else {
+ // fire this for good measure, if we have an L76B - won't harm other devices.
+ _serial_gps->write("$PMTK104*37\r\n");
+ // No PMTK_ACK for this command.
+ delay(100);
// send the UBLOX Factory Reset Command regardless of detect state, something is very wrong, just assume it's UBLOX.
// Factory Reset
byte _message_reset[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0xFF, 0xFB, 0x00, 0x00, 0x00,
@@ -1243,7 +1441,7 @@ bool GPS::lookForLocation()
#ifndef TINYGPS_OPTION_NO_STATISTICS
if (reader.failedChecksum() > lastChecksumFailCount) {
- LOG_WARN("Warning, %u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount,
+ LOG_WARN("%u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount,
reader.failedChecksum());
lastChecksumFailCount = reader.failedChecksum();
}
@@ -1342,7 +1540,7 @@ bool GPS::lookForLocation()
t.tm_mon = reader.date.month() - 1;
t.tm_year = reader.date.year() - 1900;
t.tm_isdst = false;
- p.timestamp = mktime(&t);
+ p.timestamp = gm_mktime(&t);
// Nice to have, if available
if (reader.satellites.isUpdated()) {
@@ -1386,7 +1584,7 @@ bool GPS::hasFlow()
bool GPS::whileIdle()
{
- int charsInBuf = 0;
+ uint charsInBuf = 0;
bool isValid = false;
if (!isAwake) {
clearBuffer();
@@ -1446,4 +1644,5 @@ void GPS::toggleGpsMode()
LOG_DEBUG("Flag set to true to restore power. GpsMode: ENABLED\n");
enable();
}
-}
\ No newline at end of file
+}
+#endif // Exclude GPS
\ No newline at end of file
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index 77e1d8042..77c6c0269 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -1,4 +1,6 @@
#pragma once
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_GPS
#include "GPSStatus.h"
#include "Observer.h"
@@ -21,10 +23,12 @@ struct uBloxGnssModelInfo {
};
typedef enum {
+ GNSS_MODEL_ATGM336H,
GNSS_MODEL_MTK,
GNSS_MODEL_UBLOX,
GNSS_MODEL_UC6580,
GNSS_MODEL_UNKNOWN,
+ GNSS_MODEL_MTK_L76B
} GnssModel_t;
typedef enum {
@@ -92,8 +96,11 @@ class GPS : private concurrency::OSThread
public:
/** If !NULL we will use this serial port to construct our GPS */
+#if defined(RPI_PICO_WAVESHARE)
+ static SerialUART *_serial_gps;
+#else
static HardwareSerial *_serial_gps;
-
+#endif
static uint8_t _message_PMREQ[];
static uint8_t _message_PMREQ_10[];
static const uint8_t _message_CFG_RXM_PSM[];
@@ -133,6 +140,11 @@ class GPS : private concurrency::OSThread
static const uint8_t _message_VALSET_DISABLE_SBAS_RAM[];
static const uint8_t _message_VALSET_DISABLE_SBAS_BBR[];
+ // CASIC commands for ATGM336H
+ static const uint8_t _message_CAS_CFG_RST_FACTORY[];
+ static const uint8_t _message_CAS_CFG_NAVX_CONF[];
+ static const uint8_t _message_CAS_CFG_RATE_1HZ[];
+
meshtastic_Position p = meshtastic_Position_init_default;
GPS() : concurrency::OSThread("GPS") {}
@@ -174,6 +186,7 @@ class GPS : private concurrency::OSThread
// Create a ublox packet for editing in memory
uint8_t makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg);
+ uint8_t makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg);
// scratch space for creating ublox packets
uint8_t UBXscratch[250] = {0};
@@ -184,6 +197,8 @@ class GPS : private concurrency::OSThread
GPS_RESPONSE getACK(uint8_t c, uint8_t i, uint32_t waitMillis);
GPS_RESPONSE getACK(const char *message, uint32_t waitMillis);
+ GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis);
+
/**
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
*
@@ -243,6 +258,7 @@ class GPS : private concurrency::OSThread
// Calculate checksum
void UBXChecksum(uint8_t *message, size_t length);
+ void CASChecksum(uint8_t *message, size_t length);
/** Get how long we should stay looking for each aquisition
*/
@@ -270,4 +286,5 @@ class GPS : private concurrency::OSThread
GnssModel_t gnssModel = GNSS_MODEL_UNKNOWN;
};
-extern GPS *gps;
\ No newline at end of file
+extern GPS *gps;
+#endif // Exclude GPS
\ No newline at end of file
diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp
index 19a753c02..cb4e69ff2 100644
--- a/src/gps/GeoCoord.cpp
+++ b/src/gps/GeoCoord.cpp
@@ -376,14 +376,17 @@ void GeoCoord::convertWGS84ToOSGB36(const double lat, const double lon, double &
}
/// Ported from my old java code, returns distance in meters along the globe
-/// surface (by magic?)
+/// surface (by Haversine formula)
float GeoCoord::latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
{
- double pk = (180 / 3.14169);
- double a1 = lat_a / pk;
- double a2 = lng_a / pk;
- double b1 = lat_b / pk;
- double b2 = lng_b / pk;
+ // Don't do math if the points are the same
+ if (lat_a == lat_b && lng_a == lng_b)
+ return 0.0;
+
+ double a1 = lat_a / DEG_CONVERT;
+ double a2 = lng_a / DEG_CONVERT;
+ double b1 = lat_b / DEG_CONVERT;
+ double b2 = lng_b / DEG_CONVERT;
double cos_b1 = cos(b1);
double cos_a1 = cos(a1);
double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h
index 06b11c3de..e811035db 100644
--- a/src/gps/GeoCoord.h
+++ b/src/gps/GeoCoord.h
@@ -11,6 +11,7 @@
#define PI 3.1415926535897932384626433832795
#define OLC_CODE_LEN 11
+#define DEG_CONVERT (180 / PI)
// Helper functions
// Raises a number to an exponent, handling negative exponents.
diff --git a/src/gps/NMEAWPL.cpp b/src/gps/NMEAWPL.cpp
index 9eff4d00e..71943b76c 100644
--- a/src/gps/NMEAWPL.cpp
+++ b/src/gps/NMEAWPL.cpp
@@ -1,3 +1,4 @@
+#if !MESHTASTIC_EXCLUDE_GPS
#include "NMEAWPL.h"
#include "GeoCoord.h"
#include "RTC.h"
@@ -74,10 +75,10 @@ uint32_t printWPL(char *buf, size_t bufsz, const meshtastic_Position &pos, const
uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos)
{
GeoCoord geoCoord(pos.latitude_i, pos.longitude_i, pos.altitude);
- tm *t = localtime((time_t *)&pos.timestamp);
+ tm *t = gmtime((time_t *)&pos.timestamp);
if (getRTCQuality() > 0) { // use the device clock if we got time from somewhere. If not, use the GPS timestamp.
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
- t = localtime((time_t *)&rtc_sec);
+ t = gmtime((time_t *)&rtc_sec);
}
uint32_t len = snprintf(
@@ -93,4 +94,6 @@ uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos)
}
len += snprintf(buf + len, bufsz - len, "*%02X\r\n", chk);
return len;
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 10e9e0331..85931900f 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -40,7 +40,7 @@ void readFromRTC()
t.tm_hour = rtc.getHour();
t.tm_min = rtc.getMinute();
t.tm_sec = rtc.getSecond();
- tv.tv_sec = mktime(&t);
+ tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
LOG_DEBUG("Read RTC time from RV3028 as %ld\n", tv.tv_sec);
timeStartMsec = now;
@@ -68,7 +68,7 @@ void readFromRTC()
t.tm_hour = tc.hour;
t.tm_min = tc.minute;
t.tm_sec = tc.second;
- tv.tv_sec = mktime(&t);
+ tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
LOG_DEBUG("Read RTC time from PCF8563 as %ld\n", tv.tv_sec);
timeStartMsec = now;
@@ -104,13 +104,15 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
bool shouldSet;
if (q > currentQuality) {
shouldSet = true;
- LOG_DEBUG("Upgrading time to quality %d\n", q);
- } else if (q == RTCQualityGPS && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) {
- // Every 12 hrs we will slam in a new GPS time, to correct for local RTC clock drift
+ LOG_DEBUG("Upgrading time to quality %s\n", RtcName(q));
+ } else if (q >= RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) {
+ // Every 12 hrs we will slam in a new GPS or Phone GPS / NTP time, to correct for local RTC clock drift
shouldSet = true;
LOG_DEBUG("Reapplying external time to correct clock drift %ld secs\n", tv->tv_sec);
- } else
+ } else {
shouldSet = false;
+ LOG_DEBUG("Current RTC quality: %s. Ignoring time of RTC quality of %s\n", RtcName(currentQuality), RtcName(q));
+ }
if (shouldSet) {
currentQuality = q;
@@ -128,7 +130,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
#else
rtc.initI2C();
#endif
- tm *t = localtime(&tv->tv_sec);
+ tm *t = gmtime(&tv->tv_sec);
rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec);
@@ -142,7 +144,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
#else
rtc.begin();
#endif
- tm *t = localtime(&tv->tv_sec);
+ tm *t = gmtime(&tv->tv_sec);
rtc.setDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec);
@@ -162,6 +164,24 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
}
}
+const char *RtcName(RTCQuality quality)
+{
+ switch (quality) {
+ case RTCQualityNone:
+ return "None";
+ case RTCQualityDevice:
+ return "Device";
+ case RTCQualityFromNet:
+ return "Net";
+ case RTCQualityNTP:
+ return "NTP";
+ case RTCQualityGPS:
+ return "GPS";
+ default:
+ return "Unknown";
+ }
+}
+
/**
* Sets the RTC time if the provided time is of higher quality than the current RTC time.
*
@@ -175,7 +195,9 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
*/
- time_t res = mktime(&t);
+ // horrible hack to make mktime TZ agnostic - best practise according to
+ // https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html
+ time_t res = gm_mktime(&t);
struct timeval tv;
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
@@ -189,14 +211,33 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
}
}
+/**
+ * Returns the timezone offset in seconds.
+ *
+ * @return The timezone offset in seconds.
+ */
+int32_t getTZOffset()
+{
+ time_t now;
+ struct tm *gmt;
+ now = time(NULL);
+ gmt = gmtime(&now);
+ gmt->tm_isdst = -1;
+ return (int16_t)difftime(now, mktime(gmt));
+}
+
/**
* Returns the current time in seconds since the Unix epoch (January 1, 1970).
*
* @return The current time in seconds since the Unix epoch.
*/
-uint32_t getTime()
+uint32_t getTime(bool local)
{
- return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
+ if (local) {
+ return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs + getTZOffset();
+ } else {
+ return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
+ }
}
/**
@@ -205,7 +246,19 @@ uint32_t getTime()
* @param minQuality The minimum quality of the RTC time required for it to be considered valid.
* @return The current time from the RTC if it meets the minimum quality requirement, or 0 if the time is not valid.
*/
-uint32_t getValidTime(RTCQuality minQuality)
+uint32_t getValidTime(RTCQuality minQuality, bool local)
{
- return (currentQuality >= minQuality) ? getTime() : 0;
+ return (currentQuality >= minQuality) ? getTime(local) : 0;
+}
+
+time_t gm_mktime(struct tm *tm)
+{
+ setenv("TZ", "GMT0", 1);
+ time_t res = mktime(tm);
+ if (*config.device.tzdef) {
+ setenv("TZ", config.device.tzdef, 1);
+ } else {
+ setenv("TZ", "UTC0", 1);
+ }
+ return res;
}
diff --git a/src/gps/RTC.h b/src/gps/RTC.h
index 527b31f46..1d609f136 100644
--- a/src/gps/RTC.h
+++ b/src/gps/RTC.h
@@ -28,14 +28,19 @@ RTCQuality getRTCQuality();
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
bool perhapsSetRTC(RTCQuality q, struct tm &t);
+/// Return a string name for the quality
+const char *RtcName(RTCQuality quality);
+
/// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero
-uint32_t getTime();
+uint32_t getTime(bool local = false);
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
-uint32_t getValidTime(RTCQuality minQuality);
+uint32_t getValidTime(RTCQuality minQuality, bool local = false);
void readFromRTC();
+time_t gm_mktime(struct tm *tm);
+
#define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60
\ No newline at end of file
diff --git a/src/gps/cas.h b/src/gps/cas.h
new file mode 100644
index 000000000..53d75cda9
--- /dev/null
+++ b/src/gps/cas.h
@@ -0,0 +1,63 @@
+#pragma once
+
+// CASIC binary message definitions
+// Reference: https://www.icofchina.com/d/file/xiazai/2020-09-22/20f1b42b3a11ac52089caf3603b43fb5.pdf
+// ATGM33H-5N: https://www.icofchina.com/pro/mokuai/2016-08-01/4.html
+// (https://www.icofchina.com/d/file/xiazai/2016-12-05/b5c57074f4b1fcc62ba8c7868548d18a.pdf)
+
+// NEMA (Class ID - 0x4e) message IDs
+#define CAS_NEMA_GGA 0x00
+#define CAS_NEMA_GLL 0x01
+#define CAS_NEMA_GSA 0x02
+#define CAS_NEMA_GSV 0x03
+#define CAS_NEMA_RMC 0x04
+#define CAS_NEMA_VTG 0x05
+#define CAS_NEMA_GST 0x07
+#define CAS_NEMA_ZDA 0x08
+#define CAS_NEMA_DHV 0x0D
+
+// Size of a CAS-ACK-(N)ACK message (14 bytes)
+#define CAS_ACK_NACK_MSG_SIZE 0x0E
+
+// CFG-RST (0x06, 0x02)
+// Factory reset
+const uint8_t GPS::_message_CAS_CFG_RST_FACTORY[] = {
+ 0xFF, 0x03, // Fields to clear
+ 0x01, // Reset Mode: Controlled Software reset
+ 0x03 // Startup Mode: Factory
+};
+
+// CFG_RATE (0x06, 0x01)
+// 1HZ update rate, this should always be the case after
+// factory reset but update it regardless
+const uint8_t GPS::_message_CAS_CFG_RATE_1HZ[] = {
+ 0xE8, 0x03, // Update Rate: 0x03E8 = 1000ms
+ 0x00, 0x00 // Reserved
+};
+
+// CFG-NAVX (0x06, 0x07)
+// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system
+// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
+// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used.
+const uint8_t GPS::_message_CAS_CFG_NAVX_CONF[] = {
+ 0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings
+ 0x03, // Dynamic Mode: Automotive
+ 0x03, // Fix Mode: Auto 2D/3D
+ 0x00, // Min SV
+ 0x00, // Max SVs
+ 0x00, // Min CNO
+ 0x00, // Reserved1
+ 0x00, // Init 3D fix
+ 0x00, // Min Elevation
+ 0x00, // Dr Limit
+ 0x07, // Nav System: 2^0 = GPS, 2^1 = BDS 2^2 = GLONASS: 2^3
+ // 3=GPS+BDS, 7=GPS+BDS+GLONASS
+ 0x00, 0x00, // Rollover Week
+ 0x00, 0x00, 0x00, 0x00, // Fix Altitude
+ 0x00, 0x00, 0x00, 0x00, // Fix Height Error
+ 0x00, 0x00, 0x00, 0x00, // PDOP Maximum
+ 0x00, 0x00, 0x00, 0x00, // TDOP Maximum
+ 0x00, 0x00, 0x00, 0x00, // Position Accuracy Max
+ 0x00, 0x00, 0x00, 0x00, // Time Accuracy Max
+ 0x00, 0x00, 0x00, 0x00 // Static Hold Threshold
+};
\ No newline at end of file
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index d5e3f5263..04915fe07 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -2,127 +2,49 @@
#ifdef USE_EINK
#include "EInkDisplay2.h"
-#include "GxEPD2_BW.h"
#include "SPILock.h"
#include "main.h"
#include
-#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
-SPIClass *hspi = NULL;
-#endif
+/*
+ The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
+ Previously, these macros were defined at the top of this file.
-#define COLORED GxEPD_BLACK
-#define UNCOLORED GxEPD_WHITE
+ For archival reasons, note that the following configurations had also been tested during this period:
+ * ifdef RAK4631
+ - 4.2 inch
+ EINK_DISPLAY_MODEL: GxEPD2_420_M01
+ EINK_WIDTH: 300
+ EINK_WIDTH: 400
-#if defined(TTGO_T_ECHO)
-#define TECHO_DISPLAY_MODEL GxEPD2_154_D67
-#elif defined(RAK4630)
+ - 2.9 inch
+ EINK_DISPLAY_MODEL: GxEPD2_290_T5D
+ EINK_WIDTH: 296
+ EINK_HEIGHT: 128
-// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - changed from GxEPD2_213_B74 - which was not going to give partial update
-// support
-#define TECHO_DISPLAY_MODEL GxEPD2_213_BN
-
-// 4.2 inch 300x400 - GxEPD2_420_M01
-// #define TECHO_DISPLAY_MODEL GxEPD2_420_M01
-
-// 2.9 inch 296x128 - GxEPD2_290_T5D
-// #define TECHO_DISPLAY_MODEL GxEPD2_290_T5D
-
-// 1.54 inch 200x200 - GxEPD2_154_M09
-// #define TECHO_DISPLAY_MODEL GxEPD2_154_M09
-
-#elif defined(MAKERPYTHON)
-// 2.9 inch 296x128 - GxEPD2_290_T5D
-#define TECHO_DISPLAY_MODEL GxEPD2_290_T5D
-
-#elif defined(PCA10059)
-
-// 4.2 inch 300x400 - GxEPD2_420_M01
-#define TECHO_DISPLAY_MODEL GxEPD2_420_M01
-
-#elif defined(M5_COREINK)
-// M5Stack CoreInk
-// 1.54 inch 200x200 - GxEPD2_154_M09
-#define TECHO_DISPLAY_MODEL GxEPD2_154_M09
-
-#elif defined(HELTEC_WIRELESS_PAPER)
-// #define TECHO_DISPLAY_MODEL GxEPD2_213_T5D
-#define TECHO_DISPLAY_MODEL GxEPD2_213_FC1
-
-#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
-// 2.13" 122x250 - DEPG0213BNS800
-#define TECHO_DISPLAY_MODEL GxEPD2_213_BN
-
-#endif
-
-GxEPD2_BW *adafruitDisplay;
+ - 1.54 inch
+ EINK_DISPLAY_MODEL: GxEPD2_154_M09
+ EINK_WIDTH: 200
+ EINK_HEIGHT: 200
+*/
+// Constructor
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
{
-#if defined(TTGO_T_ECHO)
- setGeometry(GEOMETRY_RAWMODE, 200, 200);
-#elif defined(RAK4630)
-
- // GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122
- setGeometry(GEOMETRY_RAWMODE, 250, 122);
- this->displayBufferSize = 250 * (128 / 8);
- // GxEPD2_420_M01
- // setGeometry(GEOMETRY_RAWMODE, 300, 400);
-
- // GxEPD2_290_T5D
- // setGeometry(GEOMETRY_RAWMODE, 296, 128);
-
- // GxEPD2_154_M09
- // setGeometry(GEOMETRY_RAWMODE, 200, 200);
-
-#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
-
- // The display's memory is actually 128px x 250px
- // Setting the buffersize manually prevents 122/8 truncating to a 15 byte width
- // (Or something like that..)
-
+ // Set dimensions in OLEDDisplay base class
this->geometry = GEOMETRY_RAWMODE;
- this->displayWidth = 250;
- this->displayHeight = 122;
- this->displayBufferSize = 250 * (128 / 8);
+ this->displayWidth = EINK_WIDTH;
+ this->displayHeight = EINK_HEIGHT;
-#elif defined(HELTEC_WIRELESS_PAPER)
- // GxEPD2_213_BN - 2.13 inch b/w 250x122
- setGeometry(GEOMETRY_RAWMODE, 250, 122);
-#elif defined(MAKERPYTHON)
- // GxEPD2_290_T5D
- setGeometry(GEOMETRY_RAWMODE, 296, 128);
+ // Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer
+ uint16_t shortSide = min(EINK_WIDTH, EINK_HEIGHT);
+ uint16_t longSide = max(EINK_WIDTH, EINK_HEIGHT);
+ if (shortSide % 8 != 0)
+ shortSide = (shortSide | 7) + 1;
-#elif defined(PCA10059)
-
- // GxEPD2_420_M01
- setGeometry(GEOMETRY_RAWMODE, 300, 400);
-
-#elif defined(M5_COREINK)
-
- // M5Stack_CoreInk 200x200
- // 1.54 inch 200x200 - GxEPD2_154_M09
- setGeometry(GEOMETRY_RAWMODE, EPD_HEIGHT, EPD_WIDTH);
-#elif defined(my)
-
- // GxEPD2_290_T5D
- setGeometry(GEOMETRY_RAWMODE, 296, 128);
- LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n");
-
-#elif defined(ESP32_S3_PICO)
-
- // GxEPD2_290_T94_V2
- setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT);
- LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n");
-
-#endif
- // setGeometry(GEOMETRY_RAWMODE, 128, 64); // old resolution
- // setGeometry(GEOMETRY_128_64); // We originally used this because I wasn't sure if rawmode worked - it does
+ this->displayBufferSize = longSide * (shortSide / 8);
}
-// FIXME quick hack to limit drawing to a very slow rate
-uint32_t lastDrawMsec;
-
/**
* Force a display update if we haven't drawn within the specified msecLimit
*/
@@ -131,13 +53,6 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
// No need to grab this lock because we are on our own SPI bus
// concurrency::LockGuard g(spiLock);
-#if defined(USE_EINK_DYNAMIC_PARTIAL)
- // Decide if update is partial or full
- bool continueUpdate = determineRefreshMode();
- if (!continueUpdate)
- return false;
-#else
-
uint32_t now = millis();
uint32_t sinceLast = now - lastDrawMsec;
@@ -146,56 +61,34 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
else
return false;
-#endif
-
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
- // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
for (uint32_t y = 0; y < displayHeight; y++) {
for (uint32_t x = 0; x < displayWidth; x++) {
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
auto b = buffer[x + (y / 8) * displayWidth];
auto isset = b & (1 << (y & 7));
- adafruitDisplay->drawPixel(x, y, isset ? COLORED : UNCOLORED);
+ adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
}
}
+ // Trigger the refresh in GxEPD2
LOG_DEBUG("Updating E-Paper... ");
-
-#if defined(TTGO_T_ECHO)
- adafruitDisplay->nextPage();
-#elif defined(RAK4630) || defined(MAKERPYTHON)
-
- // RAK14000 2.13 inch b/w 250x122 actually now does support partial updates
-
- // Full update mode (slow)
- // adafruitDisplay->display(false); // FIXME, use partial update mode
-
- // Only enable for e-Paper with support for partial updates and comment out above adafruitDisplay->display(false);
- // 1.54 inch 200x200 - GxEPD2_154_M09
- // 2.13 inch 250x122 - GxEPD2_213_BN
- // 2.9 inch 296x128 - GxEPD2_290_T5D
- // 4.2 inch 300x400 - GxEPD2_420_M01
adafruitDisplay->nextPage();
-#elif defined(PCA10059) || defined(M5_COREINK)
- adafruitDisplay->nextPage();
-#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
- adafruitDisplay->nextPage();
-#elif defined(HELTEC_WIRELESS_PAPER)
- adafruitDisplay->nextPage();
-#elif defined(ESP32_S3_PICO)
- adafruitDisplay->nextPage();
-#elif defined(PRIVATE_HW) || defined(my)
- adafruitDisplay->nextPage();
-#endif
+ // End the update process
+ endUpdate();
- // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display)
- adafruitDisplay->hibernate();
LOG_DEBUG("done\n");
-
return true;
}
+// End the update process - virtual method, overriden in derived class
+void EInkDisplay::endUpdate()
+{
+ // Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
+ adafruitDisplay->hibernate();
+}
+
// Write the buffer to the display memory
void EInkDisplay::display(void)
{
@@ -203,15 +96,9 @@ void EInkDisplay::display(void)
// at least one forceDisplay() keyframe. This prevents flashing when we should the critical
// bootscreen (that we want to look nice)
-#ifdef USE_EINK_DYNAMIC_PARTIAL
- lowPriority();
- forceDisplay();
- highPriority();
-#else
if (lastDrawMsec) {
forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
}
-#endif
}
// Send a command to the display (low level function)
@@ -226,16 +113,11 @@ void EInkDisplay::setDetected(uint8_t detected)
(void)detected;
}
-// Connect to the display
+// Connect to the display - variant specific
bool EInkDisplay::connect()
{
LOG_INFO("Doing EInk init\n");
-#ifdef PIN_EINK_PWR_ON
- pinMode(PIN_EINK_PWR_ON, OUTPUT);
- digitalWrite(PIN_EINK_PWR_ON, HIGH); // If we need to assert a pin to power external peripherals
-#endif
-
#ifdef PIN_EINK_EN
// backlight power, HIGH is backlight on, LOW is off
pinMode(PIN_EINK_EN, OUTPUT);
@@ -244,9 +126,9 @@ bool EInkDisplay::connect()
#if defined(TTGO_T_ECHO)
{
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
@@ -254,12 +136,12 @@ bool EInkDisplay::connect()
#elif defined(RAK4630) || defined(MAKERPYTHON)
{
if (eink_found) {
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
- // RAK14000 2.13 inch b/w 250x122 does actually now support partial updates
+ // RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh
adafruitDisplay->setRotation(3);
- // Partial update support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2
+ // Fast refresh support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2
// adafruitDisplay->setRotation(1);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
} else {
@@ -267,272 +149,48 @@ bool EInkDisplay::connect()
}
}
-#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
+#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER)
{
- // Is this a normal boot, or a wake from deep sleep?
- esp_sleep_wakeup_cause_t wakeReason = esp_sleep_get_wakeup_cause();
-
- // If waking from sleep, need to reverse rtc_gpio_isolate(), called in cpuDeepSleep()
- // Otherwise, SPI won't work
- if (wakeReason != ESP_SLEEP_WAKEUP_UNDEFINED) {
- // HSPI + other display pins
- rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_SCLK);
- rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_DC);
- rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_RES);
- rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_BUSY);
- rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_CS);
- rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_MOSI);
- }
-
// Start HSPI
hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
- // Enable VExt (ACTIVE LOW)
- // Unsure if called elsewhere first?
- delay(100);
- pinMode(Vext, OUTPUT);
- digitalWrite(Vext, LOW);
- delay(100);
+ // VExt already enabled in setup()
+ // RTC GPIO hold disabled in setup()
// Create GxEPD2 objects
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
}
-#elif defined(HELTEC_WIRELESS_PAPER)
- {
- hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
- delay(100);
- pinMode(Vext, OUTPUT);
- digitalWrite(Vext, LOW);
- delay(100);
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
- adafruitDisplay->init();
- adafruitDisplay->setRotation(3);
- }
#elif defined(PCA10059)
{
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(M5_COREINK)
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
adafruitDisplay->setRotation(0);
- adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT);
+ adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
#elif defined(my) || defined(ESP32_S3_PICO)
{
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
adafruitDisplay->setRotation(1);
- adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT);
+ adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#endif
- // adafruitDisplay->setFullWindow();
- // adafruitDisplay->fillScreen(UNCOLORED);
- // adafruitDisplay->drawCircle(100, 100, 20, COLORED);
- // adafruitDisplay->display(false);
return true;
}
-// Use a mix of full and partial refreshes, to preserve display health
-#if defined(USE_EINK_DYNAMIC_PARTIAL)
-
-// Suggest that subsequent updates should use partial-refresh
-void EInkDisplay::highPriority()
-{
- isHighPriority = true;
-}
-
-// Suggest that subsequent updates should use full-refresh
-void EInkDisplay::lowPriority()
-{
- isHighPriority = false;
-}
-
-// Full-refresh is explicitly requested for next one update - no skipping please
-void EInkDisplay::demandFullRefresh()
-{
- demandingFull = true;
-}
-
-// configure display for partial-refresh
-void EInkDisplay::configForPartialRefresh()
-{
- // Display-specific code can go here
-#if defined(PRIVATE_HW)
-#else
- // Otherwise:
- adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height());
-#endif
-}
-
-// Configure display for full-refresh
-void EInkDisplay::configForFullRefresh()
-{
- // Display-specific code can go here
-#if defined(PRIVATE_HW)
-#else
- // Otherwise:
- adafruitDisplay->setFullWindow();
-#endif
-}
-
-#ifdef EINK_PARTIAL_ERASURE_LIMIT
-// Count black pixels in an image. Used for "erasure tracking"
-int32_t EInkDisplay::countBlackPixels()
-{
- int32_t blackCount = 0; // Signed, to avoid underflow when comparing
- for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
- for (uint8_t i = 0; i < 7; i++) {
- // Check if each bit is black or white
- blackCount += (buffer[b] >> i) & 1;
- }
- }
- return blackCount;
-}
-
-// Evaluate the (rough) amount of black->white pixel change since last full refresh
-bool EInkDisplay::tooManyErasures()
-{
- // Ideally, we would compare the new and old buffers, to count *actual* white-to-black pixel changes
- // but that would require substantially more "code tampering"
-
- // Get the black pixel stats for this image
- int32_t blackCount = countBlackPixels();
- int32_t blackDifference = blackCount - prevBlackCount;
-
- // Update the running total of "erasures" - black pixels which have become white, since last full-refresh
- if (blackDifference < 0)
- erasedSinceFull -= blackDifference;
-
- // Store black pixel count for next time
- prevBlackCount = blackCount;
-
- // Log the running total - help devs setup new boards
- LOG_DEBUG("Dynamic Partial: erasedSinceFull=%hu, EINK_PARTIAL_ERASURE_LIMIT=%hu\n", erasedSinceFull,
- EINK_PARTIAL_ERASURE_LIMIT);
-
- // Check if too many pixels have been erased
- if (erasedSinceFull > EINK_PARTIAL_ERASURE_LIMIT)
- return true; // Too many
- else
- return false; // Still okay
-}
-#endif // ifdef EINK_PARTIAL_BRIGHTEN_LIMIT_PX
-
-bool EInkDisplay::newImageMatchesOld()
-{
- uint32_t newImageHash = 0;
-
- // Generate hash: sum all bytes in the image buffer
- for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
- newImageHash += buffer[b];
- }
-
- // Compare hashes
- bool hashMatches = (newImageHash == prevImageHash);
-
- // Update the cached hash
- prevImageHash = newImageHash;
-
- // Return the comparison result
- return hashMatches;
-}
-
-// Change between partial and full refresh config, or skip update, balancing urgency and display health.
-bool EInkDisplay::determineRefreshMode()
-{
- uint32_t now = millis();
- uint32_t sinceLast = now - lastUpdateMsec;
-
- // If rate-limiting dropped a high-priority update:
- // promote this update, so it runs ASAP
- if (missedHighPriorityUpdate) {
- isHighPriority = true;
- missedHighPriorityUpdate = false;
- }
-
- // Abort: if too soon for a new frame (unless demanding full)
- if (!demandingFull && isHighPriority && partialRefreshCount > 0 && sinceLast < highPriorityLimitMsec) {
- LOG_DEBUG("Dynamic Partial: update skipped. Exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n");
- missedHighPriorityUpdate = true;
- return false;
- }
- if (!demandingFull && !isHighPriority && !demandingFull && sinceLast < lowPriorityLimitMsec) {
- return false;
- }
-
- // If demanded full refresh: give it to them
- if (demandingFull)
- needsFull = true;
-
- // Check if old image (partial) should be redrawn (as full), for image quality
- if (partialRefreshCount > 0 && !isHighPriority)
- needsFull = true;
-
- // If too many partials, require a full-refresh (display health)
- if (partialRefreshCount >= partialRefreshLimit)
- needsFull = true;
-
-#ifdef EINK_PARTIAL_ERASURE_LIMIT
- // Some displays struggle with erasing black pixels to white, during partial refresh
- if (tooManyErasures())
- needsFull = true;
-#endif
-
- // If image matches
- // (Block must run, even if full already selected, to store hash for next time)
- if (newImageMatchesOld()) {
- // If low priority: limit rate
- // otherwise, every loop() will run the hash method
- if (!isHighPriority)
- lastUpdateMsec = now;
-
- // If update is *not* for display health or image quality, skip it
- if (!needsFull)
- return false;
- }
-
- // Conditions assessed - not skipping - load the appropriate config
-
- // If options require a full refresh
- if (!isHighPriority || needsFull) {
- if (partialRefreshCount > 0)
- configForFullRefresh();
-
- LOG_DEBUG("Dynamic Partial: conditions met for full-refresh\n");
- partialRefreshCount = 0;
- needsFull = false;
- demandingFull = false;
- erasedSinceFull = 0; // Reset the count for EINK_PARTIAL_ERASURE_LIMIT - tracks ghosting buildup
- }
-
- // If options allow a partial refresh
- else {
- if (partialRefreshCount == 0)
- configForPartialRefresh();
-
- LOG_DEBUG("Dynamic Partial: conditions met for partial-refresh\n");
- partialRefreshCount++;
- }
-
- lastUpdateMsec = now; // Mark time for rate limiting
- return true; // Instruct calling method to continue with update
-}
-
-#endif // End USE_EINK_DYNAMIC_PARTIAL
-
#endif
diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h
index aeaddee2d..f74416494 100644
--- a/src/graphics/EInkDisplay2.h
+++ b/src/graphics/EInkDisplay2.h
@@ -1,8 +1,11 @@
#pragma once
+#ifdef USE_EINK
+
+#include "GxEPD2_BW.h"
#include
-#if defined(HELTEC_WIRELESS_PAPER_V1_0)
+#if defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER)
// Re-enable SPI after deep sleep: rtc_gpio_hold_dis()
#include "driver/rtc_io.h"
#endif
@@ -10,12 +13,15 @@
/**
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
*
+ * Note: EInkDynamicDisplay derives from this class.
+ *
* Remaining TODO:
* optimize display() to only draw changed pixels (see other OLED subclasses for examples)
* implement displayOn/displayOff to turn off the TFT device (and backlight)
* Use the fast NRF52 SPI API rather than the slow standard arduino version
*
* turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted?
+ * Suggestion: perhaps similar to HELTEC_WIRELESS_PAPER issue, which resolved with rtc_gpio_hold_dis()
*/
class EInkDisplay : public OLEDDisplay
{
@@ -37,7 +43,14 @@ class EInkDisplay : public OLEDDisplay
*
* @return true if we did draw the screen
*/
- bool forceDisplay(uint32_t msecLimit = 1000);
+ virtual bool forceDisplay(uint32_t msecLimit = 1000);
+
+ /**
+ * Run any code needed to complete an update, after the physical refresh has completed.
+ * Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class.
+ *
+ */
+ virtual void endUpdate();
/**
* shim to make the abstraction happy
@@ -55,80 +68,17 @@ class EInkDisplay : public OLEDDisplay
// Connect to the display
virtual bool connect() override;
-#if defined(USE_EINK_DYNAMIC_PARTIAL)
- // Full, partial, or skip: balance urgency with display health
+ // AdafruitGFX display object - instantiated in connect(), variant specific
+ GxEPD2_BW *adafruitDisplay = NULL;
- // Use partial refresh if EITHER:
- // * highPriority() was set
- // * a highPriority() update was previously skipped, for rate-limiting - (EINK_HIGHPRIORITY_LIMIT_SECONDS)
-
- // Use full refresh if EITHER:
- // * lowPriority() was set
- // * demandFullRefresh() was called - (single shot)
- // * too many partial updates in a row: protect display - (EINK_PARTIAL_REPEAT_LIMIT)
- // * no recent updates, and last update was partial: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS)
- // * (optional) too many "erasures" since full-refresh (black pixels cleared to white)
-
- // Rate limit if:
- // * lowPriority() - (EINK_LOWPRIORITY_LIMIT_SECONDS)
- // * highPriority(), if multiple partials have run back-to-back - (EINK_HIGHPRIORITY_LIMIT_SECONDS)
-
- // Skip update entirely if ALL criteria met:
- // * new image matches old image
- // * lowPriority()
- // * no call to demandFullRefresh()
- // * not redrawing for image quality
- // * not refreshing for display health
-
- // ------------------------------------
-
- // To implement for your E-Ink display:
- // * edit configForPartialRefresh()
- // * edit configForFullRefresh()
- // * add macros to variant.h, and adjust to taste:
-
- /*
- #define USE_EINK_DYNAMIC_PARTIAL
- #define EINK_LOWPRIORITY_LIMIT_SECONDS 30
- #define EINK_HIGHPRIORITY_LIMIT_SECONDS 1
- #define EINK_PARTIAL_REPEAT_LIMIT 5
- #define EINK_PARTIAL_ERASURE_LIMIT 300 // optional
- */
-
- public:
- void highPriority(); // Suggest partial refresh
- void lowPriority(); // Suggest full refresh
- void demandFullRefresh(); // For next update: explicitly request full refresh
-
- protected:
- void configForPartialRefresh(); // Display specific code to select partial refresh mode
- void configForFullRefresh(); // Display specific code to return to full refresh mode
- bool newImageMatchesOld(); // Is the new update actually different to the last image?
- bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update
-#ifdef EINK_PARTIAL_ERASURE_LIMIT
- int32_t countBlackPixels(); // Calculate the number of black pixels in the new image
- bool tooManyErasures(); // Has too much "ghosting" (black pixels erased to white) accumulated since last full-refresh?
+ // If display uses HSPI
+#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
+ SPIClass *hspi = NULL;
#endif
- bool isHighPriority = true; // Does the method calling update believe that this is urgent?
- bool needsFull = false; // Is a full refresh forced? (display health)
- bool demandingFull = false; // Was full refresh specifically requested? (splash screens, etc)
- bool missedHighPriorityUpdate = false; // Was a high priority update skipped for rate-limiting?
- uint16_t partialRefreshCount = 0; // How many partials have occurred since last full refresh?
- uint32_t lastUpdateMsec = 0; // When did the last update occur?
- uint32_t prevImageHash = 0; // Used to check if update will change screen image (skippable or not)
- int32_t prevBlackCount = 0; // How many black pixels were in the previous image
- uint32_t erasedSinceFull = 0; // How many black pixels have been set back to white since last full-refresh? (roughly)
-
- // Set in variant.h
- const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for partial refreshes
- const uint32_t highPriorityLimitMsec = (uint32_t)1000 * EINK_HIGHPRIORITY_LIMIT_SECONDS; // Max rate for full refreshes
- const uint32_t partialRefreshLimit = EINK_PARTIAL_REPEAT_LIMIT; // Max consecutive partials, before full is triggered
-
-#else // !USE_EINK_DYNAMIC_PARTIAL
- // Tolerate calls to these methods anywhere, just to be safe
- void highPriority() {}
- void lowPriority() {}
- void demandFullRefresh() {}
-#endif
+ private:
+ // FIXME quick hack to limit drawing to a very slow rate
+ uint32_t lastDrawMsec = 0;
};
+
+#endif
\ No newline at end of file
diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp
new file mode 100644
index 000000000..b396446fa
--- /dev/null
+++ b/src/graphics/EInkDynamicDisplay.cpp
@@ -0,0 +1,553 @@
+#include "configuration.h"
+
+#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
+#include "EInkDynamicDisplay.h"
+
+// Constructor
+EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
+ : EInkDisplay(address, sda, scl, geometry, i2cBus), NotifiedWorkerThread("EInkDynamicDisplay")
+{
+ // If tracking ghost pixels, grab memory
+#ifdef EINK_LIMIT_GHOSTING_PX
+ dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros
+#endif
+}
+
+// Destructor
+EInkDynamicDisplay::~EInkDynamicDisplay()
+{
+ // If we were tracking ghost pixels, free the memory
+#ifdef EINK_LIMIT_GHOSTING_PX
+ delete[] dirtyPixels;
+#endif
+}
+
+// Screen requests a BACKGROUND frame
+void EInkDynamicDisplay::display()
+{
+ addFrameFlag(BACKGROUND);
+ update();
+}
+
+// Screen requests a RESPONSIVE frame
+bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit)
+{
+ addFrameFlag(RESPONSIVE);
+ return update(); // (Unutilized) Base class promises to return true if update ran
+}
+
+// Add flag for the next frame
+void EInkDynamicDisplay::addFrameFlag(frameFlagTypes flag)
+{
+ // OR the new flag into the existing flags
+ this->frameFlags = (frameFlagTypes)(this->frameFlags | flag);
+}
+
+// GxEPD2 code to set fast refresh
+void EInkDynamicDisplay::configForFastRefresh()
+{
+ // Variant-specific code can go here
+#if defined(PRIVATE_HW)
+#else
+ // Otherwise:
+ adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height());
+#endif
+}
+
+// GxEPD2 code to set full refresh
+void EInkDynamicDisplay::configForFullRefresh()
+{
+ // Variant-specific code can go here
+#if defined(PRIVATE_HW)
+#else
+ // Otherwise:
+ adafruitDisplay->setFullWindow();
+#endif
+}
+
+// Run any relevant GxEPD2 code, so next update will use correct refresh type
+void EInkDynamicDisplay::applyRefreshMode()
+{
+ // Change from FULL to FAST
+ if (currentConfig == FULL && refresh == FAST) {
+ configForFastRefresh();
+ currentConfig = FAST;
+ }
+
+ // Change from FAST back to FULL
+ else if (currentConfig == FAST && refresh == FULL) {
+ configForFullRefresh();
+ currentConfig = FULL;
+ }
+}
+
+// Update fastRefreshCount
+void EInkDynamicDisplay::adjustRefreshCounters()
+{
+ if (refresh == FAST)
+ fastRefreshCount++;
+
+ else if (refresh == FULL)
+ fastRefreshCount = 0;
+}
+
+// Trigger the display update by calling base class
+bool EInkDynamicDisplay::update()
+{
+ // Detemine the refresh mode to use, and start the update
+ bool refreshApproved = determineMode();
+ if (refreshApproved) {
+ EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
+ storeAndReset(); // Store the result of this loop for next time. Note: call *before* endOrDetach()
+ endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNCFULL)
+ } else
+ storeAndReset(); // No update, no post-update code, just store the results
+
+ return refreshApproved; // (Unutilized) Base class promises to return true if update ran
+}
+
+// Figure out who runs the post-update code
+void EInkDynamicDisplay::endOrDetach()
+{
+ // If the GxEPD2 version reports that it has the async modifications
+#ifdef HAS_EINK_ASYNCFULL
+ if (previousRefresh == FULL) {
+ asyncRefreshRunning = true; // Set the flag - checked in determineMode(); cleared by onNotify()
+
+ if (previousFrameFlags & BLOCKING)
+ awaitRefresh();
+ else {
+ // Async begins
+ LOG_DEBUG("Async full-refresh begins (dropping frames)\n");
+ notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread
+ }
+ }
+
+ // Fast Refresh
+ else if (previousRefresh == FAST)
+ EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves.
+
+ // Fallback - If using an unmodified version of GxEPD2 for some reason
+#else
+ if (previousRefresh == FULL || previousRefresh == FAST) { // If refresh wasn't skipped (on unspecified..)
+ LOG_WARN(
+ "GxEPD2 version has not been modified to support async refresh; using fallback behavior. Please update lib_deps in "
+ "variant's platformio.ini file\n");
+ EInkDisplay::endUpdate();
+ }
+#endif
+}
+
+// Assess situation, pick a refresh type
+bool EInkDynamicDisplay::determineMode()
+{
+ checkInitialized();
+ checkForPromotion();
+#if defined(HAS_EINK_ASYNCFULL)
+ checkBusyAsyncRefresh();
+#endif
+ checkRateLimiting();
+
+ // If too soon for a new frame, or display busy, abort early
+ if (refresh == SKIPPED)
+ return false; // No refresh
+
+ // -- New frame is due --
+
+ resetRateLimiting(); // Once determineMode() ends, will have to wait again
+ hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check
+ LOG_DEBUG("determineMode(): "); // Begin log entry
+
+ // Once mode determined, any remaining checks will bypass
+ checkCosmetic();
+ checkDemandingFast();
+ checkFrameMatchesPrevious();
+ checkConsecutiveFastRefreshes();
+#ifdef EINK_LIMIT_GHOSTING_PX
+ checkExcessiveGhosting();
+#endif
+ checkFastRequested();
+
+ if (refresh == UNSPECIFIED)
+ LOG_WARN("There was a flaw in the determineMode() logic.\n");
+
+ // -- Decision has been reached --
+ applyRefreshMode();
+ adjustRefreshCounters();
+
+#ifdef EINK_LIMIT_GHOSTING_PX
+ // Full refresh clears any ghosting
+ if (refresh == FULL)
+ resetGhostPixelTracking();
+#endif
+
+ // Return - call a refresh or not?
+ if (refresh == SKIPPED)
+ return false; // Don't trigger a refresh
+ else
+ return true; // Do trigger a refresh
+}
+
+// Is this the very first frame?
+void EInkDynamicDisplay::checkInitialized()
+{
+ if (!initialized) {
+ // Undo GxEPD2_BW::partialWindow(), if set by developer in EInkDisplay::connect()
+ configForFullRefresh();
+
+ // Clear any existing image, so we can draw logo with fast-refresh, but also to set GxEPD2_EPD::_initial_write
+ adafruitDisplay->clearScreen();
+
+ LOG_DEBUG("initialized, ");
+ initialized = true;
+
+ // Use a fast-refresh for the next frame; no skipping or else blank screen when waking from deep sleep
+ addFrameFlag(DEMAND_FAST);
+ }
+}
+
+// Was a frame skipped (rate, display busy) that should have been a FAST refresh?
+void EInkDynamicDisplay::checkForPromotion()
+{
+ // If a frame was skipped (rate, display busy), then promote a BACKGROUND frame
+ // Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it
+
+ switch (previousReason) {
+ case ASYNC_REFRESH_BLOCKED_DEMANDFAST:
+ addFrameFlag(DEMAND_FAST);
+ break;
+ case ASYNC_REFRESH_BLOCKED_COSMETIC:
+ addFrameFlag(COSMETIC);
+ break;
+ case ASYNC_REFRESH_BLOCKED_RESPONSIVE:
+ case EXCEEDED_RATELIMIT_FAST:
+ addFrameFlag(RESPONSIVE);
+ break;
+ default:
+ break;
+ }
+}
+
+// Is it too soon for another frame of this type?
+void EInkDynamicDisplay::checkRateLimiting()
+{
+ uint32_t now = millis();
+
+ // Sanity check: millis() overflow - just let the update run..
+ if (previousRunMs > now)
+ return;
+
+ // Skip update: too soon for BACKGROUND
+ if (frameFlags == BACKGROUND) {
+ if (now - previousRunMs < EINK_LIMIT_RATE_BACKGROUND_SEC * 1000) {
+ refresh = SKIPPED;
+ reason = EXCEEDED_RATELIMIT_FULL;
+ return;
+ }
+ }
+
+ // No rate-limit for these special cases
+ if (frameFlags & COSMETIC || frameFlags & DEMAND_FAST)
+ return;
+
+ // Skip update: too soon for RESPONSIVE
+ if (frameFlags & RESPONSIVE) {
+ if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
+ refresh = SKIPPED;
+ reason = EXCEEDED_RATELIMIT_FAST;
+ LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags);
+ return;
+ }
+ }
+}
+
+// Is this frame COSMETIC (splash screens?)
+void EInkDynamicDisplay::checkCosmetic()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // A full refresh is requested for cosmetic purposes: we have a decision
+ if (frameFlags & COSMETIC) {
+ refresh = FULL;
+ reason = FLAGGED_COSMETIC;
+ LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x\n", frameFlags);
+ }
+}
+
+// Is this a one-off special circumstance, where we REALLY want a fast refresh?
+void EInkDynamicDisplay::checkDemandingFast()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // A fast refresh is demanded: we have a decision
+ if (frameFlags & DEMAND_FAST) {
+ refresh = FAST;
+ reason = FLAGGED_DEMAND_FAST;
+ LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x\n", frameFlags);
+ }
+}
+
+// Does the new frame match the currently displayed image?
+void EInkDynamicDisplay::checkFrameMatchesPrevious()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // If frame is *not* a duplicate, abort the check
+ if (imageHash != previousImageHash)
+ return;
+
+#if !defined(EINK_BACKGROUND_USES_FAST)
+ // If BACKGROUND, and last update was FAST: redraw the same image in FULL (for display health + image quality)
+ if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
+ refresh = FULL;
+ reason = REDRAW_WITH_FULL;
+ LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x\n", frameFlags);
+ return;
+ }
+#endif
+
+ // Not redrawn, not COSMETIC, not DEMAND_FAST
+ refresh = SKIPPED;
+ reason = FRAME_MATCHED_PREVIOUS;
+ LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x\n", frameFlags);
+}
+
+// Have too many fast-refreshes occured consecutively, since last full refresh?
+void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // If too many FAST refreshes consecutively - force a FULL refresh
+ if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
+ refresh = FULL;
+ reason = EXCEEDED_LIMIT_FASTREFRESH;
+ LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x\n", frameFlags);
+ }
+}
+
+// No objections, we can perform fast-refresh, if desired
+void EInkDynamicDisplay::checkFastRequested()
+{
+ if (refresh != UNSPECIFIED)
+ return;
+
+ if (frameFlags == BACKGROUND) {
+#ifdef EINK_BACKGROUND_USES_FAST
+ // If we want BACKGROUND to use fast. (FULL only when a limit is hit)
+ refresh = FAST;
+ reason = BACKGROUND_USES_FAST;
+ LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount,
+ frameFlags);
+#else
+ // If we do want to use FULL for BACKGROUND updates
+ refresh = FULL;
+ reason = FLAGGED_BACKGROUND;
+ LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND\n");
+#endif
+ }
+
+ // Sanity: confirm that we did ask for a RESPONSIVE frame.
+ if (frameFlags & RESPONSIVE) {
+ refresh = FAST;
+ reason = NO_OBJECTIONS;
+ LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, frameFlags);
+ }
+}
+
+// Reset the timer used for rate-limiting
+void EInkDynamicDisplay::resetRateLimiting()
+{
+ previousRunMs = millis();
+}
+
+// Generate a hash of this frame, to compare against previous update
+void EInkDynamicDisplay::hashImage()
+{
+ imageHash = 0;
+
+ // Sum all bytes of the image buffer together
+ for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
+ imageHash += buffer[b];
+ }
+}
+
+// Store the results of determineMode() for future use, and reset for next call
+void EInkDynamicDisplay::storeAndReset()
+{
+ previousFrameFlags = frameFlags;
+ previousRefresh = refresh;
+ previousReason = reason;
+
+ // Only store image hash if the display will update
+ if (refresh != SKIPPED) {
+ previousImageHash = imageHash;
+ }
+
+ frameFlags = BACKGROUND;
+ refresh = UNSPECIFIED;
+}
+
+#ifdef EINK_LIMIT_GHOSTING_PX
+// Count how many ghost pixels the new image will display
+void EInkDynamicDisplay::countGhostPixels()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // Start a new count
+ ghostPixelCount = 0;
+
+ // Check new image, bit by bit, for any white pixels at locations marked "dirty"
+ for (uint16_t i = 0; i < displayBufferSize; i++) {
+ for (uint8_t bit = 0; bit < 7; bit++) {
+
+ const bool dirty = (dirtyPixels[i] >> bit) & 1; // Has pixel location been drawn to since full-refresh?
+ const bool shouldBeBlank = !((buffer[i] >> bit) & 1); // Is pixel location white in the new image?
+
+ // If pixel is (or has been) black since last full-refresh, and now is white: ghosting
+ if (dirty && shouldBeBlank)
+ ghostPixelCount++;
+
+ // Update the dirty status for this pixel - will this location become a ghost if set white in future?
+ if (!dirty && !shouldBeBlank)
+ dirtyPixels[i] |= (1 << bit);
+ }
+ }
+
+ LOG_DEBUG("ghostPixels=%hu, ", ghostPixelCount);
+}
+
+// Check if ghost pixel count exceeds the defined limit
+void EInkDynamicDisplay::checkExcessiveGhosting()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ countGhostPixels();
+
+ // If too many ghost pixels, select full refresh
+ if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
+ refresh = FULL;
+ reason = EXCEEDED_GHOSTINGLIMIT;
+ LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x\n", frameFlags);
+ }
+}
+
+// Clear the dirty pixels array. Call when full-refresh cleans the display.
+void EInkDynamicDisplay::resetGhostPixelTracking()
+{
+ // Copy the current frame into dirtyPixels[] from the display buffer
+ memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize);
+}
+#endif // EINK_LIMIT_GHOSTING_PX
+
+// Handle any asyc tasks
+void EInkDynamicDisplay::onNotify(uint32_t notification)
+{
+ // Which task
+ switch (notification) {
+ case DUE_POLL_ASYNCREFRESH:
+ pollAsyncRefresh();
+ break;
+ }
+}
+
+#ifdef HAS_EINK_ASYNCFULL
+// Public: wait for an refresh already in progress, then run the post-update code. See Screen::setScreensaverFrames()
+void EInkDynamicDisplay::joinAsyncRefresh()
+{
+ // If no async refresh running, nothing to do
+ if (!asyncRefreshRunning)
+ return;
+
+ LOG_DEBUG("Joining an async refresh in progress\n");
+
+ // Continually poll the BUSY pin
+ while (adafruitDisplay->epd2.isBusy())
+ yield();
+
+ // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
+ adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
+ EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
+ asyncRefreshRunning = false; // Unset the flag
+ LOG_DEBUG("Refresh complete\n");
+
+ // Note: this code only works because of a modification to meshtastic/GxEPD2.
+ // It is only equipped to intercept calls to nextPage()
+}
+
+// Called from NotifiedWorkerThread. Run the post-update code if the hardware is ready
+void EInkDynamicDisplay::pollAsyncRefresh()
+{
+ // In theory, this condition should never be met
+ if (!asyncRefreshRunning)
+ return;
+
+ // Still running, check back later
+ if (adafruitDisplay->epd2.isBusy()) {
+ // Schedule next call of pollAsyncRefresh()
+ NotifiedWorkerThread::notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true);
+ return;
+ }
+
+ // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
+ adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
+ EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
+ asyncRefreshRunning = false; // Unset the flag
+ LOG_DEBUG("Async full-refresh complete\n");
+
+ // Note: this code only works because of a modification to meshtastic/GxEPD2.
+ // It is only equipped to intercept calls to nextPage()
+}
+
+// Check the status of "async full-refresh"; skip if running
+void EInkDynamicDisplay::checkBusyAsyncRefresh()
+{
+ // No refresh taking place, continue with determineMode()
+ if (!asyncRefreshRunning)
+ return;
+
+ // Full refresh still running
+ if (adafruitDisplay->epd2.isBusy()) {
+ // No refresh
+ refresh = SKIPPED;
+
+ // Set the reason, marking what type of frame we're skipping
+ if (frameFlags & DEMAND_FAST)
+ reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST;
+ else if (frameFlags & COSMETIC)
+ reason = ASYNC_REFRESH_BLOCKED_COSMETIC;
+ else if (frameFlags & RESPONSIVE)
+ reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE;
+ else
+ reason = ASYNC_REFRESH_BLOCKED_BACKGROUND;
+
+ return;
+ }
+}
+
+// Hold control while an async refresh runs
+void EInkDynamicDisplay::awaitRefresh()
+{
+ // Continually poll the BUSY pin
+ while (adafruitDisplay->epd2.isBusy())
+ yield();
+
+ // End the full-refresh process
+ adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
+ EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
+ asyncRefreshRunning = false; // Unset the flag
+}
+#endif // HAS_EINK_ASYNCFULL
+
+#endif // USE_EINK_DYNAMICDISPLAY
\ No newline at end of file
diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h
new file mode 100644
index 000000000..8f3ce205a
--- /dev/null
+++ b/src/graphics/EInkDynamicDisplay.h
@@ -0,0 +1,148 @@
+#pragma once
+
+#include "configuration.h"
+
+#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
+
+#include "EInkDisplay2.h"
+#include "GxEPD2_BW.h"
+#include "concurrency/NotifiedWorkerThread.h"
+
+/*
+ Derives from the EInkDisplay adapter class.
+ Accepts suggestions from Screen class about frame type.
+ Determines which refresh type is most suitable.
+ (Full, Fast, Skip)
+*/
+
+class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWorkerThread
+{
+ public:
+ // Constructor
+ // ( Parameters unused, passed to EInkDisplay. Maintains compatibility OLEDDisplay class )
+ EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus);
+ ~EInkDynamicDisplay();
+
+ // What kind of frame is this
+ enum frameFlagTypes : uint8_t {
+ BACKGROUND = (1 << 0), // For frames via display()
+ RESPONSIVE = (1 << 1), // For frames via forceDisplay()
+ COSMETIC = (1 << 2), // For splashes
+ DEMAND_FAST = (1 << 3), // Special case only
+ BLOCKING = (1 << 4), // Modifier - block while refresh runs
+ };
+ void addFrameFlag(frameFlagTypes flag);
+
+ // Set the correct frame flag, then call universal "update()" method
+ void display() override;
+ bool forceDisplay(uint32_t msecLimit) override; // Shadows base class. Parameter and return val unused.
+
+ protected:
+ enum refreshTypes : uint8_t { // Which refresh operation will be used
+ UNSPECIFIED,
+ FULL,
+ FAST,
+ SKIPPED,
+ };
+ enum reasonTypes : uint8_t { // How was the decision reached
+ NO_OBJECTIONS,
+ ASYNC_REFRESH_BLOCKED_DEMANDFAST,
+ ASYNC_REFRESH_BLOCKED_COSMETIC,
+ ASYNC_REFRESH_BLOCKED_RESPONSIVE,
+ ASYNC_REFRESH_BLOCKED_BACKGROUND,
+ EXCEEDED_RATELIMIT_FAST,
+ EXCEEDED_RATELIMIT_FULL,
+ FLAGGED_COSMETIC,
+ FLAGGED_DEMAND_FAST,
+ EXCEEDED_LIMIT_FASTREFRESH,
+ EXCEEDED_GHOSTINGLIMIT,
+ FRAME_MATCHED_PREVIOUS,
+ BACKGROUND_USES_FAST,
+ FLAGGED_BACKGROUND,
+ REDRAW_WITH_FULL,
+ };
+
+ enum notificationTypes : uint8_t { // What was onNotify() called for
+ NONE = 0, // This behavior (NONE=0) is fixed by NotifiedWorkerThread class
+ DUE_POLL_ASYNCREFRESH = 1,
+ };
+ const uint32_t intervalPollAsyncRefresh = 100;
+
+ void onNotify(uint32_t notification) override; // Handle any async tasks - overrides NotifiedWorkerThread
+ void configForFastRefresh(); // GxEPD2 code to set fast-refresh
+ void configForFullRefresh(); // GxEPD2 code to set full-refresh
+ bool determineMode(); // Assess situation, pick a refresh type
+ void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type
+ void adjustRefreshCounters(); // Update fastRefreshCount
+ bool update(); // Trigger the display update - determine mode, then call base class
+ void endOrDetach(); // Run the post-update code, or delegate it off to checkBusyAsyncRefresh()
+
+ // Checks as part of determineMode()
+ void checkInitialized(); // Is this the very first frame?
+ void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh?
+ void checkRateLimiting(); // Is this frame too soon?
+ void checkCosmetic(); // Was the COSMETIC flag set?
+ void checkDemandingFast(); // Was the DEMAND_FAST flag set?
+ void checkFrameMatchesPrevious(); // Does the new frame match the existing display image?
+ void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively?
+ void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND?
+
+ void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting
+ void hashImage(); // Generate a hashed version of this frame, to compare against previous update
+ void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call
+
+ // What we are determining for this frame
+ frameFlagTypes frameFlags = BACKGROUND; // Frame characteristics - determineMode() input
+ refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output
+ reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used
+
+ // What happened last time determineMode() ran
+ frameFlagTypes previousFrameFlags = BACKGROUND; // (Previous) Frame flags
+ refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome
+ reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason
+
+ bool initialized = false; // Have we drawn at least one frame yet?
+ uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting)
+ uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed!
+ uint32_t previousImageHash = 0; // Hash of the previous update's frame
+ uint32_t fastRefreshCount = 0; // How many fast-refreshes consecutively since last full refresh?
+ refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for
+
+ // Optional - track ghosting, pixel by pixel
+#ifdef EINK_LIMIT_GHOSTING_PX
+ void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh
+ void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit
+ void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display.
+ uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
+ uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
+#endif
+
+ // Conditional - async full refresh - only with modified meshtastic/GxEPD2
+#if defined(HAS_EINK_ASYNCFULL)
+ public:
+ void joinAsyncRefresh(); // Main thread joins an async refresh already in progress. Blocks, then runs post-update code
+
+ protected:
+ void pollAsyncRefresh(); // Run the post-update code if the hardware is ready
+ void checkBusyAsyncRefresh(); // Check if display is busy running an async full-refresh (rejecting new frames)
+ void awaitRefresh(); // Hold control while an async refresh runs
+ void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay()
+ bool asyncRefreshRunning = false; // Flag, checked by checkBusyAsyncRefresh()
+#else
+ public:
+ void joinAsyncRefresh() {} // Dummy method
+
+ protected:
+ void pollAsyncRefresh() {} // Dummy method. In theory, not reachable
+#endif
+};
+
+// Hide the ugly casts used in Screen.cpp
+#define EINK_ADD_FRAMEFLAG(display, flag) static_cast(display)->addFrameFlag(EInkDynamicDisplay::flag)
+#define EINK_JOIN_ASYNCREFRESH(display) static_cast(display)->joinAsyncRefresh()
+
+#else // !USE_EINK_DYNAMICDISPLAY
+// Dummy-macro, removes the need for include guards
+#define EINK_ADD_FRAMEFLAG(display, flag)
+#define EINK_JOIN_ASYNCREFRESH(display)
+#endif
\ No newline at end of file
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index c0e55ea83..e5f392036 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -25,12 +25,15 @@ along with this program. If not, see .
#include
#include "DisplayFormatters.h"
+#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
+#endif
#include "MeshService.h"
#include "NodeDB.h"
#include "error.h"
#include "gps/GeoCoord.h"
#include "gps/RTC.h"
+#include "graphics/ScreenFonts.h"
#include "graphics/images.h"
#include "input/TouchScreenImpl1.h"
#include "main.h"
@@ -56,14 +59,6 @@ along with this program. If not, see .
#include "platform/portduino/PortduinoGlue.h"
#endif
-#ifdef OLED_RU
-#include "fonts/OLEDDisplayFontsRU.h"
-#endif
-
-#ifdef OLED_UA
-#include "fonts/OLEDDisplayFontsUA.h"
-#endif
-
using namespace meshtastic; /** @todo remove */
namespace graphics
@@ -78,7 +73,7 @@ namespace graphics
// #define SHOW_REDRAWS
// A text message frame + debug frame + all the node infos
-static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
+FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE;
static char btPIN[16] = "888888";
@@ -99,8 +94,10 @@ std::vector moduleFrames;
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
static char ourId[5];
+#if HAS_GPS
// GeoCoord object for the screen
GeoCoord geoCoord;
+#endif
#ifdef SHOW_REDRAWS
static bool heartbeat = false;
@@ -111,31 +108,7 @@ static uint16_t displayWidth, displayHeight;
#define SCREEN_WIDTH displayWidth
#define SCREEN_HEIGHT displayHeight
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
- !defined(DISPLAY_FORCE_SMALL_FONTS)
-// The screen is bigger so use bigger fonts
-#define FONT_SMALL ArialMT_Plain_16 // Height: 19
-#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
-#define FONT_LARGE ArialMT_Plain_24 // Height: 28
-#else
-#ifdef OLED_RU
-#define FONT_SMALL ArialMT_Plain_10_RU
-#else
-#ifdef OLED_UA
-#define FONT_SMALL ArialMT_Plain_10_UA
-#else
-#define FONT_SMALL ArialMT_Plain_10 // Height: 13
-#endif
-#endif
-#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
-#define FONT_LARGE ArialMT_Plain_24 // Height: 28
-#endif
-
-#define fontHeight(font) ((font)[1] + 1) // height is position 1
-
-#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
-#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
-#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
+#include "graphics/ScreenFonts.h"
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
@@ -274,7 +247,7 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
if ((millis() / 10000) % 2) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the");
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,");
- display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Flasher or CLI client.");
+ display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients.");
} else {
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org");
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information.");
@@ -289,10 +262,65 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
#ifdef USE_EINK
/// Used on eink displays while in deep sleep
-static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
+ // Next frame should use full-refresh, and block while running, else device will sleep before async callback
+ EINK_ADD_FRAMEFLAG(display, COSMETIC);
+ EINK_ADD_FRAMEFLAG(display, BLOCKING);
+
+ LOG_DEBUG("Drawing deep sleep screen\n");
drawIconScreen("Sleeping...", display, state, x, y);
}
+
+/// Used on eink displays when screen updates are paused
+static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
+{
+ LOG_DEBUG("Drawing screensaver overlay\n");
+
+ EINK_ADD_FRAMEFLAG(display, COSMETIC); // Take the opportunity for a full-refresh
+
+ // Config
+ display->setFont(FONT_SMALL);
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+ const char *pauseText = "Screen Paused";
+ const char *idText = owner.short_name;
+ constexpr uint16_t padding = 5;
+ constexpr uint8_t dividerGap = 1;
+ constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
+
+ // Dimensions
+ const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText));
+ const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
+ const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding;
+ const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
+
+ // Position
+ const int16_t boxLeft = (display->width() / 2) - (boxWidth / 2) + random(-imprecision, imprecision + 1);
+ // const int16_t boxRight = boxLeft + boxWidth - 1;
+ const int16_t boxTop = (display->height() / 2) - (boxHeight / 2 + random(-imprecision, imprecision + 1));
+ const int16_t boxBottom = boxTop + boxHeight - 1;
+ const int16_t idTextLeft = boxLeft + padding;
+ const int16_t idTextTop = boxTop + padding;
+ const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding;
+ const int16_t pauseTextTop = boxTop + padding;
+ const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
+ const int16_t dividerTop = boxTop + 1 + dividerGap;
+ const int16_t dividerBottom = boxBottom - 1 - dividerGap;
+
+ // Draw: box
+ display->setColor(EINK_WHITE);
+ display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Clear a slightly oversized area for the box
+ display->setColor(EINK_BLACK);
+ display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
+
+ // Draw: Text
+ display->drawString(idTextLeft, idTextTop, idText);
+ display->drawString(pauseTextLeft, pauseTextTop, pauseText);
+ display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
+
+ // Draw: divider
+ display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
+}
#endif
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -381,7 +409,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
static char tempBuf[237];
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp));
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
// LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from,
// mp.decoded.variant.data.decoded.bytes);
@@ -419,7 +447,7 @@ static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, i
static char tempBuf[237];
meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp));
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -500,7 +528,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat
{
char usersString[20];
snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x, y + 3, 8, 8, imgUser);
#else
@@ -510,7 +538,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat
if (config.display.heading_bold)
display->drawString(x + 11, y - 2, usersString);
}
-
+#if HAS_GPS
// Draw GPS status summary
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
@@ -652,7 +680,7 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const
}
}
}
-
+#endif
namespace
{
@@ -807,16 +835,16 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
if (state->currentFrame != prevFrame) {
prevFrame = state->currentFrame;
- nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes();
- meshtastic_NodeInfoLite *n = nodeDB.getMeshNodeByIndex(nodeIndex);
- if (n->num == nodeDB.getNodeNum()) {
+ nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes();
+ meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(nodeIndex);
+ if (n->num == nodeDB->getNodeNum()) {
// Don't show our node, just skip to next
- nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes();
- n = nodeDB.getMeshNodeByIndex(nodeIndex);
+ nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes();
+ n = nodeDB->getMeshNodeByIndex(nodeIndex);
}
}
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(nodeIndex);
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(nodeIndex);
display->setFont(FONT_SMALL);
@@ -854,7 +882,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
} else {
strncpy(distStr, "? km", sizeof(distStr));
}
- meshtastic_NodeInfoLite *ourNode = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
int16_t compassX = 0, compassY = 0;
@@ -920,18 +948,22 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry)
: concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32)
{
+ graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64)
dispdev = new SH1106Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#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);
-#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014)
+#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
-#elif defined(USE_EINK)
+#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
dispdev = new EInkDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
+ dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_ST7567)
dispdev = new ST7567Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -955,6 +987,11 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
cmdQueue.setReader(this);
}
+Screen::~Screen()
+{
+ delete[] graphics::normalFrames;
+}
+
/**
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
@@ -962,15 +999,17 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
void Screen::doDeepSleep()
{
#ifdef USE_EINK
- static FrameCallback sleepFrames[] = {drawSleepScreen};
- static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
- ui->setFrames(sleepFrames, sleepFrameCount);
- ui->update();
+ setOn(false, drawDeepSleepScreen);
+#ifdef PIN_EINK_EN
+ digitalWrite(PIN_EINK_EN, LOW); // power off backlight
#endif
+#else
+ // Without E-Ink display:
setOn(false);
+#endif
}
-void Screen::handleSetOn(bool on)
+void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
{
if (!useDisplay)
return;
@@ -989,6 +1028,10 @@ void Screen::handleSetOn(bool on)
setInterval(0); // Draw ASAP
runASAP = true;
} else {
+#ifdef USE_EINK
+ // eInkScreensaver parameter is usually NULL (default argument), default frame used instead
+ setScreensaverFrames(einkScreensaver);
+#endif
LOG_INFO("Turning off screen\n");
dispdev->displayOff();
#ifdef T_WATCH_S3
@@ -1039,6 +1082,7 @@ void Screen::setup()
logo_timeout *= 2;
// Add frames.
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
static FrameCallback bootFrames[] = {drawBootScreen};
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
ui->setFrames(bootFrames, bootFrameCount);
@@ -1057,7 +1101,7 @@ void Screen::setup()
// Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically
// flip it. If you have a headache now, you're welcome.
if (!config.display.flip_screen) {
-#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014)
+#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS)
static_cast(dispdev)->flipScreenVertically();
#else
dispdev->flipScreenVertically();
@@ -1109,10 +1153,33 @@ void Screen::setup()
MeshModule::observeUIEvents(&uiFrameEventObserver);
}
-void Screen::forceDisplay()
+void Screen::forceDisplay(bool forceUiUpdate)
{
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
#ifdef USE_EINK
+ // If requested, make sure queued commands are run, and UI has rendered a new frame
+ if (forceUiUpdate) {
+ // No delay between UI frame rendering
+ setFastFramerate();
+
+ // Make sure all CMDs have run first
+ while (!cmdQueue.isEmpty())
+ runOnce();
+
+ // Ensure at least one frame has drawn
+ uint64_t startUpdate;
+ do {
+ startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
+ delay(10);
+ ui->update();
+ } while (ui->getUiState()->lastUpdate < startUpdate);
+
+ // Return to normal frame rate
+ targetFramerate = IDLE_FRAMERATE;
+ ui->setTargetFPS(targetFramerate);
+ }
+
+ // Tell EInk class to update the display
static_cast(dispdev)->forceDisplay();
#endif
}
@@ -1195,6 +1262,7 @@ int32_t Screen::runOnce()
break;
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
case Cmd::STOP_BOOT_SCREEN:
+ EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
setFrames();
break;
case Cmd::PRINT:
@@ -1293,6 +1361,63 @@ void Screen::setWelcomeFrames()
}
}
+#ifdef USE_EINK
+/// Determine which screensaver frame to use, then set the FrameCallback
+void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
+{
+ // Remember current frame, restore position at power-on
+ uint8_t frameNumber = ui->getUiState()->currentFrame;
+
+ // Retain specified frame / overlay callback beyond scope of this method
+ static FrameCallback screensaverFrame;
+ static OverlayCallback screensaverOverlay;
+
+#if defined(HAS_EINK_ASYNCFULL) && defined(USE_EINK_DYNAMICDISPLAY)
+ // Join (await) a currently running async refresh, then run the post-update code.
+ // Avoid skipping of screensaver frame. Would otherwise be handled by NotifiedWorkerThread.
+ EINK_JOIN_ASYNCREFRESH(dispdev);
+#endif
+
+ // If: one-off screensaver frame passed as argument. Handles doDeepSleep()
+ if (einkScreensaver != NULL) {
+ screensaverFrame = einkScreensaver;
+ ui->setFrames(&screensaverFrame, 1);
+ }
+
+ // Else, display the usual "overlay" screensaver
+ else {
+ screensaverOverlay = drawScreensaverOverlay;
+ ui->setOverlays(&screensaverOverlay, 1);
+ }
+
+ // Request new frame, ASAP
+ setFastFramerate();
+ uint64_t startUpdate;
+ do {
+ startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
+ delay(1);
+ ui->update();
+ } while (ui->getUiState()->lastUpdate < startUpdate);
+
+ // Old EInkDisplay class
+#if !defined(USE_EINK_DYNAMICDISPLAY)
+ static_cast(dispdev)->forceDisplay(0); // Screen::forceDisplay(), but override rate-limit
+#endif
+
+ // Prepare now for next frame, shown when display wakes
+ ui->setOverlays(NULL, 0); // Clear overlay
+ setFrames(); // Return to normal display updates
+ ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on
+
+ // Pick a refresh method, for when display wakes
+#ifdef EINK_HASQUIRK_GHOSTING
+ EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused"
+#else
+ EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
+#endif
+}
+#endif
+
// restore our regular frame list
void Screen::setFrames()
{
@@ -1307,7 +1432,7 @@ void Screen::setFrames()
#endif
// We don't show the node info our our node (if we have it yet - we should)
- size_t numMeshNodes = nodeDB.getNumMeshNodes();
+ size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
@@ -1375,6 +1500,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
{
LOG_DEBUG("showing bluetooth screen\n");
showingNormalScreen = false;
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
static FrameCallback frames[] = {drawFrameBluetooth};
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
@@ -1392,6 +1518,11 @@ void Screen::handleShutdownScreen()
{
LOG_DEBUG("showing shutdown screen\n");
showingNormalScreen = false;
+#ifdef USE_EINK
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
+ EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
+ handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
+#endif
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Shutting down...");
@@ -1405,6 +1536,11 @@ void Screen::handleRebootScreen()
{
LOG_DEBUG("showing reboot screen\n");
showingNormalScreen = false;
+#ifdef USE_EINK
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
+ EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
+ handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?)
+#endif
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Rebooting...");
@@ -1417,6 +1553,7 @@ void Screen::handleStartFirmwareUpdateScreen()
{
LOG_DEBUG("showing firmware screen\n");
showingNormalScreen = false;
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
static FrameCallback frames[] = {drawFrameFirmware};
setFrameImmediateDraw(frames);
@@ -1529,8 +1666,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
char channelStr[20];
{
concurrency::LockGuard guard(&lock);
- auto chName = channels.getPrimaryName();
- snprintf(channelStr, sizeof(channelStr), "%s", chName);
+ snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex()));
}
// Display power status
@@ -1553,6 +1689,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
} else {
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
}
+#if HAS_GPS
// Display GPS status
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
drawGPSpowerstat(display, x, y + 2, gpsStatus);
@@ -1563,7 +1700,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
}
}
-
+#endif
display->setColor(WHITE);
// Draw the channel name
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
@@ -1572,7 +1709,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
#ifdef ARCH_ESP32
if (millis() - storeForwardModule->lastHeartbeat >
(storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgQuestionL1);
@@ -1583,7 +1720,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
imgQuestion);
#endif
} else {
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8,
imgSFL1);
@@ -1597,7 +1734,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
#endif
} else {
// TODO: Raspberry Pi supports more than just the one screen size
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \
+ ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);
@@ -1758,7 +1896,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
// Show uptime as days, hours, minutes OR seconds
std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds);
- uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
@@ -1782,6 +1920,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
char chUtil[13];
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
+#if HAS_GPS
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// Line 3
if (config.display.gps_format !=
@@ -1793,6 +1932,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
} else {
drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
}
+#endif
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
@@ -1809,7 +1949,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
setFrames(); // Regen the list of screens
}
- nodeDB.updateGUI = false;
+ nodeDB->updateGUI = false;
break;
}
@@ -1858,4 +1998,4 @@ int Screen::handleInputEvent(const InputEvent *event)
} // namespace graphics
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
-#endif // HAS_SCREEN
\ No newline at end of file
+#endif // HAS_SCREEN
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index baee4b140..2cb1cd5a9 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -20,7 +20,7 @@ class Screen
void setOn(bool) {}
void print(const char *) {}
void doDeepSleep() {}
- void forceDisplay() {}
+ void forceDisplay(bool forceUiUpdate = false) {}
void startBluetoothPinScreen(uint32_t pin) {}
void stopBluetoothPinScreen() {}
void startRebootScreen() {}
@@ -47,6 +47,7 @@ class Screen
#endif
#include "EInkDisplay2.h"
+#include "EInkDynamicDisplay.h"
#include "TFTDisplay.h"
#include "TypedQueue.h"
#include "commands.h"
@@ -72,6 +73,10 @@ class Screen
#define MILES_TO_FEET 5280
#endif
+// Intuitive colors. E-Ink display is inverted from OLED(?)
+#define EINK_BLACK OLEDDISPLAY_COLOR::WHITE
+#define EINK_WHITE OLEDDISPLAY_COLOR::BLACK
+
namespace graphics
{
@@ -124,6 +129,8 @@ class Screen : public concurrency::OSThread
public:
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
+ ~Screen();
+
Screen(const Screen &) = delete;
Screen &operator=(const Screen &) = delete;
@@ -136,14 +143,14 @@ class Screen : public concurrency::OSThread
// Not thread safe - must be called before any other methods are called.
void setup();
- /// Turns the screen on/off.
- void setOn(bool on)
+ /// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
+ void setOn(bool on, FrameCallback einkScreensaver = NULL)
{
if (!on)
- handleSetOn(
- false); // We handle off commands immediately, because they might be called because the CPU is shutting down
+ // We handle off commands immediately, because they might be called because the CPU is shutting down
+ handleSetOn(false, einkScreensaver);
else
- enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
+ enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
}
/**
@@ -311,13 +318,18 @@ class Screen : public concurrency::OSThread
int handleInputEvent(const InputEvent *arg);
/// Used to force (super slow) eink displays to draw critical frames
- void forceDisplay();
+ void forceDisplay(bool forceUiUpdate = false);
/// Draws our SSL cert screen during boot (called from WebServer)
void setSSLFrames();
void setWelcomeFrames();
+#ifdef USE_EINK
+ /// Draw an image to remain on E-Ink display after screen off
+ void setScreensaverFrames(FrameCallback einkScreensaver = NULL);
+#endif
+
protected:
/// Updates the UI.
//
@@ -348,7 +360,7 @@ class Screen : public concurrency::OSThread
}
// Implementations of various commands, called from doTask().
- void handleSetOn(bool on);
+ void handleSetOn(bool on, FrameCallback einkScreensaver = NULL);
void handleOnPress();
void handleShowNextFrame();
void handleShowPrevFrame();
diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h
new file mode 100644
index 000000000..4b34563f7
--- /dev/null
+++ b/src/graphics/ScreenFonts.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#ifdef OLED_RU
+#include "graphics/fonts/OLEDDisplayFontsRU.h"
+#endif
+
+#ifdef OLED_UA
+#include "graphics/fonts/OLEDDisplayFontsUA.h"
+#endif
+
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
+ !defined(DISPLAY_FORCE_SMALL_FONTS)
+// The screen is bigger so use bigger fonts
+#define FONT_SMALL ArialMT_Plain_16 // Height: 19
+#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
+#define FONT_LARGE ArialMT_Plain_24 // Height: 28
+#else
+#ifdef OLED_RU
+#define FONT_SMALL ArialMT_Plain_10_RU
+#else
+#ifdef OLED_UA
+#define FONT_SMALL ArialMT_Plain_10_UA
+#else
+#define FONT_SMALL ArialMT_Plain_10 // Height: 13
+#endif
+#endif
+#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
+#define FONT_LARGE ArialMT_Plain_24 // Height: 28
+#endif
+
+#define fontHeight(font) ((font)[1] + 1) // height is position 1
+
+#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
+#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
+#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 9475e0296..12e549424 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -1,6 +1,7 @@
#include "configuration.h"
#include "main.h"
#if ARCH_PORTDUINO
+#include "mesh_bus_spi.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
@@ -333,13 +334,13 @@ static LGFX *tft = nullptr;
#include // Graphics and font library for ILI9341 driver chip
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
-#elif ARCH_PORTDUINO
+#elif ARCH_PORTDUINO && HAS_SCREEN != 0
#include // Graphics and font library for ST7735 driver chip
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_LCD *_panel_instance;
- lgfx::Bus_SPI _bus_instance;
+ lgfx::Mesh_Bus_SPI _bus_instance;
lgfx::ITouch *_touch_instance;
@@ -356,6 +357,7 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance = new lgfx::Panel_ILI9341;
auto buscfg = _bus_instance.config();
buscfg.spi_mode = 0;
+ _bus_instance.spi_device(DisplaySPI, settingsStrings[displayspidev]);
buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable)
@@ -402,13 +404,103 @@ class LGFX : public lgfx::LGFX_Device
};
static LGFX *tft = nullptr;
+
+#elif defined(HX8357_CS)
+#include // Graphics and font library for HX8357 driver chip
+
+class LGFX : public lgfx::LGFX_Device
+{
+ lgfx::Panel_HX8357D _panel_instance;
+ lgfx::Bus_SPI _bus_instance;
+#if defined(USE_XPT2046)
+ lgfx::Touch_XPT2046 _touch_instance;
#endif
-#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || ARCH_PORTDUINO
+ public:
+ LGFX(void)
+ {
+ // Panel_HX8357D
+ {
+ // configure SPI
+ auto cfg = _bus_instance.config();
+
+ cfg.spi_host = HX8357_SPI_HOST;
+ cfg.spi_mode = 0;
+ cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
+ // 80MHz by an integer)
+ cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
+ cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin
+ cfg.use_lock = true; // Set to true to use transaction locking
+ cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
+ // SPI_DMA_CH_AUTO=auto setting)
+ cfg.pin_sclk = HX8357_SCK; // Set SPI SCLK pin number
+ cfg.pin_mosi = HX8357_MOSI; // Set SPI MOSI pin number
+ cfg.pin_miso = HX8357_MISO; // Set SPI MISO pin number (-1 = disable)
+ cfg.pin_dc = HX8357_RS; // Set SPI DC pin number (-1 = disable)
+
+ _bus_instance.config(cfg); // applies the set value to the bus.
+ _panel_instance.setBus(&_bus_instance); // set the bus on the panel.
+ }
+ {
+ // Set the display panel control.
+ auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
+
+ cfg.pin_cs = HX8357_CS; // Pin number where CS is connected (-1 = disable)
+ cfg.pin_rst = HX8357_RESET; // Pin number where RST is connected (-1 = disable)
+ cfg.pin_busy = HX8357_BUSY; // Pin number where BUSY is connected (-1 = disable)
+
+ cfg.panel_width = TFT_WIDTH; // actual displayable width
+ cfg.panel_height = TFT_HEIGHT; // actual displayable height
+ cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
+ cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
+ cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is upside down)
+ cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
+ cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
+ cfg.readable = true; // Set to true if data can be read
+ cfg.invert = TFT_INVERT; // Set to true if the light/darkness of the panel is reversed
+ cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
+ cfg.dlen_16bit = false;
+ cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
+
+ _panel_instance.config(cfg);
+ }
+#if defined(USE_XPT2046)
+ {
+ // Configure settings for touch control.
+ auto touch_cfg = _touch_instance.config();
+
+ touch_cfg.pin_cs = TOUCH_CS;
+ touch_cfg.x_min = 0;
+ touch_cfg.x_max = TFT_HEIGHT - 1;
+ touch_cfg.y_min = 0;
+ touch_cfg.y_max = TFT_WIDTH - 1;
+ touch_cfg.pin_int = -1;
+ touch_cfg.bus_shared = true;
+ touch_cfg.offset_rotation = 1;
+
+ _touch_instance.config(touch_cfg);
+ _panel_instance.setTouch(&_touch_instance);
+ }
+#endif
+ setPanel(&_panel_instance);
+ }
+};
+
+static LGFX *tft = nullptr;
+
+#endif
+
+#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || \
+ (ARCH_PORTDUINO && HAS_SCREEN != 0)
#include "SPILock.h"
#include "TFTDisplay.h"
#include
+#ifdef UNPHONE
+#include "unPhone.h"
+extern unPhone unphone;
+#endif
+
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
{
LOG_DEBUG("TFTDisplay!\n");
@@ -474,8 +566,10 @@ void TFTDisplay::sendCommand(uint8_t com)
#elif defined(ST7735_BL_V05)
pinMode(ST7735_BL_V05, OUTPUT);
digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON);
-#endif
-#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
+#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
+ tft->wakeup();
+ tft->powerSaveOff();
+#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
#endif
@@ -486,7 +580,9 @@ void TFTDisplay::sendCommand(uint8_t com)
#ifdef VTFT_CTRL
digitalWrite(VTFT_CTRL, LOW);
#endif
-
+#ifdef UNPHONE
+ unphone.backlight(true); // using unPhone library
+#endif
#ifdef RAK14014
#elif !defined(M5STACK)
tft->setBrightness(172);
@@ -503,16 +599,22 @@ void TFTDisplay::sendCommand(uint8_t com)
#elif defined(ST7735_BL_V05)
pinMode(ST7735_BL_V05, OUTPUT);
digitalWrite(ST7735_BL_V05, !TFT_BACKLIGHT_ON);
-#endif
-#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
+#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
+ tft->sleep();
+ tft->powerSaveOn();
+#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON);
#endif
+
#ifdef VTFT_CTRL_V03
digitalWrite(VTFT_CTRL_V03, HIGH);
#endif
#ifdef VTFT_CTRL
digitalWrite(VTFT_CTRL, HIGH);
#endif
+#ifdef UNPHONE
+ unphone.backlight(false); // using unPhone library
+#endif
#ifdef RAK14014
#elif !defined(M5STACK)
tft->setBrightness(0);
@@ -584,6 +686,10 @@ bool TFTDisplay::connect()
pinMode(ST7735_BL_V05, OUTPUT);
digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON);
#endif
+#ifdef UNPHONE
+ unphone.backlight(true); // using unPhone library
+ LOG_INFO("Power to TFT Backlight\n");
+#endif
tft->init();
@@ -605,4 +711,4 @@ bool TFTDisplay::connect()
return true;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/images.h b/src/graphics/images.h
index 207fc3a86..5c6fb4275 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -14,7 +14,8 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3
const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF};
const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF};
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \
+ ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
@@ -30,4 +31,4 @@ const uint8_t imgQuestion[] PROGMEM = {0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1,
const uint8_t imgSF[] PROGMEM = {0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5};
#endif
-#include "img/icon.xbm"
\ No newline at end of file
+#include "img/icon.xbm"
diff --git a/src/graphics/mesh_bus_spi.cpp b/src/graphics/mesh_bus_spi.cpp
new file mode 100644
index 000000000..a9536d490
--- /dev/null
+++ b/src/graphics/mesh_bus_spi.cpp
@@ -0,0 +1,188 @@
+// This code has been copied from LovyanGFX to make the SPI device selectable for touchscreens.
+// Ideally this could eventually be an inherited class from BUS_SPI,
+// but currently too many internal objects are set private.
+
+#include "configuration.h"
+#if ARCH_PORTDUINO
+#include "lgfx/v1/misc/pixelcopy.hpp"
+#include "main.h"
+#include "mesh_bus_spi.h"
+#include
+#include
+
+namespace lgfx
+{
+inline namespace v1
+{
+//----------------------------------------------------------------------------
+
+void Mesh_Bus_SPI::config(const config_t &config)
+{
+ _cfg = config;
+
+ if (_cfg.pin_dc >= 0) {
+ pinMode(_cfg.pin_dc, pin_mode_t::output);
+ gpio_hi(_cfg.pin_dc);
+ }
+}
+
+bool Mesh_Bus_SPI::init(void)
+{
+ dc_h();
+ pinMode(_cfg.pin_dc, pin_mode_t::output);
+ if (SPIName != "")
+ PrivateSPI->begin(SPIName.c_str());
+ else
+ PrivateSPI->begin();
+ return true;
+}
+
+void Mesh_Bus_SPI::release(void)
+{
+ PrivateSPI->end();
+}
+
+void Mesh_Bus_SPI::spi_device(HardwareSPI *newSPI, std::string newSPIName)
+{
+ PrivateSPI = newSPI;
+ SPIName = newSPIName;
+}
+void Mesh_Bus_SPI::beginTransaction(void)
+{
+ dc_h();
+ SPISettings setting(_cfg.freq_write, MSBFIRST, _cfg.spi_mode);
+ PrivateSPI->beginTransaction(setting);
+}
+
+void Mesh_Bus_SPI::endTransaction(void)
+{
+ PrivateSPI->endTransaction();
+ dc_h();
+}
+
+void Mesh_Bus_SPI::beginRead(void)
+{
+ PrivateSPI->endTransaction();
+ // SPISettings setting(_cfg.freq_read, BitOrder::MSBFIRST, _cfg.spi_mode, false);
+ SPISettings setting(_cfg.freq_read, MSBFIRST, _cfg.spi_mode);
+ PrivateSPI->beginTransaction(setting);
+}
+
+void Mesh_Bus_SPI::endRead(void)
+{
+ PrivateSPI->endTransaction();
+ beginTransaction();
+}
+
+void Mesh_Bus_SPI::wait(void) {}
+
+bool Mesh_Bus_SPI::busy(void) const
+{
+ return false;
+}
+
+bool Mesh_Bus_SPI::writeCommand(uint32_t data, uint_fast8_t bit_length)
+{
+ dc_l();
+ PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3);
+ dc_h();
+ return true;
+}
+
+void Mesh_Bus_SPI::writeData(uint32_t data, uint_fast8_t bit_length)
+{
+ PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3);
+}
+
+void Mesh_Bus_SPI::writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t length)
+{
+ const uint8_t dst_bytes = bit_length >> 3;
+ uint32_t limit = (dst_bytes == 3) ? 12 : 16;
+ auto buf = _flip_buffer.getBuffer(512);
+ size_t fillpos = 0;
+ reinterpret_cast(buf)[0] = data;
+ fillpos += dst_bytes;
+ uint32_t len;
+ do {
+ len = ((length - 1) % limit) + 1;
+ if (limit <= 64)
+ limit <<= 1;
+
+ while (fillpos < len * dst_bytes) {
+ memcpy(&buf[fillpos], buf, fillpos);
+ fillpos += fillpos;
+ }
+
+ PrivateSPI->transfer(buf, len * dst_bytes);
+ } while (length -= len);
+}
+
+void Mesh_Bus_SPI::writePixels(pixelcopy_t *param, uint32_t length)
+{
+ const uint8_t dst_bytes = param->dst_bits >> 3;
+ uint32_t limit = (dst_bytes == 3) ? 12 : 16;
+ uint32_t len;
+ do {
+ len = ((length - 1) % limit) + 1;
+ if (limit <= 32)
+ limit <<= 1;
+ auto buf = _flip_buffer.getBuffer(len * dst_bytes);
+ param->fp_copy(buf, 0, len, param);
+ PrivateSPI->transfer(buf, len * dst_bytes);
+ } while (length -= len);
+}
+
+void Mesh_Bus_SPI::writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma)
+{
+ if (dc)
+ dc_h();
+ else
+ dc_l();
+ PrivateSPI->transfer(const_cast(data), length);
+ if (!dc)
+ dc_h();
+}
+
+uint32_t Mesh_Bus_SPI::readData(uint_fast8_t bit_length)
+{
+ uint32_t res = 0;
+ bit_length >>= 3;
+ if (!bit_length)
+ return res;
+ int idx = 0;
+ do {
+ res |= PrivateSPI->transfer(0) << idx;
+ idx += 8;
+ } while (--bit_length);
+ return res;
+}
+
+bool Mesh_Bus_SPI::readBytes(uint8_t *dst, uint32_t length, bool use_dma)
+{
+ do {
+ dst[0] = PrivateSPI->transfer(0);
+ ++dst;
+ } while (--length);
+ return true;
+}
+
+void Mesh_Bus_SPI::readPixels(void *dst, pixelcopy_t *param, uint32_t length)
+{
+ uint32_t bytes = param->src_bits >> 3;
+ uint32_t dstindex = 0;
+ uint32_t len = 4;
+ uint8_t buf[24];
+ param->src_data = buf;
+ do {
+ if (len > length)
+ len = length;
+ readBytes((uint8_t *)buf, len * bytes, true);
+ param->src_x = 0;
+ dstindex = param->fp_copy(dst, dstindex, dstindex + len, param);
+ length -= len;
+ } while (length);
+}
+
+} // namespace v1
+} // namespace lgfx
+#endif
\ No newline at end of file
diff --git a/src/graphics/mesh_bus_spi.h b/src/graphics/mesh_bus_spi.h
new file mode 100644
index 000000000..903f7ad9d
--- /dev/null
+++ b/src/graphics/mesh_bus_spi.h
@@ -0,0 +1,100 @@
+#if ARCH_PORTDUINO
+/*----------------------------------------------------------------------------/
+ Lovyan GFX - Graphics library for embedded devices.
+
+Original Source:
+ https://github.com/lovyan03/LovyanGFX/
+
+Licence:
+ [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
+
+Author:
+ [lovyan03](https://twitter.com/lovyan03)
+
+Contributors:
+ [ciniml](https://github.com/ciniml)
+ [mongonta0716](https://github.com/mongonta0716)
+ [tobozo](https://github.com/tobozo)
+/----------------------------------------------------------------------------*/
+#pragma once
+
+#include
+
+#include "lgfx/v1/Bus.hpp"
+#include "lgfx/v1/platforms/common.hpp"
+
+namespace lgfx
+{
+inline namespace v1
+{
+//----------------------------------------------------------------------------
+
+class Mesh_Bus_SPI : public IBus
+{
+ public:
+ struct config_t {
+ uint32_t freq_write = 16000000;
+ uint32_t freq_read = 8000000;
+ // bool spi_3wire = true;
+ // bool use_lock = true;
+ int16_t pin_sclk = -1;
+ int16_t pin_miso = -1;
+ int16_t pin_mosi = -1;
+ int16_t pin_dc = -1;
+ uint8_t spi_mode = 0;
+ };
+
+ const config_t &config(void) const { return _cfg; }
+
+ void config(const config_t &config);
+
+ bus_type_t busType(void) const override { return bus_type_t::bus_spi; }
+
+ bool init(void) override;
+ void release(void) override;
+ void spi_device(HardwareSPI *newSPI, std::string newSPIName);
+
+ void beginTransaction(void) override;
+ void endTransaction(void) override;
+ void wait(void) override;
+ bool busy(void) const override;
+
+ bool writeCommand(uint32_t data, uint_fast8_t bit_length) override;
+ void writeData(uint32_t data, uint_fast8_t bit_length) override;
+ void writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) override;
+ void writePixels(pixelcopy_t *param, uint32_t length) override;
+ void writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) override;
+
+ void initDMA(void) {}
+ void flush(void) {}
+ void addDMAQueue(const uint8_t *data, uint32_t length) override { writeBytes(data, length, true, true); }
+ void execDMAQueue(void) {}
+ uint8_t *getDMABuffer(uint32_t length) override { return _flip_buffer.getBuffer(length); }
+
+ void beginRead(void) override;
+ void endRead(void) override;
+ uint32_t readData(uint_fast8_t bit_length) override;
+ bool readBytes(uint8_t *dst, uint32_t length, bool use_dma) override;
+ void readPixels(void *dst, pixelcopy_t *param, uint32_t length) override;
+
+ private:
+ HardwareSPI *PrivateSPI;
+ std::string SPIName;
+ __attribute__((always_inline)) inline void dc_h(void) { gpio_hi(_cfg.pin_dc); }
+ __attribute__((always_inline)) inline void dc_l(void) { gpio_lo(_cfg.pin_dc); }
+
+ config_t _cfg;
+ FlipBuffer _flip_buffer;
+ bool _need_wait;
+ uint32_t _mask_reg_dc;
+ uint32_t _last_apb_freq = -1;
+ uint32_t _clkdiv_write;
+ uint32_t _clkdiv_read;
+ volatile uint32_t *_gpio_reg_dc_h;
+ volatile uint32_t *_gpio_reg_dc_l;
+};
+
+//----------------------------------------------------------------------------
+} // namespace v1
+} // namespace lgfx
+#endif
\ No newline at end of file
diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp
index d2a94e94e..1ace2044c 100644
--- a/src/input/LinuxInput.cpp
+++ b/src/input/LinuxInput.cpp
@@ -24,7 +24,8 @@ LinuxInput::LinuxInput(const char *name) : concurrency::OSThread(name)
void LinuxInput::deInit()
{
- close(fd);
+ if (fd >= 0)
+ close(fd);
}
int32_t LinuxInput::runOnce()
diff --git a/src/input/LinuxInput.h b/src/input/LinuxInput.h
index aa1e8e340..43d08493c 100644
--- a/src/input/LinuxInput.h
+++ b/src/input/LinuxInput.h
@@ -38,7 +38,7 @@ class LinuxInput : public Observable, public concurrency::OS
int queue_progress = 0;
struct epoll_event events[MAX_EVENTS];
- int fd;
+ int fd = -1;
int ret;
uint8_t report[8];
int epollfd;
diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp
index 3e4ed4163..c863ead69 100644
--- a/src/input/TouchScreenImpl1.cpp
+++ b/src/input/TouchScreenImpl1.cpp
@@ -4,7 +4,7 @@
#include "configuration.h"
#include "modules/ExternalNotificationModule.h"
-#ifdef ARCH_PORTDUINO
+#if ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp
index ecc3b944a..b1f83c56b 100644
--- a/src/input/UpDownInterruptBase.cpp
+++ b/src/input/UpDownInterruptBase.cpp
@@ -1,7 +1,7 @@
#include "UpDownInterruptBase.h"
#include "configuration.h"
-UpDownInterruptBase::UpDownInterruptBase(const char *name)
+UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThread(name)
{
this->_originName = name;
}
@@ -24,31 +24,48 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
attachInterrupt(this->_pinUp, onIntUp, RISING);
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)\n", this->_pinUp, this->_pinDown, pinPress);
+
+ this->setInterval(100);
+}
+
+int32_t UpDownInterruptBase::runOnce()
+{
+ InputEvent e;
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
+
+ if (this->action == UPDOWN_ACTION_PRESSED) {
+ LOG_DEBUG("GPIO event Press\n");
+ e.inputEvent = this->_eventPressed;
+ } else if (this->action == UPDOWN_ACTION_UP) {
+ LOG_DEBUG("GPIO event Up\n");
+ e.inputEvent = this->_eventUp;
+ } else if (this->action == UPDOWN_ACTION_DOWN) {
+ LOG_DEBUG("GPIO event Down\n");
+ e.inputEvent = this->_eventDown;
+ }
+
+ if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) {
+ e.source = this->_originName;
+ e.kbchar = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
+ this->notifyObservers(&e);
+ }
+
+ this->action = UPDOWN_ACTION_NONE;
+
+ return 100;
}
void UpDownInterruptBase::intPressHandler()
{
- InputEvent e;
- e.source = this->_originName;
- LOG_DEBUG("GPIO event Press\n");
- e.inputEvent = this->_eventPressed;
- this->notifyObservers(&e);
+ this->action = UPDOWN_ACTION_PRESSED;
}
void UpDownInterruptBase::intDownHandler()
{
- InputEvent e;
- e.source = this->_originName;
- LOG_DEBUG("GPIO event Down\n");
- e.inputEvent = this->_eventDown;
- this->notifyObservers(&e);
+ this->action = UPDOWN_ACTION_DOWN;
}
void UpDownInterruptBase::intUpHandler()
{
- InputEvent e;
- e.source = this->_originName;
- LOG_DEBUG("GPIO event Up\n");
- e.inputEvent = this->_eventUp;
- this->notifyObservers(&e);
+ this->action = UPDOWN_ACTION_UP;
}
diff --git a/src/input/UpDownInterruptBase.h b/src/input/UpDownInterruptBase.h
index afa64d28d..7060a0d80 100644
--- a/src/input/UpDownInterruptBase.h
+++ b/src/input/UpDownInterruptBase.h
@@ -3,7 +3,7 @@
#include "InputBroker.h"
#include "mesh/NodeDB.h"
-class UpDownInterruptBase : public Observable
+class UpDownInterruptBase : public Observable, public concurrency::OSThread
{
public:
explicit UpDownInterruptBase(const char *name);
@@ -13,6 +13,13 @@ class UpDownInterruptBase : public Observable
void intDownHandler();
void intUpHandler();
+ int32_t runOnce() override;
+
+ protected:
+ enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN };
+
+ volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE;
+
private:
uint8_t _pinDown = 0;
uint8_t _pinUp = 0;
diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp
index 1dba4e34d..74a6c718d 100644
--- a/src/input/kbI2cBase.cpp
+++ b/src/input/kbI2cBase.cpp
@@ -216,6 +216,16 @@ int32_t KbI2cBase::runOnce()
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
e.kbchar = 0xb7;
break;
+ case 0x90: // fn+r
+ case 0x91: // fn+t
+ case 0x9b: // fn+s
+ case 0xac: // fn+m
+ case 0x9e: // fn+g
+ case 0xaf: // fn+space
+ // just pass those unmodified
+ e.inputEvent = ANYKEY;
+ e.kbchar = c;
+ break;
case 0x0d: // Enter
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
break;
diff --git a/src/main.cpp b/src/main.cpp
index fbfb983d2..f40fd0789 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,4 +1,7 @@
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
+#endif
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
@@ -6,7 +9,7 @@
#include "ReliableRouter.h"
#include "airtime.h"
#include "buzz.h"
-#include "configuration.h"
+
#include "error.h"
#include "power.h"
// #include "debug.h"
@@ -33,10 +36,14 @@
// #include
#ifdef ARCH_ESP32
+#if !MESHTASTIC_EXCLUDE_WEBSERVER
#include "mesh/http/WebServer.h"
+#endif
+#if !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "nimble/NimbleBluetooth.h"
NimbleBluetooth *nimbleBluetooth;
#endif
+#endif
#ifdef ARCH_NRF52
#include "NRF52Bluetooth.h"
@@ -52,22 +59,29 @@ NRF52Bluetooth *nrf52Bluetooth;
#include "mesh/api/ethServerAPI.h"
#include "mesh/eth/ethClient.h"
#endif
+
+#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
+#endif
#include "LLCC68Interface.h"
#include "RF95Interface.h"
#include "SX1262Interface.h"
#include "SX1268Interface.h"
#include "SX1280Interface.h"
+#include "detect/LoRaRadioType.h"
+
#ifdef ARCH_STM32WL
#include "STM32WLE5JCInterface.h"
#endif
+
#if !HAS_RADIO && defined(ARCH_PORTDUINO)
#include "platform/portduino/SimRadio.h"
#endif
#ifdef ARCH_PORTDUINO
#include "linux/LinuxHardwareI2C.h"
+#include "mesh/raspihttp/PiWebServer.h"
#include "platform/portduino/PortduinoGlue.h"
#include
#include
@@ -77,6 +91,7 @@ NRF52Bluetooth *nrf52Bluetooth;
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
#include "ButtonThread.h"
#endif
+
#include "PowerFSMThread.h"
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
@@ -128,6 +143,9 @@ ATECCX08A atecc;
Adafruit_DRV2605 drv;
#endif
+// Global LoRa radio type
+LoRaRadioType radioType = NO_RADIO;
+
bool isVibrating = false;
bool eink_found = true;
@@ -161,6 +179,11 @@ const char *getDeviceName()
static int32_t ledBlinker()
{
+ // Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
+ // config.device.led_heartbeat_disabled is changed
+ if (config.device.led_heartbeat_disabled)
+ return 1000;
+
static bool ledOn;
ledOn ^= 1;
@@ -174,9 +197,6 @@ uint32_t timeLastPowered = 0;
static Periodic *ledPeriodic;
static OSThread *powerFSMthread;
-#if HAS_BUTTON || defined(ARCH_PORTDUINO)
-static OSThread *buttonThread;
-#endif
static OSThread *accelerometerThread;
static OSThread *ambientLightingThread;
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
@@ -218,10 +238,16 @@ void setup()
initDeepSleep();
- // Testing this fix für erratic T-Echo boot behaviour
-#if defined(TTGO_T_ECHO) && defined(PIN_EINK_PWR_ON)
- pinMode(PIN_EINK_PWR_ON, OUTPUT);
- digitalWrite(PIN_EINK_PWR_ON, HIGH);
+ // power on peripherals
+#if defined(TTGO_T_ECHO) && defined(PIN_POWER_EN)
+ pinMode(PIN_POWER_EN, OUTPUT);
+ digitalWrite(PIN_POWER_EN, HIGH);
+ // digitalWrite(PIN_POWER_EN1, INPUT);
+#endif
+
+#if defined(LORA_TCXO_GPIO)
+ pinMode(LORA_TCXO_GPIO, OUTPUT);
+ digitalWrite(LORA_TCXO_GPIO, HIGH);
#endif
#if defined(VEXT_ENABLE_V03)
@@ -340,7 +366,7 @@ void setup()
pinMode(PIN_3V3_EN, OUTPUT);
digitalWrite(PIN_3V3_EN, HIGH);
#endif
-#ifndef USE_EINK
+#ifdef AQ_SET_PIN
// RAK-12039 set pin for Air quality sensor
pinMode(AQ_SET_PIN, OUTPUT);
digitalWrite(AQ_SET_PIN, HIGH);
@@ -366,7 +392,7 @@ void setup()
// We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to
// accessories
auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire());
-#ifdef HAS_WIRE
+#if HAS_WIRE
LOG_INFO("Scanning for i2c devices...\n");
#endif
@@ -499,6 +525,7 @@ void setup()
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221)
@@ -546,7 +573,7 @@ void setup()
// We do this as early as possible because this loads preferences from flash
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
- nodeDB.init();
+ nodeDB = new NodeDB;
// If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
@@ -608,39 +635,51 @@ void setup()
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
SPI1.begin(false);
-#else // HW_SPI1_DEVICE
+#else // HW_SPI1_DEVICE
SPI.setSCK(LORA_SCK);
SPI.setTX(LORA_MOSI);
SPI.setRX(LORA_MISO);
SPI.begin(false);
-#endif // HW_SPI1_DEVICE
-#elif ARCH_PORTDUINO
- SPI.begin(settingsStrings[spidev].c_str());
+#endif // HW_SPI1_DEVICE
#elif !defined(ARCH_ESP32) // ARCH_RP2040
SPI.begin();
#else
// ESP32
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
- LOG_WARN("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
+ LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
SPI.setFrequency(4000000);
#endif
// Initialize the screen first so we can show the logo while we start up everything else.
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
+ // setup TZ prior to time actions.
+ if (*config.device.tzdef) {
+ setenv("TZ", config.device.tzdef, 1);
+ } else {
+ setenv("TZ", "GMT0", 1);
+ }
+ tzset();
+ LOG_DEBUG("Set Timezone to %s\n", getenv("TZ"));
+
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
+#if !MESHTASTIC_EXCLUDE_GPS
// If we're taking on the repeater role, ignore GPS
- if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
- config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
- gps = GPS::createGps();
+ if (HAS_GPS) {
+ if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
+ config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
+ gps = GPS::createGps();
+ if (gps) {
+ gpsStatus->observe(&gps->newStatus);
+ } else {
+ LOG_DEBUG("Running without GPS.\n");
+ }
+ }
}
- if (gps) {
- gpsStatus->observe(&gps->newStatus);
- } else {
- LOG_DEBUG("Running without GPS.\n");
- }
- nodeStatus->observe(&nodeDB.newStatus);
+#endif
+
+ nodeStatus->observe(&nodeDB->newStatus);
#ifdef HAS_I2S
LOG_DEBUG("Starting audio thread\n");
@@ -660,7 +699,7 @@ void setup()
// Don't call screen setup until after nodedb is setup (because we need
// the current region name)
-#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS)
+#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS)
screen->setup();
#elif defined(ARCH_PORTDUINO)
if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) {
@@ -679,11 +718,16 @@ void setup()
digitalWrite(SX126X_ANT_SW, 1);
#endif
+#ifdef PIN_PWR_DELAY_MS
+ // This may be required to give the peripherals time to power up.
+ delay(PIN_PWR_DELAY_MS);
+#endif
+
#ifdef ARCH_PORTDUINO
if (settingsMap[use_sx1262]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
+ LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -697,7 +741,7 @@ void setup()
} else if (settingsMap[use_rf95]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
+ LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -712,7 +756,7 @@ void setup()
} else if (settingsMap[use_sx1280]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s\n", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
+ LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -742,6 +786,7 @@ void setup()
rIf = NULL;
} else {
LOG_INFO("STM32WL Radio init succeeded, using STM32WL radio\n");
+ radioType = STM32WLx_RADIO;
}
}
#endif
@@ -755,6 +800,7 @@ void setup()
rIf = NULL;
} else {
LOG_INFO("Using SIMULATED radio!\n");
+ radioType = SIM_RADIO;
}
}
#endif
@@ -768,6 +814,7 @@ void setup()
rIf = NULL;
} else {
LOG_INFO("RF95 Radio init succeeded, using RF95 radio\n");
+ radioType = RF95_RADIO;
}
}
#endif
@@ -781,6 +828,7 @@ void setup()
rIf = NULL;
} else {
LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio\n");
+ radioType = SX1262_RADIO;
}
}
#endif
@@ -794,6 +842,7 @@ void setup()
rIf = NULL;
} else {
LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n");
+ radioType = SX1268_RADIO;
}
}
#endif
@@ -807,6 +856,7 @@ void setup()
rIf = NULL;
} else {
LOG_INFO("LLCC68 Radio init succeeded, using LLCC68 radio\n");
+ radioType = LLCC68_RADIO;
}
}
#endif
@@ -820,6 +870,7 @@ void setup()
rIf = NULL;
} else {
LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n");
+ radioType = SX1280_RADIO;
}
}
#endif
@@ -829,7 +880,7 @@ void setup()
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) {
LOG_WARN("Radio chip does not support 2.4GHz LoRa. Reverting to unset.\n");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
- nodeDB.saveToDisk(SEGMENT_CONFIG);
+ nodeDB->saveToDisk(SEGMENT_CONFIG);
if (!rIf->reconfigure()) {
LOG_WARN("Reconfigure failed, rebooting\n");
screen->startRebootScreen();
@@ -837,9 +888,12 @@ void setup()
}
}
+#if !MESHTASTIC_EXCLUDE_MQTT
mqttInit();
+#endif
#ifndef ARCH_PORTDUINO
+
// Initialize Wifi
#if HAS_WIFI
initWifi();
@@ -851,12 +905,17 @@ void setup()
#endif
#endif
-#ifdef ARCH_ESP32
+#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
// Start web server thread.
webServerThread = new WebServerThread();
#endif
#ifdef ARCH_PORTDUINO
+#if __has_include()
+ if (settingsMap[webserverport] != -1) {
+ piwebServerThread = new PiWebServerThread();
+ }
+#endif
initApiServer(TCPPort);
#endif
@@ -947,4 +1006,4 @@ void loop()
mainDelay.delay(delayMsec);
}
// if (didWake) LOG_DEBUG("wake!\n");
-}
\ No newline at end of file
+}
diff --git a/src/main.h b/src/main.h
index 5af0b4082..bb812b7b6 100644
--- a/src/main.h
+++ b/src/main.h
@@ -22,6 +22,11 @@ extern NimbleBluetooth *nimbleBluetooth;
extern NRF52Bluetooth *nrf52Bluetooth;
#endif
+#if ARCH_PORTDUINO
+extern HardwareSPI *DisplaySPI;
+extern HardwareSPI *LoraSPI;
+
+#endif
extern ScanI2C::DeviceAddress screen_found;
extern ScanI2C::DeviceAddress cardkb_found;
extern uint8_t kb_model;
@@ -54,6 +59,7 @@ extern int TCPPort; // set by Portduino
// Global Screen singleton.
extern graphics::Screen *screen;
+
// extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
// extern meshtastic::PowerStatus *powerStatus;
diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index fe1041d3d..079af4eca 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -2,10 +2,15 @@
#include "CryptoEngine.h"
#include "DisplayFormatters.h"
#include "NodeDB.h"
+#include "RadioInterface.h"
#include "configuration.h"
#include
+#if !MESHTASTIC_EXCLUDE_MQTT
+#include "mqtt/MQTT.h"
+#endif
+
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01};
@@ -15,7 +20,9 @@ Channels channels;
const char *Channels::adminChannel = "admin";
const char *Channels::gpioChannel = "gpio";
const char *Channels::serialChannel = "serial";
+#if !MESHTASTIC_EXCLUDE_MQTT
const char *Channels::mqttChannel = "mqtt";
+#endif
uint8_t xorHash(const uint8_t *p, size_t len)
{
@@ -88,6 +95,7 @@ void Channels::initDefaultChannel(ChannelIndex chIndex)
channelSettings.psk.size = 1;
strncpy(channelSettings.name, "", sizeof(channelSettings.name));
channelSettings.module_settings.position_precision = 32; // default to sending location on the primary channel
+ channelSettings.has_module_settings = true;
ch.has_settings = true;
ch.role = meshtastic_Channel_Role_PRIMARY;
@@ -191,6 +199,12 @@ void Channels::onConfigChanged()
if (ch.role == meshtastic_Channel_Role_PRIMARY)
primaryIndex = i;
}
+#if !MESHTASTIC_EXCLUDE_MQTT
+ if (channels.anyMqttEnabled() && mqtt && !mqtt->isEnabled()) {
+ LOG_DEBUG("MQTT is enabled on at least one channel, so set MQTT thread to run immediately\n");
+ mqtt->start();
+ }
+#endif
}
meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex)
@@ -235,6 +249,16 @@ void Channels::setChannel(const meshtastic_Channel &c)
old = c; // slam in the new settings/role
}
+bool Channels::anyMqttEnabled()
+{
+ for (int i = 0; i < getNumChannels(); i++)
+ if (channelFile.channels[i].role != meshtastic_Channel_Role_DISABLED && channelFile.channels[i].has_settings &&
+ (channelFile.channels[i].settings.downlink_enabled || channelFile.channels[i].settings.uplink_enabled))
+ return true;
+
+ return false;
+}
+
const char *Channels::getName(size_t chIndex)
{
// Convert the short "" representation for Default into a usable string
@@ -253,38 +277,23 @@ const char *Channels::getName(size_t chIndex)
return channelName;
}
-/**
-* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
-* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
-their nodes
-* aren't talking to each other.
-*
-* This string is of the form "#name-X".
-*
-* Where X is either:
-* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together,
-*
-* This function will also need to be implemented in GUI apps that talk to the radio.
-*
-* https://github.com/meshtastic/firmware/issues/269
-*/
-const char *Channels::getPrimaryName()
+bool Channels::hasDefaultChannel()
{
- static char buf[32];
-
- char suffix;
- // auto channelSettings = getPrimary();
- // if (channelSettings.psk.size != 1) {
- // We have a standard PSK, so generate a letter based hash.
- uint8_t code = getHash(primaryIndex);
-
- suffix = 'A' + (code % 26);
- /* } else {
- suffix = '0' + channelSettings.psk.bytes[0];
- } */
-
- snprintf(buf, sizeof(buf), "#%s-%c", getName(primaryIndex), suffix);
- return buf;
+ // If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel
+ if (!config.lora.use_preset || !RadioInterface::uses_default_frequency_slot || config.lora.override_frequency)
+ return false;
+ // Check if any of the channels are using the default name and PSK
+ for (size_t i = 0; i < getNumChannels(); i++) {
+ const auto &ch = getByIndex(i);
+ if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) {
+ const char *name = getName(i);
+ const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
+ // Check if the name is the default derived from the modem preset
+ if (strcmp(name, presetName) == 0)
+ return true;
+ }
+ }
+ return false;
}
/** Given a channel hash setup crypto for decoding that channel (or the primary channel if that channel is unsecured)
diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h
index 87a72e07b..952445a1d 100644
--- a/src/mesh/Channels.h
+++ b/src/mesh/Channels.h
@@ -61,25 +61,6 @@ class Channels
ChannelIndex getNumChannels() { return channelFile.channels_count; }
- /**
- * Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different
- PSKs.
- * The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why
- they their nodes
- * aren't talking to each other.
- *
- * This string is of the form "#name-X".
- *
- * Where X is either:
- * (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together,
- * OR (for the standard minimially secure PSKs) a number from 0 to 9.
- *
- * This function will also need to be implemented in GUI apps that talk to the radio.
- *
- * https://github.com/meshtastic/firmware/issues/269
- */
- const char *getPrimaryName();
-
/// Called by NodeDB on initial boot when the radio config settings are unset. Set a default single channel config.
void initDefaults();
@@ -102,6 +83,12 @@ class Channels
*/
int16_t setActiveByIndex(ChannelIndex channelIndex);
+ // Returns true if we can be reached via a channel with the default settings given a region and modem preset
+ bool hasDefaultChannel();
+
+ // Returns true if any of our channels have enabled MQTT uplink or downlink
+ bool anyMqttEnabled();
+
private:
/** Given a channel index, change to use the crypto key specified by that index
*
diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp
new file mode 100644
index 000000000..db058c5b0
--- /dev/null
+++ b/src/mesh/Default.cpp
@@ -0,0 +1,23 @@
+#include "Default.h"
+
+uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval)
+{
+ if (configuredInterval > 0)
+ return configuredInterval * 1000;
+ return default_broadcast_interval_secs * 1000;
+}
+
+uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval)
+{
+ if (configuredInterval > 0)
+ return configuredInterval * 1000;
+ return defaultInterval * 1000;
+}
+
+uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue)
+{
+ if (configured > 0)
+ return configured;
+
+ return defaultValue;
+}
\ No newline at end of file
diff --git a/src/mesh/Default.h b/src/mesh/Default.h
new file mode 100644
index 000000000..95723744b
--- /dev/null
+++ b/src/mesh/Default.h
@@ -0,0 +1,31 @@
+#pragma once
+#include
+#include
+#define ONE_DAY 24 * 60 * 60
+#define ONE_MINUTE_MS 60 * 1000
+
+#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60)
+#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60)
+#define default_wait_bluetooth_secs IF_ROUTER(1, 60)
+#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep
+#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60)
+#define default_min_wake_secs 10
+#define default_screen_on_secs IF_ROUTER(1, 60 * 10)
+#define default_node_info_broadcast_secs 3 * 60 * 60
+#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
+
+#define default_mqtt_address "mqtt.meshtastic.org"
+#define default_mqtt_username "meshdev"
+#define default_mqtt_password "large4cats"
+#define default_mqtt_root "msh"
+
+#define IF_ROUTER(routerVal, normalVal) \
+ ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal))
+
+class Default
+{
+ public:
+ static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
+ static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
+ static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
+};
\ No newline at end of file
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index db3f3f35e..4cfe982d8 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -49,15 +49,6 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
tosend->hop_limit--; // bump down the hop count
- if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
- // If it is a traceRoute request, update the route that it went via me
- if (traceRouteModule && traceRouteModule->wantPacket(p))
- traceRouteModule->updateRoute(tosend);
- // If it is a neighborInfo packet, update last_sent_by_id
- if (neighborInfoModule && neighborInfoModule->wantPacket(p))
- neighborInfoModule->updateLastSentById(tosend);
- }
-
LOG_INFO("Rebroadcasting received floodmsg to neighbors\n");
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index 309035cb3..a3adfe70c 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -2,8 +2,6 @@
#include "PacketHistory.h"
#include "Router.h"
-#include "modules/NeighborInfoModule.h"
-#include "modules/TraceRouteModule.h"
/**
* This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense)
diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp
index 9c6ca78ee..2ef46e4db 100644
--- a/src/mesh/MeshModule.cpp
+++ b/src/mesh/MeshModule.cpp
@@ -12,7 +12,7 @@ const meshtastic_MeshPacket *MeshModule::currentRequest;
/**
* If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow
- * the RoutingPlugin to avoid sending redundant acks
+ * the RoutingModule to avoid sending redundant acks
*/
meshtastic_MeshPacket *MeshModule::currentReply;
@@ -32,14 +32,15 @@ MeshModule::~MeshModule()
assert(0); // FIXME - remove from list of modules once someone needs this feature
}
-meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex)
+meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
+ uint8_t hopStart, uint8_t hopLimit)
{
meshtastic_Routing c = meshtastic_Routing_init_default;
c.error_reason = err;
c.which_variant = meshtastic_Routing_error_reason_tag;
- // Now that we have moded sendAckNak up one level into the class hierarchy we can no longer assume we are a RoutingPlugin
+ // Now that we have moded sendAckNak up one level into the class hierarchy we can no longer assume we are a RoutingModule
// So we manually call pb_encode_to_bytes and specify routing port number
// auto p = allocDataProtobuf(c);
meshtastic_MeshPacket *p = router->allocForSending();
@@ -49,11 +50,12 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod
p->priority = meshtastic_MeshPacket_Priority_ACK;
- p->hop_limit = config.lora.hop_limit; // Flood ACK back to original sender
+ p->hop_limit = routingModule->getHopLimitForResponse(hopStart, hopLimit); // Flood ACK back to original sender
p->to = to;
p->decoded.request_id = idFrom;
p->channel = chIndex;
- LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id);
+ if (err != meshtastic_Routing_Error_NONE)
+ LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id);
return p;
}
@@ -67,7 +69,7 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e
return r;
}
-void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src)
+void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
{
// LOG_DEBUG("In call modules\n");
bool moduleFound = false;
@@ -80,7 +82,7 @@ void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src)
bool ignoreRequest = false; // No module asked to ignore the request yet
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
- auto ourNodeNum = nodeDB.getNodeNum();
+ auto ourNodeNum = nodeDB->getNodeNum();
bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum;
for (auto i = modules->begin(); i != modules->end(); ++i) {
@@ -176,7 +178,8 @@ void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src)
// SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded)
// but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs
// bad.
- routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel);
+ routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel, mp.hop_start,
+ mp.hop_limit);
}
}
@@ -217,6 +220,7 @@ void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to)
assert(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // Should already be set by now
p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0
p->channel = to.channel; // Use the same channel that the request came in on
+ p->hop_limit = routingModule->getHopLimitForResponse(to.hop_start, to.hop_limit);
// No need for an ack if we are just delivering locally (it just generates an ignored ack)
p->want_ack = (to.from != 0) ? to.want_ack : false;
@@ -255,7 +259,7 @@ void MeshModule::observeUIEvents(Observer *observer)
}
}
-AdminMessageHandleResult MeshModule::handleAdminMessageForAllPlugins(const meshtastic_MeshPacket &mp,
+AdminMessageHandleResult MeshModule::handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response)
{
@@ -276,4 +280,4 @@ AdminMessageHandleResult MeshModule::handleAdminMessageForAllPlugins(const mesht
}
}
return handled;
-}
+}
\ No newline at end of file
diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h
index ebe3af1a0..2e2af33e0 100644
--- a/src/mesh/MeshModule.h
+++ b/src/mesh/MeshModule.h
@@ -64,11 +64,11 @@ class MeshModule
/** For use only by MeshService
*/
- static void callPlugins(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO);
+ static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO);
static std::vector GetMeshModulesWithUIFrames();
static void observeUIEvents(Observer *observer);
- static AdminMessageHandleResult handleAdminMessageForAllPlugins(const meshtastic_MeshPacket &mp,
+ static AdminMessageHandleResult handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response);
#if HAS_SCREEN
@@ -153,7 +153,8 @@ class MeshModule
virtual bool wantUIFrame() { return false; }
virtual Observable *getUIFrameObservable() { return NULL; }
- meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
+ meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
+ uint8_t hopStart = 0, uint8_t hopLimit = 0);
/// Send an error response for the specified packet.
meshtastic_MeshPacket *allocErrorResponse(meshtastic_Routing_Error err, const meshtastic_MeshPacket *p);
@@ -194,4 +195,4 @@ class MeshModule
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
*/
-void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
+void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
\ No newline at end of file
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index db0dd88ec..2c1969e30 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -1,10 +1,11 @@
#include "configuration.h"
-#include
-#include
+
+#if !MESHTASTIC_EXCLUDE_GPS
+#include "GPS.h"
+#endif
#include "../concurrency/Periodic.h"
#include "BluetoothCommon.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here
-#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
@@ -15,8 +16,10 @@
#include "modules/NodeInfoModule.h"
#include "modules/PositionModule.h"
#include "power.h"
+#include
+#include
-#ifdef ARCH_ESP32
+#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "nimble/NimbleBluetooth.h"
#endif
@@ -72,22 +75,23 @@ void MeshService::init()
{
// moved much earlier in boot (called from setup())
// nodeDB.init();
-
+#if HAS_GPS
if (gps)
gpsObserver.observe(&gps->newStatus);
+#endif
}
int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
{
powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
- nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
+ nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
LOG_DEBUG(
"Received telemetry response. Skip sending our NodeInfo because this potentially a Repeater which will ignore our "
"request for its NodeInfo.\n");
- } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB.getMeshNode(mp->from)->has_user &&
+ } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
nodeInfoModule) {
LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel);
nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel);
@@ -120,10 +124,10 @@ bool MeshService::reloadConfig(int saveWhat)
// If we can successfully set this radio to these settings, save them to disk
// This will also update the region as needed
- bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
+ bool didReset = nodeDB->resetRadioConfig(); // Don't let the phone send us fatally bad settings
configChanged.notifyObservers(NULL); // This will cause radio hardware to change freqs etc
- nodeDB.saveToDisk(saveWhat);
+ nodeDB->saveToDisk(saveWhat);
return didReset;
}
@@ -133,7 +137,7 @@ void MeshService::reloadOwner(bool shouldSave)
{
// LOG_DEBUG("reloadOwner()\n");
// update our local data directly
- nodeDB.updateUser(nodeDB.getNodeNum(), owner);
+ nodeDB->updateUser(nodeDB->getNodeNum(), owner);
assert(nodeInfoModule);
// update everyone else and save to disk
if (nodeInfoModule && shouldSave) {
@@ -192,7 +196,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
LOG_WARN("phone tried to pick a nodenum, we don't allow that.\n");
p.from = 0;
} else {
- // p.from = nodeDB.getNodeNum();
+ // p.from = nodeDB->getNodeNum();
}
if (p.id == 0)
@@ -217,7 +221,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
/** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */
bool MeshService::cancelSending(PacketId id)
{
- return router->cancelSending(nodeDB.getNodeNum(), id);
+ return router->cancelSending(nodeDB->getNodeNum(), id);
}
ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id)
@@ -245,7 +249,7 @@ ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs,
void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPhone)
{
uint32_t mesh_packet_id = p->id;
- nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
+ nodeDB->updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
ErrorCode res = router->sendLocal(p, src);
@@ -265,16 +269,18 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
{
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
assert(node);
if (hasValidPosition(node)) {
+#if HAS_GPS
if (positionModule) {
LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel);
positionModule->sendOurPosition(dest, wantReplies, node->channel);
}
} else {
+#endif
if (nodeInfoModule) {
LOG_INFO("Sending nodeinfo ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel);
nodeInfoModule->sendOurNodeInfo(dest, wantReplies, node->channel);
@@ -320,7 +326,7 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage
meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
{
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
assert(node);
// We might not have a position yet for our local node, in that case, at least try to send the time
@@ -344,6 +350,7 @@ meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
return node;
}
+#if HAS_GPS
int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
{
// Update our local node info with our position (even if we don't decide to update anyone else)
@@ -359,7 +366,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
LOG_DEBUG("onGPSchanged() - lost validLocation\n");
#endif
}
- // Used fixed position if configured regalrdless of GPS lock
+ // Used fixed position if configured regardless of GPS lock
if (config.position.fixed_position) {
LOG_WARN("Using fixed position\n");
pos = TypeConversions::ConvertToPosition(node->position);
@@ -373,11 +380,11 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
pos.longitude_i, pos.altitude);
// Update our current position in the local DB
- nodeDB.updatePosition(nodeDB.getNodeNum(), pos, RX_SRC_LOCAL);
+ nodeDB->updatePosition(nodeDB->getNodeNum(), pos, RX_SRC_LOCAL);
return 0;
}
-
+#endif
bool MeshService::isToPhoneQueueEmpty()
{
return toPhoneQueue.isEmpty();
diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h
index 68287efc2..8d1434030 100644
--- a/src/mesh/MeshService.h
+++ b/src/mesh/MeshService.h
@@ -23,9 +23,10 @@ extern Allocator &mqttClientProxyMessagePool;
*/
class MeshService
{
+#if HAS_GPS
CallbackObserver gpsObserver =
CallbackObserver(this, &MeshService::onGPSChanged);
-
+#endif
/// received packets waiting for the phone to process them
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
/// we never hang because android hasn't been there in a while
@@ -132,10 +133,11 @@ class MeshService
ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id);
private:
+#if HAS_GPS
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
/// returns 0 to allow further processing
int onGPSChanged(const meshtastic::GPSStatus *arg);
-
+#endif
/// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it
/// needs to keep the packet around it makes a copy
int handleFromRadio(const meshtastic_MeshPacket *p);
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index add1b1296..39422b454 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1,10 +1,12 @@
#include "configuration.h"
-
+#if !MESHTASTIC_EXCLUDE_GPS
+#include "GPS.h"
+#endif
#include "../detect/ScanI2C.h"
#include "Channels.h"
#include "CryptoEngine.h"
+#include "Default.h"
#include "FSCommon.h"
-#include "GPS.h"
#include "MeshRadio.h"
#include "NodeDB.h"
#include "PacketHistory.h"
@@ -17,11 +19,16 @@
#include "mesh-pb-constants.h"
#include "modules/NeighborInfoModule.h"
#include
+#include
+#include
#include
#include
+#include
#ifdef ARCH_ESP32
+#if !MESHTASTIC_EXCLUDE_WIFI
#include "mesh/wifi/WiFiAPClient.h"
+#endif
#include "modules/esp32/StoreForwardModule.h"
#include
#include
@@ -36,7 +43,7 @@
#include
#endif
-NodeDB nodeDB;
+NodeDB *nodeDB = nullptr;
// we have plenty of ram so statically alloc this tempbuf (for now)
EXT_RAM_ATTR meshtastic_DeviceState devicestate;
@@ -46,6 +53,26 @@ meshtastic_LocalModuleConfig moduleConfig;
meshtastic_ChannelFile channelFile;
meshtastic_OEMStore oemStore;
+bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
+{
+ if (ostream) {
+ std::vector *vec = (std::vector *)field->pData;
+ for (auto item : *vec) {
+ if (!pb_encode_tag_for_field(ostream, field))
+ return false;
+ pb_encode_submessage(ostream, meshtastic_NodeInfoLite_fields, &item);
+ }
+ }
+ if (istream) {
+ meshtastic_NodeInfoLite node; // this gets good data
+ std::vector *vec = (std::vector *)field->pData;
+
+ if (istream->bytes_left && pb_decode(istream, meshtastic_NodeInfoLite_fields, &node))
+ vec->push_back(node);
+ }
+ return true;
+}
+
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
* might have changed is incremented. Allows others to detect they might now be on a new channel.
*/
@@ -68,7 +95,63 @@ uint32_t error_address = 0;
static uint8_t ourMacAddr[6];
-NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestate.node_db_lite_count) {}
+NodeDB::NodeDB()
+{
+ LOG_INFO("Initializing NodeDB\n");
+ loadFromDisk();
+ cleanupMeshDB();
+
+ uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate));
+ uint32_t configCRC = crc32Buffer(&config, sizeof(config));
+ uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
+
+ int saveWhat = 0;
+
+ // likewise - we always want the app requirements to come from the running appload
+ myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00
+ // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
+ // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
+ pickNewNodeNum();
+
+ // Set our board type so we can share it with others
+ owner.hw_model = HW_VENDOR;
+ // Ensure user (nodeinfo) role is set to whatever we're configured to
+ owner.role = config.device.role;
+
+ // Include our owner in the node db under our nodenum
+ meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
+ info->user = owner;
+ info->has_user = true;
+
+#ifdef ARCH_ESP32
+ Preferences preferences;
+ preferences.begin("meshtastic", false);
+ myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0);
+ preferences.end();
+ LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count);
+#endif
+
+ resetRadioConfig(); // If bogus settings got saved, then fix them
+ // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
+
+ if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate)))
+ saveWhat |= SEGMENT_DEVICESTATE;
+ if (configCRC != crc32Buffer(&config, sizeof(config)))
+ saveWhat |= SEGMENT_CONFIG;
+ if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile)))
+ saveWhat |= SEGMENT_CHANNELS;
+
+ if (!devicestate.node_remote_hardware_pins) {
+ meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default};
+ memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty));
+ }
+
+ if (config.position.gps_enabled) {
+ config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
+ config.position.gps_enabled = 0;
+ }
+ saveToDisk(saveWhat);
+}
/**
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on
@@ -76,7 +159,7 @@ NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestat
*/
NodeNum getFrom(const meshtastic_MeshPacket *p)
{
- return (p->from == 0) ? nodeDB.getNodeNum() : p->from;
+ return (p->from == 0) ? nodeDB->getNodeNum() : p->from;
}
bool NodeDB::resetRadioConfig(bool factory_reset)
@@ -97,22 +180,6 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
channels.onConfigChanged();
- // temp hack for quicker testing
- // devicestate.no_save = true;
- if (devicestate.no_save) {
- LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n");
-
- // Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins
- config.display.screen_on_secs = 10;
- config.power.wait_bluetooth_secs = 10;
- config.position.position_broadcast_secs = 6 * 60;
- config.power.ls_secs = 60;
- config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_TW;
-
- // Enter super deep sleep soon and stay there not very long
- // radioConfig.preferences.sds_secs = 60;
- }
-
// Update the global myRegion
initRegion();
@@ -130,6 +197,9 @@ bool NodeDB::factoryReset()
LOG_INFO("Performing factory reset!\n");
// first, remove the "/prefs" (this removes most prefs)
rmDir("/prefs");
+ if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) {
+ LOG_ERROR("Could not remove rangetest.csv file\n");
+ }
// second, install default state (this will deal with the duplicate mac address issue)
installDefaultDeviceState();
installDefaultConfig();
@@ -163,7 +233,7 @@ void NodeDB::installDefaultConfig()
config.has_position = true;
config.has_power = true;
config.has_network = true;
- config.has_bluetooth = true;
+ config.has_bluetooth = (HAS_BLUETOOTH ? true : false);
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
config.lora.sx126x_rx_boosted_gain = true;
@@ -196,7 +266,7 @@ void NodeDB::installDefaultConfig()
config.position.broadcast_smart_minimum_distance = 100;
config.position.broadcast_smart_minimum_interval_secs = 30;
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER)
- config.device.node_info_broadcast_secs = 3 * 60 * 60;
+ config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
config.device.serial_enabled = true;
resetRadioConfig();
strncpy(config.network.ntp_server, "0.pool.ntp.org", 32);
@@ -241,6 +311,12 @@ void NodeDB::initConfigIntervals()
config.power.wait_bluetooth_secs = default_wait_bluetooth_secs;
config.display.screen_on_secs = default_screen_on_secs;
+
+#if defined(T_WATCH_S3) || defined(T_DECK)
+ config.power.is_power_saving = true;
+ config.display.screen_on_secs = 30;
+ config.power.wait_bluetooth_secs = 30;
+#endif
}
void NodeDB::installDefaultModuleConfig()
@@ -276,6 +352,9 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.alert_message = true;
moduleConfig.external_notification.output_ms = 100;
moduleConfig.external_notification.active = true;
+#endif
+#ifdef TTGO_T_ECHO
+ config.display.wake_on_tap_or_motion = true; // Enable touch button for screen-on / refresh
#endif
moduleConfig.has_canned_message = true;
@@ -365,8 +444,9 @@ void NodeDB::installDefaultChannels()
void NodeDB::resetNodes()
{
- devicestate.node_db_lite_count = 1;
- std::fill(&devicestate.node_db_lite[1], &devicestate.node_db_lite[MAX_NUM_NODES - 1], meshtastic_NodeInfoLite());
+ numMeshNodes = 1;
+ std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite());
+ clearLocalPosition();
saveDeviceStateToDisk();
if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
neighborInfoModule->resetNeighbors();
@@ -375,41 +455,56 @@ void NodeDB::resetNodes()
void NodeDB::removeNodeByNum(uint nodeNum)
{
int newPos = 0, removed = 0;
- for (int i = 0; i < *numMeshNodes; i++) {
- if (meshNodes[i].num != nodeNum)
- meshNodes[newPos++] = meshNodes[i];
+ for (int i = 0; i < numMeshNodes; i++) {
+ if (meshNodes->at(i).num != nodeNum)
+ meshNodes->at(newPos++) = meshNodes->at(i);
else
removed++;
}
- *numMeshNodes -= removed;
+ numMeshNodes -= removed;
+ std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + 1,
+ meshtastic_NodeInfoLite());
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Saving changes...\n", removed);
saveDeviceStateToDisk();
}
+void NodeDB::clearLocalPosition()
+{
+ meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum());
+ node->position.latitude_i = 0;
+ node->position.longitude_i = 0;
+ node->position.altitude = 0;
+ node->position.time = 0;
+ setLocalPosition(meshtastic_Position_init_default);
+}
+
void NodeDB::cleanupMeshDB()
{
int newPos = 0, removed = 0;
- for (int i = 0; i < *numMeshNodes; i++) {
- if (meshNodes[i].has_user)
- meshNodes[newPos++] = meshNodes[i];
+ for (int i = 0; i < numMeshNodes; i++) {
+ if (meshNodes->at(i).has_user)
+ meshNodes->at(newPos++) = meshNodes->at(i);
else
removed++;
}
- *numMeshNodes -= removed;
+ numMeshNodes -= removed;
+ std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed,
+ meshtastic_NodeInfoLite());
LOG_DEBUG("cleanupMeshDB purged %d entries\n", removed);
}
void NodeDB::installDefaultDeviceState()
{
LOG_INFO("Installing default DeviceState\n");
- memset(&devicestate, 0, sizeof(meshtastic_DeviceState));
+ // memset(&devicestate, 0, sizeof(meshtastic_DeviceState));
- *numMeshNodes = 0;
+ numMeshNodes = 0;
+ meshNodes = &devicestate.node_db_lite;
// init our devicestate with valid flags so protobuf writing/reading will work
devicestate.has_my_node = true;
devicestate.has_owner = true;
- devicestate.node_db_lite_count = 0;
+ // devicestate.node_db_lite_count = 0;
devicestate.version = DEVICESTATE_CUR_VER;
devicestate.receive_queue_count = 0; // Not yet implemented FIXME
@@ -423,65 +518,6 @@ void NodeDB::installDefaultDeviceState()
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
}
-void NodeDB::init()
-{
- LOG_INFO("Initializing NodeDB\n");
- loadFromDisk();
- cleanupMeshDB();
-
- uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate));
- uint32_t configCRC = crc32Buffer(&config, sizeof(config));
- uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
-
- int saveWhat = 0;
-
- // likewise - we always want the app requirements to come from the running appload
- myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00
- // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
- // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
- pickNewNodeNum();
-
- // Set our board type so we can share it with others
- owner.hw_model = HW_VENDOR;
- // Ensure user (nodeinfo) role is set to whatever we're configured to
- owner.role = config.device.role;
-
- // Include our owner in the node db under our nodenum
- meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
- info->user = owner;
- info->has_user = true;
-
-#ifdef ARCH_ESP32
- Preferences preferences;
- preferences.begin("meshtastic", false);
- myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0);
- preferences.end();
- LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count);
-#endif
-
- resetRadioConfig(); // If bogus settings got saved, then fix them
- LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, *numMeshNodes);
-
- if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate)))
- saveWhat |= SEGMENT_DEVICESTATE;
- if (configCRC != crc32Buffer(&config, sizeof(config)))
- saveWhat |= SEGMENT_CONFIG;
- if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile)))
- saveWhat |= SEGMENT_CHANNELS;
-
- if (!devicestate.node_remote_hardware_pins) {
- meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default};
- memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty));
- }
-
- if (config.position.gps_enabled) {
- config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
- config.position.gps_enabled = 0;
- }
-
- saveToDisk(saveWhat);
-}
-
// We reserve a few nodenums for future use
#define NUM_RESERVED 4
@@ -490,11 +526,12 @@ void NodeDB::init()
*/
void NodeDB::pickNewNodeNum()
{
-
+ NodeNum nodeNum = myNodeInfo.my_node_num;
getMacAddr(ourMacAddr); // Make sure ourMacAddr is set
-
- // Pick an initial nodenum based on the macaddr
- NodeNum nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
+ if (nodeNum == 0) {
+ // Pick an initial nodenum based on the macaddr
+ nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
+ }
meshtastic_NodeInfoLite *found;
while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) ||
@@ -503,7 +540,7 @@ void NodeDB::pickNewNodeNum()
LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, so trying for 0x%x\n", nodeNum, candidate);
nodeNum = candidate;
}
- LOG_WARN("Using nodenum 0x%x \n", nodeNum);
+ LOG_DEBUG("Using nodenum 0x%x \n", nodeNum);
myNodeInfo.my_node_num = nodeNum;
}
@@ -514,12 +551,17 @@ static const char *moduleConfigFileName = "/prefs/module.proto";
static const char *channelFileName = "/prefs/channels.proto";
static const char *oemConfigFile = "/oem/oem.proto";
-/** Load a protobuf from a file, return true for success */
-bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct)
+/** Load a protobuf from a file, return LoadFileResult */
+LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
+ void *dest_struct)
{
- bool okay = false;
+ LoadFileResult state = LoadFileResult::OTHER_FAILURE;
#ifdef FSCom
- // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
+
+ if (!FSCom.exists(filename)) {
+ LOG_INFO("File %s not found\n", filename);
+ return LoadFileResult::NOT_FOUND;
+ }
auto f = FSCom.open(filename, FILE_O_READ);
@@ -527,42 +569,49 @@ bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, c
LOG_INFO("Loading %s\n", filename);
pb_istream_t stream = {&readcb, &f, protoSize};
- // LOG_DEBUG("Preload channel name=%s\n", channelSettings.name);
-
memset(dest_struct, 0, objSize);
if (!pb_decode(&stream, fields, dest_struct)) {
LOG_ERROR("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
+ state = LoadFileResult::DECODE_FAILED;
} else {
- okay = true;
+ LOG_INFO("Loaded %s successfully\n", filename);
+ state = LoadFileResult::SUCCESS;
}
-
f.close();
} else {
- LOG_INFO("No %s preferences found\n", filename);
+ LOG_ERROR("Could not open / read %s\n", filename);
}
#else
LOG_ERROR("ERROR: Filesystem not implemented\n");
+ state = LoadFileState::NO_FILESYSTEM;
#endif
- return okay;
+ return state;
}
void NodeDB::loadFromDisk()
{
// static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
- if (!loadProto(prefFileName, meshtastic_DeviceState_size, sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg,
- &devicestate)) {
+ auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo),
+ sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate);
+
+ if (state != LoadFileResult::SUCCESS) {
installDefaultDeviceState(); // Our in RAM copy might now be corrupt
} else {
if (devicestate.version < DEVICESTATE_MIN_VER) {
LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version);
factoryReset();
} else {
- LOG_INFO("Loaded saved devicestate version %d\n", devicestate.version);
+ LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version,
+ devicestate.node_db_lite.size());
+ meshNodes = &devicestate.node_db_lite;
+ numMeshNodes = devicestate.node_db_lite.size();
}
}
+ meshNodes->resize(MAX_NUM_NODES);
- if (!loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg,
- &config)) {
+ state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg,
+ &config);
+ if (state != LoadFileResult::SUCCESS) {
installDefaultConfig(); // Our in RAM copy might now be corrupt
} else {
if (config.version < DEVICESTATE_MIN_VER) {
@@ -573,8 +622,9 @@ void NodeDB::loadFromDisk()
}
}
- if (!loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
- &meshtastic_LocalModuleConfig_msg, &moduleConfig)) {
+ state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
+ &meshtastic_LocalModuleConfig_msg, &moduleConfig);
+ if (state != LoadFileResult::SUCCESS) {
installDefaultModuleConfig(); // Our in RAM copy might now be corrupt
} else {
if (moduleConfig.version < DEVICESTATE_MIN_VER) {
@@ -585,8 +635,9 @@ void NodeDB::loadFromDisk()
}
}
- if (!loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg,
- &channelFile)) {
+ state = loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg,
+ &channelFile);
+ if (state != LoadFileResult::SUCCESS) {
installDefaultChannels(); // Our in RAM copy might now be corrupt
} else {
if (channelFile.version < DEVICESTATE_MIN_VER) {
@@ -597,7 +648,8 @@ void NodeDB::loadFromDisk()
}
}
- if (loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore)) {
+ state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore);
+ if (state == LoadFileResult::SUCCESS) {
LOG_INFO("Loaded OEMStore\n");
}
}
@@ -636,9 +688,13 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
static uint8_t failedCounter = 0;
failedCounter++;
if (failedCounter >= 2) {
- FSCom.format();
- // After formatting, the device needs to be restarted
- nodeDB.resetRadioConfig(true);
+ LOG_ERROR("Failed to save file twice. Rebooting...\n");
+ delay(100);
+ NVIC_SystemReset();
+ // We used to blow away the filesystem here, but that's a bit extreme
+ // FSCom.format();
+ // // After formatting, the device needs to be restarted
+ // nodeDB->resetRadioConfig(true);
}
#endif
}
@@ -650,68 +706,68 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
void NodeDB::saveChannelsToDisk()
{
- if (!devicestate.no_save) {
#ifdef FSCom
- FSCom.mkdir("/prefs");
+ FSCom.mkdir("/prefs");
#endif
- saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile);
- }
+ saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile);
}
void NodeDB::saveDeviceStateToDisk()
{
- if (!devicestate.no_save) {
#ifdef FSCom
- FSCom.mkdir("/prefs");
+ FSCom.mkdir("/prefs");
#endif
- saveProto(prefFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate);
- }
+ saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg,
+ &devicestate);
}
void NodeDB::saveToDisk(int saveWhat)
{
- if (!devicestate.no_save) {
#ifdef FSCom
- FSCom.mkdir("/prefs");
+ FSCom.mkdir("/prefs");
#endif
- if (saveWhat & SEGMENT_DEVICESTATE) {
- saveDeviceStateToDisk();
- }
+ if (saveWhat & SEGMENT_DEVICESTATE) {
+ saveDeviceStateToDisk();
+ }
- if (saveWhat & SEGMENT_CONFIG) {
- config.has_device = true;
- config.has_display = true;
- config.has_lora = true;
- config.has_position = true;
- config.has_power = true;
- config.has_network = true;
- config.has_bluetooth = true;
- saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config);
- }
+ if (saveWhat & SEGMENT_CONFIG) {
+ config.has_device = true;
+ config.has_display = true;
+ config.has_lora = true;
+ config.has_position = true;
+ config.has_power = true;
+ config.has_network = true;
+ config.has_bluetooth = true;
- if (saveWhat & SEGMENT_MODULECONFIG) {
- moduleConfig.has_canned_message = true;
- moduleConfig.has_external_notification = true;
- moduleConfig.has_mqtt = true;
- moduleConfig.has_range_test = true;
- moduleConfig.has_serial = true;
- moduleConfig.has_store_forward = true;
- moduleConfig.has_telemetry = true;
- saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
- }
+ saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config);
+ }
- if (saveWhat & SEGMENT_CHANNELS) {
- saveChannelsToDisk();
- }
- } else {
- LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE - not saving to flash *****\n");
+ if (saveWhat & SEGMENT_MODULECONFIG) {
+ moduleConfig.has_canned_message = true;
+ moduleConfig.has_external_notification = true;
+ moduleConfig.has_mqtt = true;
+ moduleConfig.has_range_test = true;
+ moduleConfig.has_serial = true;
+ moduleConfig.has_store_forward = true;
+ moduleConfig.has_telemetry = true;
+ moduleConfig.has_neighbor_info = true;
+ moduleConfig.has_detection_sensor = true;
+ moduleConfig.has_ambient_lighting = true;
+ moduleConfig.has_audio = true;
+ moduleConfig.has_paxcounter = true;
+
+ saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
+ }
+
+ if (saveWhat & SEGMENT_CHANNELS) {
+ saveChannelsToDisk();
}
}
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
{
- if (readIndex < *numMeshNodes)
- return &meshNodes[readIndex++];
+ if (readIndex < numMeshNodes)
+ return &meshNodes->at(readIndex++);
else
return NULL;
}
@@ -741,19 +797,23 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
-size_t NodeDB::getNumOnlineMeshNodes()
+size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
{
size_t numseen = 0;
// FIXME this implementation is kinda expensive
- for (int i = 0; i < *numMeshNodes; i++)
- if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS)
+ for (int i = 0; i < numMeshNodes; i++) {
+ if (localOnly && meshNodes->at(i).via_mqtt)
+ continue;
+ if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
numseen++;
+ }
return numseen;
}
#include "MeshModule.h"
+#include "Throttle.h"
/** Update position info for this node based on received position data
*/
@@ -848,8 +908,10 @@ bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t chann
powerFSM.trigger(EVENT_NODEDB_UPDATED);
notifyObservers(true); // Force an update whether or not our node counts have changed
- // We just changed something important about the user, store our DB
- saveToDisk(SEGMENT_DEVICESTATE);
+ // We just changed something about the user, store our DB
+ Throttle::execute(
+ &lastNodeDbSave, ONE_MINUTE_MS, []() { nodeDB->saveToDisk(SEGMENT_DEVICESTATE); },
+ []() { LOG_DEBUG("Deferring NodeDB saveToDisk for now, since we saved less than a minute ago\n"); });
}
return changed;
@@ -872,6 +934,12 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
if (mp.rx_snr)
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
+
+ info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
+
+ // If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
+ if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start)
+ info->hops_away = mp.hop_start - mp.hop_limit;
}
}
@@ -888,9 +956,9 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
/// NOTE: This function might be called from an ISR
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
{
- for (int i = 0; i < *numMeshNodes; i++)
- if (meshNodes[i].num == n)
- return &meshNodes[i];
+ for (int i = 0; i < numMeshNodes; i++)
+ if (meshNodes->at(i).num == n)
+ return &meshNodes->at(i);
return NULL;
}
@@ -901,27 +969,27 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
meshtastic_NodeInfoLite *lite = getMeshNode(n);
if (!lite) {
- if ((*numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) {
+ if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) {
if (screen)
- screen->print("warning: node_db_lite full! erasing oldest entry\n");
- LOG_INFO("warning: node_db_lite full! erasing oldest entry\n");
+ screen->print("Warn: node database full!\nErasing oldest entry\n");
+ LOG_WARN("Node database full! Erasing oldest entry\n");
// look for oldest node and erase it
uint32_t oldest = UINT32_MAX;
int oldestIndex = -1;
- for (int i = 1; i < *numMeshNodes; i++) {
- if (meshNodes[i].last_heard < oldest) {
- oldest = meshNodes[i].last_heard;
+ for (int i = 1; i < numMeshNodes; i++) {
+ if (!meshNodes->at(i).is_favorite && meshNodes->at(i).last_heard < oldest) {
+ oldest = meshNodes->at(i).last_heard;
oldestIndex = i;
}
}
// Shove the remaining nodes down the chain
- for (int i = oldestIndex; i < *numMeshNodes - 1; i++) {
- meshNodes[i] = meshNodes[i + 1];
+ for (int i = oldestIndex; i < numMeshNodes - 1; i++) {
+ meshNodes->at(i) = meshNodes->at(i + 1);
}
- (*numMeshNodes)--;
+ (numMeshNodes)--;
}
// add the node at the end
- lite = &meshNodes[(*numMeshNodes)++];
+ lite = &meshNodes->at((numMeshNodes)++);
// everything is missing except the nodenum
memset(lite, 0, sizeof(*lite));
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index e24a971c1..4946672ec 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -3,6 +3,7 @@
#include "Observer.h"
#include
#include
+#include
#include "MeshTypes.h"
#include "NodeStatus.h"
@@ -37,6 +38,19 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
/// Given a packet, return how many seconds in the past (vs now) it was received
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
+enum LoadFileResult {
+ // Successfully opened the file
+ SUCCESS = 1,
+ // File does not exist
+ NOT_FOUND = 2,
+ // Device does not have a filesystem
+ NO_FILESYSTEM = 3,
+ // File exists, but could not decode protobufs
+ DECODE_FAILED = 4,
+ // File exists, but open failed for some reason
+ OTHER_FAILURE = 5
+};
+
class NodeDB
{
// NodeNum provisionalNodeNum; // if we are trying to find a node num this is our current attempt
@@ -45,21 +59,18 @@ class NodeDB
// Eventually use a smarter datastructure
// HashMap nodes;
// Note: these two references just point into our static array we serialize to/from disk
- meshtastic_NodeInfoLite *meshNodes;
- pb_size_t *numMeshNodes;
public:
+ std::vector *meshNodes;
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
Observable newStatus;
+ pb_size_t numMeshNodes;
/// don't do mesh based algorithm for node id assignment (initially)
/// instead just store in flash - possibly even in the initial alpha release do this hack
NodeDB();
- /// Called from service after app start, to do init which can only be done after OS load
- void init();
-
/// write to flash
void saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS),
saveChannelsToDisk(), saveDeviceStateToDisk();
@@ -108,14 +119,17 @@ class NodeDB
// get channel channel index we heard a nodeNum on, defaults to 0 if not found
uint8_t getMeshNodeChannel(NodeNum n);
- /// Return the number of nodes we've heard from recently (within the last 2 hrs?)
- size_t getNumOnlineMeshNodes();
+ /* Return the number of nodes we've heard from recently (within the last 2 hrs?)
+ * @param localOnly if true, ignore nodes heard via MQTT
+ */
+ size_t getNumOnlineMeshNodes(bool localOnly = false);
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum);
bool factoryReset();
- bool loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct);
+ LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
+ void *dest_struct);
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct);
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
@@ -124,21 +138,30 @@ class NodeDB
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
{
- assert(x < *numMeshNodes);
- return &meshNodes[x];
+ assert(x < numMeshNodes);
+ return &meshNodes->at(x);
}
meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
- size_t getNumMeshNodes() { return *numMeshNodes; }
+ size_t getNumMeshNodes() { return numMeshNodes; }
- void setLocalPosition(meshtastic_Position position)
+ void clearLocalPosition();
+
+ void setLocalPosition(meshtastic_Position position, bool timeOnly = false)
{
- LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%i\n", position.latitude_i, position.longitude_i,
- position.time);
+ if (timeOnly) {
+ LOG_DEBUG("Setting local position time only: time=%u timestamp=%u\n", position.time, position.timestamp);
+ localPosition.time = position.time;
+ localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time;
+ return;
+ }
+ LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%u, timestamp=%u\n", position.latitude_i,
+ position.longitude_i, position.time, position.timestamp);
localPosition = position;
}
private:
+ uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
/// Find a node in our DB, create an empty NodeInfoLite if missing
meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n);
@@ -160,7 +183,7 @@ class NodeDB
void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig();
};
-extern NodeDB nodeDB;
+extern NodeDB *nodeDB;
/*
If is_router is set, we use a number of different default values
@@ -184,46 +207,6 @@ extern NodeDB nodeDB;
// Our delay functions check for this for times that should never expire
#define NODE_DELAY_FOREVER 0xffffffff
-#define IF_ROUTER(routerVal, normalVal) \
- ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal))
-
-#define ONE_DAY 24 * 60 * 60
-
-#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60)
-#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60)
-#define default_wait_bluetooth_secs IF_ROUTER(1, 60)
-#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep
-#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60)
-#define default_min_wake_secs 10
-#define default_screen_on_secs IF_ROUTER(1, 60 * 10)
-
-#define default_mqtt_address "mqtt.meshtastic.org"
-#define default_mqtt_username "meshdev"
-#define default_mqtt_password "large4cats"
-#define default_mqtt_root "msh"
-
-inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval)
-{
- if (configuredInterval > 0)
- return configuredInterval * 1000;
- return default_broadcast_interval_secs * 1000;
-}
-
-inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval)
-{
- if (configuredInterval > 0)
- return configuredInterval * 1000;
- return defaultInterval * 1000;
-}
-
-inline uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue)
-{
- if (configured > 0)
- return configured;
-
- return defaultValue;
-}
-
/// Sometimes we will have Position objects that only have a time, so check for
/// valid lat/lon
static inline bool hasValidPosition(const meshtastic_NodeInfoLite *n)
@@ -247,3 +230,5 @@ extern uint32_t error_address;
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
+
+// Please do not remove this comment, it makes trunk and compiler happy at the same time.
\ No newline at end of file
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 9ecad47cd..26a73a3fe 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -2,6 +2,10 @@
#include "configuration.h"
#include "mesh-pb-constants.h"
+#ifdef ARCH_PORTDUINO
+#include "platform/portduino/PortduinoGlue.h"
+#endif
+
PacketHistory::PacketHistory()
{
recentPackets.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 270bf613f..2a69d6d56 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -1,12 +1,16 @@
-#include "PhoneAPI.h"
-#include "Channels.h"
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
+#endif
+
+#include "Channels.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
+#include "PhoneAPI.h"
#include "PowerFSM.h"
#include "RadioInterface.h"
#include "TypeConversions.h"
-#include "configuration.h"
#include "main.h"
#include "xmodem.h"
@@ -17,8 +21,9 @@
#if ToRadio_size > MAX_TO_FROM_RADIO_SIZE
#error ToRadio is too big
#endif
-
+#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
+#endif
PhoneAPI::PhoneAPI()
{
@@ -103,12 +108,21 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
LOG_INFO("Got xmodem packet\n");
xModem.handlePacket(toRadioScratch.xmodemPacket);
break;
+#if !MESHTASTIC_EXCLUDE_MQTT
case meshtastic_ToRadio_mqttClientProxyMessage_tag:
LOG_INFO("Got MqttClientProxy message\n");
- if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled) {
+ if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled &&
+ (channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) {
mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage);
+ } else {
+ LOG_WARN("MqttClientProxy received but proxy is not enabled, no channels have up/downlink, or map reporting "
+ "not enabled\n");
}
break;
+#endif
+ case meshtastic_ToRadio_heartbeat_tag:
+ LOG_DEBUG("Got client heartbeat\n");
+ break;
default:
// Ignore nop messages
// LOG_DEBUG("Error: unexpected ToRadio variant\n");
@@ -413,9 +427,12 @@ bool PhoneAPI::available()
case STATE_SEND_NODEINFO:
if (nodeInfoForPhone.num == 0) {
- auto nextNode = nodeDB.readNextMeshNode(readIndex);
+ auto nextNode = nodeDB->readNextMeshNode(readIndex);
if (nextNode) {
nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode);
+ nodeInfoForPhone.hops_away = nodeInfoForPhone.num == nodeDB->getNodeNum() ? 0 : nodeInfoForPhone.hops_away;
+ nodeInfoForPhone.is_favorite =
+ nodeInfoForPhone.is_favorite || nodeInfoForPhone.num == nodeDB->getNodeNum(); // Our node is always a favorite
}
}
return true; // Always say we have something, because we might need to advance our state machine
diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h
index d87bb47c3..a2e89e98a 100644
--- a/src/mesh/ProtobufModule.h
+++ b/src/mesh/ProtobufModule.h
@@ -56,7 +56,7 @@ template class ProtobufModule : protected SinglePortModule
*/
const char *getSenderShortName(const meshtastic_MeshPacket &mp)
{
- auto node = nodeDB.getMeshNode(getFrom(&mp));
+ auto node = nodeDB->getMeshNode(getFrom(&mp));
const char *sender = (node) ? node->user.short_name : "???";
return sender;
}
@@ -108,8 +108,8 @@ template class ProtobufModule : protected SinglePortModule
// if we can't decode it, nobody can process it!
return;
}
- }
- return alterReceivedProtobuf(mp, decoded);
+ return alterReceivedProtobuf(mp, decoded);
+ }
}
};
\ No newline at end of file
diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp
index 72e0f823f..8c6c349fd 100644
--- a/src/mesh/RF95Interface.cpp
+++ b/src/mesh/RF95Interface.cpp
@@ -19,7 +19,7 @@ RF95Interface::RF95Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIO
RADIOLIB_PIN_TYPE busy)
: RadioLibInterface(hal, cs, irq, rst, busy)
{
- LOG_WARN("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
+ LOG_DEBUG("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
}
/** Some boards require GPIO control of tx vs rx paths */
@@ -128,12 +128,18 @@ bool RF95Interface::reconfigure()
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
err = lora->setSyncWord(syncWord);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting RF95 setSyncWord!\n", err);
assert(err == RADIOLIB_ERR_NONE);
err = lora->setCurrentLimit(currentLimit);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting RF95 setCurrentLimit!\n", err);
assert(err == RADIOLIB_ERR_NONE);
err = lora->setPreambleLength(preambleLength);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting RF95 setPreambleLength!\n", err);
assert(err == RADIOLIB_ERR_NONE);
err = lora->setFrequency(getFreq());
@@ -164,6 +170,8 @@ void RF95Interface::addReceiveMetadata(meshtastic_MeshPacket *mp)
void RF95Interface::setStandby()
{
int err = lora->standby();
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting RF95 standby!\n", err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = false; // If we were receiving, not any more
@@ -185,6 +193,8 @@ void RF95Interface::startReceive()
setTransmitEnable(false);
setStandby();
int err = lora->startReceive();
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting RF95 startReceive!\n", err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = true;
@@ -205,6 +215,8 @@ bool RF95Interface::isChannelActive()
// LOG_DEBUG("Channel is busy!\n");
return true;
}
+ if (result != RADIOLIB_CHANNEL_FREE)
+ LOG_ERROR("Radiolib error %d when attempting RF95 isChannelActive!\n", result);
assert(result != RADIOLIB_ERR_WRONG_MODEM);
// LOG_DEBUG("Channel is free!\n");
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index cea3968ce..4fa0bef7a 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -1,5 +1,6 @@
#include "RadioInterface.h"
#include "Channels.h"
+#include "DisplayFormatters.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
@@ -143,16 +144,23 @@ const RegionInfo regions[] = {
};
const RegionInfo *myRegion;
+bool RadioInterface::uses_default_frequency_slot = true;
static uint8_t bytes[MAX_RHPACKETLEN];
void initRegion()
{
const RegionInfo *r = regions;
+#ifdef LORA_REGIONCODE
+ for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != LORA_REGIONCODE; r++)
+ ;
+ LOG_INFO("Wanted region %d, regulatory override to %s\n", config.lora.region, r->name);
+#else
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != config.lora.region; r++)
;
- myRegion = r;
LOG_INFO("Wanted region %d, using %s\n", config.lora.region, r->name);
+#endif
+ myRegion = r;
}
/**
@@ -302,6 +310,8 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
out += DEBUG_PORT.mt_sprintf(" rxRSSI=%i", p->rx_rssi);
if (p->via_mqtt != 0)
out += DEBUG_PORT.mt_sprintf(" via MQTT");
+ if (p->hop_start != 0)
+ out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start);
if (p->priority != 0)
out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority);
@@ -330,8 +340,8 @@ bool RadioInterface::init()
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
// we now expect interfaces to operate in promiscuous mode
- // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
- // time.
+ // radioIf.setThisAddress(nodeDB->getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at
+ // constructor time.
applyModemConfig();
@@ -482,7 +492,11 @@ void RadioInterface::applyModemConfig()
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
const char *channelName = channels.getName(channels.getPrimaryIndex());
// channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1)
- int channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
+ uint channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
+
+ // Check if we use the default frequency slot
+ RadioInterface::uses_default_frequency_slot =
+ channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels;
// Old frequency selection formula
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
@@ -556,11 +570,14 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
h->to = p->to;
h->id = p->id;
h->channel = p->channel;
+ h->next_hop = 0; // *** For future use ***
+ h->relay_node = 0; // *** For future use ***
if (p->hop_limit > HOP_MAX) {
LOG_WARN("hop limit %d is too high, setting to %d\n", p->hop_limit, HOP_RELIABLE);
p->hop_limit = HOP_RELIABLE;
}
h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0);
+ h->flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK;
// if the sender nodenum is zero, that means uninitialized
assert(h->from);
@@ -569,4 +586,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
sendingPacket = p;
return p->encrypted.size + sizeof(PacketHeader);
-}
+}
\ No newline at end of file
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index 83c5dae64..b965328e4 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -10,9 +10,11 @@
#define MAX_RHPACKETLEN 256
-#define PACKET_FLAGS_HOP_MASK 0x07
+#define PACKET_FLAGS_HOP_LIMIT_MASK 0x07
#define PACKET_FLAGS_WANT_ACK_MASK 0x08
#define PACKET_FLAGS_VIA_MQTT_MASK 0x10
+#define PACKET_FLAGS_HOP_START_MASK 0xE0
+#define PACKET_FLAGS_HOP_START_SHIFT 5
/**
* This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility
@@ -32,6 +34,12 @@ typedef struct {
/** The channel hash - used as a hint for the decoder to limit which channels we consider */
uint8_t channel;
+
+ // ***For future use*** Last byte of the NodeNum of the next-hop for this packet
+ uint8_t next_hop;
+
+ // ***For future use*** Last byte of the NodeNum of the node that will relay/relayed this packet
+ uint8_t relay_node;
} PacketHeader;
/**
@@ -173,6 +181,9 @@ class RadioInterface
/// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
virtual bool isIRQPending() { return false; }
+ // Whether we use the default frequency slot given our LoRa config (region and modem preset)
+ static bool uses_default_frequency_slot;
+
protected:
int8_t power = 17; // Set by applyModemConfig()
@@ -224,4 +235,4 @@ class RadioInterface
};
/// Debug printing for packets
-void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
+void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
\ No newline at end of file
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index 8a2bc53e5..fc1563ee3 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -22,6 +22,12 @@ void LockingArduinoHal::spiEndTransaction()
ArduinoHal::spiEndTransaction();
}
+#if ARCH_PORTDUINO
+void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
+{
+ spi->transfer(out, in, len);
+}
+#endif
RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy, PhysicalLayer *_iface)
@@ -313,7 +319,7 @@ void RadioLibInterface::handleReceiveInterrupt()
// when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
// Condition?
if (!isReceiving) {
- LOG_DEBUG("*** WAS_ASSERT *** handleReceiveInterrupt called when not in receive mode\n");
+ LOG_ERROR("handleReceiveInterrupt called when not in receive mode, which shouldn't happen.\n");
return;
}
@@ -359,8 +365,9 @@ void RadioLibInterface::handleReceiveInterrupt()
mp->to = h->to;
mp->id = h->id;
mp->channel = h->channel;
- assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code
- mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK;
+ assert(HOP_MAX <= PACKET_FLAGS_HOP_LIMIT_MASK); // If hopmax changes, carefully check this code
+ mp->hop_limit = h->flags & PACKET_FLAGS_HOP_LIMIT_MASK;
+ mp->hop_start = (h->flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK);
mp->via_mqtt = !!(h->flags & PACKET_FLAGS_VIA_MQTT_MASK);
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 4634ca7ee..62720cfc9 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -25,6 +25,9 @@ class LockingArduinoHal : public ArduinoHal
void spiBeginTransaction() override;
void spiEndTransaction() override;
+#if ARCH_PORTDUINO
+ void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override;
+#endif
};
#if defined(USE_STM32WLx)
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index a1e9f281d..d3246b48d 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -71,12 +71,12 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
i->second.nextTxMsec += iface->getPacketTime(p);
}
- /* Resend implicit ACKs for repeated packets (assuming the original packet was sent with HOP_RELIABLE)
+ /* Resend implicit ACKs for repeated packets (hopStart equals hopLimit);
* this way if an implicit ACK is dropped and a packet is resent we'll rebroadcast again.
* Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and
* flooding this ACK back to the original sender already adds redundancy. */
- if (wasSeenRecently(p, false) && p->hop_limit == HOP_RELIABLE && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) {
- // retransmission on broadcast has hop_limit still equal to HOP_RELIABLE
+ bool isRepeated = p->hop_start == 0 ? (p->hop_limit == HOP_RELIABLE) : (p->hop_start == p->hop_limit);
+ if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && p->to != nodeDB->getNodeNum()) {
LOG_DEBUG("Resending implicit ack for a repeated floodmsg\n");
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p);
tosend->hop_limit--; // bump down the hop count
@@ -107,10 +107,11 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
if (MeshModule::currentReply) {
LOG_DEBUG("Some other module has replied to this message, no need for a 2nd ack\n");
} else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
- sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel);
+ sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit);
} else {
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
- sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex());
+ sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(), p->hop_start,
+ p->hop_limit);
}
}
@@ -166,8 +167,6 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key)
auto old = findPendingPacket(key);
if (old) {
auto p = old->packet;
- auto numErased = pending.erase(key);
- assert(numErased == 1);
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) {
@@ -176,6 +175,8 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key)
// now free the pooled copy for retransmission too
packetPool.release(p);
}
+ auto numErased = pending.erase(key);
+ assert(numErased == 1);
return true;
} else
return false;
@@ -255,4 +256,4 @@ void ReliableRouter::setNextTx(PendingPacket *pending)
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
printPacket("", pending->packet);
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
-}
+}
\ No newline at end of file
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 4a6dc9007..4189bca66 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -8,12 +8,9 @@
#include "main.h"
#include "mesh-pb-constants.h"
#include "modules/RoutingModule.h"
-extern "C" {
-#include "mesh/compression/unishox2.h"
-}
-
+#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
-
+#endif
/**
* Router todo
*
@@ -119,7 +116,7 @@ meshtastic_MeshPacket *Router::allocForSending()
meshtastic_MeshPacket *p = packetPool.allocZeroed();
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // Assume payload is decoded at start.
- p->from = nodeDB.getNodeNum();
+ p->from = nodeDB->getNodeNum();
p->to = NODENUM_BROADCAST;
p->hop_limit = (config.lora.hop_limit >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit;
p->id = generatePacketId();
@@ -132,9 +129,10 @@ meshtastic_MeshPacket *Router::allocForSending()
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
-void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex)
+void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart,
+ uint8_t hopLimit)
{
- routingModule->sendAckNak(err, to, idFrom, chIndex);
+ routingModule->sendAckNak(err, to, idFrom, chIndex, hopStart, hopLimit);
}
void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p)
@@ -164,7 +162,7 @@ meshtastic_QueueStatus Router::getQueueStatus()
ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
{
// No need to deliver externally if the destination is the local node
- if (p->to == nodeDB.getNodeNum()) {
+ if (p->to == nodeDB->getNodeNum()) {
printPacket("Enqueued local", p);
enqueueReceivedMessage(p);
return ERRNO_OK;
@@ -181,7 +179,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
}
if (!p->channel) { // don't override if a channel was requested
- p->channel = nodeDB.getMeshNodeChannel(p->to);
+ p->channel = nodeDB->getMeshNodeChannel(p->to);
LOG_DEBUG("localSend to channel %d\n", p->channel);
}
@@ -204,7 +202,7 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes)
*/
ErrorCode Router::send(meshtastic_MeshPacket *p)
{
- if (p->to == nodeDB.getNodeNum()) {
+ if (p->to == nodeDB->getNodeNum()) {
LOG_ERROR("BUG! send() called with packet destined for local node!\n");
packetPool.release(p);
return meshtastic_Routing_Error_BAD_REQUEST;
@@ -219,7 +217,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes);
#endif
meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT;
- if (getFrom(p) == nodeDB.getNodeNum()) { // only send NAK to API, not to the mesh
+ if (getFrom(p) == nodeDB->getNodeNum()) { // only send NAK to API, not to the mesh
abortSendAndNak(err, p);
} else {
packetPool.release(p);
@@ -240,6 +238,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// the lora we need to make sure we have replaced it with our local address
p->from = getFrom(p);
+ // If we are the original transmitter, set the hop limit with which we start
+ if (p->from == getNodeNum())
+ p->hop_start = p->hop_limit;
+
// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)
assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag ||
@@ -256,11 +258,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
abortSendAndNak(encodeResult, p);
return encodeResult; // FIXME - this isn't a valid ErrorCode
}
-
+#if !MESHTASTIC_EXCLUDE_MQTT
// Only publish to MQTT if we're the original transmitter of the packet
- if (moduleConfig.mqtt.enabled && p->from == nodeDB.getNodeNum() && mqtt) {
+ if (moduleConfig.mqtt.enabled && p->from == nodeDB->getNodeNum() && mqtt) {
mqtt->onSend(*p, *p_decoded, chIndex);
}
+#endif
packetPool.release(p_decoded);
}
@@ -292,8 +295,8 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
return false;
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
- !nodeDB.getMeshNode(p->from)->has_user) {
- LOG_DEBUG("Node 0x%x not in NodeDB. Rebroadcast mode KNOWN_ONLY will ignore packet\n", p->from);
+ (nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) {
+ LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet\n", p->from);
return false;
}
@@ -326,6 +329,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
p->channel = chIndex; // change to store the index instead of the hash
+ /* Not actually ever used.
// Decompress if needed. jm
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) {
// Decompress the payload
@@ -343,7 +347,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
// Switch the port from PortNum_TEXT_MESSAGE_COMPRESSED_APP to PortNum_TEXT_MESSAGE_APP
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
- }
+ } */
printPacket("decoded message", p);
return true;
@@ -365,6 +369,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
+ /* Not actually used, so save the cycles
// Only allow encryption on the text message app.
// TODO: Allow modules to opt into compression.
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
@@ -398,7 +403,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP;
}
- }
+ } */
if (numbytes > MAX_RHPACKETLEN)
return meshtastic_Routing_Error_TOO_LARGE;
@@ -426,7 +431,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
NodeNum Router::getNodeNum()
{
- return nodeDB.getNodeNum();
+ return nodeDB->getNodeNum();
}
/**
@@ -435,6 +440,7 @@ NodeNum Router::getNodeNum()
*/
void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
{
+ bool skipHandle = false;
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
// Store a copy of encrypted packet for MQTT
@@ -451,17 +457,30 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
else
printPacket("handleReceived(REMOTE)", p);
- // Publish received message to MQTT if we're not the original transmitter of the packet
- if (moduleConfig.mqtt.enabled && getFrom(p) != nodeDB.getNodeNum() && mqtt)
- mqtt->onSend(*p_encrypted, *p, p->channel);
+ // Neighbor info module is disabled, ignore expensive neighbor info packets
+ if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+ p->decoded.portnum == meshtastic_PortNum_NEIGHBORINFO_APP &&
+ (!moduleConfig.has_neighbor_info || !moduleConfig.neighbor_info.enabled)) {
+ LOG_DEBUG("Neighbor info module is disabled, ignoring neighbor packet\n");
+ cancelSending(p->from, p->id);
+ skipHandle = true;
+ }
} else {
printPacket("packet decoding failed or skipped (no PSK?)", p);
}
- packetPool.release(p_encrypted); // Release the encrypted packet
-
// call modules here
- MeshModule::callPlugins(*p, src);
+ if (!skipHandle) {
+ MeshModule::callModules(*p, src);
+
+#if !MESHTASTIC_EXCLUDE_MQTT
+ // After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
+ if (decoded && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt)
+ mqtt->onSend(*p_encrypted, *p, p->channel);
+#endif
+ }
+
+ packetPool.release(p_encrypted); // Release the encrypted packet
}
void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
diff --git a/src/mesh/Router.h b/src/mesh/Router.h
index db810e42e..98486745b 100644
--- a/src/mesh/Router.h
+++ b/src/mesh/Router.h
@@ -104,7 +104,8 @@ class Router : protected concurrency::OSThread
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
- void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
+ void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart = 0,
+ uint8_t hopLimit = 0);
private:
/**
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index 7220dd3e5..afaa13b7f 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -17,7 +17,7 @@ SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs
RADIOLIB_PIN_TYPE busy)
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
{
- LOG_WARN("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
+ LOG_DEBUG("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
}
/// Initialise the Driver transport hardware and software.
@@ -181,12 +181,18 @@ template bool SX126xInterface::reconfigure()
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
err = lora.setSyncWord(syncWord);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX126X setSyncWord!\n", err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setCurrentLimit(currentLimit);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX126X setCurrentLimit!\n", err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setPreambleLength(preambleLength);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX126X setPreambleLength!\n", err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setFrequency(getFreq());
@@ -197,6 +203,8 @@ template bool SX126xInterface::reconfigure()
power = SX126X_MAX_POWER;
err = lora.setOutputPower(power);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX126X setOutputPower!\n", err);
assert(err == RADIOLIB_ERR_NONE);
startReceive(); // restart receiving
@@ -215,10 +223,8 @@ template void SX126xInterface::setStandby()
int err = lora.standby();
- if (err != RADIOLIB_ERR_NONE) {
+ if (err != RADIOLIB_ERR_NONE)
LOG_DEBUG("SX126x standby failed with error %d\n", err);
- }
-
assert(err == RADIOLIB_ERR_NONE);
isReceiving = false; // If we were receiving, not any more
@@ -260,6 +266,8 @@ template void SX126xInterface::startReceive()
int err = lora.startReceiveDutyCycleAuto(preambleLength, 8,
RADIOLIB_SX126X_IRQ_RX_DEFAULT | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED |
RADIOLIB_SX126X_IRQ_HEADER_VALID);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = true;
@@ -279,7 +287,8 @@ template bool SX126xInterface::isChannelActive()
result = lora.scanChannel();
if (result == RADIOLIB_LORA_DETECTED)
return true;
-
+ if (result != RADIOLIB_CHANNEL_FREE)
+ LOG_ERROR("Radiolib error %d when attempting SX126X scanChannel!\n", result);
assert(result != RADIOLIB_ERR_WRONG_MODEM);
return false;
diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp
index d0103ec29..9e4fbfa77 100644
--- a/src/mesh/SX128xInterface.cpp
+++ b/src/mesh/SX128xInterface.cpp
@@ -17,7 +17,7 @@ SX128xInterface::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs
RADIOLIB_PIN_TYPE busy)
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
{
- LOG_WARN("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
+ LOG_DEBUG("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
}
/// Initialise the Driver transport hardware and software.
@@ -71,7 +71,7 @@ template bool SX128xInterface::init()
if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) {
LOG_WARN("Radio chip only supports 2.4GHz LoRa. Adjusting Region and rebooting.\n");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24;
- nodeDB.saveToDisk(SEGMENT_CONFIG);
+ nodeDB->saveToDisk(SEGMENT_CONFIG);
delay(2000);
#if defined(ARCH_ESP32)
ESP.restart();
@@ -126,9 +126,13 @@ template bool SX128xInterface::reconfigure()
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
err = lora.setSyncWord(syncWord);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX128X setSyncWord!\n", err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setPreambleLength(preambleLength);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX128X setPreambleLength!\n", err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setFrequency(getFreq());
@@ -139,6 +143,8 @@ template bool SX128xInterface::reconfigure()
power = SX128X_MAX_POWER;
err = lora.setOutputPower(power);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX128X setOutputPower!\n", err);
assert(err == RADIOLIB_ERR_NONE);
startReceive(); // restart receiving
@@ -162,10 +168,8 @@ template void SX128xInterface::setStandby()
int err = lora.standby();
- if (err != RADIOLIB_ERR_NONE) {
+ if (err != RADIOLIB_ERR_NONE)
LOG_ERROR("SX128x standby failed with error %d\n", err);
- }
-
assert(err == RADIOLIB_ERR_NONE);
#if ARCH_PORTDUINO
if (settingsMap[rxen] != RADIOLIB_NC) {
@@ -251,10 +255,12 @@ template void SX128xInterface::startReceive()
#endif
// We use the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving
- int err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT |
- RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED |
- RADIOLIB_SX128X_IRQ_HEADER_VALID);
+ int err =
+ lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED |
+ RADIOLIB_SX128X_IRQ_HEADER_VALID);
+ if (err != RADIOLIB_ERR_NONE)
+ LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = true;
@@ -274,7 +280,8 @@ template bool SX128xInterface::isChannelActive()
result = lora.scanChannel();
if (result == RADIOLIB_LORA_DETECTED)
return true;
-
+ if (result != RADIOLIB_CHANNEL_FREE)
+ LOG_ERROR("Radiolib error %d when attempting SX128X scanChannel!\n", result);
assert(result != RADIOLIB_ERR_WRONG_MODEM);
return false;
@@ -327,4 +334,4 @@ template bool SX128xInterface::sleep()
#endif
return true;
-}
+}
\ No newline at end of file
diff --git a/src/mesh/Throttle.cpp b/src/mesh/Throttle.cpp
new file mode 100644
index 000000000..d8f23f9dc
--- /dev/null
+++ b/src/mesh/Throttle.cpp
@@ -0,0 +1,27 @@
+#include "Throttle.h"
+#include
+
+/// @brief Execute a function throttled to a minimum interval
+/// @param lastExecutionMs Pointer to the last execution time in milliseconds
+/// @param minumumIntervalMs Minimum execution interval in milliseconds
+/// @param throttleFunc Function to execute if the execution is not deferred
+/// @param onDefer Default to NULL, execute the function if the execution is deferred
+/// @return true if the function was executed, false if it was deferred
+bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*throttleFunc)(void), void (*onDefer)(void))
+{
+ if (*lastExecutionMs == 0) {
+ *lastExecutionMs = millis();
+ throttleFunc();
+ return true;
+ }
+ uint32_t now = millis();
+
+ if ((now - *lastExecutionMs) >= minumumIntervalMs) {
+ throttleFunc();
+ *lastExecutionMs = now;
+ return true;
+ } else if (onDefer != NULL) {
+ onDefer();
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/src/mesh/Throttle.h b/src/mesh/Throttle.h
new file mode 100644
index 000000000..8115595a4
--- /dev/null
+++ b/src/mesh/Throttle.h
@@ -0,0 +1,9 @@
+#pragma once
+#include
+#include
+
+class Throttle
+{
+ public:
+ static bool execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*func)(void), void (*onDefer)(void) = NULL);
+};
\ No newline at end of file
diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp
index 4e0fdd385..bcd600f24 100644
--- a/src/mesh/TypeConversions.cpp
+++ b/src/mesh/TypeConversions.cpp
@@ -10,6 +10,9 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo
info.snr = lite->snr;
info.last_heard = lite->last_heard;
info.channel = lite->channel;
+ info.via_mqtt = lite->via_mqtt;
+ info.hops_away = lite->hops_away;
+ info.is_favorite = lite->is_favorite;
if (lite->has_position) {
info.has_position = true;
diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp
index 97f5027bd..5373f243e 100644
--- a/src/mesh/eth/ethClient.cpp
+++ b/src/mesh/eth/ethClient.cpp
@@ -2,9 +2,12 @@
#include "NodeDB.h"
#include "RTC.h"
#include "concurrency/Periodic.h"
+#include "configuration.h"
#include "main.h"
#include "mesh/api/ethServerAPI.h"
+#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
+#endif
#include "target_specific.h"
#include
#include
@@ -66,11 +69,12 @@ static int32_t reconnectETH()
ethStartupComplete = true;
}
-
+#if !MESHTASTIC_EXCLUDE_MQTT
// FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected'
if (mqtt && !moduleConfig.mqtt.proxy_to_client_enabled && !mqtt->isConnectedDirectly()) {
mqtt->reconnect();
}
+#endif
}
#ifndef DISABLE_NTP
diff --git a/src/mesh/generated/meshtastic/admin.pb.c b/src/mesh/generated/meshtastic/admin.pb.cpp
similarity index 93%
rename from src/mesh/generated/meshtastic/admin.pb.c
rename to src/mesh/generated/meshtastic/admin.pb.cpp
index 92835c89c..339960302 100644
--- a/src/mesh/generated/meshtastic/admin.pb.c
+++ b/src/mesh/generated/meshtastic/admin.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/admin.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 28bda429d..d692a3f30 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED
@@ -7,7 +7,6 @@
#include "meshtastic/channel.pb.h"
#include "meshtastic/config.pb.h"
#include "meshtastic/connection_status.pb.h"
-#include "meshtastic/deviceonly.pb.h"
#include "meshtastic/mesh.pb.h"
#include "meshtastic/module_config.pb.h"
@@ -154,6 +153,14 @@ typedef struct _meshtastic_AdminMessage {
char set_ringtone_message[231];
/* Remove the node by the specified node-num from the NodeDB on the device */
uint32_t remove_by_nodenum;
+ /* Set specified node-num to be favorited on the NodeDB on the device */
+ uint32_t set_favorite_node;
+ /* Set specified node-num to be un-favorited on the NodeDB on the device */
+ uint32_t remove_favorite_node;
+ /* Set fixed position data on the node and then set the position.fixed_position = true */
+ meshtastic_Position set_fixed_position;
+ /* Clear fixed position coordinates and then set position.fixed_position = false */
+ bool remove_fixed_position;
/* Begins an edit transaction for config, module config, owner, and channel settings changes
This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
bool begin_edit_settings;
@@ -238,6 +245,10 @@ extern "C" {
#define meshtastic_AdminMessage_set_canned_message_module_messages_tag 36
#define meshtastic_AdminMessage_set_ringtone_message_tag 37
#define meshtastic_AdminMessage_remove_by_nodenum_tag 38
+#define meshtastic_AdminMessage_set_favorite_node_tag 39
+#define meshtastic_AdminMessage_remove_favorite_node_tag 40
+#define meshtastic_AdminMessage_set_fixed_position_tag 41
+#define meshtastic_AdminMessage_remove_fixed_position_tag 42
#define meshtastic_AdminMessage_begin_edit_settings_tag 64
#define meshtastic_AdminMessage_commit_edit_settings_tag 65
#define meshtastic_AdminMessage_reboot_ota_seconds_tag 95
@@ -277,6 +288,10 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_module_config,set_module
X(a, STATIC, ONEOF, STRING, (payload_variant,set_canned_message_module_messages,set_canned_message_module_messages), 36) \
X(a, STATIC, ONEOF, STRING, (payload_variant,set_ringtone_message,set_ringtone_message), 37) \
X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_by_nodenum,remove_by_nodenum), 38) \
+X(a, STATIC, ONEOF, UINT32, (payload_variant,set_favorite_node,set_favorite_node), 39) \
+X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_favorite_node,remove_favorite_node), 40) \
+X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed_position), 41) \
+X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \
@@ -299,6 +314,7 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset),
#define meshtastic_AdminMessage_payload_variant_set_channel_MSGTYPE meshtastic_Channel
#define meshtastic_AdminMessage_payload_variant_set_config_MSGTYPE meshtastic_Config
#define meshtastic_AdminMessage_payload_variant_set_module_config_MSGTYPE meshtastic_ModuleConfig
+#define meshtastic_AdminMessage_payload_variant_set_fixed_position_MSGTYPE meshtastic_Position
#define meshtastic_HamParameters_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, call_sign, 1) \
@@ -324,6 +340,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg;
#define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
#define meshtastic_AdminMessage_size 500
#define meshtastic_HamParameters_size 32
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
diff --git a/src/mesh/generated/meshtastic/apponly.pb.c b/src/mesh/generated/meshtastic/apponly.pb.cpp
similarity index 89%
rename from src/mesh/generated/meshtastic/apponly.pb.c
rename to src/mesh/generated/meshtastic/apponly.pb.cpp
index 8c3801ed7..44b0ea3cc 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.c
+++ b/src/mesh/generated/meshtastic/apponly.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/apponly.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index 253fdd8ef..54629f522 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED
@@ -54,6 +54,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
#define meshtastic_ChannelSet_fields &meshtastic_ChannelSet_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
#define meshtastic_ChannelSet_size 658
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/atak.pb.c b/src/mesh/generated/meshtastic/atak.pb.cpp
similarity index 94%
rename from src/mesh/generated/meshtastic/atak.pb.c
rename to src/mesh/generated/meshtastic/atak.pb.cpp
index 1413b748e..491336bcf 100644
--- a/src/mesh/generated/meshtastic/atak.pb.c
+++ b/src/mesh/generated/meshtastic/atak.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/atak.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h
index 17d3cd3b9..c094727ed 100644
--- a/src/mesh/generated/meshtastic/atak.pb.h
+++ b/src/mesh/generated/meshtastic/atak.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED
@@ -260,6 +260,7 @@ extern const pb_msgdesc_t meshtastic_PLI_msg;
#define meshtastic_PLI_fields &meshtastic_PLI_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size
#define meshtastic_Contact_size 242
#define meshtastic_GeoChat_size 323
#define meshtastic_Group_size 4
diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.c b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp
similarity index 90%
rename from src/mesh/generated/meshtastic/cannedmessages.pb.c
rename to src/mesh/generated/meshtastic/cannedmessages.pb.cpp
index fffa3fdf9..71e659be2 100644
--- a/src/mesh/generated/meshtastic/cannedmessages.pb.c
+++ b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/cannedmessages.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.h b/src/mesh/generated/meshtastic/cannedmessages.pb.h
index b81f65d0d..c3f9a8b9b 100644
--- a/src/mesh/generated/meshtastic/cannedmessages.pb.h
+++ b/src/mesh/generated/meshtastic/cannedmessages.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED
@@ -40,6 +40,7 @@ extern const pb_msgdesc_t meshtastic_CannedMessageModuleConfig_msg;
#define meshtastic_CannedMessageModuleConfig_fields &meshtastic_CannedMessageModuleConfig_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_MAX_SIZE meshtastic_CannedMessageModuleConfig_size
#define meshtastic_CannedMessageModuleConfig_size 203
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/channel.pb.c b/src/mesh/generated/meshtastic/channel.pb.cpp
similarity index 92%
rename from src/mesh/generated/meshtastic/channel.pb.c
rename to src/mesh/generated/meshtastic/channel.pb.cpp
index f604f64e9..fe76d8140 100644
--- a/src/mesh/generated/meshtastic/channel.pb.c
+++ b/src/mesh/generated/meshtastic/channel.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/channel.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index 1587483c0..185a47a98 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED
@@ -181,6 +181,7 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
#define meshtastic_Channel_fields &meshtastic_Channel_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
#define meshtastic_ChannelSettings_size 70
#define meshtastic_Channel_size 85
#define meshtastic_ModuleSettings_size 6
diff --git a/src/mesh/generated/meshtastic/clientonly.pb.c b/src/mesh/generated/meshtastic/clientonly.pb.cpp
similarity index 89%
rename from src/mesh/generated/meshtastic/clientonly.pb.c
rename to src/mesh/generated/meshtastic/clientonly.pb.cpp
index ebc2ffabc..44c6f95ce 100644
--- a/src/mesh/generated/meshtastic/clientonly.pb.c
+++ b/src/mesh/generated/meshtastic/clientonly.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/clientonly.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/clientonly.pb.h b/src/mesh/generated/meshtastic/clientonly.pb.h
index 0f70e09c6..dc323292a 100644
--- a/src/mesh/generated/meshtastic/clientonly.pb.h
+++ b/src/mesh/generated/meshtastic/clientonly.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED
diff --git a/src/mesh/generated/meshtastic/config.pb.c b/src/mesh/generated/meshtastic/config.pb.cpp
similarity index 96%
rename from src/mesh/generated/meshtastic/config.pb.c
rename to src/mesh/generated/meshtastic/config.pb.cpp
index 0fa8ba588..f05e47573 100644
--- a/src/mesh/generated/meshtastic/config.pb.c
+++ b/src/mesh/generated/meshtastic/config.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/config.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index c56cf65a0..0830ed851 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED
@@ -30,12 +30,12 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
meshtastic_Config_DeviceConfig_Role_REPEATER = 4,
/* Description: Broadcasts GPS position packets as priority.
Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default.
- When used in conjunction with power.is_power_saving = true, nodes will wake up,
+ When used in conjunction with power.is_power_saving = true, nodes will wake up,
send position, and then sleep for position.position_broadcast_secs seconds. */
meshtastic_Config_DeviceConfig_Role_TRACKER = 5,
/* Description: Broadcasts telemetry packets as priority.
Technical Details: Telemetry Mesh packets will be prioritized higher and sent more frequently by default.
- When used in conjunction with power.is_power_saving = true, nodes will wake up,
+ When used in conjunction with power.is_power_saving = true, nodes will wake up,
send environment telemetry, and then sleep for telemetry.environment_update_interval seconds. */
meshtastic_Config_DeviceConfig_Role_SENSOR = 6,
/* Description: Optimized for ATAK system communication and reduces routine broadcasts.
@@ -50,7 +50,7 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
Can be used for clandestine operation or to dramatically reduce airtime / power consumption */
meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN = 8,
/* Description: Broadcasts location as message to default channel regularly for to assist with device recovery.
- Technical Details: Used to automatically send a text message to the mesh
+ Technical Details: Used to automatically send a text message to the mesh
with the current position of the device on a frequent interval:
"I'm lost! Position: lat / long" */
meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND = 9,
@@ -281,6 +281,10 @@ typedef struct _meshtastic_Config_DeviceConfig {
bool is_managed;
/* Disables the triple-press of user button to enable or disable GPS */
bool disable_triple_click;
+ /* POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv. */
+ char tzdef[65];
+ /* If true, disable the default blinking LED (LED_PIN) behavior on the device */
+ bool led_heartbeat_disabled;
} meshtastic_Config_DeviceConfig;
/* Position Config */
@@ -322,35 +326,30 @@ typedef struct _meshtastic_Config_PositionConfig {
/* Power Config\
See [Power Config](/docs/settings/config/power) for additional power config details. */
typedef struct _meshtastic_Config_PowerConfig {
- /* If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in
- we should try to minimize power consumption as much as possible.
- YOU DO NOT NEED TO SET THIS IF YOU'VE set is_router (it is implied in that case).
- Advanced Option */
+ /* Description: Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio.
+ Don't use this setting if you want to use your device with the phone apps or are using a device without a user button.
+ Technical Details: Works for ESP32 devices and NRF52 devices in the Sensor or Tracker roles */
bool is_power_saving;
- /* If non-zero, the device will fully power off this many seconds after external power is removed. */
+ /* Description: If non-zero, the device will fully power off this many seconds after external power is removed. */
uint32_t on_battery_shutdown_after_secs;
/* Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k)
Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation.
- Should be set to floating point value between 2 and 4
- Fixes issues on Heltec v2 */
+ https://meshtastic.org/docs/configuration/radio/power/#adc-multiplier-override
+ Should be set to floating point value between 2 and 6 */
float adc_multiplier_override;
- /* Wait Bluetooth Seconds
- The number of seconds for to wait before turning off BLE in No Bluetooth states
- 0 for default of 1 minute */
+ /* Description: The number of seconds for to wait before turning off BLE in No Bluetooth states
+ Technical Details: ESP32 Only 0 for default of 1 minute */
uint32_t wait_bluetooth_secs;
/* Super Deep Sleep Seconds
While in Light Sleep if mesh_sds_timeout_secs is exceeded we will lower into super deep sleep
for this value (default 1 year) or a button press
0 for default of one year */
uint32_t sds_secs;
- /* Light Sleep Seconds
- In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on
- ESP32 Only
- 0 for default of 300 */
+ /* Description: In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on
+ Technical Details: ESP32 Only 0 for default of 300 */
uint32_t ls_secs;
- /* Minimum Wake Seconds
- While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value
- 0 for default of 10 seconds */
+ /* Description: While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value
+ Technical Details: ESP32 Only 0 for default of 10 seconds */
uint32_t min_wake_secs;
/* I2C address of INA_2XX to use for reading device battery voltage */
uint8_t device_battery_ina_address;
@@ -583,7 +582,7 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}}
-#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0}
+#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0}
#define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN}
#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""}
@@ -592,7 +591,7 @@ extern "C" {
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0}
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}}
-#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0}
+#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0}
#define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN}
#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""}
@@ -612,6 +611,8 @@ extern "C" {
#define meshtastic_Config_DeviceConfig_double_tap_as_button_press_tag 8
#define meshtastic_Config_DeviceConfig_is_managed_tag 9
#define meshtastic_Config_DeviceConfig_disable_triple_click_tag 10
+#define meshtastic_Config_DeviceConfig_tzdef_tag 11
+#define meshtastic_Config_DeviceConfig_led_heartbeat_disabled_tag 12
#define meshtastic_Config_PositionConfig_position_broadcast_secs_tag 1
#define meshtastic_Config_PositionConfig_position_broadcast_smart_enabled_tag 2
#define meshtastic_Config_PositionConfig_fixed_position_tag 3
@@ -711,7 +712,9 @@ X(a, STATIC, SINGULAR, UENUM, rebroadcast_mode, 6) \
X(a, STATIC, SINGULAR, UINT32, node_info_broadcast_secs, 7) \
X(a, STATIC, SINGULAR, BOOL, double_tap_as_button_press, 8) \
X(a, STATIC, SINGULAR, BOOL, is_managed, 9) \
-X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10)
+X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) \
+X(a, STATIC, SINGULAR, STRING, tzdef, 11) \
+X(a, STATIC, SINGULAR, BOOL, led_heartbeat_disabled, 12)
#define meshtastic_Config_DeviceConfig_CALLBACK NULL
#define meshtastic_Config_DeviceConfig_DEFAULT NULL
@@ -828,8 +831,9 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg;
#define meshtastic_Config_BluetoothConfig_fields &meshtastic_Config_BluetoothConfig_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
#define meshtastic_Config_BluetoothConfig_size 10
-#define meshtastic_Config_DeviceConfig_size 32
+#define meshtastic_Config_DeviceConfig_size 100
#define meshtastic_Config_DisplayConfig_size 28
#define meshtastic_Config_LoRaConfig_size 80
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
diff --git a/src/mesh/generated/meshtastic/connection_status.pb.c b/src/mesh/generated/meshtastic/connection_status.pb.cpp
similarity index 95%
rename from src/mesh/generated/meshtastic/connection_status.pb.c
rename to src/mesh/generated/meshtastic/connection_status.pb.cpp
index 0675bc815..fc5a364dd 100644
--- a/src/mesh/generated/meshtastic/connection_status.pb.c
+++ b/src/mesh/generated/meshtastic/connection_status.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/connection_status.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/connection_status.pb.h b/src/mesh/generated/meshtastic/connection_status.pb.h
index 19ed69455..1c618e4d4 100644
--- a/src/mesh/generated/meshtastic/connection_status.pb.h
+++ b/src/mesh/generated/meshtastic/connection_status.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED
@@ -175,6 +175,7 @@ extern const pb_msgdesc_t meshtastic_SerialConnectionStatus_msg;
#define meshtastic_SerialConnectionStatus_fields &meshtastic_SerialConnectionStatus_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_MAX_SIZE meshtastic_DeviceConnectionStatus_size
#define meshtastic_BluetoothConnectionStatus_size 19
#define meshtastic_DeviceConnectionStatus_size 106
#define meshtastic_EthernetConnectionStatus_size 13
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.c b/src/mesh/generated/meshtastic/deviceonly.pb.cpp
similarity index 72%
rename from src/mesh/generated/meshtastic/deviceonly.pb.c
rename to src/mesh/generated/meshtastic/deviceonly.pb.cpp
index 82c3fc44c..672192f67 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.c
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp
@@ -1,18 +1,18 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/deviceonly.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
-PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 4)
+PB_BIND(meshtastic_PositionLite, meshtastic_PositionLite, AUTO)
PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO)
-PB_BIND(meshtastic_PositionLite, meshtastic_PositionLite, AUTO)
+PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2)
PB_BIND(meshtastic_ChannelFile, meshtastic_ChannelFile, 2)
@@ -21,8 +21,5 @@ PB_BIND(meshtastic_ChannelFile, meshtastic_ChannelFile, 2)
PB_BIND(meshtastic_OEMStore, meshtastic_OEMStore, 2)
-PB_BIND(meshtastic_NodeRemoteHardwarePin, meshtastic_NodeRemoteHardwarePin, AUTO)
-
-
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 735644c47..2506ec647 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -1,21 +1,22 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED
#include
+#include
#include "meshtastic/channel.pb.h"
#include "meshtastic/localonly.pb.h"
#include "meshtastic/mesh.pb.h"
-#include "meshtastic/telemetry.pb.h"
#include "meshtastic/module_config.pb.h"
+#include "meshtastic/telemetry.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
-/* TODO: REPLACE */
+/* Font sizes for the device screen */
typedef enum _meshtastic_ScreenFonts {
/* TODO: REPLACE */
meshtastic_ScreenFonts_FONT_SMALL = 0,
@@ -65,8 +66,57 @@ typedef struct _meshtastic_NodeInfoLite {
meshtastic_DeviceMetrics device_metrics;
/* local channel index we heard that node on. Only populated if its not the default channel. */
uint8_t channel;
+ /* True if we witnessed the node over MQTT instead of LoRA transport */
+ bool via_mqtt;
+ /* Number of hops away from us this node is (0 if adjacent) */
+ uint8_t hops_away;
+ /* True if node is in our favorites list
+ Persists between NodeDB internal clean ups */
+ bool is_favorite;
} meshtastic_NodeInfoLite;
+/* This message is never sent over the wire, but it is used for serializing DB
+ state to flash in the device code
+ FIXME, since we write this each time we enter deep sleep (and have infinite
+ flash) it would be better to use some sort of append only data structure for
+ the receive queue and use the preferences store for the other stuff */
+typedef struct _meshtastic_DeviceState {
+ /* Read only settings/info about this node */
+ bool has_my_node;
+ meshtastic_MyNodeInfo my_node;
+ /* My owner info */
+ bool has_owner;
+ meshtastic_User owner;
+ /* Received packets saved for delivery to the phone */
+ pb_size_t receive_queue_count;
+ meshtastic_MeshPacket receive_queue[1];
+ /* We keep the last received text message (only) stored in the device flash,
+ so we can show it on the screen.
+ Might be null */
+ bool has_rx_text_message;
+ meshtastic_MeshPacket rx_text_message;
+ /* A version integer used to invalidate old save files when we make
+ incompatible changes This integer is set at build time and is private to
+ NodeDB.cpp in the device code. */
+ uint32_t version;
+ /* Used only during development.
+ Indicates developer is testing and changes should never be saved to flash.
+ Deprecated in 2.3.1 */
+ bool no_save;
+ /* Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset. */
+ bool did_gps_reset;
+ /* We keep the last received waypoint stored in the device flash,
+ so we can show it on the screen.
+ Might be null */
+ bool has_rx_waypoint;
+ meshtastic_MeshPacket rx_waypoint;
+ /* The mesh's nodes with their available gpio pins for RemoteHardware module */
+ pb_size_t node_remote_hardware_pins_count;
+ meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12];
+ /* New lite version of NodeDB to decrease memory footprint */
+ std::vector node_db_lite;
+} meshtastic_DeviceState;
+
/* The on-disk saved channels */
typedef struct _meshtastic_ChannelFile {
/* The channels our node knows about */
@@ -103,57 +153,6 @@ typedef struct _meshtastic_OEMStore {
meshtastic_LocalModuleConfig oem_local_module_config;
} meshtastic_OEMStore;
-/* RemoteHardwarePins associated with a node */
-typedef struct _meshtastic_NodeRemoteHardwarePin {
- /* The node_num exposing the available gpio pin */
- uint32_t node_num;
- /* The the available gpio pin for usage with RemoteHardware module */
- bool has_pin;
- meshtastic_RemoteHardwarePin pin;
-} meshtastic_NodeRemoteHardwarePin;
-
-/* This message is never sent over the wire, but it is used for serializing DB
- state to flash in the device code
- FIXME, since we write this each time we enter deep sleep (and have infinite
- flash) it would be better to use some sort of append only data structure for
- the receive queue and use the preferences store for the other stuff */
-typedef struct _meshtastic_DeviceState {
- /* Read only settings/info about this node */
- bool has_my_node;
- meshtastic_MyNodeInfo my_node;
- /* My owner info */
- bool has_owner;
- meshtastic_User owner;
- /* Received packets saved for delivery to the phone */
- pb_size_t receive_queue_count;
- meshtastic_MeshPacket receive_queue[1];
- /* We keep the last received text message (only) stored in the device flash,
- so we can show it on the screen.
- Might be null */
- bool has_rx_text_message;
- meshtastic_MeshPacket rx_text_message;
- /* A version integer used to invalidate old save files when we make
- incompatible changes This integer is set at build time and is private to
- NodeDB.cpp in the device code. */
- uint32_t version;
- /* Used only during development.
- Indicates developer is testing and changes should never be saved to flash. */
- bool no_save;
- /* Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset. */
- bool did_gps_reset;
- /* We keep the last received waypoint stored in the device flash,
- so we can show it on the screen.
- Might be null */
- bool has_rx_waypoint;
- meshtastic_MeshPacket rx_waypoint;
- /* The mesh's nodes with their available gpio pins for RemoteHardware module */
- pb_size_t node_remote_hardware_pins_count;
- meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12];
- /* New lite version of NodeDB to decrease memory footprint */
- pb_size_t node_db_lite_count;
- meshtastic_NodeInfoLite node_db_lite[100];
-} meshtastic_DeviceState;
-
#ifdef __cplusplus
extern "C" {
@@ -164,28 +163,25 @@ extern "C" {
#define _meshtastic_ScreenFonts_MAX meshtastic_ScreenFonts_FONT_LARGE
#define _meshtastic_ScreenFonts_ARRAYSIZE ((meshtastic_ScreenFonts)(meshtastic_ScreenFonts_FONT_LARGE+1))
-
-
#define meshtastic_PositionLite_location_source_ENUMTYPE meshtastic_Position_LocSource
+
+
#define meshtastic_OEMStore_oem_font_ENUMTYPE meshtastic_ScreenFonts
-
/* Initializer values for message structs */
-#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_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}, 0, {meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default}}
-#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0}
#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
+#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0}
+#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_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}, {0}}
#define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
#define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default}
-#define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default}
-#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_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}, 0, {meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero}}
-#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0}
#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
+#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0}
+#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_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}, {0}}
#define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
#define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero}
-#define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_PositionLite_latitude_i_tag 1
@@ -200,18 +196,9 @@ extern "C" {
#define meshtastic_NodeInfoLite_last_heard_tag 5
#define meshtastic_NodeInfoLite_device_metrics_tag 6
#define meshtastic_NodeInfoLite_channel_tag 7
-#define meshtastic_ChannelFile_channels_tag 1
-#define meshtastic_ChannelFile_version_tag 2
-#define meshtastic_OEMStore_oem_icon_width_tag 1
-#define meshtastic_OEMStore_oem_icon_height_tag 2
-#define meshtastic_OEMStore_oem_icon_bits_tag 3
-#define meshtastic_OEMStore_oem_font_tag 4
-#define meshtastic_OEMStore_oem_text_tag 5
-#define meshtastic_OEMStore_oem_aes_key_tag 6
-#define meshtastic_OEMStore_oem_local_config_tag 7
-#define meshtastic_OEMStore_oem_local_module_config_tag 8
-#define meshtastic_NodeRemoteHardwarePin_node_num_tag 1
-#define meshtastic_NodeRemoteHardwarePin_pin_tag 2
+#define meshtastic_NodeInfoLite_via_mqtt_tag 8
+#define meshtastic_NodeInfoLite_hops_away_tag 9
+#define meshtastic_NodeInfoLite_is_favorite_tag 10
#define meshtastic_DeviceState_my_node_tag 2
#define meshtastic_DeviceState_owner_tag 3
#define meshtastic_DeviceState_receive_queue_tag 5
@@ -222,8 +209,44 @@ extern "C" {
#define meshtastic_DeviceState_rx_waypoint_tag 12
#define meshtastic_DeviceState_node_remote_hardware_pins_tag 13
#define meshtastic_DeviceState_node_db_lite_tag 14
+#define meshtastic_ChannelFile_channels_tag 1
+#define meshtastic_ChannelFile_version_tag 2
+#define meshtastic_OEMStore_oem_icon_width_tag 1
+#define meshtastic_OEMStore_oem_icon_height_tag 2
+#define meshtastic_OEMStore_oem_icon_bits_tag 3
+#define meshtastic_OEMStore_oem_font_tag 4
+#define meshtastic_OEMStore_oem_text_tag 5
+#define meshtastic_OEMStore_oem_aes_key_tag 6
+#define meshtastic_OEMStore_oem_local_config_tag 7
+#define meshtastic_OEMStore_oem_local_module_config_tag 8
/* Struct field encoding specification for nanopb */
+#define meshtastic_PositionLite_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \
+X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \
+X(a, STATIC, SINGULAR, INT32, altitude, 3) \
+X(a, STATIC, SINGULAR, FIXED32, time, 4) \
+X(a, STATIC, SINGULAR, UENUM, location_source, 5)
+#define meshtastic_PositionLite_CALLBACK NULL
+#define meshtastic_PositionLite_DEFAULT NULL
+
+#define meshtastic_NodeInfoLite_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UINT32, num, 1) \
+X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
+X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \
+X(a, STATIC, SINGULAR, FLOAT, snr, 4) \
+X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \
+X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \
+X(a, STATIC, SINGULAR, UINT32, channel, 7) \
+X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \
+X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \
+X(a, STATIC, SINGULAR, BOOL, is_favorite, 10)
+#define meshtastic_NodeInfoLite_CALLBACK NULL
+#define meshtastic_NodeInfoLite_DEFAULT NULL
+#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User
+#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite
+#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics
+
#define meshtastic_DeviceState_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \
X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \
@@ -234,8 +257,9 @@ X(a, STATIC, SINGULAR, BOOL, no_save, 9) \
X(a, STATIC, SINGULAR, BOOL, did_gps_reset, 11) \
X(a, STATIC, OPTIONAL, MESSAGE, rx_waypoint, 12) \
X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 13) \
-X(a, STATIC, REPEATED, MESSAGE, node_db_lite, 14)
-#define meshtastic_DeviceState_CALLBACK NULL
+X(a, CALLBACK, REPEATED, MESSAGE, node_db_lite, 14)
+extern bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
+#define meshtastic_DeviceState_CALLBACK meshtastic_DeviceState_callback
#define meshtastic_DeviceState_DEFAULT NULL
#define meshtastic_DeviceState_my_node_MSGTYPE meshtastic_MyNodeInfo
#define meshtastic_DeviceState_owner_MSGTYPE meshtastic_User
@@ -245,29 +269,6 @@ X(a, STATIC, REPEATED, MESSAGE, node_db_lite, 14)
#define meshtastic_DeviceState_node_remote_hardware_pins_MSGTYPE meshtastic_NodeRemoteHardwarePin
#define meshtastic_DeviceState_node_db_lite_MSGTYPE meshtastic_NodeInfoLite
-#define meshtastic_NodeInfoLite_FIELDLIST(X, a) \
-X(a, STATIC, SINGULAR, UINT32, num, 1) \
-X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
-X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \
-X(a, STATIC, SINGULAR, FLOAT, snr, 4) \
-X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \
-X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \
-X(a, STATIC, SINGULAR, UINT32, channel, 7)
-#define meshtastic_NodeInfoLite_CALLBACK NULL
-#define meshtastic_NodeInfoLite_DEFAULT NULL
-#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User
-#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite
-#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics
-
-#define meshtastic_PositionLite_FIELDLIST(X, a) \
-X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \
-X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \
-X(a, STATIC, SINGULAR, INT32, altitude, 3) \
-X(a, STATIC, SINGULAR, FIXED32, time, 4) \
-X(a, STATIC, SINGULAR, UENUM, location_source, 5)
-#define meshtastic_PositionLite_CALLBACK NULL
-#define meshtastic_PositionLite_DEFAULT NULL
-
#define meshtastic_ChannelFile_FIELDLIST(X, a) \
X(a, STATIC, REPEATED, MESSAGE, channels, 1) \
X(a, STATIC, SINGULAR, UINT32, version, 2)
@@ -289,34 +290,25 @@ X(a, STATIC, OPTIONAL, MESSAGE, oem_local_module_config, 8)
#define meshtastic_OEMStore_oem_local_config_MSGTYPE meshtastic_LocalConfig
#define meshtastic_OEMStore_oem_local_module_config_MSGTYPE meshtastic_LocalModuleConfig
-#define meshtastic_NodeRemoteHardwarePin_FIELDLIST(X, a) \
-X(a, STATIC, SINGULAR, UINT32, node_num, 1) \
-X(a, STATIC, OPTIONAL, MESSAGE, pin, 2)
-#define meshtastic_NodeRemoteHardwarePin_CALLBACK NULL
-#define meshtastic_NodeRemoteHardwarePin_DEFAULT NULL
-#define meshtastic_NodeRemoteHardwarePin_pin_MSGTYPE meshtastic_RemoteHardwarePin
-
-extern const pb_msgdesc_t meshtastic_DeviceState_msg;
-extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg;
extern const pb_msgdesc_t meshtastic_PositionLite_msg;
+extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg;
+extern const pb_msgdesc_t meshtastic_DeviceState_msg;
extern const pb_msgdesc_t meshtastic_ChannelFile_msg;
extern const pb_msgdesc_t meshtastic_OEMStore_msg;
-extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
-#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg
-#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg
#define meshtastic_PositionLite_fields &meshtastic_PositionLite_msg
+#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg
+#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg
#define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg
#define meshtastic_OEMStore_fields &meshtastic_OEMStore_msg
-#define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg
/* Maximum encoded size of messages (where known) */
+/* meshtastic_DeviceState_size depends on runtime parameters */
+#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size
#define meshtastic_ChannelFile_size 702
-#define meshtastic_DeviceState_size 17062
-#define meshtastic_NodeInfoLite_size 153
-#define meshtastic_NodeRemoteHardwarePin_size 29
-#define meshtastic_OEMStore_size 3246
+#define meshtastic_NodeInfoLite_size 166
+#define meshtastic_OEMStore_size 3346
#define meshtastic_PositionLite_size 28
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/localonly.pb.c b/src/mesh/generated/meshtastic/localonly.pb.cpp
similarity index 91%
rename from src/mesh/generated/meshtastic/localonly.pb.c
rename to src/mesh/generated/meshtastic/localonly.pb.cpp
index 8fc3f1139..9bc98fb85 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.c
+++ b/src/mesh/generated/meshtastic/localonly.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/localonly.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h
index 7d39da01f..1799f49da 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.h
+++ b/src/mesh/generated/meshtastic/localonly.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED
@@ -180,8 +180,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
#define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg
/* Maximum encoded size of messages (where known) */
-#define meshtastic_LocalConfig_size 469
-#define meshtastic_LocalModuleConfig_size 631
+#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
+#define meshtastic_LocalConfig_size 537
+#define meshtastic_LocalModuleConfig_size 663
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.c b/src/mesh/generated/meshtastic/mesh.pb.cpp
similarity index 88%
rename from src/mesh/generated/meshtastic/mesh.pb.c
rename to src/mesh/generated/meshtastic/mesh.pb.cpp
index 790f8be2d..4907affc6 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.c
+++ b/src/mesh/generated/meshtastic/mesh.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/mesh.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
@@ -60,6 +60,12 @@ PB_BIND(meshtastic_Neighbor, meshtastic_Neighbor, AUTO)
PB_BIND(meshtastic_DeviceMetadata, meshtastic_DeviceMetadata, AUTO)
+PB_BIND(meshtastic_Heartbeat, meshtastic_Heartbeat, AUTO)
+
+
+PB_BIND(meshtastic_NodeRemoteHardwarePin, meshtastic_NodeRemoteHardwarePin, AUTO)
+
+
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index e8a27d43f..67b2edd15 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED
@@ -75,6 +75,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_CANARYONE = 29,
/* Waveshare RP2040 LoRa - https://www.waveshare.com/rp2040-lora.htm */
meshtastic_HardwareModel_RP2040_LORA = 30,
+ /* B&Q Consulting Station G2: https://wiki.uniteng.com/en/meshtastic/station-g2 */
+ meshtastic_HardwareModel_STATION_G2 = 31,
/* ---------------------------------------------------------------------------
Less common/prototype boards listed here (needs one more byte over the air)
--------------------------------------------------------------------------- */
@@ -139,6 +141,13 @@ typedef enum _meshtastic_HardwareModel {
/* Heltec Wireless Tracker with ESP32-S3 CPU, built-in GPS, and TFT
Older "V1.0" Variant */
meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V1_0 = 58,
+ /* unPhone with ESP32-S3, TFT touchscreen, LSM6DS3TR-C accelerometer and gyroscope */
+ meshtastic_HardwareModel_UNPHONE = 59,
+ /* Teledatics TD-LORAC NRF52840 based M.2 LoRA module
+ Compatible with the TD-WRLS development board */
+ meshtastic_HardwareModel_TD_LORAC = 60,
+ /* CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3 */
+ meshtastic_HardwareModel_CDEBYTE_EORA_S3 = 61,
/* ------------------------------------------------------------------------------------------------------------------------------------------
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.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -530,11 +539,9 @@ typedef PB_BYTES_ARRAY_T(256) meshtastic_MeshPacket_encrypted_t;
typedef struct _meshtastic_MeshPacket {
/* The sending node number.
Note: Our crypto implementation uses this field as well.
- See [crypto](/docs/overview/encryption) for details.
- FIXME - really should be fixed32 instead, this encoding only hurts the ble link though. */
+ See [crypto](/docs/overview/encryption) for details. */
uint32_t from;
- /* The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only
- hurts the ble link though. */
+ /* The (immediate) destination for this packet */
uint32_t to;
/* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on.
If unset, packet was on the primary channel.
@@ -558,9 +565,7 @@ typedef struct _meshtastic_MeshPacket {
needs to be unique for a few minutes (long enough to last for the length of
any ACK or the completion of a mesh broadcast flood).
Note: Our crypto implementation uses this id as well.
- See [crypto](/docs/overview/encryption) for details.
- FIXME - really should be fixed32 instead, this encoding only
- hurts the ble link though. */
+ See [crypto](/docs/overview/encryption) for details. */
uint32_t id;
/* The time this message was received by the esp32 (secs since 1970).
Note: this field is _never_ sent on the radio link itself (to save space) Times
@@ -595,6 +600,9 @@ typedef struct _meshtastic_MeshPacket {
meshtastic_MeshPacket_Delayed delayed;
/* Describes whether this packet passed via MQTT somewhere along the path it currently took. */
bool via_mqtt;
+ /* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
+ When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. */
+ uint8_t hop_start;
} meshtastic_MeshPacket;
/* The bluetooth to device link:
@@ -633,6 +641,13 @@ typedef struct _meshtastic_NodeInfo {
meshtastic_DeviceMetrics device_metrics;
/* local channel index we heard that node on. Only populated if its not the default channel. */
uint8_t channel;
+ /* True if we witnessed the node over MQTT instead of LoRA transport */
+ bool via_mqtt;
+ /* Number of hops away from us this node is (0 if adjacent) */
+ uint8_t hops_away;
+ /* True if node is in our favorites list
+ Persists between NodeDB internal clean ups */
+ bool is_favorite;
} meshtastic_NodeInfo;
/* Unique local debugging info for this node
@@ -677,32 +692,6 @@ typedef struct _meshtastic_QueueStatus {
uint32_t mesh_packet_id;
} meshtastic_QueueStatus;
-/* Packets/commands to the radio will be written (reliably) to the toRadio characteristic.
- Once the write completes the phone can assume it is handled. */
-typedef struct _meshtastic_ToRadio {
- pb_size_t which_payload_variant;
- union {
- /* Send this packet on the mesh */
- meshtastic_MeshPacket packet;
- /* Phone wants radio to send full node db to the phone, This is
- typically the first packet sent to the radio when the phone gets a
- bluetooth connection. The radio will respond by sending back a
- MyNodeInfo, a owner, a radio config and a series of
- FromRadio.node_infos, and config_complete
- the integer you write into this field will be reported back in the
- config_complete_id response this allows clients to never be confused by
- a stale old partially sent config. */
- uint32_t want_config_id;
- /* Tell API server we are disconnecting now.
- This is useful for serial links where there is no hardware/protocol based notification that the client has dropped the link.
- (Sending this message is optional for clients) */
- bool disconnect;
- meshtastic_XModem xmodemPacket;
- /* MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device) */
- meshtastic_MqttClientProxyMessage mqttClientProxyMessage;
- };
-} meshtastic_ToRadio;
-
typedef PB_BYTES_ARRAY_T(237) meshtastic_Compressed_data_t;
/* Compressed message payload */
typedef struct _meshtastic_Compressed {
@@ -810,6 +799,49 @@ typedef struct _meshtastic_FromRadio {
};
} meshtastic_FromRadio;
+/* A heartbeat message is sent to the node from the client to keep the connection alive.
+ This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI. */
+typedef struct _meshtastic_Heartbeat {
+ char dummy_field;
+} meshtastic_Heartbeat;
+
+/* Packets/commands to the radio will be written (reliably) to the toRadio characteristic.
+ Once the write completes the phone can assume it is handled. */
+typedef struct _meshtastic_ToRadio {
+ pb_size_t which_payload_variant;
+ union {
+ /* Send this packet on the mesh */
+ meshtastic_MeshPacket packet;
+ /* Phone wants radio to send full node db to the phone, This is
+ typically the first packet sent to the radio when the phone gets a
+ bluetooth connection. The radio will respond by sending back a
+ MyNodeInfo, a owner, a radio config and a series of
+ FromRadio.node_infos, and config_complete
+ the integer you write into this field will be reported back in the
+ config_complete_id response this allows clients to never be confused by
+ a stale old partially sent config. */
+ uint32_t want_config_id;
+ /* Tell API server we are disconnecting now.
+ This is useful for serial links where there is no hardware/protocol based notification that the client has dropped the link.
+ (Sending this message is optional for clients) */
+ bool disconnect;
+ meshtastic_XModem xmodemPacket;
+ /* MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device) */
+ meshtastic_MqttClientProxyMessage mqttClientProxyMessage;
+ /* Heartbeat message (used to keep the device connection awake on serial) */
+ meshtastic_Heartbeat heartbeat;
+ };
+} meshtastic_ToRadio;
+
+/* RemoteHardwarePins associated with a node */
+typedef struct _meshtastic_NodeRemoteHardwarePin {
+ /* The node_num exposing the available gpio pin */
+ uint32_t node_num;
+ /* The the available gpio pin for usage with RemoteHardware module */
+ bool has_pin;
+ meshtastic_RemoteHardwarePin pin;
+} meshtastic_NodeRemoteHardwarePin;
+
#ifdef __cplusplus
extern "C" {
@@ -883,6 +915,8 @@ extern "C" {
#define meshtastic_DeviceMetadata_hw_model_ENUMTYPE meshtastic_HardwareModel
+
+
/* Initializer values for message structs */
#define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN}
@@ -891,8 +925,8 @@ extern "C" {
#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0}
#define meshtastic_Waypoint_init_default {0, 0, 0, 0, 0, "", "", 0}
#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}
-#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 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}
+#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, 0, 0}
#define meshtastic_MyNodeInfo_init_default {0, 0, 0}
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_default {0, 0, 0, 0}
@@ -902,6 +936,8 @@ extern "C" {
#define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}}
#define meshtastic_Neighbor_init_default {0, 0, 0, 0}
#define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0}
+#define meshtastic_Heartbeat_init_default {0}
+#define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default}
#define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN}
#define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}}
@@ -909,8 +945,8 @@ extern "C" {
#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0}
#define meshtastic_Waypoint_init_zero {0, 0, 0, 0, 0, "", "", 0}
#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}
-#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 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}
+#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, 0, 0}
#define meshtastic_MyNodeInfo_init_zero {0, 0, 0}
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_zero {0, 0, 0, 0}
@@ -920,6 +956,8 @@ extern "C" {
#define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}}
#define meshtastic_Neighbor_init_zero {0, 0, 0, 0}
#define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0}
+#define meshtastic_Heartbeat_init_zero {0}
+#define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_Position_latitude_i_tag 1
@@ -990,6 +1028,7 @@ extern "C" {
#define meshtastic_MeshPacket_rx_rssi_tag 12
#define meshtastic_MeshPacket_delayed_tag 13
#define meshtastic_MeshPacket_via_mqtt_tag 14
+#define meshtastic_MeshPacket_hop_start_tag 15
#define meshtastic_NodeInfo_num_tag 1
#define meshtastic_NodeInfo_user_tag 2
#define meshtastic_NodeInfo_position_tag 3
@@ -997,6 +1036,9 @@ extern "C" {
#define meshtastic_NodeInfo_last_heard_tag 5
#define meshtastic_NodeInfo_device_metrics_tag 6
#define meshtastic_NodeInfo_channel_tag 7
+#define meshtastic_NodeInfo_via_mqtt_tag 8
+#define meshtastic_NodeInfo_hops_away_tag 9
+#define meshtastic_NodeInfo_is_favorite_tag 10
#define meshtastic_MyNodeInfo_my_node_num_tag 1
#define meshtastic_MyNodeInfo_reboot_count_tag 8
#define meshtastic_MyNodeInfo_min_app_version_tag 11
@@ -1008,11 +1050,6 @@ extern "C" {
#define meshtastic_QueueStatus_free_tag 2
#define meshtastic_QueueStatus_maxlen_tag 3
#define meshtastic_QueueStatus_mesh_packet_id_tag 4
-#define meshtastic_ToRadio_packet_tag 1
-#define meshtastic_ToRadio_want_config_id_tag 3
-#define meshtastic_ToRadio_disconnect_tag 4
-#define meshtastic_ToRadio_xmodemPacket_tag 5
-#define meshtastic_ToRadio_mqttClientProxyMessage_tag 6
#define meshtastic_Compressed_portnum_tag 1
#define meshtastic_Compressed_data_tag 2
#define meshtastic_Neighbor_node_id_tag 1
@@ -1047,6 +1084,14 @@ extern "C" {
#define meshtastic_FromRadio_xmodemPacket_tag 12
#define meshtastic_FromRadio_metadata_tag 13
#define meshtastic_FromRadio_mqttClientProxyMessage_tag 14
+#define meshtastic_ToRadio_packet_tag 1
+#define meshtastic_ToRadio_want_config_id_tag 3
+#define meshtastic_ToRadio_disconnect_tag 4
+#define meshtastic_ToRadio_xmodemPacket_tag 5
+#define meshtastic_ToRadio_mqttClientProxyMessage_tag 6
+#define meshtastic_ToRadio_heartbeat_tag 7
+#define meshtastic_NodeRemoteHardwarePin_node_num_tag 1
+#define meshtastic_NodeRemoteHardwarePin_pin_tag 2
/* Struct field encoding specification for nanopb */
#define meshtastic_Position_FIELDLIST(X, a) \
@@ -1147,7 +1192,8 @@ X(a, STATIC, SINGULAR, BOOL, want_ack, 10) \
X(a, STATIC, SINGULAR, UENUM, priority, 11) \
X(a, STATIC, SINGULAR, INT32, rx_rssi, 12) \
X(a, STATIC, SINGULAR, UENUM, delayed, 13) \
-X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14)
+X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) \
+X(a, STATIC, SINGULAR, UINT32, hop_start, 15)
#define meshtastic_MeshPacket_CALLBACK NULL
#define meshtastic_MeshPacket_DEFAULT NULL
#define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data
@@ -1159,7 +1205,10 @@ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \
X(a, STATIC, SINGULAR, FLOAT, snr, 4) \
X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \
X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \
-X(a, STATIC, SINGULAR, UINT32, channel, 7)
+X(a, STATIC, SINGULAR, UINT32, channel, 7) \
+X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \
+X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \
+X(a, STATIC, SINGULAR, BOOL, is_favorite, 10)
#define meshtastic_NodeInfo_CALLBACK NULL
#define meshtastic_NodeInfo_DEFAULT NULL
#define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User
@@ -1223,12 +1272,14 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,packet,packet), 1) \
X(a, STATIC, ONEOF, UINT32, (payload_variant,want_config_id,want_config_id), 3) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,disconnect,disconnect), 4) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 5) \
-X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 6)
+X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 6) \
+X(a, STATIC, ONEOF, MESSAGE, (payload_variant,heartbeat,heartbeat), 7)
#define meshtastic_ToRadio_CALLBACK NULL
#define meshtastic_ToRadio_DEFAULT NULL
#define meshtastic_ToRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket
#define meshtastic_ToRadio_payload_variant_xmodemPacket_MSGTYPE meshtastic_XModem
#define meshtastic_ToRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage
+#define meshtastic_ToRadio_payload_variant_heartbeat_MSGTYPE meshtastic_Heartbeat
#define meshtastic_Compressed_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, portnum, 1) \
@@ -1267,6 +1318,18 @@ X(a, STATIC, SINGULAR, BOOL, hasRemoteHardware, 10)
#define meshtastic_DeviceMetadata_CALLBACK NULL
#define meshtastic_DeviceMetadata_DEFAULT NULL
+#define meshtastic_Heartbeat_FIELDLIST(X, a) \
+
+#define meshtastic_Heartbeat_CALLBACK NULL
+#define meshtastic_Heartbeat_DEFAULT NULL
+
+#define meshtastic_NodeRemoteHardwarePin_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UINT32, node_num, 1) \
+X(a, STATIC, OPTIONAL, MESSAGE, pin, 2)
+#define meshtastic_NodeRemoteHardwarePin_CALLBACK NULL
+#define meshtastic_NodeRemoteHardwarePin_DEFAULT NULL
+#define meshtastic_NodeRemoteHardwarePin_pin_MSGTYPE meshtastic_RemoteHardwarePin
+
extern const pb_msgdesc_t meshtastic_Position_msg;
extern const pb_msgdesc_t meshtastic_User_msg;
extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg;
@@ -1285,6 +1348,8 @@ extern const pb_msgdesc_t meshtastic_Compressed_msg;
extern const pb_msgdesc_t meshtastic_NeighborInfo_msg;
extern const pb_msgdesc_t meshtastic_Neighbor_msg;
extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg;
+extern const pb_msgdesc_t meshtastic_Heartbeat_msg;
+extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_Position_fields &meshtastic_Position_msg
@@ -1305,19 +1370,24 @@ extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg;
#define meshtastic_NeighborInfo_fields &meshtastic_NeighborInfo_msg
#define meshtastic_Neighbor_fields &meshtastic_Neighbor_msg
#define meshtastic_DeviceMetadata_fields &meshtastic_DeviceMetadata_msg
+#define meshtastic_Heartbeat_fields &meshtastic_Heartbeat_msg
+#define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size
#define meshtastic_Compressed_size 243
#define meshtastic_Data_size 270
#define meshtastic_DeviceMetadata_size 46
#define meshtastic_FromRadio_size 510
+#define meshtastic_Heartbeat_size 0
#define meshtastic_LogRecord_size 81
-#define meshtastic_MeshPacket_size 323
+#define meshtastic_MeshPacket_size 326
#define meshtastic_MqttClientProxyMessage_size 501
#define meshtastic_MyNodeInfo_size 18
#define meshtastic_NeighborInfo_size 258
#define meshtastic_Neighbor_size 22
-#define meshtastic_NodeInfo_size 270
+#define meshtastic_NodeInfo_size 283
+#define meshtastic_NodeRemoteHardwarePin_size 29
#define meshtastic_Position_size 144
#define meshtastic_QueueStatus_size 23
#define meshtastic_RouteDiscovery_size 40
diff --git a/src/mesh/generated/meshtastic/module_config.pb.c b/src/mesh/generated/meshtastic/module_config.pb.cpp
similarity index 88%
rename from src/mesh/generated/meshtastic/module_config.pb.c
rename to src/mesh/generated/meshtastic/module_config.pb.cpp
index 38965f3e2..88a771d5b 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.c
+++ b/src/mesh/generated/meshtastic/module_config.pb.cpp
@@ -1,15 +1,18 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/module_config.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
-PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, AUTO)
+PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, 2)
-PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, AUTO)
+PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, 2)
+
+
+PB_BIND(meshtastic_ModuleConfig_MapReportSettings, meshtastic_ModuleConfig_MapReportSettings, AUTO)
PB_BIND(meshtastic_ModuleConfig_RemoteHardwareConfig, meshtastic_ModuleConfig_RemoteHardwareConfig, AUTO)
diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h
index edfd56e4c..ffda48704 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.h
+++ b/src/mesh/generated/meshtastic/module_config.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED
@@ -84,6 +84,14 @@ typedef enum _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar {
} meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar;
/* Struct definitions */
+/* Settings for reporting unencrypted information about our node to a map via MQTT */
+typedef struct _meshtastic_ModuleConfig_MapReportSettings {
+ /* How often we should report our info to the map (in seconds) */
+ uint32_t publish_interval_secs;
+ /* Bits of precision for the location sent (default of 32 is full precision). */
+ uint32_t position_precision;
+} meshtastic_ModuleConfig_MapReportSettings;
+
/* MQTT Client Config */
typedef struct _meshtastic_ModuleConfig_MQTTConfig {
/* If a meshtastic node is able to reach the internet it will normally attempt to gateway any channels that are marked as
@@ -111,9 +119,14 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig {
bool tls_enabled;
/* The root topic to use for MQTT messages. Default is "msh".
This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs */
- char root[16];
+ char root[32];
/* If true, we can use the connected phone / client to proxy messages to MQTT instead of a direct connection */
bool proxy_to_client_enabled;
+ /* If true, we will periodically report unencrypted information about our node to a map via MQTT */
+ bool map_reporting_enabled;
+ /* Settings for reporting information about our node to a map via MQTT */
+ bool has_map_report_settings;
+ meshtastic_ModuleConfig_MapReportSettings map_report_settings;
} meshtastic_ModuleConfig_MQTTConfig;
/* NeighborInfoModule Config */
@@ -427,6 +440,7 @@ extern "C" {
+
#define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud
@@ -447,7 +461,8 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_ModuleConfig_init_default {0, {meshtastic_ModuleConfig_MQTTConfig_init_default}}
-#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0}
+#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_default}
+#define meshtastic_ModuleConfig_MapReportSettings_init_default {0, 0}
#define meshtastic_ModuleConfig_RemoteHardwareConfig_init_default {0, 0, 0, {meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default}}
#define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0}
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0}
@@ -462,7 +477,8 @@ extern "C" {
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
#define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
#define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}}
-#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0}
+#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero}
+#define meshtastic_ModuleConfig_MapReportSettings_init_zero {0, 0}
#define meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero {0, 0, 0, {meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero}}
#define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0}
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0}
@@ -478,6 +494,8 @@ extern "C" {
#define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN}
/* Field tags (for use in manual encoding/decoding) */
+#define meshtastic_ModuleConfig_MapReportSettings_publish_interval_secs_tag 1
+#define meshtastic_ModuleConfig_MapReportSettings_position_precision_tag 2
#define meshtastic_ModuleConfig_MQTTConfig_enabled_tag 1
#define meshtastic_ModuleConfig_MQTTConfig_address_tag 2
#define meshtastic_ModuleConfig_MQTTConfig_username_tag 3
@@ -487,6 +505,8 @@ extern "C" {
#define meshtastic_ModuleConfig_MQTTConfig_tls_enabled_tag 7
#define meshtastic_ModuleConfig_MQTTConfig_root_tag 8
#define meshtastic_ModuleConfig_MQTTConfig_proxy_to_client_enabled_tag 9
+#define meshtastic_ModuleConfig_MQTTConfig_map_reporting_enabled_tag 10
+#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_tag 11
#define meshtastic_ModuleConfig_NeighborInfoConfig_enabled_tag 1
#define meshtastic_ModuleConfig_NeighborInfoConfig_update_interval_tag 2
#define meshtastic_ModuleConfig_DetectionSensorConfig_enabled_tag 1
@@ -623,9 +643,18 @@ X(a, STATIC, SINGULAR, BOOL, encryption_enabled, 5) \
X(a, STATIC, SINGULAR, BOOL, json_enabled, 6) \
X(a, STATIC, SINGULAR, BOOL, tls_enabled, 7) \
X(a, STATIC, SINGULAR, STRING, root, 8) \
-X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9)
+X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9) \
+X(a, STATIC, SINGULAR, BOOL, map_reporting_enabled, 10) \
+X(a, STATIC, OPTIONAL, MESSAGE, map_report_settings, 11)
#define meshtastic_ModuleConfig_MQTTConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_MQTTConfig_DEFAULT NULL
+#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_MSGTYPE meshtastic_ModuleConfig_MapReportSettings
+
+#define meshtastic_ModuleConfig_MapReportSettings_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UINT32, publish_interval_secs, 1) \
+X(a, STATIC, SINGULAR, UINT32, position_precision, 2)
+#define meshtastic_ModuleConfig_MapReportSettings_CALLBACK NULL
+#define meshtastic_ModuleConfig_MapReportSettings_DEFAULT NULL
#define meshtastic_ModuleConfig_RemoteHardwareConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
@@ -764,6 +793,7 @@ X(a, STATIC, SINGULAR, UENUM, type, 3)
extern const pb_msgdesc_t meshtastic_ModuleConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_MQTTConfig_msg;
+extern const pb_msgdesc_t meshtastic_ModuleConfig_MapReportSettings_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_RemoteHardwareConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_NeighborInfoConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_DetectionSensorConfig_msg;
@@ -781,6 +811,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_ModuleConfig_fields &meshtastic_ModuleConfig_msg
#define meshtastic_ModuleConfig_MQTTConfig_fields &meshtastic_ModuleConfig_MQTTConfig_msg
+#define meshtastic_ModuleConfig_MapReportSettings_fields &meshtastic_ModuleConfig_MapReportSettings_msg
#define meshtastic_ModuleConfig_RemoteHardwareConfig_fields &meshtastic_ModuleConfig_RemoteHardwareConfig_msg
#define meshtastic_ModuleConfig_NeighborInfoConfig_fields &meshtastic_ModuleConfig_NeighborInfoConfig_msg
#define meshtastic_ModuleConfig_DetectionSensorConfig_fields &meshtastic_ModuleConfig_DetectionSensorConfig_msg
@@ -796,12 +827,14 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_MAX_SIZE meshtastic_ModuleConfig_size
#define meshtastic_ModuleConfig_AmbientLightingConfig_size 14
#define meshtastic_ModuleConfig_AudioConfig_size 19
#define meshtastic_ModuleConfig_CannedMessageConfig_size 49
#define meshtastic_ModuleConfig_DetectionSensorConfig_size 44
#define meshtastic_ModuleConfig_ExternalNotificationConfig_size 42
-#define meshtastic_ModuleConfig_MQTTConfig_size 222
+#define meshtastic_ModuleConfig_MQTTConfig_size 254
+#define meshtastic_ModuleConfig_MapReportSettings_size 12
#define meshtastic_ModuleConfig_NeighborInfoConfig_size 8
#define meshtastic_ModuleConfig_PaxcounterConfig_size 8
#define meshtastic_ModuleConfig_RangeTestConfig_size 10
@@ -809,7 +842,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_SerialConfig_size 28
#define meshtastic_ModuleConfig_StoreForwardConfig_size 22
#define meshtastic_ModuleConfig_TelemetryConfig_size 36
-#define meshtastic_ModuleConfig_size 225
+#define meshtastic_ModuleConfig_size 257
#define meshtastic_RemoteHardwarePin_size 21
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/mqtt.pb.c b/src/mesh/generated/meshtastic/mqtt.pb.cpp
similarity index 75%
rename from src/mesh/generated/meshtastic/mqtt.pb.c
rename to src/mesh/generated/meshtastic/mqtt.pb.cpp
index 3046e6109..f00dd823b 100644
--- a/src/mesh/generated/meshtastic/mqtt.pb.c
+++ b/src/mesh/generated/meshtastic/mqtt.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/mqtt.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
@@ -9,4 +9,7 @@
PB_BIND(meshtastic_ServiceEnvelope, meshtastic_ServiceEnvelope, AUTO)
+PB_BIND(meshtastic_MapReport, meshtastic_MapReport, AUTO)
+
+
diff --git a/src/mesh/generated/meshtastic/mqtt.pb.h b/src/mesh/generated/meshtastic/mqtt.pb.h
index 12e83c724..8ec9f98c3 100644
--- a/src/mesh/generated/meshtastic/mqtt.pb.h
+++ b/src/mesh/generated/meshtastic/mqtt.pb.h
@@ -1,9 +1,10 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED
#include
+#include "meshtastic/config.pb.h"
#include "meshtastic/mesh.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
@@ -23,6 +24,38 @@ typedef struct _meshtastic_ServiceEnvelope {
char *gateway_id;
} meshtastic_ServiceEnvelope;
+/* Information about a node intended to be reported unencrypted to a map using MQTT. */
+typedef struct _meshtastic_MapReport {
+ /* A full name for this user, i.e. "Kevin Hester" */
+ char long_name[40];
+ /* A VERY short name, ideally two characters.
+ Suitable for a tiny OLED screen */
+ char short_name[5];
+ /* Role of the node that applies specific settings for a particular use-case */
+ meshtastic_Config_DeviceConfig_Role role;
+ /* Hardware model of the node, i.e. T-Beam, Heltec V3, etc... */
+ meshtastic_HardwareModel hw_model;
+ /* Device firmware version string */
+ char firmware_version[18];
+ /* The region code for the radio (US, CN, EU433, etc...) */
+ meshtastic_Config_LoRaConfig_RegionCode region;
+ /* Modem preset used by the radio (LongFast, MediumSlow, etc...) */
+ meshtastic_Config_LoRaConfig_ModemPreset modem_preset;
+ /* Whether the node has a channel with default PSK and name (LongFast, MediumSlow, etc...)
+ and it uses the default frequency slot given the region and modem preset. */
+ bool has_default_channel;
+ /* Latitude: multiply by 1e-7 to get degrees in floating point */
+ int32_t latitude_i;
+ /* Longitude: multiply by 1e-7 to get degrees in floating point */
+ int32_t longitude_i;
+ /* Altitude in meters above MSL */
+ int32_t altitude;
+ /* Indicates the bits of precision for latitude and longitude set by the sending node */
+ uint32_t position_precision;
+ /* Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT) */
+ uint16_t num_online_local_nodes;
+} meshtastic_MapReport;
+
#ifdef __cplusplus
extern "C" {
@@ -30,12 +63,27 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_ServiceEnvelope_init_default {NULL, NULL, NULL}
+#define meshtastic_MapReport_init_default {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0}
#define meshtastic_ServiceEnvelope_init_zero {NULL, NULL, NULL}
+#define meshtastic_MapReport_init_zero {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_ServiceEnvelope_packet_tag 1
#define meshtastic_ServiceEnvelope_channel_id_tag 2
#define meshtastic_ServiceEnvelope_gateway_id_tag 3
+#define meshtastic_MapReport_long_name_tag 1
+#define meshtastic_MapReport_short_name_tag 2
+#define meshtastic_MapReport_role_tag 3
+#define meshtastic_MapReport_hw_model_tag 4
+#define meshtastic_MapReport_firmware_version_tag 5
+#define meshtastic_MapReport_region_tag 6
+#define meshtastic_MapReport_modem_preset_tag 7
+#define meshtastic_MapReport_has_default_channel_tag 8
+#define meshtastic_MapReport_latitude_i_tag 9
+#define meshtastic_MapReport_longitude_i_tag 10
+#define meshtastic_MapReport_altitude_tag 11
+#define meshtastic_MapReport_position_precision_tag 12
+#define meshtastic_MapReport_num_online_local_nodes_tag 13
/* Struct field encoding specification for nanopb */
#define meshtastic_ServiceEnvelope_FIELDLIST(X, a) \
@@ -46,13 +94,34 @@ X(a, POINTER, SINGULAR, STRING, gateway_id, 3)
#define meshtastic_ServiceEnvelope_DEFAULT NULL
#define meshtastic_ServiceEnvelope_packet_MSGTYPE meshtastic_MeshPacket
+#define meshtastic_MapReport_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, STRING, long_name, 1) \
+X(a, STATIC, SINGULAR, STRING, short_name, 2) \
+X(a, STATIC, SINGULAR, UENUM, role, 3) \
+X(a, STATIC, SINGULAR, UENUM, hw_model, 4) \
+X(a, STATIC, SINGULAR, STRING, firmware_version, 5) \
+X(a, STATIC, SINGULAR, UENUM, region, 6) \
+X(a, STATIC, SINGULAR, UENUM, modem_preset, 7) \
+X(a, STATIC, SINGULAR, BOOL, has_default_channel, 8) \
+X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 9) \
+X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 10) \
+X(a, STATIC, SINGULAR, INT32, altitude, 11) \
+X(a, STATIC, SINGULAR, UINT32, position_precision, 12) \
+X(a, STATIC, SINGULAR, UINT32, num_online_local_nodes, 13)
+#define meshtastic_MapReport_CALLBACK NULL
+#define meshtastic_MapReport_DEFAULT NULL
+
extern const pb_msgdesc_t meshtastic_ServiceEnvelope_msg;
+extern const pb_msgdesc_t meshtastic_MapReport_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_ServiceEnvelope_fields &meshtastic_ServiceEnvelope_msg
+#define meshtastic_MapReport_fields &meshtastic_MapReport_msg
/* Maximum encoded size of messages (where known) */
/* meshtastic_ServiceEnvelope_size depends on runtime parameters */
+#define MESHTASTIC_MESHTASTIC_MQTT_PB_H_MAX_SIZE meshtastic_MapReport_size
+#define meshtastic_MapReport_size 108
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/paxcount.pb.c b/src/mesh/generated/meshtastic/paxcount.pb.cpp
similarity index 89%
rename from src/mesh/generated/meshtastic/paxcount.pb.c
rename to src/mesh/generated/meshtastic/paxcount.pb.cpp
index 57d5f5be9..67f07a31b 100644
--- a/src/mesh/generated/meshtastic/paxcount.pb.c
+++ b/src/mesh/generated/meshtastic/paxcount.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/paxcount.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/paxcount.pb.h b/src/mesh/generated/meshtastic/paxcount.pb.h
index 4b643293c..09377d833 100644
--- a/src/mesh/generated/meshtastic/paxcount.pb.h
+++ b/src/mesh/generated/meshtastic/paxcount.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED
@@ -48,6 +48,7 @@ extern const pb_msgdesc_t meshtastic_Paxcount_msg;
#define meshtastic_Paxcount_fields &meshtastic_Paxcount_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_MAX_SIZE meshtastic_Paxcount_size
#define meshtastic_Paxcount_size 18
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/portnums.pb.c b/src/mesh/generated/meshtastic/portnums.pb.cpp
similarity index 86%
rename from src/mesh/generated/meshtastic/portnums.pb.c
rename to src/mesh/generated/meshtastic/portnums.pb.cpp
index dd0d00e20..8f32c0851 100644
--- a/src/mesh/generated/meshtastic/portnums.pb.c
+++ b/src/mesh/generated/meshtastic/portnums.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/portnums.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h
index 88342e5dc..233e8d653 100644
--- a/src/mesh/generated/meshtastic/portnums.pb.h
+++ b/src/mesh/generated/meshtastic/portnums.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED
@@ -38,19 +38,19 @@ typedef enum _meshtastic_PortNum {
ENCODING: Protobuf */
meshtastic_PortNum_REMOTE_HARDWARE_APP = 2,
/* The built-in position messaging app.
- Payload is a [Position](/docs/developers/protobufs/api#position) message
+ Payload is a Position message.
ENCODING: Protobuf */
meshtastic_PortNum_POSITION_APP = 3,
/* The built-in user info app.
- Payload is a [User](/docs/developers/protobufs/api#user) message
+ Payload is a User message.
ENCODING: Protobuf */
meshtastic_PortNum_NODEINFO_APP = 4,
/* Protocol control packets for mesh protocol use.
- Payload is a [Routing](/docs/developers/protobufs/api#routing) message
+ Payload is a Routing message.
ENCODING: Protobuf */
meshtastic_PortNum_ROUTING_APP = 5,
/* Admin control packets.
- Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message
+ Payload is a AdminMessage message.
ENCODING: Protobuf */
meshtastic_PortNum_ADMIN_APP = 6,
/* Compressed TEXT_MESSAGE payloads.
@@ -60,7 +60,7 @@ typedef enum _meshtastic_PortNum {
any incoming TEXT_MESSAGE_COMPRESSED_APP payload and convert to TEXT_MESSAGE_APP. */
meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP = 7,
/* Waypoint payloads.
- Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message
+ Payload is a Waypoint message.
ENCODING: Protobuf */
meshtastic_PortNum_WAYPOINT_APP = 8,
/* Audio Payloads.
@@ -122,6 +122,8 @@ typedef enum _meshtastic_PortNum {
/* ATAK Plugin
Portnum for payloads from the official Meshtastic ATAK plugin */
meshtastic_PortNum_ATAK_PLUGIN = 72,
+ /* Provides unencrypted information about a node for consumption by a map via MQTT */
+ meshtastic_PortNum_MAP_REPORT_APP = 73,
/* Private applications should use portnums >= 256.
To simplify initial development and testing you can use "PRIVATE_APP"
in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */
diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.c b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp
similarity index 90%
rename from src/mesh/generated/meshtastic/remote_hardware.pb.c
rename to src/mesh/generated/meshtastic/remote_hardware.pb.cpp
index f368ec1ef..4a23698b2 100644
--- a/src/mesh/generated/meshtastic/remote_hardware.pb.c
+++ b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/remote_hardware.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.h b/src/mesh/generated/meshtastic/remote_hardware.pb.h
index 26df97616..936034b62 100644
--- a/src/mesh/generated/meshtastic/remote_hardware.pb.h
+++ b/src/mesh/generated/meshtastic/remote_hardware.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED
@@ -84,6 +84,7 @@ extern const pb_msgdesc_t meshtastic_HardwareMessage_msg;
#define meshtastic_HardwareMessage_fields &meshtastic_HardwareMessage_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_MAX_SIZE meshtastic_HardwareMessage_size
#define meshtastic_HardwareMessage_size 24
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/rtttl.pb.c b/src/mesh/generated/meshtastic/rtttl.pb.cpp
similarity index 89%
rename from src/mesh/generated/meshtastic/rtttl.pb.c
rename to src/mesh/generated/meshtastic/rtttl.pb.cpp
index 685bbde45..8367fdbce 100644
--- a/src/mesh/generated/meshtastic/rtttl.pb.c
+++ b/src/mesh/generated/meshtastic/rtttl.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/rtttl.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/rtttl.pb.h b/src/mesh/generated/meshtastic/rtttl.pb.h
index aa55d0b7d..452b0cf4b 100644
--- a/src/mesh/generated/meshtastic/rtttl.pb.h
+++ b/src/mesh/generated/meshtastic/rtttl.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED
@@ -40,6 +40,7 @@ extern const pb_msgdesc_t meshtastic_RTTTLConfig_msg;
#define meshtastic_RTTTLConfig_fields &meshtastic_RTTTLConfig_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_RTTTL_PB_H_MAX_SIZE meshtastic_RTTTLConfig_size
#define meshtastic_RTTTLConfig_size 232
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/storeforward.pb.c b/src/mesh/generated/meshtastic/storeforward.pb.cpp
similarity index 94%
rename from src/mesh/generated/meshtastic/storeforward.pb.c
rename to src/mesh/generated/meshtastic/storeforward.pb.cpp
index 44a1c70c1..5b3fadd9a 100644
--- a/src/mesh/generated/meshtastic/storeforward.pb.c
+++ b/src/mesh/generated/meshtastic/storeforward.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/storeforward.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/storeforward.pb.h b/src/mesh/generated/meshtastic/storeforward.pb.h
index 55ab0b510..311596c7f 100644
--- a/src/mesh/generated/meshtastic/storeforward.pb.h
+++ b/src/mesh/generated/meshtastic/storeforward.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED
@@ -207,6 +207,7 @@ extern const pb_msgdesc_t meshtastic_StoreAndForward_Heartbeat_msg;
#define meshtastic_StoreAndForward_Heartbeat_fields &meshtastic_StoreAndForward_Heartbeat_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_MAX_SIZE meshtastic_StoreAndForward_size
#define meshtastic_StoreAndForward_Heartbeat_size 12
#define meshtastic_StoreAndForward_History_size 18
#define meshtastic_StoreAndForward_Statistics_size 50
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.c b/src/mesh/generated/meshtastic/telemetry.pb.cpp
similarity index 94%
rename from src/mesh/generated/meshtastic/telemetry.pb.c
rename to src/mesh/generated/meshtastic/telemetry.pb.cpp
index 046998ae9..6388e37a0 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.c
+++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/telemetry.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index fc2780a96..e670dd340 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED
@@ -41,7 +41,11 @@ typedef enum _meshtastic_TelemetrySensorType {
/* PM2.5 air quality sensor */
meshtastic_TelemetrySensorType_PMSA003I = 13,
/* INA3221 3 Channel Voltage / Current Sensor */
- meshtastic_TelemetrySensorType_INA3221 = 14
+ meshtastic_TelemetrySensorType_INA3221 = 14,
+ /* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) */
+ meshtastic_TelemetrySensorType_BMP085 = 15,
+ /* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */
+ meshtastic_TelemetrySensorType_RCWL9620 = 16
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@@ -55,6 +59,8 @@ typedef struct _meshtastic_DeviceMetrics {
float channel_utilization;
/* Percent of airtime for transmission used within the last hour. */
float air_util_tx;
+ /* How long the device has been running since the last reboot (in seconds) */
+ uint32_t uptime_seconds;
} meshtastic_DeviceMetrics;
/* Weather station or other environmental metrics */
@@ -71,6 +77,11 @@ typedef struct _meshtastic_EnvironmentMetrics {
float voltage;
/* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) */
float current;
+ /* relative scale IAQ value as measured by Bosch BME680 . value 0-500.
+ Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */
+ uint16_t iaq;
+ /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */
+ float distance;
} meshtastic_EnvironmentMetrics;
/* Power Metrics (voltage / current / etc) */
@@ -141,8 +152,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
-#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_INA3221
-#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_INA3221+1))
+#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_RCWL9620
+#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_RCWL9620+1))
@@ -151,13 +162,13 @@ extern "C" {
/* Initializer values for message structs */
-#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0}
-#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0}
+#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0}
+#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0}
#define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
-#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0}
-#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0}
+#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0}
+#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0}
#define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
@@ -167,12 +178,15 @@ extern "C" {
#define meshtastic_DeviceMetrics_voltage_tag 2
#define meshtastic_DeviceMetrics_channel_utilization_tag 3
#define meshtastic_DeviceMetrics_air_util_tx_tag 4
+#define meshtastic_DeviceMetrics_uptime_seconds_tag 5
#define meshtastic_EnvironmentMetrics_temperature_tag 1
#define meshtastic_EnvironmentMetrics_relative_humidity_tag 2
#define meshtastic_EnvironmentMetrics_barometric_pressure_tag 3
#define meshtastic_EnvironmentMetrics_gas_resistance_tag 4
#define meshtastic_EnvironmentMetrics_voltage_tag 5
#define meshtastic_EnvironmentMetrics_current_tag 6
+#define meshtastic_EnvironmentMetrics_iaq_tag 7
+#define meshtastic_EnvironmentMetrics_distance_tag 8
#define meshtastic_PowerMetrics_ch1_voltage_tag 1
#define meshtastic_PowerMetrics_ch1_current_tag 2
#define meshtastic_PowerMetrics_ch2_voltage_tag 3
@@ -202,7 +216,8 @@ extern "C" {
X(a, STATIC, SINGULAR, UINT32, battery_level, 1) \
X(a, STATIC, SINGULAR, FLOAT, voltage, 2) \
X(a, STATIC, SINGULAR, FLOAT, channel_utilization, 3) \
-X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 4)
+X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 4) \
+X(a, STATIC, SINGULAR, UINT32, uptime_seconds, 5)
#define meshtastic_DeviceMetrics_CALLBACK NULL
#define meshtastic_DeviceMetrics_DEFAULT NULL
@@ -212,7 +227,9 @@ X(a, STATIC, SINGULAR, FLOAT, relative_humidity, 2) \
X(a, STATIC, SINGULAR, FLOAT, barometric_pressure, 3) \
X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \
X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \
-X(a, STATIC, SINGULAR, FLOAT, current, 6)
+X(a, STATIC, SINGULAR, FLOAT, current, 6) \
+X(a, STATIC, SINGULAR, UINT32, iaq, 7) \
+X(a, STATIC, SINGULAR, FLOAT, distance, 8)
#define meshtastic_EnvironmentMetrics_CALLBACK NULL
#define meshtastic_EnvironmentMetrics_DEFAULT NULL
@@ -269,9 +286,10 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg;
#define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size
#define meshtastic_AirQualityMetrics_size 72
-#define meshtastic_DeviceMetrics_size 21
-#define meshtastic_EnvironmentMetrics_size 30
+#define meshtastic_DeviceMetrics_size 27
+#define meshtastic_EnvironmentMetrics_size 39
#define meshtastic_PowerMetrics_size 30
#define meshtastic_Telemetry_size 79
diff --git a/src/mesh/generated/meshtastic/xmodem.pb.c b/src/mesh/generated/meshtastic/xmodem.pb.cpp
similarity index 89%
rename from src/mesh/generated/meshtastic/xmodem.pb.c
rename to src/mesh/generated/meshtastic/xmodem.pb.cpp
index 9692a5eb4..8e5cde457 100644
--- a/src/mesh/generated/meshtastic/xmodem.pb.c
+++ b/src/mesh/generated/meshtastic/xmodem.pb.cpp
@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#include "meshtastic/xmodem.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
diff --git a/src/mesh/generated/meshtastic/xmodem.pb.h b/src/mesh/generated/meshtastic/xmodem.pb.h
index 48d5aa5cd..67bd0869f 100644
--- a/src/mesh/generated/meshtastic/xmodem.pb.h
+++ b/src/mesh/generated/meshtastic/xmodem.pb.h
@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.7 */
+/* Generated by nanopb-0.4.8 */
#ifndef PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED
@@ -68,6 +68,7 @@ extern const pb_msgdesc_t meshtastic_XModem_msg;
#define meshtastic_XModem_fields &meshtastic_XModem_msg
/* Maximum encoded size of messages (where known) */
+#define MESHTASTIC_MESHTASTIC_XMODEM_PB_H_MAX_SIZE meshtastic_XModem_size
#define meshtastic_XModem_size 141
#ifdef __cplusplus
diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index 7640e879c..7f9df058d 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -1,3 +1,4 @@
+#if !MESHTASTIC_EXCLUDE_WEBSERVER
#include "NodeDB.h"
#include "PowerFSM.h"
#include "RadioLibInterface.h"
@@ -5,7 +6,9 @@
#include "main.h"
#include "mesh/http/ContentHelper.h"
#include "mesh/http/WebServer.h"
+#if !MESHTASTIC_EXCLUDE_WIFI
#include "mesh/wifi/WiFiAPClient.h"
+#endif
#include "mqtt/JSON.h"
#include "power.h"
#include "sleep.h"
@@ -855,3 +858,4 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
res->print(value->Stringify().c_str());
delete value;
}
+#endif
\ No newline at end of file
diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp
index 7814f2c29..fc8535257 100644
--- a/src/mesh/http/WebServer.cpp
+++ b/src/mesh/http/WebServer.cpp
@@ -1,13 +1,14 @@
-#include "mesh/http/WebServer.h"
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_WEBSERVER
#include "NodeDB.h"
#include "graphics/Screen.h"
#include "main.h"
+#include "mesh/http/WebServer.h"
#include "mesh/wifi/WiFiAPClient.h"
#include "sleep.h"
#include
#include
#include
-
#include
#include
@@ -92,7 +93,6 @@ static void taskCreateCert(void *parameter)
LOG_DEBUG("Retrieved Private Key: %d Bytes\n", cert->getPKLength());
LOG_DEBUG("Retrieved Certificate: %d Bytes\n", cert->getCertLength());
-
} else {
LOG_INFO("Creating the certificate. This may take a while. Please wait...\n");
@@ -105,7 +105,6 @@ static void taskCreateCert(void *parameter)
if (createCertResult != 0) {
LOG_ERROR("Creating the certificate failed\n");
-
} else {
LOG_INFO("Creating the certificate was successful\n");
@@ -210,4 +209,5 @@ void initWebServer()
} else {
LOG_ERROR("Web Servers Failed! ;-( \n");
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h
index 22a80c8e3..b8ef236c9 100644
--- a/src/mesh/mesh-pb-constants.h
+++ b/src/mesh/mesh-pb-constants.h
@@ -1,4 +1,5 @@
#pragma once
+#include
#include "mesh/generated/meshtastic/admin.pb.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
@@ -16,7 +17,9 @@
#define MAX_RX_TOPHONE 32
/// max number of nodes allowed in the mesh
-#define MAX_NUM_NODES (member_size(meshtastic_DeviceState, node_db_lite) / member_size(meshtastic_DeviceState, node_db_lite[0]))
+#ifndef MAX_NUM_NODES
+#define MAX_NUM_NODES 100
+#endif
/// Max number of channels allowed
#define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0]))
diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp
new file mode 100644
index 000000000..bffd6c340
--- /dev/null
+++ b/src/mesh/raspihttp/PiWebServer.cpp
@@ -0,0 +1,530 @@
+/*
+Adds a WebServer and WebService callbacks to meshtastic as Linux Version. The WebServer & Webservices
+runs in a real linux thread beside the portdunio threading emulation. It replaces the complete ESP32
+Webserver libs including generation of SSL certifcicates, because the use ESP specific details in
+the lib that can't be emulated.
+
+The WebServices adapt to the two major phoneapi functions "handleAPIv1FromRadio,handleAPIv1ToRadio"
+The WebServer just adds basaic support to deliver WebContent, so it can be used to
+deliver the WebGui definded by the WebClient Project.
+
+Steps to get it running:
+1.) Add these Linux Libs to the compile and target machine:
+
+ sudo apt update && \
+ apt -y install openssl libssl-dev libopenssl libsdl2-dev \
+ libulfius-dev liborcania-dev
+
+2.) Configure the root directory of the web Content in the config.yaml file.
+ The followinng tags should be included and set at your needs
+
+ Example entry in the config.yaml
+ Webserver:
+ Port: 9001 # Port for Webserver & Webservices
+ RootPath: /home/marc/web # Root Dir of WebServer
+
+3.) Checkout the web project
+ https://github.com/meshtastic/web.git
+
+ Build it and copy the content of the folder web/dist/* to the folder you did set as "RootPath"
+
+!!!The WebServer should not be used as production system or exposed to the Internet. Its a raw basic version!!!
+
+Author: Marc Philipp Hammermann
+mail: marchammermann@googlemail.com
+
+*/
+#ifdef PORTDUINO_LINUX_HARDWARE
+#if __has_include()
+#include "PiWebServer.h"
+#include "NodeDB.h"
+#include "PhoneAPI.h"
+#include "PowerFSM.h"
+#include "RadioLibInterface.h"
+#include "airtime.h"
+#include "graphics/Screen.h"
+#include "main.h"
+#include "mesh/wifi/WiFiAPClient.h"
+#include "sleep.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "PortduinoFS.h"
+#include "platform/portduino/PortduinoGlue.h"
+
+#define DEFAULT_REALM "default_realm"
+#define PREFIX ""
+
+struct _file_config configWeb;
+
+// We need to specify some content-type mapping, so the resources get delivered with the
+// right content type and are displayed correctly in the browser
+char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"},
+ {".js", "text/javascript"}, {".png", "image/png"},
+ {".jpg", "image/jpg"}, {".gz", "application/gzip"},
+ {".gif", "image/gif"}, {".json", "application/json"},
+ {".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
+ {".svg", "image/svg+xml"}, {".ts", "text/javascript"},
+ {".tsx", "text/javascript"}, {"", ""}};
+
+#undef str
+
+volatile bool isWebServerReady;
+volatile bool isCertReady;
+
+HttpAPI webAPI;
+
+PiWebServerThread *piwebServerThread;
+
+/**
+ * Return the filename extension
+ */
+const char *get_filename_ext(const char *path)
+{
+ const char *dot = strrchr(path, '.');
+ if (!dot || dot == path)
+ return "*";
+ if (strchr(dot, '?') != NULL) {
+ //*strchr(dot, '?') = '\0';
+ const char *empty = "\0";
+ return empty;
+ }
+ return dot;
+}
+
+/**
+ * Streaming callback function to ease sending large files
+ */
+static ssize_t callback_static_file_stream(void *cls, uint64_t pos, char *buf, size_t max)
+{
+ (void)(pos);
+ if (cls != NULL) {
+ return fread(buf, 1, max, (FILE *)cls);
+ } else {
+ return U_STREAM_END;
+ }
+}
+
+/**
+ * Cleanup FILE* structure when streaming is complete
+ */
+static void callback_static_file_stream_free(void *cls)
+{
+ if (cls != NULL) {
+ fclose((FILE *)cls);
+ }
+}
+
+/**
+ * static file callback endpoint that delivers the content for WebServer calls
+ */
+int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data)
+{
+ size_t length;
+ FILE *f;
+ char *file_requested, *file_path, *url_dup_save, *real_path = NULL;
+ const char *content_type;
+
+ /*
+ * Comment this if statement if you don't access static files url from root dir, like /app
+ */
+ if (request->callback_position > 0) {
+ return U_CALLBACK_CONTINUE;
+ } else if (user_data != NULL && (configWeb.files_path != NULL)) {
+ file_requested = o_strdup(request->http_url);
+ url_dup_save = file_requested;
+
+ while (file_requested[0] == '/') {
+ file_requested++;
+ }
+ file_requested += o_strlen(configWeb.url_prefix);
+ while (file_requested[0] == '/') {
+ file_requested++;
+ }
+
+ if (strchr(file_requested, '#') != NULL) {
+ *strchr(file_requested, '#') = '\0';
+ }
+
+ if (strchr(file_requested, '?') != NULL) {
+ *strchr(file_requested, '?') = '\0';
+ }
+
+ if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) {
+ o_free(url_dup_save);
+ url_dup_save = file_requested = o_strdup("index.html");
+ }
+
+ file_path = msprintf("%s/%s", configWeb.files_path, file_requested);
+ real_path = realpath(file_path, NULL);
+ if (0 == o_strncmp(configWeb.files_path, real_path, o_strlen(configWeb.files_path))) {
+ if (access(file_path, F_OK) != -1) {
+ f = fopen(file_path, "rb");
+ if (f) {
+ fseek(f, 0, SEEK_END);
+ length = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ content_type = u_map_get_case(&configWeb.mime_types, get_filename_ext(file_requested));
+ if (content_type == NULL) {
+ content_type = u_map_get(&configWeb.mime_types, "*");
+ LOG_DEBUG("Static File Server - Unknown mime type for extension %s \n", get_filename_ext(file_requested));
+ }
+ u_map_put(response->map_header, "Content-Type", content_type);
+ u_map_copy_into(response->map_header, &configWeb.map_header);
+
+ if (ulfius_set_stream_response(response, 200, callback_static_file_stream, callback_static_file_stream_free,
+ length, STATIC_FILE_CHUNK, f) != U_OK) {
+ LOG_DEBUG("callback_static_file - Error ulfius_set_stream_response\n ");
+ }
+ }
+ } else {
+ if (configWeb.redirect_on_404 == NULL) {
+ ulfius_set_string_body_response(response, 404, "File not found");
+ } else {
+ ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
+ response->status = 302;
+ }
+ }
+ } else {
+ if (configWeb.redirect_on_404 == NULL) {
+ ulfius_set_string_body_response(response, 404, "File not found");
+ } else {
+ ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
+ response->status = 302;
+ }
+ }
+
+ o_free(file_path);
+ o_free(url_dup_save);
+ free(real_path); // realpath uses malloc
+ return U_CALLBACK_CONTINUE;
+ } else {
+ LOG_DEBUG("Static File Server - Error, user_data is NULL or inconsistent\n");
+ return U_CALLBACK_ERROR;
+ }
+}
+
+static void handleWebResponse() {}
+
+/*
+ * Adapt the radioapi to the Webservice handleAPIv1ToRadio
+ * Trigger : WebGui(SAVE)->WebServcice->phoneApi
+ */
+int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
+{
+ LOG_DEBUG("handleAPIv1ToRadio web -> radio \n");
+
+ ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Headers", "Content-Type");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "PUT, OPTIONS");
+ ulfius_add_header_to_response(res, "X-Protobuf-Schema",
+ "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
+
+ if (req->http_verb == "OPTIONS") {
+ ulfius_set_response_properties(res, U_OPT_STATUS, 204);
+ return U_CALLBACK_CONTINUE;
+ }
+
+ byte buffer[MAX_TO_FROM_RADIO_SIZE];
+ size_t s = req->binary_body_length;
+
+ memcpy(buffer, req->binary_body, MAX_TO_FROM_RADIO_SIZE);
+
+ // FIXME* Problem with portdunio loosing mountpoint maybe because of running in a real sep. thread
+
+ portduinoVFS->mountpoint(configWeb.rootPath);
+
+ LOG_DEBUG("Received %d bytes from PUT request\n", s);
+ webAPI.handleToRadio(buffer, s);
+ LOG_DEBUG("end web->radio \n");
+ return U_CALLBACK_COMPLETE;
+}
+
+/*
+ * Adapt the radioapi to the Webservice handleAPIv1FromRadio
+ * Trigger : WebGui(POLL)->handleAPIv1FromRadio->phoneapi->Meshtastic(Radio) events
+ */
+int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
+{
+
+ // LOG_DEBUG("handleAPIv1FromRadio radio -> web\n");
+ std::string valueAll;
+
+ // Status code is 200 OK by default.
+ ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "GET");
+ ulfius_add_header_to_response(res, "X-Protobuf-Schema",
+ "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
+
+ uint8_t txBuf[MAX_STREAM_BUF_SIZE];
+ uint32_t len = 1;
+
+ if (valueAll == "true") {
+ while (len) {
+ len = webAPI.getFromRadio(txBuf);
+ ulfius_set_response_properties(res, U_OPT_STATUS, 200, U_OPT_BINARY_BODY, txBuf, len);
+ const char *tmpa = (const char *)txBuf;
+ ulfius_set_string_body_response(res, 200, tmpa);
+ // LOG_DEBUG("\n----webAPI response all:----\n");
+ // LOG_DEBUG(tmpa);
+ // LOG_DEBUG("\n");
+ }
+ // Otherwise, just return one protobuf
+ } else {
+ len = webAPI.getFromRadio(txBuf);
+ const char *tmpa = (const char *)txBuf;
+ ulfius_set_binary_body_response(res, 200, tmpa, len);
+ // LOG_DEBUG("\n----webAPI response:\n");
+ // LOG_DEBUG(tmpa);
+ // LOG_DEBUG("\n");
+ }
+
+ // LOG_DEBUG("end radio->web\n", len);
+ return U_CALLBACK_COMPLETE;
+}
+
+/*
+OpenSSL RSA Key Gen
+*/
+int generate_rsa_key(EVP_PKEY **pkey)
+{
+ EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (!pkey_ctx)
+ return -1;
+ if (EVP_PKEY_keygen_init(pkey_ctx) <= 0)
+ return -1;
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, 2048) <= 0)
+ return -1;
+ if (EVP_PKEY_keygen(pkey_ctx, pkey) <= 0)
+ return -1;
+ EVP_PKEY_CTX_free(pkey_ctx);
+ return 0; // SUCCESS
+}
+
+int generate_self_signed_x509(EVP_PKEY *pkey, X509 **x509)
+{
+ *x509 = X509_new();
+ if (!*x509)
+ return -1;
+ if (X509_set_version(*x509, 2) != 1)
+ return -1;
+ ASN1_INTEGER_set(X509_get_serialNumber(*x509), 1);
+ X509_gmtime_adj(X509_get_notBefore(*x509), 0);
+ X509_gmtime_adj(X509_get_notAfter(*x509), 31536000L); // 1 YEAR ACCESS
+
+ X509_set_pubkey(*x509, pkey);
+
+ // SET Subject Name
+ X509_NAME *name = X509_get_subject_name(*x509);
+ X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"DE", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Meshtastic", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"meshtastic.local", -1, -1, 0);
+ // Selfsigned, Issuer = Subject
+ X509_set_issuer_name(*x509, name);
+
+ // Certificate signed with our privte key
+ if (X509_sign(*x509, pkey, EVP_sha256()) <= 0)
+ return -1;
+
+ return 0;
+}
+
+char *read_file_into_string(const char *filename)
+{
+ FILE *file = fopen(filename, "rb");
+ if (file == NULL) {
+ LOG_ERROR("Error reading File : %s \n", filename);
+ return NULL;
+ }
+
+ // Size of file
+ fseek(file, 0, SEEK_END);
+ long filesize = ftell(file);
+ rewind(file);
+
+ // reserve mem for file + 1 byte
+ char *buffer = (char *)malloc(filesize + 1);
+ if (buffer == NULL) {
+ LOG_ERROR("Malloc of mem failed for file : %s \n", filename);
+ fclose(file);
+ return NULL;
+ }
+
+ // read content
+ size_t readSize = fread(buffer, 1, filesize, file);
+ if (readSize != filesize) {
+ LOG_ERROR("Error reading file into buffer\n");
+ free(buffer);
+ fclose(file);
+ return NULL;
+ }
+
+ // add terminator sign at the end
+ buffer[filesize] = '\0';
+ fclose(file);
+ return buffer; // return pointer
+}
+
+int PiWebServerThread::CheckSSLandLoad()
+{
+ // read certificate
+ cert_pem = read_file_into_string("certificate.pem");
+ if (cert_pem == NULL) {
+ LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing\n");
+ return 1;
+ }
+ // read private key
+ key_pem = read_file_into_string("private_key.pem");
+ if (key_pem == NULL) {
+ LOG_ERROR("ERROR file private_key can't be loaded or is missing\n");
+ return 2;
+ }
+
+ return 0;
+}
+
+int PiWebServerThread::CreateSSLCertificate()
+{
+
+ EVP_PKEY *pkey = NULL;
+ X509 *x509 = NULL;
+
+ if (generate_rsa_key(&pkey) != 0) {
+ LOG_ERROR("Error generating RSA-Key.\n");
+ return 1;
+ }
+
+ if (generate_self_signed_x509(pkey, &x509) != 0) {
+ LOG_ERROR("Error generating of X509-Certificat.\n");
+ return 2;
+ }
+
+ // Ope file to write private key file
+ FILE *pkey_file = fopen("private_key.pem", "wb");
+ if (!pkey_file) {
+ LOG_ERROR("Error opening private key file.\n");
+ return 3;
+ }
+ // write private key file
+ PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL);
+ fclose(pkey_file);
+
+ // open Certificate file
+ FILE *x509_file = fopen("certificate.pem", "wb");
+ if (!x509_file) {
+ LOG_ERROR("Error opening certificate.\n");
+ return 4;
+ }
+ // write cirtificate
+ PEM_write_X509(x509_file, x509);
+ fclose(x509_file);
+
+ EVP_PKEY_free(pkey);
+ X509_free(x509);
+ LOG_INFO("Create SSL Certifictate -certificate.pem- succesfull \n");
+ return 0;
+}
+
+void initWebServer() {}
+
+PiWebServerThread::PiWebServerThread()
+{
+ int ret, retssl, webservport;
+
+ if (CheckSSLandLoad() != 0) {
+ CreateSSLCertificate();
+ if (CheckSSLandLoad() != 0) {
+ LOG_ERROR("Major Error Gen & Read SSL Certificate\n");
+ }
+ }
+
+ if (settingsMap[webserverport] != 0) {
+ webservport = settingsMap[webserverport];
+ LOG_INFO("Using webserver port from yaml config. %i \n", webservport);
+ } else {
+ LOG_INFO("Webserver port in yaml config set to 0, so defaulting to port 443.\n");
+ webservport = 443;
+ }
+
+ // Web Content Service Instance
+ if (ulfius_init_instance(&instanceWeb, webservport, NULL, DEFAULT_REALM) != U_OK) {
+ LOG_ERROR("Webserver couldn't be started, abort execution\n");
+ } else {
+
+ LOG_INFO("Webserver started ....\n");
+ u_map_init(&configWeb.mime_types);
+ u_map_put(&configWeb.mime_types, "*", "application/octet-stream");
+ u_map_put(&configWeb.mime_types, ".html", "text/html");
+ u_map_put(&configWeb.mime_types, ".htm", "text/html");
+ u_map_put(&configWeb.mime_types, ".tsx", "application/javascript");
+ u_map_put(&configWeb.mime_types, ".ts", "application/javascript");
+ u_map_put(&configWeb.mime_types, ".css", "text/css");
+ u_map_put(&configWeb.mime_types, ".js", "application/javascript");
+ u_map_put(&configWeb.mime_types, ".json", "application/json");
+ u_map_put(&configWeb.mime_types, ".png", "image/png");
+ u_map_put(&configWeb.mime_types, ".gif", "image/gif");
+ u_map_put(&configWeb.mime_types, ".jpeg", "image/jpeg");
+ u_map_put(&configWeb.mime_types, ".jpg", "image/jpeg");
+ u_map_put(&configWeb.mime_types, ".ttf", "font/ttf");
+ u_map_put(&configWeb.mime_types, ".woff", "font/woff");
+ u_map_put(&configWeb.mime_types, ".ico", "image/x-icon");
+ u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml");
+
+ webrootpath = settingsStrings[webserverrootpath];
+
+ configWeb.files_path = (char *)webrootpath.c_str();
+ configWeb.url_prefix = "";
+ configWeb.rootPath = strdup(portduinoVFS->mountpoint());
+
+ u_map_put(instanceWeb.default_headers, "Access-Control-Allow-Origin", "*");
+ // Maximum body size sent by the client is 1 Kb
+ instanceWeb.max_post_body_size = 1024;
+ ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL);
+ ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, configWeb.rootPath);
+
+ // Add callback function to all endpoints for the Web Server
+ ulfius_add_endpoint_by_val(&instanceWeb, "GET", NULL, "/*", 2, &callback_static_file, &configWeb);
+
+ // thats for serving without SSL
+ // retssl = ulfius_start_framework(&instanceWeb);
+
+ // thats for serving with SSL
+ retssl = ulfius_start_secure_framework(&instanceWeb, key_pem, cert_pem);
+
+ if (retssl == U_OK) {
+ LOG_INFO("Web Server framework started on port: %i \n", webservport);
+ LOG_INFO("Web Server root %s\n", (char *)webrootpath.c_str());
+ } else {
+ LOG_ERROR("Error starting Web Server framework, error number: %d\n", retssl);
+ }
+ }
+}
+
+PiWebServerThread::~PiWebServerThread()
+{
+ u_map_clean(&configWeb.mime_types);
+
+ ulfius_stop_framework(&instanceWeb);
+ ulfius_stop_framework(&instanceWeb);
+ free(configWeb.rootPath);
+ ulfius_clean_instance(&instanceService);
+ ulfius_clean_instance(&instanceService);
+ free(cert_pem);
+ LOG_INFO("End framework");
+}
+
+#endif
+#endif
\ No newline at end of file
diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h
new file mode 100644
index 000000000..c4c49e919
--- /dev/null
+++ b/src/mesh/raspihttp/PiWebServer.h
@@ -0,0 +1,61 @@
+#pragma once
+#ifdef PORTDUINO_LINUX_HARDWARE
+#if __has_include()
+#include "PhoneAPI.h"
+#include "ulfius-cfg.h"
+#include "ulfius.h"
+#include
+#include
+
+#define STATIC_FILE_CHUNK 256
+
+void initWebServer();
+void createSSLCert();
+int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data);
+const char *get_filename_ext(const char *path);
+
+struct _file_config {
+ char *files_path;
+ char *url_prefix;
+ struct _u_map mime_types;
+ struct _u_map map_header;
+ char *redirect_on_404;
+ char *rootPath;
+};
+
+class PiWebServerThread
+{
+ private:
+ char *key_pem = NULL;
+ char *cert_pem = NULL;
+ // struct _u_map mime_types;
+ std::string webrootpath;
+
+ public:
+ PiWebServerThread();
+ ~PiWebServerThread();
+ int CreateSSLCertificate();
+ int CheckSSLandLoad();
+ uint32_t requestRestart = 0;
+ struct _u_instance instanceWeb;
+ struct _u_instance instanceService;
+};
+
+class HttpAPI : public PhoneAPI
+{
+
+ public:
+ // Nothing here yet
+
+ private:
+ // Nothing here yet
+
+ protected:
+ /// Check the current underlying physical link to see if the client is currently connected
+ virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
+};
+
+extern PiWebServerThread *piwebServerThread;
+
+#endif
+#endif
\ No newline at end of file
diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp
index b0b033ba0..1de4d7669 100644
--- a/src/mesh/wifi/WiFiAPClient.cpp
+++ b/src/mesh/wifi/WiFiAPClient.cpp
@@ -1,16 +1,24 @@
-#include "mesh/wifi/WiFiAPClient.h"
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_WIFI
#include "NodeDB.h"
#include "RTC.h"
#include "concurrency/Periodic.h"
-#include "configuration.h"
+#include "mesh/wifi/WiFiAPClient.h"
+
#include "main.h"
#include "mesh/api/WiFiServerAPI.h"
+#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
+#endif
#include "target_specific.h"
#include
#include
#ifdef ARCH_ESP32
+#if !MESHTASTIC_EXCLUDE_WEBSERVER
+#if !MESHTASTIC_EXCLUDE_WEBSERVER
#include "mesh/http/WebServer.h"
+#endif
+#endif
#include
#include
static void WiFiEvent(WiFiEvent_t event);
@@ -92,11 +100,10 @@ static void onNetworkConnected()
syslog.enable();
}
-#ifdef ARCH_ESP32
+#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
initWebServer();
#endif
initApiServer();
-
APStartupComplete = true;
}
@@ -146,7 +153,6 @@ static int32_t reconnectWiFi()
perhapsSetRTC(RTCQualityNTP, &tv);
lastrun_ntp = millis();
-
} else {
LOG_DEBUG("NTP Update failed\n");
}
@@ -204,7 +210,9 @@ bool initWifi()
const char *wifiPsw = config.network.wifi_psk;
#ifndef ARCH_RP2040
- createSSLCert(); // For WebServer
+#if !MESHTASTIC_EXCLUDE_WEBSERVER
+ createSSLCert(); // For WebServer
+#endif
esp_wifi_set_storage(WIFI_STORAGE_RAM); // Disable flash storage for WiFi credentials
#endif
if (!*wifiPsw) // Treat empty password as no password
@@ -405,4 +413,5 @@ static void WiFiEvent(WiFiEvent_t event)
uint8_t getWifiDisconnectReason()
{
return wifiDisconnectReason;
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/wifi/WiFiAPClient.h b/src/mesh/wifi/WiFiAPClient.h
index 6625d3e46..5f4e2f5c9 100644
--- a/src/mesh/wifi/WiFiAPClient.h
+++ b/src/mesh/wifi/WiFiAPClient.h
@@ -5,7 +5,7 @@
#include
#include
-#if defined(HAS_WIFI) && !defined(ARCH_PORTDUINO)
+#if HAS_WIFI && !defined(ARCH_PORTDUINO)
#include
#endif
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 94df601d8..54eb577f7 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -4,7 +4,7 @@
#include "NodeDB.h"
#include "PowerFSM.h"
#include
-#ifdef ARCH_ESP32
+#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "BleOta.h"
#endif
#include "Router.h"
@@ -16,10 +16,12 @@
#ifdef ARCH_PORTDUINO
#include "unistd.h"
#endif
+#include "Default.h"
+#include "TypeConversions.h"
+#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
-
-#define DEFAULT_REBOOT_SECONDS 7
+#endif
AdminModule *adminModule;
bool hasOpenEditTransaction;
@@ -49,7 +51,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
// if handled == false, then let others look at this message also if they want
bool handled = false;
assert(r);
- bool fromOthers = mp.from != 0 && mp.from != nodeDB.getNodeNum();
+ bool fromOthers = mp.from != 0 && mp.from != nodeDB->getNodeNum();
switch (r->which_payload_variant) {
@@ -120,7 +122,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
}
case meshtastic_AdminMessage_reboot_ota_seconds_tag: {
int32_t s = r->reboot_ota_seconds;
-#ifdef ARCH_ESP32
+#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
if (BleOta::getOtaAppVersion().isEmpty()) {
LOG_INFO("No OTA firmware available, scheduling regular reboot in %d seconds\n", s);
screen->startRebootScreen();
@@ -149,13 +151,13 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
}
case meshtastic_AdminMessage_factory_reset_tag: {
LOG_INFO("Initiating factory reset\n");
- nodeDB.factoryReset();
+ nodeDB->factoryReset();
reboot(DEFAULT_REBOOT_SECONDS);
break;
}
case meshtastic_AdminMessage_nodedb_reset_tag: {
LOG_INFO("Initiating node-db reset\n");
- nodeDB.resetNodes();
+ nodeDB->resetNodes();
reboot(DEFAULT_REBOOT_SECONDS);
break;
}
@@ -185,7 +187,48 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
}
case meshtastic_AdminMessage_remove_by_nodenum_tag: {
LOG_INFO("Client is receiving a remove_nodenum command.\n");
- nodeDB.removeNodeByNum(r->remove_by_nodenum);
+ nodeDB->removeNodeByNum(r->remove_by_nodenum);
+ break;
+ }
+ case meshtastic_AdminMessage_set_favorite_node_tag: {
+ LOG_INFO("Client is receiving a set_favorite_node command.\n");
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node);
+ if (node != NULL) {
+ node->is_favorite = true;
+ }
+ break;
+ }
+ case meshtastic_AdminMessage_remove_favorite_node_tag: {
+ LOG_INFO("Client is receiving a remove_favorite_node command.\n");
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node);
+ if (node != NULL) {
+ node->is_favorite = false;
+ }
+ break;
+ }
+ case meshtastic_AdminMessage_set_fixed_position_tag: {
+ if (fromOthers) {
+ LOG_INFO("Ignoring set_fixed_position command from another node.\n");
+ } else {
+ LOG_INFO("Client is receiving a set_fixed_position command.\n");
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
+ node->has_position = true;
+ node->position = TypeConversions::ConvertToPositionLite(r->set_fixed_position);
+ nodeDB->setLocalPosition(r->set_fixed_position);
+ config.position.fixed_position = true;
+ saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false);
+ }
+ break;
+ }
+ case meshtastic_AdminMessage_remove_fixed_position_tag: {
+ if (fromOthers) {
+ LOG_INFO("Ignoring remove_fixed_position command from another node.\n");
+ } else {
+ LOG_INFO("Client is receiving a remove_fixed_position command.\n");
+ nodeDB->clearLocalPosition();
+ config.position.fixed_position = false;
+ saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false);
+ }
break;
}
case meshtastic_AdminMessage_enter_dfu_mode_request_tag: {
@@ -213,7 +256,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
default:
meshtastic_AdminMessage res = meshtastic_AdminMessage_init_default;
- AdminMessageHandleResult handleResult = MeshModule::handleAdminMessageForAllPlugins(mp, r, &res);
+ AdminMessageHandleResult handleResult = MeshModule::handleAdminMessageForAllModules(mp, r, &res);
if (handleResult == AdminMessageHandleResult::HANDLED_WITH_RESPONSE) {
myReply = allocDataProtobuf(res);
@@ -293,6 +336,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
auto changes = SEGMENT_CONFIG;
auto existingRole = config.device.role;
bool isRegionUnset = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
+ bool requiresReboot = true;
switch (c.which_payload_variant) {
case meshtastic_Config_device_tag:
@@ -301,7 +345,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
config.device = c.payload_variant.device;
// If we're setting router role for the first time, install its intervals
if (existingRole != c.payload_variant.device.role)
- nodeDB.installRoleDefaults(c.payload_variant.device.role);
+ nodeDB->installRoleDefaults(c.payload_variant.device.role);
+ if (config.device.node_info_broadcast_secs < min_node_info_broadcast_secs) {
+ LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs);
+ config.device.node_info_broadcast_secs = min_node_info_broadcast_secs;
+ }
break;
case meshtastic_Config_position_tag:
LOG_INFO("Setting config: Position\n");
@@ -328,10 +376,27 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
case meshtastic_Config_lora_tag:
LOG_INFO("Setting config: LoRa\n");
config.has_lora = true;
+ // If no lora radio parameters change, don't need to reboot
+ if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region &&
+ config.lora.modem_preset == c.payload_variant.lora.modem_preset &&
+ config.lora.bandwidth == c.payload_variant.lora.bandwidth &&
+ config.lora.spread_factor == c.payload_variant.lora.spread_factor &&
+ config.lora.coding_rate == c.payload_variant.lora.coding_rate &&
+ config.lora.tx_power == c.payload_variant.lora.tx_power &&
+ config.lora.frequency_offset == c.payload_variant.lora.frequency_offset &&
+ config.lora.override_frequency == c.payload_variant.lora.override_frequency &&
+ config.lora.channel_num == c.payload_variant.lora.channel_num &&
+ config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) {
+ requiresReboot = false;
+ }
config.lora = c.payload_variant.lora;
+ // If we're setting region for the first time, init the region
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
config.lora.tx_enabled = true;
initRegion();
+ if (myRegion->dutyCycle < 100) {
+ config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
+ }
if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) {
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
@@ -345,7 +410,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
break;
}
- saveChanges(changes);
+ saveChanges(changes, requiresReboot);
}
void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
@@ -600,7 +665,7 @@ void AdminModule::handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &r
continue;
}
meshtastic_NodeRemoteHardwarePin nodePin = meshtastic_NodeRemoteHardwarePin_init_default;
- nodePin.node_num = nodeDB.getNodeNum();
+ nodePin.node_num = nodeDB->getNodeNum();
nodePin.pin = moduleConfig.remote_hardware.available_pins[i];
r.get_node_remote_hardware_pins_response.node_remote_hardware_pins[i + 12] = nodePin;
}
@@ -644,7 +709,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
if (Ethernet.linkStatus() == LinkON) {
conn.ethernet.status.is_connected = true;
conn.ethernet.status.ip_address = Ethernet.localIP();
+#if !MESHTASTIC_EXCLUDE_MQTT
conn.ethernet.status.is_mqtt_connected = mqtt && mqtt->isConnectedDirectly();
+#endif
conn.ethernet.status.is_syslog_connected = false; // FIXME wire this up
} else {
conn.ethernet.status.is_connected = false;
diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h
index 6ecc88829..32b32c253 100644
--- a/src/modules/AdminModule.h
+++ b/src/modules/AdminModule.h
@@ -1,6 +1,6 @@
#pragma once
#include "ProtobufModule.h"
-#if HAS_WIFI
+#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp
index ffc4fe68a..64a85e2bf 100644
--- a/src/modules/AtakPluginModule.cpp
+++ b/src/modules/AtakPluginModule.cpp
@@ -1,4 +1,5 @@
#include "AtakPluginModule.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index 3127b0986..cbd6fee72 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -12,46 +12,17 @@
#include "detect/ScanI2C.h"
#include "mesh/generated/meshtastic/cannedmessages.pb.h"
-#include "main.h" // for cardkb_found
+#include "main.h" // for cardkb_found
+#include "modules/ExternalNotificationModule.h" // for buzzer control
+#if !MESHTASTIC_EXCLUDE_GPS
+#include "GPS.h"
+#endif
#ifndef INPUTBROKER_MATRIX_TYPE
#define INPUTBROKER_MATRIX_TYPE 0
#endif
-#ifdef OLED_RU
-#include "graphics/fonts/OLEDDisplayFontsRU.h"
-#endif
-
-#ifdef OLED_UA
-#include "graphics/fonts/OLEDDisplayFontsUA.h"
-#endif
-
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
- !defined(DISPLAY_FORCE_SMALL_FONTS)
-
-// The screen is bigger so use bigger fonts
-#define FONT_SMALL ArialMT_Plain_16
-#define FONT_MEDIUM ArialMT_Plain_24
-#define FONT_LARGE ArialMT_Plain_24
-#else
-#ifdef OLED_RU
-#define FONT_SMALL ArialMT_Plain_10_RU
-#else
-#ifdef OLED_UA
-#define FONT_SMALL ArialMT_Plain_10_UA
-#else
-#define FONT_SMALL ArialMT_Plain_10
-#endif
-#endif
-#define FONT_MEDIUM ArialMT_Plain_16
-#define FONT_LARGE ArialMT_Plain_24
-#endif
-
-#define fontHeight(font) ((font)[1] + 1) // height is position 1
-
-#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
-#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
-#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
+#include "graphics/ScreenFonts.h"
// Remove Canned message screen if no action is taken for some milliseconds
#define INACTIVATE_AFTER_MS 20000
@@ -344,18 +315,18 @@ int32_t CannedMessageModule::runOnce()
switch (this->payload) {
case 0xb4: // left
if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) {
- size_t numMeshNodes = nodeDB.getNumMeshNodes();
+ size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (this->dest == NODENUM_BROADCAST) {
- this->dest = nodeDB.getNodeNum();
+ this->dest = nodeDB->getNodeNum();
}
for (unsigned int i = 0; i < numMeshNodes; i++) {
- if (nodeDB.getMeshNodeByIndex(i)->num == this->dest) {
+ if (nodeDB->getMeshNodeByIndex(i)->num == this->dest) {
this->dest =
- (i > 0) ? nodeDB.getMeshNodeByIndex(i - 1)->num : nodeDB.getMeshNodeByIndex(numMeshNodes - 1)->num;
+ (i > 0) ? nodeDB->getMeshNodeByIndex(i - 1)->num : nodeDB->getMeshNodeByIndex(numMeshNodes - 1)->num;
break;
}
}
- if (this->dest == nodeDB.getNodeNum()) {
+ if (this->dest == nodeDB->getNodeNum()) {
this->dest = NODENUM_BROADCAST;
}
} else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) {
@@ -379,18 +350,18 @@ int32_t CannedMessageModule::runOnce()
break;
case 0xb7: // right
if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) {
- size_t numMeshNodes = nodeDB.getNumMeshNodes();
+ size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (this->dest == NODENUM_BROADCAST) {
- this->dest = nodeDB.getNodeNum();
+ this->dest = nodeDB->getNodeNum();
}
for (unsigned int i = 0; i < numMeshNodes; i++) {
- if (nodeDB.getMeshNodeByIndex(i)->num == this->dest) {
+ if (nodeDB->getMeshNodeByIndex(i)->num == this->dest) {
this->dest =
- (i < numMeshNodes - 1) ? nodeDB.getMeshNodeByIndex(i + 1)->num : nodeDB.getMeshNodeByIndex(0)->num;
+ (i < numMeshNodes - 1) ? nodeDB->getMeshNodeByIndex(i + 1)->num : nodeDB->getMeshNodeByIndex(0)->num;
break;
}
}
- if (this->dest == nodeDB.getNodeNum()) {
+ if (this->dest == nodeDB->getNodeNum()) {
this->dest = NODENUM_BROADCAST;
}
} else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) {
@@ -430,6 +401,7 @@ int32_t CannedMessageModule::runOnce()
}
break;
case 0x09: // tab
+ case 0x91: // alt+t for T-Deck that doesn't have a tab key
if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) {
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
} else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) {
@@ -442,6 +414,47 @@ int32_t CannedMessageModule::runOnce()
case 0xb7: // right
// already handled above
break;
+ // handle fn+s for shutdown
+ case 0x9b:
+ if (screen)
+ screen->startShutdownScreen();
+ shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000;
+ runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ break;
+ // and fn+r for reboot
+ case 0x90:
+ if (screen)
+ screen->startRebootScreen();
+ rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
+ runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ break;
+ case 0x9e: // toggle GPS like triple press does
+ if (gps != nullptr) {
+ gps->toggleGpsMode();
+ }
+ if (screen)
+ screen->forceDisplay();
+ runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ break;
+
+ // mute (switch off/toggle) external notifications on fn+m
+ case 0xac:
+ if (moduleConfig.external_notification.enabled == true) {
+ if (externalNotificationModule->getMute()) {
+ externalNotificationModule->setMute(false);
+ runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ } else {
+ externalNotificationModule->stopNow(); // this will turn off all GPIO and sounds and idle the loop
+ externalNotificationModule->setMute(true);
+ runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ }
+ }
+ break;
+ case 0xaf: // fn+space send network ping like double press does
+ service.refreshLocalMeshNode();
+ service.sendNetworkPing(NODENUM_BROADCAST, true);
+ runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ break;
default:
if (this->cursor == this->freetext.length()) {
this->freetext += this->payload;
@@ -495,7 +508,7 @@ const char *CannedMessageModule::getNodeName(NodeNum node)
if (node == NODENUM_BROADCAST) {
return "Broadcast";
} else {
- meshtastic_NodeInfoLite *info = nodeDB.getMeshNode(node);
+ meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node);
if (info != NULL) {
return info->user.long_name;
} else {
@@ -651,9 +664,9 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
void CannedMessageModule::loadProtoForModule()
{
- if (!nodeDB.loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
+ if (nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg,
- &cannedMessageModuleConfig)) {
+ &cannedMessageModuleConfig) != LoadFileResult::SUCCESS) {
installDefaultCannedMessageModuleConfig();
}
}
@@ -672,8 +685,8 @@ bool CannedMessageModule::saveProtoForModule()
FS.mkdir("/prefs");
#endif
- okay &= nodeDB.saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
- &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
+ okay &= nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
+ &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
return okay;
}
diff --git a/src/modules/DetectionSensorModule.cpp b/src/modules/DetectionSensorModule.cpp
index 6c35e94ae..fd26749c1 100644
--- a/src/modules/DetectionSensorModule.cpp
+++ b/src/modules/DetectionSensorModule.cpp
@@ -1,10 +1,10 @@
#include "DetectionSensorModule.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "main.h"
-
DetectionSensorModule *detectionSensorModule;
#define GPIO_POLLING_INTERVAL 100
@@ -49,7 +49,7 @@ int32_t DetectionSensorModule::runOnce()
// LOG_DEBUG("Detection Sensor Module: Current pin state: %i\n", digitalRead(moduleConfig.detection_sensor.monitor_pin));
- if ((millis() - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) &&
+ if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) &&
hasDetectionEvent()) {
sendDetectionMessage();
return DELAYED_INTERVAL;
@@ -58,7 +58,8 @@ int32_t DetectionSensorModule::runOnce()
// of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state
// change detections.
else if (moduleConfig.detection_sensor.state_broadcast_secs > 0 &&
- (millis() - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs)) {
+ (millis() - lastSentToMesh) >=
+ Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs)) {
sendCurrentStateMessage();
return DELAYED_INTERVAL;
}
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 9af1f9e00..b898e72ee 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -81,7 +81,7 @@ int32_t ExternalNotificationModule::runOnce()
// let the song finish if we reach timeout
nagCycleCutoff = UINT32_MAX;
LOG_INFO("Turning off external notification: ");
- for (int i = 0; i < 2; i++) {
+ for (int i = 0; i < 3; i++) {
setExternalOff(i);
externalTurnedOn[i] = 0;
LOG_INFO("%d ", i);
@@ -244,7 +244,8 @@ void ExternalNotificationModule::stopNow()
{
rtttl::stop();
#ifdef HAS_I2S
- audioThread->stop();
+ if (audioThread->isPlaying())
+ audioThread->stop();
#endif
nagCycleCutoff = 1; // small value
isNagging = false;
@@ -284,8 +285,8 @@ ExternalNotificationModule::ExternalNotificationModule()
// moduleConfig.external_notification.alert_message_buzzer = true;
if (moduleConfig.external_notification.enabled) {
- if (!nodeDB.loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig),
- &meshtastic_RTTTLConfig_msg, &rtttlConfig)) {
+ if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig),
+ &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::SUCCESS) {
memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone));
strncpy(rtttlConfig.ringtone,
"24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p",
@@ -336,14 +337,14 @@ ExternalNotificationModule::ExternalNotificationModule()
ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp)
{
- if (moduleConfig.external_notification.enabled) {
+ if (moduleConfig.external_notification.enabled && !isMuted) {
#ifdef T_WATCH_S3
drv.setWaveform(0, 75);
drv.setWaveform(1, 56);
drv.setWaveform(2, 0);
drv.go();
#endif
- if (getFrom(&mp) != nodeDB.getNodeNum()) {
+ if (getFrom(&mp) != nodeDB->getNodeNum()) {
// Check if the message contains a bell character. Don't do this loop for every pin, just once.
auto &p = mp.decoded;
bool containsBell = false;
@@ -445,7 +446,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
setIntervalFromNow(0); // run once so we know if we should do something
}
} else {
- LOG_INFO("External Notification Module Disabled\n");
+ LOG_INFO("External Notification Module Disabled or muted\n");
}
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
@@ -506,6 +507,6 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg)
}
if (changed) {
- nodeDB.saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig);
+ nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig);
}
}
\ No newline at end of file
diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h
index 3331ec428..08e72c35a 100644
--- a/src/modules/ExternalNotificationModule.h
+++ b/src/modules/ExternalNotificationModule.h
@@ -38,6 +38,9 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
void setExternalOff(uint8_t index = 0);
bool getExternal(uint8_t index = 0);
+ void setMute(bool mute) { isMuted = mute; }
+ bool getMute() { return isMuted; }
+
void stopNow();
void handleGetRingtone(const meshtastic_MeshPacket &req, meshtastic_AdminMessage *response);
@@ -56,6 +59,8 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
bool isNagging = false;
+ bool isMuted = false;
+
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response) override;
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 07c79e7d3..33ae9cadc 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -1,4 +1,5 @@
#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_INPUTBROKER
#include "input/InputBroker.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/TrackballInterruptImpl1.h"
@@ -6,43 +7,68 @@
#include "input/cardKbI2cImpl.h"
#include "input/kbMatrixImpl.h"
#include "input/kbUsbImpl.h"
+#endif
+
#include "modules/AdminModule.h"
+#if !MESHTASTIC_EXCLUDE_ATAK
#include "modules/AtakPluginModule.h"
+#endif
+#if !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
#include "modules/CannedMessageModule.h"
+#endif
+#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
#include "modules/DetectionSensorModule.h"
+#endif
+#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
#include "modules/NeighborInfoModule.h"
+#endif
#include "modules/NodeInfoModule.h"
+#if !MESHTASTIC_EXCLUDE_GPS
#include "modules/PositionModule.h"
+#endif
+#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
#include "modules/RemoteHardwareModule.h"
-#include "modules/ReplyModule.h"
+#endif
#include "modules/RoutingModule.h"
#include "modules/TextMessageModule.h"
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
#include "modules/TraceRouteModule.h"
+#endif
+#if !MESHTASTIC_EXCLUDE_WAYPOINT
#include "modules/WaypointModule.h"
+#endif
#if ARCH_PORTDUINO
#include "input/LinuxInputImpl.h"
#endif
#if HAS_TELEMETRY
#include "modules/Telemetry/DeviceTelemetry.h"
#endif
-#if HAS_SENSOR
+#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "modules/Telemetry/AirQualityTelemetry.h"
#include "modules/Telemetry/EnvironmentTelemetry.h"
#endif
-#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
+#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
#include "modules/Telemetry/PowerTelemetry.h"
#endif
#ifdef ARCH_ESP32
-#ifdef USE_SX1280
+#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
#include "modules/esp32/AudioModule.h"
#endif
+#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
#include "modules/esp32/PaxcounterModule.h"
+#endif
+#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/esp32/StoreForwardModule.h"
#endif
+#endif
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)
+#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
#include "modules/ExternalNotificationModule.h"
+#endif
+#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
#include "modules/RangeTestModule.h"
-#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2)
+#endif
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_SERIAL
#include "modules/SerialModule.h"
#endif
#endif
@@ -52,24 +78,39 @@
void setupModules()
{
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
-#if HAS_BUTTON || ARCH_PORTDUINO
+#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
inputBroker = new InputBroker();
#endif
adminModule = new AdminModule();
nodeInfoModule = new NodeInfoModule();
+#if !MESHTASTIC_EXCLUDE_GPS
positionModule = new PositionModule();
+#endif
+#if !MESHTASTIC_EXCLUDE_WAYPOINT
waypointModule = new WaypointModule();
+#endif
textMessageModule = new TextMessageModule();
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
traceRouteModule = new TraceRouteModule();
+#endif
+#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
neighborInfoModule = new NeighborInfoModule();
+#endif
+#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
detectionSensorModule = new DetectionSensorModule();
+#endif
+#if !MESHTASTIC_EXCLUDE_ATAK
atakPluginModule = new AtakPluginModule();
+#endif
// Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
// to a global variable.
+#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
new RemoteHardwareModule();
- new ReplyModule();
-#if HAS_BUTTON || ARCH_PORTDUINO
+#endif
+ // Example: Put your module here
+ // new ReplyModule();
+#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
@@ -95,47 +136,59 @@ void setupModules()
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
#endif
-#if HAS_TRACKBALL
+#if HAS_TRACKBALL && !MESHTASTIC_EXCLUDE_INPUTBROKER
trackballInterruptImpl1 = new TrackballInterruptImpl1();
trackballInterruptImpl1->init();
#endif
-#if HAS_SCREEN
+#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
cannedMessageModule = new CannedMessageModule();
#endif
-#if HAS_TELEMETRY
+#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
new DeviceTelemetryModule();
#endif
-#if HAS_SENSOR
+#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
new EnvironmentTelemetryModule();
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
new AirQualityTelemetryModule();
}
#endif
-#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
+#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
new PowerTelemetryModule();
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
!defined(CONFIG_IDF_TARGET_ESP32C3)
+#if !MESHTASTIC_EXCLUDE_SERIAL
new SerialModule();
#endif
+#endif
#ifdef ARCH_ESP32
// Only run on an esp32 based device.
-#ifdef USE_SX1280
+#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
audioModule = new AudioModule();
#endif
+#if !MESHTASTIC_EXCLUDE_STOREFORWARD
storeForwardModule = new StoreForwardModule();
+#endif
+#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
paxcounterModule = new PaxcounterModule();
#endif
+#endif
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)
+#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
externalNotificationModule = new ExternalNotificationModule();
+#endif
+#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
new RangeTestModule();
+#endif
#endif
} else {
adminModule = new AdminModule();
#if HAS_TELEMETRY
new DeviceTelemetryModule();
#endif
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
traceRouteModule = new TraceRouteModule();
+#endif
}
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
// acks
diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp
index 4541958fa..8c8135deb 100644
--- a/src/modules/NeighborInfoModule.cpp
+++ b/src/modules/NeighborInfoModule.cpp
@@ -1,13 +1,11 @@
#include "NeighborInfoModule.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
-#define MAX_NUM_NEIGHBORS 10 // also defined in NeighborInfo protobuf options
NeighborInfoModule *neighborInfoModule;
-static const char *neighborInfoConfigFile = "/prefs/neighbors.proto";
-
/*
Prints a single neighbor info packet and associated neighbors
Uses LOG_DEBUG, which equates to Console.log
@@ -16,87 +14,36 @@ NOTE: For debugging only
void NeighborInfoModule::printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np)
{
LOG_DEBUG("%s NEIGHBORINFO PACKET from Node 0x%x to Node 0x%x (last sent by 0x%x)\n", header, np->node_id,
- nodeDB.getNodeNum(), np->last_sent_by_id);
- LOG_DEBUG("----------------\n");
+ nodeDB->getNodeNum(), np->last_sent_by_id);
LOG_DEBUG("Packet contains %d neighbors\n", np->neighbors_count);
for (int i = 0; i < np->neighbors_count; i++) {
LOG_DEBUG("Neighbor %d: node_id=0x%x, snr=%.2f\n", i, np->neighbors[i].node_id, np->neighbors[i].snr);
}
- LOG_DEBUG("----------------\n");
-}
-/*
-Prints the nodeDB nodes so we can see whose nodeInfo we have
-NOTE: for debugging only
-*/
-void NeighborInfoModule::printNodeDBNodes(const char *header)
-{
- int num_nodes = nodeDB.getNumMeshNodes();
- LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum());
- LOG_DEBUG("----------------\n");
- LOG_DEBUG("DB contains %d nodes\n", num_nodes);
- for (int i = 0; i < num_nodes; i++) {
- const meshtastic_NodeInfoLite *dbEntry = nodeDB.getMeshNodeByIndex(i);
- LOG_DEBUG(" Node %d: node_id=0x%x, snr=%.2f\n", i, dbEntry->num, dbEntry->snr);
- }
- LOG_DEBUG("----------------\n");
}
/*
Prints the nodeDB neighbors
NOTE: for debugging only
*/
-void NeighborInfoModule::printNodeDBNeighbors(const char *header)
+void NeighborInfoModule::printNodeDBNeighbors()
{
- int num_neighbors = getNumNeighbors();
- LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum());
- LOG_DEBUG("----------------\n");
- LOG_DEBUG("DB contains %d neighbors\n", num_neighbors);
- for (int i = 0; i < num_neighbors; i++) {
- const meshtastic_Neighbor *dbEntry = getNeighborByIndex(i);
- LOG_DEBUG(" Node %d: node_id=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr);
+ LOG_DEBUG("Our NodeDB contains %d neighbors\n", neighbors.size());
+ for (size_t i = 0; i < neighbors.size(); i++) {
+ LOG_DEBUG("Node %d: node_id=0x%x, snr=%.2f\n", i, neighbors[i].node_id, neighbors[i].snr);
}
- LOG_DEBUG("----------------\n");
-}
-
-/*
-Prints the nodeDB with selectors for the neighbors we've chosen to send (inefficiently)
-Uses LOG_DEBUG, which equates to Console.log
-NOTE: For debugging only
-*/
-void NeighborInfoModule::printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np)
-{
- int num_neighbors = getNumNeighbors();
- LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum());
- LOG_DEBUG("----------------\n");
- LOG_DEBUG("Selected %d neighbors of %d DB neighbors\n", np->neighbors_count, num_neighbors);
- for (int i = 0; i < num_neighbors; i++) {
- meshtastic_Neighbor *dbEntry = getNeighborByIndex(i);
- bool chosen = false;
- for (int j = 0; j < np->neighbors_count; j++) {
- if (np->neighbors[j].node_id == dbEntry->node_id) {
- chosen = true;
- }
- }
- if (!chosen) {
- LOG_DEBUG(" Node %d: neighbor=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr);
- } else {
- LOG_DEBUG("---> Node %d: neighbor=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr);
- }
- }
- LOG_DEBUG("----------------\n");
}
/* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */
NeighborInfoModule::NeighborInfoModule()
: ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg),
- concurrency::OSThread("NeighborInfoModule"), neighbors(neighborState.neighbors),
- numNeighbors(&neighborState.neighbors_count)
+ concurrency::OSThread("NeighborInfoModule")
{
ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP;
if (moduleConfig.neighbor_info.enabled) {
- this->loadProtoForModule();
- setIntervalFromNow(35 * 1000);
+ isPromiscuous = true; // Update neighbors from all packets
+ setIntervalFromNow(
+ Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs));
} else {
LOG_DEBUG("NeighborInfoModule is disabled\n");
disable();
@@ -110,65 +57,43 @@ Assumes that the neighborInfo packet has been allocated
*/
uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo)
{
- uint my_node_id = nodeDB.getNodeNum();
+ NodeNum my_node_id = nodeDB->getNodeNum();
neighborInfo->node_id = my_node_id;
neighborInfo->last_sent_by_id = my_node_id;
neighborInfo->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval;
- int num_neighbors = cleanUpNeighbors();
+ cleanUpNeighbors();
- for (int i = 0; i < num_neighbors; i++) {
- const meshtastic_Neighbor *dbEntry = getNeighborByIndex(i);
- if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (dbEntry->node_id != my_node_id)) {
- neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = dbEntry->node_id;
- neighborInfo->neighbors[neighborInfo->neighbors_count].snr = dbEntry->snr;
+ for (auto nbr : neighbors) {
+ if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) {
+ neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id;
+ neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr;
// Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over
// the mesh
neighborInfo->neighbors_count++;
}
}
- printNodeDBNodes("DBSTATE");
- printNodeDBNeighbors("NEIGHBORS");
- printNodeDBSelection("COLLECTED", neighborInfo);
+ printNodeDBNeighbors();
return neighborInfo->neighbors_count;
}
/*
-Remove neighbors from the database that we haven't heard from in a while
-@returns new number of neighbors
+ Remove neighbors from the database that we haven't heard from in a while
*/
-size_t NeighborInfoModule::cleanUpNeighbors()
+void NeighborInfoModule::cleanUpNeighbors()
{
uint32_t now = getTime();
- int num_neighbors = getNumNeighbors();
- NodeNum my_node_id = nodeDB.getNodeNum();
-
- // Find neighbors to remove
- std::vector indices_to_remove;
- for (int i = 0; i < num_neighbors; i++) {
- const meshtastic_Neighbor *dbEntry = getNeighborByIndex(i);
+ NodeNum my_node_id = nodeDB->getNodeNum();
+ for (auto it = neighbors.rbegin(); it != neighbors.rend();) {
// We will remove a neighbor if we haven't heard from them in twice the broadcast interval
- if ((now - dbEntry->last_rx_time > dbEntry->node_broadcast_interval_secs * 2) && (dbEntry->node_id != my_node_id)) {
- indices_to_remove.push_back(i);
+ if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) {
+ LOG_DEBUG("Removing neighbor with node ID 0x%x\n", it->node_id);
+ it = std::vector::reverse_iterator(
+ neighbors.erase(std::next(it).base())); // Erase the element and update the iterator
+ } else {
+ ++it;
}
}
-
- // Update the neighbor list
- for (uint i = 0; i < indices_to_remove.size(); i++) {
- int index = indices_to_remove[i];
- LOG_DEBUG("Removing neighbor with node ID 0x%x\n", neighbors[index].node_id);
- for (int j = index; j < num_neighbors - 1; j++) {
- neighbors[j] = neighbors[j + 1];
- }
- (*numNeighbors)--;
- }
-
- // Save the neighbor list if we removed any neighbors
- if (indices_to_remove.size() > 0) {
- saveProtoForModule();
- }
-
- return *numNeighbors;
}
/* Send neighbor info to the mesh */
@@ -192,8 +117,10 @@ Will be used for broadcast.
int32_t NeighborInfoModule::runOnce()
{
bool requestReplies = false;
- sendNeighborInfo(NODENUM_BROADCAST, requestReplies);
- return getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs);
+ if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) {
+ sendNeighborInfo(NODENUM_BROADCAST, requestReplies);
+ }
+ return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs);
}
/*
@@ -202,9 +129,12 @@ Pass it to an upper client; do not persist this data on the mesh
*/
bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np)
{
- if (enabled) {
+ if (np) {
printNeighborInfo("RECEIVED", np);
updateNeighbors(mp, np);
+ } else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
+ // If the hopLimit is the same as hopStart, then it is a neighbor
+ getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
}
// Allow others to handle this packet
return false;
@@ -213,28 +143,18 @@ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
/*
Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum
*/
-void NeighborInfoModule::updateLastSentById(meshtastic_MeshPacket *p)
+void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n)
{
- auto &incoming = p->decoded;
- meshtastic_NeighborInfo scratch;
- meshtastic_NeighborInfo *updated = NULL;
- memset(&scratch, 0, sizeof(scratch));
- pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &meshtastic_NeighborInfo_msg, &scratch);
- updated = &scratch;
-
- updated->last_sent_by_id = nodeDB.getNodeNum();
+ n->last_sent_by_id = nodeDB->getNodeNum();
// Set updated last_sent_by_id to the payload of the to be flooded packet
- p->decoded.payload.size =
- pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_NeighborInfo_msg, updated);
+ p.decoded.payload.size =
+ pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_NeighborInfo_msg, n);
}
void NeighborInfoModule::resetNeighbors()
{
- *numNeighbors = 0;
- neighborState.neighbors_count = 0;
- memset(neighborState.neighbors, 0, sizeof(neighborState.neighbors));
- saveProtoForModule();
+ neighbors.clear();
}
void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np)
@@ -251,61 +171,39 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
{
// our node and the phone are the same node (not neighbors)
if (n == 0) {
- n = nodeDB.getNodeNum();
+ n = nodeDB->getNodeNum();
}
// look for one in the existing list
- for (int i = 0; i < (*numNeighbors); i++) {
- meshtastic_Neighbor *nbr = &neighbors[i];
- if (nbr->node_id == n) {
+ for (size_t i = 0; i < neighbors.size(); i++) {
+ if (neighbors[i].node_id == n) {
// if found, update it
- nbr->snr = snr;
- nbr->last_rx_time = getTime();
+ neighbors[i].snr = snr;
+ neighbors[i].last_rx_time = getTime();
// Only if this is the original sender, the broadcast interval corresponds to it
- if (originalSender == n)
- nbr->node_broadcast_interval_secs = node_broadcast_interval_secs;
- saveProtoForModule(); // Save the updated neighbor
- return nbr;
+ if (originalSender == n && node_broadcast_interval_secs != 0)
+ neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs;
+ return &neighbors[i];
}
}
// otherwise, allocate one and assign data to it
- // TODO: max memory for the database should take neighbors into account, but currently doesn't
- if (*numNeighbors < MAX_NUM_NEIGHBORS) {
- (*numNeighbors)++;
- }
- meshtastic_Neighbor *new_nbr = &neighbors[((*numNeighbors) - 1)];
- new_nbr->node_id = n;
- new_nbr->snr = snr;
- new_nbr->last_rx_time = getTime();
+
+ meshtastic_Neighbor new_nbr = meshtastic_Neighbor_init_zero;
+ new_nbr.node_id = n;
+ new_nbr.snr = snr;
+ new_nbr.last_rx_time = getTime();
// Only if this is the original sender, the broadcast interval corresponds to it
- if (originalSender == n)
- new_nbr->node_broadcast_interval_secs = node_broadcast_interval_secs;
- saveProtoForModule(); // Save the new neighbor
- return new_nbr;
-}
+ if (originalSender == n && node_broadcast_interval_secs != 0)
+ new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs;
+ else // Assume the same broadcast interval as us for the neighbor if we don't know it
+ new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval;
-void NeighborInfoModule::loadProtoForModule()
-{
- if (!nodeDB.loadProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, sizeof(meshtastic_NeighborInfo),
- &meshtastic_NeighborInfo_msg, &neighborState)) {
- neighborState = meshtastic_NeighborInfo_init_zero;
+ if (neighbors.size() < MAX_NUM_NEIGHBORS) {
+ neighbors.push_back(new_nbr);
+ } else {
+ // If we have too many neighbors, replace the oldest one
+ LOG_WARN("Neighbor DB is full, replacing oldest neighbor\n");
+ neighbors.erase(neighbors.begin());
+ neighbors.push_back(new_nbr);
}
-}
-
-/**
- * @brief Save the module config to file.
- *
- * @return true On success.
- * @return false On error.
- */
-bool NeighborInfoModule::saveProtoForModule()
-{
- bool okay = true;
-
-#ifdef FS
- FS.mkdir("/prefs");
-#endif
-
- okay &= nodeDB.saveProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, &meshtastic_NeighborInfo_msg, &neighborState);
-
- return okay;
+ return &neighbors.back();
}
\ No newline at end of file
diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h
index 0e3ec09ca..496fdece5 100644
--- a/src/modules/NeighborInfoModule.h
+++ b/src/modules/NeighborInfoModule.h
@@ -1,13 +1,13 @@
#pragma once
#include "ProtobufModule.h"
+#define MAX_NUM_NEIGHBORS 10 // also defined in NeighborInfo protobuf options
/*
* Neighborinfo module for sending info on each node's 0-hop neighbors to the mesh
*/
class NeighborInfoModule : public ProtobufModule, private concurrency::OSThread
{
- meshtastic_Neighbor *neighbors;
- pb_size_t *numNeighbors;
+ std::vector neighbors;
public:
/*
@@ -18,15 +18,7 @@ class NeighborInfoModule : public ProtobufModule, priva
/* Reset neighbor info after clearing nodeDB*/
void resetNeighbors();
- bool saveProtoForModule();
-
- // Let FloodingRouter call updateLastSentById upon rebroadcasting a NeighborInfo packet
- friend class FloodingRouter;
-
protected:
- // Note: this holds our local info.
- meshtastic_NeighborInfo neighborState;
-
/*
* Called to handle a particular incoming message
* @return true if you've guaranteed you've handled this message and no other handlers should be considered for it
@@ -40,10 +32,9 @@ class NeighborInfoModule : public ProtobufModule, priva
uint32_t collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo);
/*
- Remove neighbors from the database that we haven't heard from in a while
- @returns new number of neighbors
+ Remove neighbors from the database that we haven't heard from in a while
*/
- size_t cleanUpNeighbors();
+ void cleanUpNeighbors();
/* Allocate a new NeighborInfo packet */
meshtastic_NeighborInfo *allocateNeighborInfoPacket();
@@ -56,29 +47,21 @@ class NeighborInfoModule : public ProtobufModule, priva
*/
void sendNeighborInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
- size_t getNumNeighbors() { return *numNeighbors; }
-
- meshtastic_Neighbor *getNeighborByIndex(size_t x)
- {
- assert(x < *numNeighbors);
- return &neighbors[x];
- }
-
/* update neighbors with subpacket sniffed from network */
void updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np);
/* update a NeighborInfo packet with our NodeNum as last_sent_by_id */
- void updateLastSentById(meshtastic_MeshPacket *p);
-
- void loadProtoForModule();
+ void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n) override;
/* Does our periodic broadcast */
int32_t runOnce() override;
+ /* Override wantPacket to say we want to see all packets when enabled, not just those for our port number.
+ Exception is when the packet came via MQTT */
+ virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return enabled && !p->via_mqtt; }
+
/* These are for debugging only */
void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np);
- void printNodeDBNodes(const char *header);
- void printNodeDBNeighbors(const char *header);
- void printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np);
+ void printNodeDBNeighbors();
};
extern NeighborInfoModule *neighborInfoModule;
\ No newline at end of file
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index b0b4bbdcd..f77026708 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -1,4 +1,5 @@
#include "NodeInfoModule.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
@@ -12,7 +13,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
{
auto p = *pptr;
- bool hasChanged = nodeDB.updateUser(getFrom(&mp), p, mp.channel);
+ bool hasChanged = nodeDB->updateUser(getFrom(&mp), p, mp.channel);
bool wasBroadcast = mp.to == NODENUM_BROADCAST;
@@ -24,7 +25,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
}
// if user has changed while packet was not for us, inform phone
- if (hasChanged && !wasBroadcast && mp.to != nodeDB.getNodeNum())
+ if (hasChanged && !wasBroadcast && mp.to != nodeDB->getNodeNum())
service.sendToPhone(packetPool.allocCopy(mp));
// LOG_DEBUG("did handleReceived\n");
@@ -58,8 +59,8 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
meshtastic_MeshPacket *NodeInfoModule::allocReply()
{
uint32_t now = millis();
- // If we sent our NodeInfo less than 1 min. ago, don't send it again as it may be still underway.
- if (lastSentToMesh && (now - lastSentToMesh) < 60 * 1000) {
+ // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway.
+ if (lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) {
LOG_DEBUG("Sending NodeInfo will be ignored since we just sent it.\n");
ignoreRequest = true; // Mark it as ignored for MeshModule
return NULL;
@@ -91,6 +92,5 @@ int32_t NodeInfoModule::runOnce()
LOG_INFO("Sending our nodeinfo to mesh (wantReplies=%d)\n", requestReplies);
sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
}
-
- return getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_broadcast_interval_secs);
+ return Default::getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs);
}
diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp
index 7ef539b1e..658b8b5a7 100644
--- a/src/modules/PositionModule.cpp
+++ b/src/modules/PositionModule.cpp
@@ -1,4 +1,6 @@
+#if !MESHTASTIC_EXCLUDE_GPS
#include "PositionModule.h"
+#include "Default.h"
#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
@@ -15,6 +17,7 @@
extern "C" {
#include "mesh/compression/unishox2.h"
+#include
}
PositionModule *positionModule;
@@ -33,21 +36,11 @@ PositionModule::PositionModule()
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) &&
config.power.is_power_saving) {
- clearPosition();
+ LOG_DEBUG("Clearing position on startup for sleepy tracker (ー。ー) zzz\n");
+ nodeDB->clearLocalPosition();
}
}
-void PositionModule::clearPosition()
-{
- LOG_DEBUG("Clearing position on startup for sleepy tracker (ー。ー) zzz\n");
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
- node->position.latitude_i = 0;
- node->position.longitude_i = 0;
- node->position.altitude = 0;
- node->position.time = 0;
- nodeDB.setLocalPosition(meshtastic_Position_init_default);
-}
-
bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *pptr)
{
auto p = *pptr;
@@ -58,35 +51,52 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
// FIXME this can in fact happen with packets sent from EUD (src=RX_SRC_USER)
// to set fixed location, EUD-GPS location or just the time (see also issue #900)
bool isLocal = false;
- if (nodeDB.getNodeNum() == getFrom(&mp)) {
- LOG_DEBUG("Incoming update from MYSELF\n");
+ if (nodeDB->getNodeNum() == getFrom(&mp)) {
isLocal = true;
- nodeDB.setLocalPosition(p);
+ if (config.position.fixed_position) {
+ LOG_DEBUG("Ignore incoming position update from myself except for time, because position.fixed_position is true\n");
+ nodeDB->setLocalPosition(p, true);
+ return false;
+ } else {
+ LOG_DEBUG("Incoming update from MYSELF\n");
+ nodeDB->setLocalPosition(p);
+ }
}
// Log packet size and data fields
- LOG_INFO("POSITION node=%08x l=%d latI=%d lonI=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d "
- "time=%d\n",
- getFrom(&mp), mp.decoded.payload.size, p.latitude_i, p.longitude_i, p.altitude, p.altitude_hae,
- p.altitude_geoidal_separation, p.PDOP, p.HDOP, p.VDOP, p.sats_in_view, p.fix_quality, p.fix_type, p.timestamp,
- p.time);
+ LOG_DEBUG("POSITION node=%08x l=%d latI=%d lonI=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d "
+ "time=%d\n",
+ getFrom(&mp), mp.decoded.payload.size, p.latitude_i, p.longitude_i, p.altitude, p.altitude_hae,
+ p.altitude_geoidal_separation, p.PDOP, p.HDOP, p.VDOP, p.sats_in_view, p.fix_quality, p.fix_type, p.timestamp,
+ p.time);
if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) {
- struct timeval tv;
- uint32_t secs = p.time;
-
- tv.tv_sec = secs;
- tv.tv_usec = 0;
-
// Set from phone RTC Quality to RTCQualityNTP since it should be approximately so
- perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv);
+ trySetRtc(p, isLocal);
}
- nodeDB.updatePosition(getFrom(&mp), p);
+ nodeDB->updatePosition(getFrom(&mp), p);
+ 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
}
+void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal)
+{
+ struct timeval tv;
+ uint32_t secs = p.time;
+
+ tv.tv_sec = secs;
+ tv.tv_usec = 0;
+ perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv);
+}
+
meshtastic_MeshPacket *PositionModule::allocReply()
{
if (precision == 0) {
@@ -105,14 +115,28 @@ meshtastic_MeshPacket *PositionModule::allocReply()
meshtastic_Position p = meshtastic_Position_init_default; // Start with an empty structure
// if localPosition is totally empty, put our last saved position (lite) in there
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) {
- nodeDB.setLocalPosition(TypeConversions::ConvertToPosition(node->position));
+ nodeDB->setLocalPosition(TypeConversions::ConvertToPosition(node->position));
}
localPosition.seq_number++;
+ if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) {
+ LOG_WARN("Skipping position send because lat/lon are zero!\n");
+ return nullptr;
+ }
+
// lat/lon are unconditionally included - IF AVAILABLE!
LOG_DEBUG("Sending location with precision %i\n", precision);
- p.latitude_i = localPosition.latitude_i & (INT32_MAX << (32 - precision));
- p.longitude_i = localPosition.longitude_i & (INT32_MAX << (32 - precision));
+ 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.time = localPosition.time;
@@ -203,6 +227,16 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli()
return mp;
}
+void PositionModule::sendOurPosition()
+{
+ bool requestReplies = currentGeneration != radioGeneration;
+ currentGeneration = radioGeneration;
+
+ // If we changed channels, ask everyone else for their latest info
+ LOG_INFO("Sending pos@%x:6 to mesh (wantReplies=%d)\n", localPosition.timestamp, requestReplies);
+ sendOurPosition(NODENUM_BROADCAST, requestReplies);
+}
+
void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t channel)
{
// cancel any not yet sent (now stale) position packets
@@ -210,7 +244,13 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha
service.cancelSending(prevPacketId);
// Set's the class precision value for this particular packet
- precision = channels.getByIndex(channel).settings.module_settings.position_precision;
+ if (channels.getByIndex(channel).settings.has_module_settings) {
+ precision = channels.getByIndex(channel).settings.module_settings.position_precision;
+ } else if (channels.getByIndex(channel).role == meshtastic_Channel_Role_PRIMARY) {
+ precision = 32;
+ } else {
+ precision = 0;
+ }
meshtastic_MeshPacket *p = allocReply();
if (p == nullptr) {
@@ -247,18 +287,19 @@ int32_t PositionModule::runOnce()
{
if (sleepOnNextExecution == true) {
sleepOnNextExecution = false;
- uint32_t nightyNightMs = getConfiguredOrDefaultMs(config.position.position_broadcast_secs);
+ uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs);
LOG_DEBUG("Sleeping for %ims, then awaking to send position again.\n", nightyNightMs);
doDeepSleep(nightyNightMs, false);
}
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
if (node == nullptr)
return RUNONCE_INTERVAL;
// We limit our GPS broadcasts to a max rate
uint32_t now = millis();
- uint32_t intervalMs = getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs);
+ uint32_t intervalMs =
+ Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs);
uint32_t msSinceLastSend = now - lastGpsSend;
// Only send packets if the channel util. is less than 25% utilized or we're a tracker with less than 40% utilized.
if (!airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
@@ -273,12 +314,7 @@ int32_t PositionModule::runOnce()
lastGpsLatitude = node->position.latitude_i;
lastGpsLongitude = node->position.longitude_i;
- // If we changed channels, ask everyone else for their latest info
- bool requestReplies = currentGeneration != radioGeneration;
- currentGeneration = radioGeneration;
-
- LOG_INFO("Sending pos@%x:6 to mesh (wantReplies=%d)\n", localPosition.timestamp, requestReplies);
- sendOurPosition(NODENUM_BROADCAST, requestReplies);
+ sendOurPosition();
if (config.device.role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) {
sendLostAndFoundText();
}
@@ -288,29 +324,23 @@ int32_t PositionModule::runOnce()
if (hasValidPosition(node2)) {
// The minimum time (in seconds) that would pass before we are able to send a new position packet.
- const uint32_t minimumTimeThreshold =
- getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30);
auto smartPosition = getDistanceTraveledSinceLastSend(node->position);
+ uint32_t msSinceLastSend = now - lastGpsSend;
- if (smartPosition.hasTraveledOverThreshold && msSinceLastSend >= minimumTimeThreshold) {
- bool requestReplies = currentGeneration != radioGeneration;
- currentGeneration = radioGeneration;
+ if (smartPosition.hasTraveledOverThreshold &&
+ Throttle::execute(
+ &lastGpsSend, minimumTimeThreshold, []() { positionModule->sendOurPosition(); },
+ []() { LOG_DEBUG("Skipping send smart broadcast due to time throttling\n"); })) {
- LOG_INFO("Sending smart pos@%x:6 to mesh (distanceTraveled=%fm, minDistanceThreshold=%im, timeElapsed=%ims, "
- "minTimeInterval=%ims)\n",
- localPosition.timestamp, smartPosition.distanceTraveled, smartPosition.distanceThreshold,
- msSinceLastSend, minimumTimeThreshold);
- sendOurPosition(NODENUM_BROADCAST, requestReplies);
+ LOG_DEBUG("Sent smart pos@%x:6 to mesh (distanceTraveled=%fm, minDistanceThreshold=%im, timeElapsed=%ims, "
+ "minTimeInterval=%ims)\n",
+ localPosition.timestamp, smartPosition.distanceTraveled, smartPosition.distanceThreshold,
+ msSinceLastSend, minimumTimeThreshold);
// Set the current coords as our last ones, after we've compared distance with current and decided to send
lastGpsLatitude = node->position.latitude_i;
lastGpsLongitude = node->position.longitude_i;
-
- /* Update lastGpsSend to now. This means if the device is stationary, then
- getPref_position_broadcast_secs will still apply.
- */
- lastGpsSend = now;
}
}
}
@@ -336,7 +366,8 @@ void PositionModule::sendLostAndFoundText()
struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition)
{
// The minimum distance to travel before we are able to send a new position packet.
- const uint32_t distanceTravelThreshold = getConfiguredOrDefault(config.position.broadcast_smart_minimum_distance, 100);
+ const uint32_t distanceTravelThreshold =
+ Default::getConfiguredOrDefault(config.position.broadcast_smart_minimum_distance, 100);
// Determine the distance in meters between two points on the globe
float distanceTraveledSinceLastSend = GeoCoord::latLongToMeter(
@@ -350,7 +381,7 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic
LOG_DEBUG("currentPosition.latitude_i=%i, currentPosition.longitude_i=%i\n", lastGpsLatitude, lastGpsLongitude);
LOG_DEBUG("--------SMART POSITION-----------------------------------\n");
- LOG_DEBUG("hasTraveledOverThreshold=%i, distanceTraveled=%d, distanceThreshold=% u\n",
+ LOG_DEBUG("hasTraveledOverThreshold=%i, distanceTraveled=%f, distanceThreshold=%f\n",
abs(distanceTraveledSinceLastSend) >= distanceTravelThreshold, abs(distanceTraveledSinceLastSend),
distanceTravelThreshold);
@@ -366,30 +397,26 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic
void PositionModule::handleNewPosition()
{
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
const meshtastic_NodeInfoLite *node2 = service.refreshLocalMeshNode(); // should guarantee there is now a position
// We limit our GPS broadcasts to a max rate
- uint32_t now = millis();
- uint32_t msSinceLastSend = now - lastGpsSend;
-
if (hasValidPosition(node2)) {
auto smartPosition = getDistanceTraveledSinceLastSend(node->position);
- if (smartPosition.hasTraveledOverThreshold) {
- bool requestReplies = currentGeneration != radioGeneration;
- currentGeneration = radioGeneration;
-
- LOG_INFO("Sending smart pos@%x:6 to mesh (distanceTraveled=%fm, minDistanceThreshold=%im, timeElapsed=%ims)\n",
- localPosition.timestamp, smartPosition.distanceTraveled, smartPosition.distanceThreshold, msSinceLastSend);
- sendOurPosition(NODENUM_BROADCAST, requestReplies);
+ uint32_t msSinceLastSend = millis() - lastGpsSend;
+ if (smartPosition.hasTraveledOverThreshold &&
+ Throttle::execute(
+ &lastGpsSend, minimumTimeThreshold, []() { positionModule->sendOurPosition(); },
+ []() { LOG_DEBUG("Skipping send smart broadcast due to time throttling\n"); })) {
+ LOG_DEBUG("Sent smart pos@%x:6 to mesh (distanceTraveled=%fm, minDistanceThreshold=%im, timeElapsed=%ims, "
+ "minTimeInterval=%ims)\n",
+ localPosition.timestamp, smartPosition.distanceTraveled, smartPosition.distanceThreshold, msSinceLastSend,
+ minimumTimeThreshold);
// Set the current coords as our last ones, after we've compared distance with current and decided to send
lastGpsLatitude = node->position.latitude_i;
lastGpsLongitude = node->position.longitude_i;
-
- /* Update lastGpsSend to now. This means if the device is stationary, then
- getPref_position_broadcast_secs will still apply.
- */
- lastGpsSend = now;
}
}
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h
index fddafef6f..89ff50c64 100644
--- a/src/modules/PositionModule.h
+++ b/src/modules/PositionModule.h
@@ -1,4 +1,5 @@
#pragma once
+#include "Default.h"
#include "ProtobufModule.h"
#include "concurrency/OSThread.h"
@@ -29,7 +30,8 @@ class PositionModule : public ProtobufModule, private concu
/**
* Send our position into the mesh
*/
- void sendOurPosition(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false, uint8_t channel = 0);
+ void sendOurPosition(NodeNum dest, bool wantReplies = false, uint8_t channel = 0);
+ void sendOurPosition();
void handleNewPosition();
@@ -50,11 +52,12 @@ class PositionModule : public ProtobufModule, private concu
private:
struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition);
meshtastic_MeshPacket *allocAtakPli();
+ void trySetRtc(meshtastic_Position p, bool isLocal);
uint32_t precision;
-
- /** Only used in power saving trackers for now */
- void clearPosition();
void sendLostAndFoundText();
+
+ const uint32_t minimumTimeThreshold =
+ Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30);
};
struct SmartPosition {
diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp
index ecf4b70c7..a66a0513e 100644
--- a/src/modules/RangeTestModule.cpp
+++ b/src/modules/RangeTestModule.cpp
@@ -113,8 +113,8 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
meshtastic_MeshPacket *p = allocDataPacket();
p->to = dest;
p->decoded.want_response = wantReplies;
-
- p->want_ack = true;
+ p->hop_limit = 0;
+ p->want_ack = false;
packetSequence++;
@@ -142,14 +142,14 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
LOG_INFO.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
*/
- if (getFrom(&mp) != nodeDB.getNodeNum()) {
+ if (getFrom(&mp) != nodeDB->getNodeNum()) {
if (moduleConfig.range_test.save) {
appendFile(mp);
}
/*
- NodeInfoLite *n = nodeDB.getMeshNode(getFrom(&mp));
+ NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp));
LOG_DEBUG("-----------------------------------------\n");
LOG_DEBUG("p.payload.bytes \"%s\"\n", p.payload.bytes);
@@ -188,7 +188,7 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
#ifdef ARCH_ESP32
auto &p = mp.decoded;
- meshtastic_NodeInfoLite *n = nodeDB.getMeshNode(getFrom(&mp));
+ meshtastic_NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp));
/*
LOG_DEBUG("-----------------------------------------\n");
LOG_DEBUG("p.payload.bytes \"%s\"\n", p.payload.bytes);
diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp
index edeb1fb86..fe1abab05 100644
--- a/src/modules/RoutingModule.cpp
+++ b/src/modules/RoutingModule.cpp
@@ -14,7 +14,7 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh
// FIXME - move this to a non promsicious PhoneAPI module?
// Note: we are careful not to send back packets that started with the phone back to the phone
- if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) && (mp.from != 0)) {
+ if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB->getNodeNum()) && (mp.from != 0)) {
printPacket("Delivering rx packet", &mp);
service.handleFromRadio(&mp);
}
@@ -36,16 +36,31 @@ meshtastic_MeshPacket *RoutingModule::allocReply()
return NULL;
}
-void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex)
+void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart,
+ uint8_t hopLimit)
{
- auto p = allocAckNak(err, to, idFrom, chIndex);
+ auto p = allocAckNak(err, to, idFrom, chIndex, hopStart, hopLimit);
router->sendLocal(p); // we sometimes send directly to the local node
}
+uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit)
+{
+ if (hopStart != 0) {
+ // Hops used by the request. If somebody in between running modified firmware modified it, ignore it
+ uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit;
+ if (hopsUsed > config.lora.hop_limit) {
+ return hopsUsed; // If the request used more hops than the limit, use the same amount of hops
+ } else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) {
+ return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different
+ }
+ }
+ return config.lora.hop_limit; // Use the default hop limit
+}
+
RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
{
isPromiscuous = true;
encryptedOk = config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY &&
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY;
-}
+}
\ No newline at end of file
diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h
index 06e76cfb4..f085b307b 100644
--- a/src/modules/RoutingModule.h
+++ b/src/modules/RoutingModule.h
@@ -13,7 +13,11 @@ class RoutingModule : public ProtobufModule
*/
RoutingModule();
- void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
+ void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart = 0,
+ uint8_t hopLimit = 0);
+
+ // Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response
+ uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit);
protected:
friend class Router;
diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp
index 820e1fb62..96a99b13e 100644
--- a/src/modules/SerialModule.cpp
+++ b/src/modules/SerialModule.cpp
@@ -126,8 +126,13 @@ int32_t SerialModule::runOnce()
uint32_t baud = getBaudRate();
if (moduleConfig.serial.override_console_serial_port) {
+#ifdef RP2040_SLOW_CLOCK
+ Serial2.flush();
+ serialPrint = &Serial2;
+#else
Serial.flush();
serialPrint = &Serial;
+#endif
// Give it a chance to flush out 💩
delay(10);
}
@@ -151,8 +156,13 @@ int32_t SerialModule::runOnce()
Serial2.begin(baud, SERIAL_8N1);
Serial2.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
} else {
+#ifdef RP2040_SLOW_CLOCK
+ Serial2.begin(baud, SERIAL_8N1);
+ Serial2.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
+#else
Serial.begin(baud, SERIAL_8N1);
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
+#endif
}
#else
Serial.begin(baud, SERIAL_8N1);
@@ -169,22 +179,22 @@ int32_t SerialModule::runOnce()
} else {
if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO) {
return runOncePart();
- } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA) {
+ } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA) && HAS_GPS) {
// in NMEA mode send out GGA every 2 seconds, Don't read from Port
if (millis() - lastNmeaTime > 2000) {
lastNmeaTime = millis();
printGGA(outbuf, sizeof(outbuf), localPosition);
serialPrint->printf("%s", outbuf);
}
- } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) {
+ } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) && HAS_GPS) {
if (millis() - lastNmeaTime > 10000) {
lastNmeaTime = millis();
uint32_t readIndex = 0;
- const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB.readNextMeshNode(readIndex);
+ const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
while (tempNodeInfo != NULL && tempNodeInfo->has_user && hasValidPosition(tempNodeInfo)) {
printWPL(outbuf, sizeof(outbuf), tempNodeInfo->position, tempNodeInfo->user.long_name, true);
serialPrint->printf("%s", outbuf);
- tempNodeInfo = nodeDB.readNextMeshNode(readIndex);
+ tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
}
}
}
@@ -255,9 +265,9 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp
auto &p = mp.decoded;
// LOG_DEBUG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n",
- // nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
+ // nodeDB->getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
- if (getFrom(&mp) == nodeDB.getNodeNum()) {
+ if (getFrom(&mp) == nodeDB->getNodeNum()) {
/*
* If moduleConfig.serial.echo is true, then echo the packets that are sent out
@@ -280,13 +290,14 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp
moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE) {
serialPrint->write(p.payload.bytes, p.payload.size);
} else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) {
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp));
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
String sender = (node && node->has_user) ? node->user.short_name : "???";
serialPrint->println();
serialPrint->printf("%s: %s", sender, p.payload.bytes);
serialPrint->println();
- } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA ||
- moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) {
+ } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA ||
+ moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) &&
+ HAS_GPS) {
// Decode the Payload some more
meshtastic_Position scratch;
meshtastic_Position *decoded = NULL;
@@ -296,7 +307,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp
decoded = &scratch;
}
// send position packet as WPL to the serial port
- printWPL(outbuf, sizeof(outbuf), *decoded, nodeDB.getMeshNode(getFrom(&mp))->user.long_name,
+ printWPL(outbuf, sizeof(outbuf), *decoded, nodeDB->getMeshNode(getFrom(&mp))->user.long_name,
moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO);
serialPrint->printf("%s", outbuf);
}
diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp
index f87ea504b..a51a7cea9 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.cpp
+++ b/src/modules/Telemetry/AirQualityTelemetry.cpp
@@ -1,5 +1,6 @@
#include "AirQualityTelemetry.h"
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
@@ -43,7 +44,8 @@ int32_t AirQualityTelemetryModule::runOnce()
uint32_t now = millis();
if (((lastSentToMesh == 0) ||
- ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) &&
+ ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) &&
+ airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = now;
@@ -112,7 +114,7 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
else
- p->priority = meshtastic_MeshPacket_Priority_MIN;
+ p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// release previous packet before occupying a new spot
if (lastMeasurementPacket != nullptr)
diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index a6eecda80..3529267cb 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -1,5 +1,6 @@
#include "DeviceTelemetry.h"
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
@@ -14,14 +15,14 @@
int32_t DeviceTelemetryModule::runOnce()
{
- uint32_t now = millis();
+ refreshUptime();
if (((lastSentToMesh == 0) ||
- ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) &&
- airTime->isTxAllowedChannelUtil() && airTime->isTxAllowedAirUtil() &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
+ ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) &&
+ airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
+ airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) {
sendTelemetry();
- lastSentToMesh = now;
+ lastSentToMesh = uptimeLastMs;
} else if (service.isToPhoneQueueEmpty()) {
// Just send to phone when it's not our time to send to mesh yet
// Only send while queue is empty (phone assumed connected)
@@ -44,7 +45,7 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
t->variant.device_metrics.air_util_tx, t->variant.device_metrics.channel_utilization,
t->variant.device_metrics.battery_level, t->variant.device_metrics.voltage);
#endif
- nodeDB.updateTelemetry(getFrom(&mp), *t, RX_SRC_RADIO);
+ nodeDB->updateTelemetry(getFrom(&mp), *t, RX_SRC_RADIO);
}
return false; // Let others look at this message also if they want
}
@@ -67,16 +68,12 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry()
t.time = getTime();
t.which_variant = meshtastic_Telemetry_device_metrics_tag;
-
t.variant.device_metrics.air_util_tx = airTime->utilizationTXPercent();
- if (powerStatus->getIsCharging()) {
- t.variant.device_metrics.battery_level = MAGIC_USB_BATTERY_LEVEL;
- } else {
- t.variant.device_metrics.battery_level = powerStatus->getBatteryChargePercent();
- }
-
+ t.variant.device_metrics.battery_level =
+ powerStatus->getIsCharging() ? MAGIC_USB_BATTERY_LEVEL : powerStatus->getBatteryChargePercent();
t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent();
t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
+ t.variant.device_metrics.uptime_seconds = getUptimeSeconds();
return t;
}
@@ -84,16 +81,17 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry()
bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
{
meshtastic_Telemetry telemetry = getDeviceTelemetry();
- LOG_INFO("(Sending): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f\n",
+ LOG_INFO("(Sending): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f, uptime=%i\n",
telemetry.variant.device_metrics.air_util_tx, telemetry.variant.device_metrics.channel_utilization,
- telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage);
+ telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage,
+ telemetry.variant.device_metrics.uptime_seconds);
meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
p->to = dest;
p->decoded.want_response = false;
- p->priority = meshtastic_MeshPacket_Priority_MIN;
+ p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
- nodeDB.updateTelemetry(nodeDB.getNodeNum(), telemetry, RX_SRC_LOCAL);
+ nodeDB->updateTelemetry(nodeDB->getNodeNum(), telemetry, RX_SRC_LOCAL);
if (phoneOnly) {
LOG_INFO("Sending packet to phone\n");
service.sendToPhone(p);
diff --git a/src/modules/Telemetry/DeviceTelemetry.h b/src/modules/Telemetry/DeviceTelemetry.h
index 81f83ce0a..5f4e761f9 100644
--- a/src/modules/Telemetry/DeviceTelemetry.h
+++ b/src/modules/Telemetry/DeviceTelemetry.h
@@ -12,6 +12,8 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
: concurrency::OSThread("DeviceTelemetryModule"),
ProtobufModule("DeviceTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
{
+ uptimeWrapCount = 0;
+ uptimeLastMs = millis();
setIntervalFromNow(45 * 1000); // Wait until NodeInfo is sent
}
virtual bool wantUIFrame() { return false; }
@@ -28,8 +30,27 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
*/
bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool phoneOnly = false);
+ /**
+ * Get the uptime in seconds
+ * Loses some accuracy after 49 days, but that's fine
+ */
+ uint32_t getUptimeSeconds() { return (0xFFFFFFFF / 1000) * uptimeWrapCount + (uptimeLastMs / 1000); }
+
private:
meshtastic_Telemetry getDeviceTelemetry();
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
+
+ void refreshUptime()
+ {
+ auto now = millis();
+ // If we wrapped around (~49 days), increment the wrap count
+ if (now < uptimeLastMs)
+ uptimeWrapCount++;
+
+ uptimeLastMs = now;
+ }
+
+ uint32_t uptimeWrapCount;
+ uint32_t uptimeLastMs;
};
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 9c7b406e9..189ab7ed0 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -1,5 +1,6 @@
#include "EnvironmentTelemetry.h"
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
@@ -16,12 +17,14 @@
// Sensors
#include "Sensor/BME280Sensor.h"
#include "Sensor/BME680Sensor.h"
+#include "Sensor/BMP085Sensor.h"
#include "Sensor/BMP280Sensor.h"
#include "Sensor/LPS22HBSensor.h"
#include "Sensor/MCP9808Sensor.h"
#include "Sensor/SHT31Sensor.h"
#include "Sensor/SHTC3Sensor.h"
+BMP085Sensor bmp085Sensor;
BMP280Sensor bmp280Sensor;
BME280Sensor bme280Sensor;
BME680Sensor bme680Sensor;
@@ -33,29 +36,13 @@ SHT31Sensor sht31Sensor;
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
- !defined(DISPLAY_FORCE_SMALL_FONTS)
-
-// The screen is bigger so use bigger fonts
-#define FONT_SMALL ArialMT_Plain_16
-#define FONT_MEDIUM ArialMT_Plain_24
-#define FONT_LARGE ArialMT_Plain_24
-#else
-#define FONT_SMALL ArialMT_Plain_10
-#define FONT_MEDIUM ArialMT_Plain_16
-#define FONT_LARGE ArialMT_Plain_24
-#endif
-
-#define fontHeight(font) ((font)[1] + 1) // height is position 1
-
-#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
-#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
+#include "graphics/ScreenFonts.h"
int32_t EnvironmentTelemetryModule::runOnce()
{
if (sleepOnNextExecution == true) {
sleepOnNextExecution = false;
- uint32_t nightyNightMs = getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval);
+ uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval);
LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs);
doDeepSleep(nightyNightMs, true);
}
@@ -83,6 +70,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
LOG_INFO("Environment Telemetry: Initializing\n");
// it's possible to have this module enabled, only for displaying values on the screen.
// therefore, we should only enable the sensor loop if measurement is also enabled
+ if (bmp085Sensor.hasSensor())
+ result = bmp085Sensor.runOnce();
if (bmp280Sensor.hasSensor())
result = bmp280Sensor.runOnce();
if (bme280Sensor.hasSensor())
@@ -114,7 +103,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
uint32_t now = millis();
if (((lastSentToMesh == 0) ||
- ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) &&
+ ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) &&
+ airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = now;
@@ -191,6 +181,8 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
display->drawString(x, y += fontHeight(FONT_SMALL),
"Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " +
String(lastMeasurement.variant.environment_metrics.current, 0) + "mA");
+ if (lastMeasurement.variant.environment_metrics.iaq != 0)
+ display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq));
}
bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
@@ -235,6 +227,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
valid = lps22hbSensor.getMetrics(&m);
if (shtc3Sensor.hasSensor())
valid = shtc3Sensor.getMetrics(&m);
+ if (bmp085Sensor.hasSensor())
+ valid = bmp085Sensor.getMetrics(&m);
if (bmp280Sensor.hasSensor())
valid = bmp280Sensor.getMetrics(&m);
if (bme280Sensor.hasSensor())
@@ -263,7 +257,7 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
else
- p->priority = meshtastic_MeshPacket_Priority_MIN;
+ p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// release previous packet before occupying a new spot
if (lastMeasurementPacket != nullptr)
packetPool.release(lastMeasurementPacket);
diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp
index 032d7fc27..713f6aacb 100644
--- a/src/modules/Telemetry/PowerTelemetry.cpp
+++ b/src/modules/Telemetry/PowerTelemetry.cpp
@@ -1,5 +1,6 @@
#include "PowerTelemetry.h"
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
@@ -14,29 +15,13 @@
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
- !defined(DISPLAY_FORCE_SMALL_FONTS)
-
-// The screen is bigger so use bigger fonts
-#define FONT_SMALL ArialMT_Plain_16
-#define FONT_MEDIUM ArialMT_Plain_24
-#define FONT_LARGE ArialMT_Plain_24
-#else
-#define FONT_SMALL ArialMT_Plain_10
-#define FONT_MEDIUM ArialMT_Plain_16
-#define FONT_LARGE ArialMT_Plain_24
-#endif
-
-#define fontHeight(font) ((font)[1] + 1) // height is position 1
-
-#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
-#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
+#include "graphics/ScreenFonts.h"
int32_t PowerTelemetryModule::runOnce()
{
if (sleepOnNextExecution == true) {
sleepOnNextExecution = false;
- uint32_t nightyNightMs = getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval);
+ uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval);
LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs);
doDeepSleep(nightyNightMs, true);
}
@@ -82,7 +67,7 @@ int32_t PowerTelemetryModule::runOnce()
uint32_t now = millis();
if (((lastSentToMesh == 0) ||
- ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) &&
+ ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = now;
@@ -211,7 +196,7 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
else
- p->priority = meshtastic_MeshPacket_Priority_MIN;
+ p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// release previous packet before occupying a new spot
if (lastMeasurementPacket != nullptr)
packetPool.release(lastMeasurementPacket);
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
index 323dce31f..e1222bba4 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
@@ -57,6 +57,7 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.environment_metrics.barometric_pressure = bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal / 100.0F;
measurement->variant.environment_metrics.gas_resistance = bme680.getData(BSEC_OUTPUT_RAW_GAS).signal / 1000.0;
// Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms)
+ measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal;
updateState();
return true;
}
@@ -85,17 +86,17 @@ void BME680Sensor::updateState()
if (stateUpdateCounter == 0) {
/* First state update when IAQ accuracy is >= 3 */
accuracy = bme680.getData(BSEC_OUTPUT_IAQ).accuracy;
- if (accuracy >= 3) {
- LOG_DEBUG("%s state update IAQ accuracy %u >= 3\n", sensorName, accuracy);
+ if (accuracy >= 2) {
+ LOG_DEBUG("%s state update IAQ accuracy %u >= 2\n", sensorName, accuracy);
update = true;
stateUpdateCounter++;
} else {
- LOG_DEBUG("%s not updated, IAQ accuracy is %u >= 3\n", sensorName, accuracy);
+ LOG_DEBUG("%s not updated, IAQ accuracy is %u < 2\n", sensorName, accuracy);
}
} else {
/* Update every STATE_SAVE_PERIOD minutes */
if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) {
- LOG_DEBUG("%s state update every %d minutes\n", sensorName, STATE_SAVE_PERIOD);
+ LOG_DEBUG("%s state update every %d minutes\n", sensorName, STATE_SAVE_PERIOD / 60000);
update = true;
stateUpdateCounter++;
}
@@ -103,22 +104,15 @@ void BME680Sensor::updateState()
if (update) {
bme680.getState(bsecState);
- std::string filenameTmp = bsecConfigFileName;
- filenameTmp += ".tmp";
+ if (FSCom.exists(bsecConfigFileName) && !FSCom.remove(bsecConfigFileName)) {
+ LOG_WARN("Can't remove old state file\n");
+ }
auto file = FSCom.open(bsecConfigFileName, FILE_O_WRITE);
if (file) {
LOG_INFO("%s state write to %s.\n", sensorName, bsecConfigFileName);
file.write((uint8_t *)&bsecState, BSEC_MAX_STATE_BLOB_SIZE);
file.flush();
file.close();
- // brief window of risk here ;-)
- if (FSCom.exists(bsecConfigFileName) && !FSCom.remove(bsecConfigFileName)) {
- LOG_WARN("Can't remove old state file\n");
- }
- if (!renameFile(filenameTmp.c_str(), bsecConfigFileName)) {
- LOG_ERROR("Error: can't rename new state file\n");
- }
-
} else {
LOG_INFO("Can't write %s state (File: %s).\n", sensorName, bsecConfigFileName);
}
diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
new file mode 100644
index 000000000..b0991749b
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
@@ -0,0 +1,31 @@
+#include "BMP085Sensor.h"
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include "configuration.h"
+#include
+#include
+
+BMP085Sensor::BMP085Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP085, "BMP085") {}
+
+int32_t BMP085Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+ bmp085 = Adafruit_BMP085();
+ status = bmp085.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
+
+ return initI2CSensor();
+}
+
+void BMP085Sensor::setup() {}
+
+bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ LOG_DEBUG("BMP085Sensor::getMetrics\n");
+ measurement->variant.environment_metrics.temperature = bmp085.readTemperature();
+ measurement->variant.environment_metrics.barometric_pressure = bmp085.readPressure() / 100.0F;
+
+ return true;
+}
diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h
new file mode 100644
index 000000000..c4a9479b9
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h
@@ -0,0 +1,17 @@
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class BMP085Sensor : public TelemetrySensor
+{
+ private:
+ Adafruit_BMP085 bmp085;
+
+ protected:
+ virtual void setup() override;
+
+ public:
+ BMP085Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp
index 5a1faa99f..ecb564368 100644
--- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp
@@ -4,6 +4,10 @@
#include "configuration.h"
#include
+#ifndef INA219_MULTIPLIER
+#define INA219_MULTIPLIER 1.0f
+#endif
+
INA219Sensor::INA219Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA219, "INA219") {}
int32_t INA219Sensor::runOnce()
@@ -26,7 +30,7 @@ void INA219Sensor::setup() {}
bool INA219Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.voltage = ina219.getBusVoltage_V();
- measurement->variant.environment_metrics.current = ina219.getCurrent_mA();
+ measurement->variant.environment_metrics.current = ina219.getCurrent_mA() * INA219_MULTIPLIER;
return true;
}
diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
index 7ffb68254..35978d970 100644
--- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
@@ -12,7 +12,8 @@ int32_t SHT31Sensor::runOnce()
if (!hasSensor()) {
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
- status = sht31.begin();
+ sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second);
+ status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first);
return initI2CSensor();
}
diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h
index 940361325..c6f8f1596 100644
--- a/src/modules/Telemetry/Sensor/SHT31Sensor.h
+++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h
@@ -5,7 +5,7 @@
class SHT31Sensor : public TelemetrySensor
{
private:
- Adafruit_SHT31 sht31 = Adafruit_SHT31();
+ Adafruit_SHT31 sht31;
protected:
virtual void setup() override;
diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index 311e211f3..aa0b6a1eb 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -1,5 +1,4 @@
#include "TraceRouteModule.h"
-#include "FloodingRouter.h"
#include "MeshService.h"
TraceRouteModule *traceRouteModule;
@@ -14,23 +13,17 @@ bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m
return false; // let it be handled by RoutingModule
}
-void TraceRouteModule::updateRoute(meshtastic_MeshPacket *p)
+void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r)
{
- auto &incoming = p->decoded;
- // Only append an ID for the request (one way)
- if (!incoming.request_id) {
- meshtastic_RouteDiscovery scratch;
- meshtastic_RouteDiscovery *updated = NULL;
- memset(&scratch, 0, sizeof(scratch));
- pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &meshtastic_RouteDiscovery_msg, &scratch);
- updated = &scratch;
-
- appendMyID(updated);
- printRoute(updated, p->from, NODENUM_BROADCAST);
+ auto &incoming = p.decoded;
+ // Only append an ID for the request (one way) and if we are not the destination (the reply will have our NodeNum already)
+ if (!incoming.request_id && p.to != nodeDB->getNodeNum()) {
+ appendMyID(r);
+ printRoute(r, p.from, NODENUM_BROADCAST);
// Set updated route to the payload of the to be flooded packet
- p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
- &meshtastic_RouteDiscovery_msg, updated);
+ p.decoded.payload.size =
+ pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, r);
}
}
@@ -85,4 +78,5 @@ TraceRouteModule::TraceRouteModule()
: ProtobufModule("traceroute", meshtastic_PortNum_TRACEROUTE_APP, &meshtastic_RouteDiscovery_msg)
{
ourPortNum = meshtastic_PortNum_TRACEROUTE_APP;
+ isPromiscuous = true; // We need to update the route even if it is not destined to us
}
\ No newline at end of file
diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h
index 674846ef1..15e01debd 100644
--- a/src/modules/TraceRouteModule.h
+++ b/src/modules/TraceRouteModule.h
@@ -9,17 +9,14 @@ class TraceRouteModule : public ProtobufModule
public:
TraceRouteModule();
- // Let FloodingRouter call updateRoute upon rebroadcasting a TraceRoute request
- friend class FloodingRouter;
-
protected:
bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r) override;
virtual meshtastic_MeshPacket *allocReply() override;
- /* Call before rebroadcasting a RouteDiscovery payload in order to update
+ /* Called before rebroadcasting a RouteDiscovery payload in order to update
the route array containing the IDs of nodes this packet went through */
- void updateRoute(meshtastic_MeshPacket *p);
+ void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) override;
private:
// Call to add your ID to the route array of a RouteDiscovery message
diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp
index bc104df11..4a7b1c2c6 100644
--- a/src/modules/esp32/AudioModule.cpp
+++ b/src/modules/esp32/AudioModule.cpp
@@ -1,4 +1,3 @@
-
#include "configuration.h"
#if defined(ARCH_ESP32) && defined(USE_SX1280)
#include "AudioModule.h"
@@ -8,14 +7,6 @@
#include "RTC.h"
#include "Router.h"
-#ifdef OLED_RU
-#include "graphics/fonts/OLEDDisplayFontsRU.h"
-#endif
-
-#ifdef OLED_UA
-#include "graphics/fonts/OLEDDisplayFontsUA.h"
-#endif
-
/*
AudioModule
A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2 project.
@@ -48,32 +39,7 @@ AudioModule *audioModule;
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x)
#endif
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
- !defined(DISPLAY_FORCE_SMALL_FONTS)
-
-// The screen is bigger so use bigger fonts
-#define FONT_SMALL ArialMT_Plain_16
-#define FONT_MEDIUM ArialMT_Plain_24
-#define FONT_LARGE ArialMT_Plain_24
-#else
-#ifdef OLED_RU
-#define FONT_SMALL ArialMT_Plain_10_RU
-#else
-#ifdef OLED_UA
-#define FONT_SMALL ArialMT_Plain_10_UA
-#else
-#define FONT_SMALL ArialMT_Plain_10
-#endif
-#endif
-#define FONT_MEDIUM ArialMT_Plain_16
-#define FONT_LARGE ArialMT_Plain_24
-#endif
-
-#define fontHeight(font) ((font)[1] + 1) // height is position 1
-
-#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
-#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
-#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
+#include "graphics/ScreenFonts.h"
void run_codec2(void *parameter)
{
@@ -307,7 +273,7 @@ ProcessMessage AudioModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
auto &p = mp.decoded;
- if (getFrom(&mp) != nodeDB.getNodeNum()) {
+ if (getFrom(&mp) != nodeDB->getNodeNum()) {
memcpy(rx_encode_frame, p.payload.bytes, p.payload.size);
radio_state = RadioState::rx;
rx_encode_frame_index = p.payload.size;
diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp
index f3df7ffdf..b9fdfcb63 100644
--- a/src/modules/esp32/PaxcounterModule.cpp
+++ b/src/modules/esp32/PaxcounterModule.cpp
@@ -1,15 +1,25 @@
#include "configuration.h"
-#if defined(ARCH_ESP32)
+#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_PAXCOUNTER
+#include "Default.h"
#include "MeshService.h"
#include "PaxcounterModule.h"
-
#include
PaxcounterModule *paxcounterModule;
-void NullFunc(){};
-
-// paxcounterModule->sendInfo(NODENUM_BROADCAST);
+/**
+ * Callback function for libpax.
+ * We only clear our sent flag here, since this function is called from another thread, so we
+ * cannot send to the mesh directly.
+ */
+void PaxcounterModule::handlePaxCounterReportRequest()
+{
+ // The libpax library already updated our data structure, just before invoking this callback.
+ LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu\n",
+ paxcounterModule->count_from_libpax.wifi_count, paxcounterModule->count_from_libpax.ble_count, millis() / 1000);
+ paxcounterModule->reportedDataSent = false;
+ paxcounterModule->setIntervalFromNow(0);
+}
PaxcounterModule::PaxcounterModule()
: concurrency::OSThread("PaxcounterModule"),
@@ -17,11 +27,20 @@ PaxcounterModule::PaxcounterModule()
{
}
+/**
+ * Send the Pax information to the mesh if we got new data from libpax.
+ * This is called periodically from our runOnce() method and will actually send the data to the mesh
+ * if libpax updated it since the last transmission through the callback.
+ * @param dest - destination node (usually NODENUM_BROADCAST)
+ * @return false if sending is unnecessary, true if information was sent
+ */
bool PaxcounterModule::sendInfo(NodeNum dest)
{
- libpax_counter_count(&count_from_libpax);
- LOG_INFO("(Sending): pax: wifi=%d; ble=%d; uptime=%d\n", count_from_libpax.wifi_count, count_from_libpax.ble_count,
- millis() / 1000);
+ if (paxcounterModule->reportedDataSent)
+ return false;
+
+ LOG_INFO("PaxcounterModule: sending pax info wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count,
+ count_from_libpax.ble_count, millis() / 1000);
meshtastic_Paxcount pl = meshtastic_Paxcount_init_default;
pl.wifi = count_from_libpax.wifi_count;
@@ -31,9 +50,12 @@ bool PaxcounterModule::sendInfo(NodeNum dest)
meshtastic_MeshPacket *p = allocDataProtobuf(pl);
p->to = dest;
p->decoded.want_response = false;
- p->priority = meshtastic_MeshPacket_Priority_MIN;
+ p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
service.sendToMesh(p, RX_SRC_LOCAL, true);
+
+ paxcounterModule->reportedDataSent = true;
+
return true;
}
@@ -57,17 +79,17 @@ meshtastic_MeshPacket *PaxcounterModule::allocReply()
int32_t PaxcounterModule::runOnce()
{
- if (moduleConfig.paxcounter.enabled && !config.bluetooth.enabled && !config.network.wifi_enabled) {
+ if (isActive()) {
if (firstTime) {
firstTime = false;
- LOG_DEBUG(
- "Paxcounter starting up with interval of %d seconds\n",
- getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval, default_broadcast_interval_secs));
+ LOG_DEBUG("Paxcounter starting up with interval of %d seconds\n",
+ Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval,
+ default_broadcast_interval_secs));
struct libpax_config_t configuration;
libpax_default_config(&configuration);
configuration.blecounter = 1;
- configuration.blescantime = 0; // infinit
+ configuration.blescantime = 0; // infinite
configuration.wificounter = 1;
configuration.wifi_channel_map = WIFI_CHANNEL_ALL;
configuration.wifi_channel_switch_interval = 50;
@@ -76,15 +98,37 @@ int32_t PaxcounterModule::runOnce()
libpax_update_config(&configuration);
// internal processing initialization
- libpax_counter_init(NullFunc, &count_from_libpax, UINT16_MAX, 1);
+ libpax_counter_init(handlePaxCounterReportRequest, &count_from_libpax,
+ moduleConfig.paxcounter.paxcounter_update_interval, 0);
libpax_counter_start();
} else {
sendInfo(NODENUM_BROADCAST);
}
- return getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, default_broadcast_interval_secs);
+ return Default::getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval,
+ default_broadcast_interval_secs);
} else {
return disable();
}
}
-#endif
\ No newline at end of file
+#if HAS_SCREEN
+
+#include "graphics/ScreenFonts.h"
+
+void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+{
+ char buffer[50];
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+ display->setFont(FONT_SMALL);
+ display->drawString(x + 0, y + 0, "PAX");
+
+ libpax_counter_count(&count_from_libpax);
+
+ display->setTextAlignment(TEXT_ALIGN_CENTER);
+ display->setFont(FONT_SMALL);
+ display->drawStringf(display->getWidth() / 2 + x, 0 + y + 12, buffer, "WiFi: %d\nBLE: %d\nuptime: %ds",
+ count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000);
+}
+#endif // HAS_SCREEN
+
+#endif
diff --git a/src/modules/esp32/PaxcounterModule.h b/src/modules/esp32/PaxcounterModule.h
index 0aa9be68d..ebd6e7191 100644
--- a/src/modules/esp32/PaxcounterModule.h
+++ b/src/modules/esp32/PaxcounterModule.h
@@ -8,11 +8,15 @@
#include
/**
- * A simple example module that just replies with "Message received" to any message it receives.
+ * Wrapper module for the estimate passenger (PAX) count library (https://github.com/dbinfrago/libpax) which
+ * implements the core functionality of the ESP32 Paxcounter project (https://github.com/cyberman54/ESP32-Paxcounter)
*/
class PaxcounterModule : private concurrency::OSThread, public ProtobufModule
{
bool firstTime = true;
+ bool reportedDataSent = true;
+
+ static void handlePaxCounterReportRequest();
public:
PaxcounterModule();
@@ -23,6 +27,11 @@ class PaxcounterModule : private concurrency::OSThread, public ProtobufModulebusy = false;
}
}
- } else if ((millis() - lastHeartbeat > (heartbeatInterval * 1000)) && airTime->isTxAllowedChannelUtil(true)) {
+ } else if (this->heartbeat && (millis() - lastHeartbeat > (heartbeatInterval * 1000)) &&
+ airTime->isTxAllowedChannelUtil(true)) {
lastHeartbeat = millis();
LOG_INFO("*** Sending heartbeat\n");
meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero;
@@ -141,25 +142,31 @@ uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to, uin
LOG_DEBUG("SF historyQueueCreate - millis %d\n", millis());
LOG_DEBUG("SF historyQueueCreate - math %d\n", (millis() - msAgo));
*/
- if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) {
- /* Copy the messages that were received by the router in the last msAgo
- to the packetHistoryTXQueue structure.
- Client not interested in packets from itself and only in broadcast packets or packets towards it. */
- if (this->packetHistory[i].from != to &&
- (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) {
- this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time;
- this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to;
- this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from;
- this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel;
- this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = this->packetHistory[i].payload_size;
- memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload,
- meshtastic_Constants_DATA_PAYLOAD_LEN);
- this->packetHistoryTXQueue_size++;
- *last_request_index = i + 1; // Set to one higher such that we don't send the same message again
+ if (this->packetHistoryTXQueue_size < this->historyReturnMax) {
+ if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) {
+ /* Copy the messages that were received by the router in the last msAgo
+ to the packetHistoryTXQueue structure.
+ Client not interested in packets from itself and only in broadcast packets or packets towards it. */
+ if (this->packetHistory[i].from != to &&
+ (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) {
+ this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time;
+ this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to;
+ this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from;
+ this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel;
+ this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size =
+ this->packetHistory[i].payload_size;
+ memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload,
+ meshtastic_Constants_DATA_PAYLOAD_LEN);
+ this->packetHistoryTXQueue_size++;
+ *last_request_index = i + 1; // Set to one higher such that we don't send the same message again
- LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time,
- this->packetHistory[i].payload);
+ LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time,
+ this->packetHistory[i].payload);
+ }
}
+ } else {
+ LOG_WARN("*** S&F - Maximum history return reached.\n");
+ return this->packetHistoryTXQueue_size;
}
}
return this->packetHistoryTXQueue_size;
@@ -174,20 +181,24 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
{
const auto &p = mp.decoded;
- if (this->packetHistoryCurrent < this->records) {
- this->packetHistory[this->packetHistoryCurrent].time = millis();
- this->packetHistory[this->packetHistoryCurrent].to = mp.to;
- this->packetHistory[this->packetHistoryCurrent].channel = mp.channel;
- this->packetHistory[this->packetHistoryCurrent].from = mp.from;
- this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size;
- memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
-
- this->packetHistoryCurrent++;
- this->packetHistoryMax++;
- } else {
- // TODO: Overwrite the oldest message in the history buffer when it is full.
- LOG_WARN("*** S&F - PSRAM Full. Packet is not added to the history.\n");
+ if (this->packetHistoryCurrent == this->records) {
+ LOG_WARN("*** S&F - PSRAM Full. Starting overwrite now.\n");
+ this->packetHistoryCurrent = 0;
+ this->packetHistoryMax = 0;
+ for (auto &i : lastRequest) {
+ i.second = 0; // Clear the last request index for each client device
+ }
}
+
+ this->packetHistory[this->packetHistoryCurrent].time = millis();
+ this->packetHistory[this->packetHistoryCurrent].to = mp.to;
+ this->packetHistory[this->packetHistoryCurrent].channel = mp.channel;
+ this->packetHistory[this->packetHistoryCurrent].from = mp.from;
+ this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size;
+ memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
+
+ this->packetHistoryCurrent++;
+ this->packetHistoryMax++;
}
meshtastic_MeshPacket *StoreForwardModule::allocReply()
@@ -244,7 +255,7 @@ void StoreForwardModule::sendMessage(NodeNum dest, const meshtastic_StoreAndForw
p->to = dest;
- p->priority = meshtastic_MeshPacket_Priority_MIN;
+ p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// FIXME - Determine if the delayed packet is broadcast or delayed. For now, assume
// everything is broadcast.
@@ -309,11 +320,12 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m
if (moduleConfig.store_forward.enabled) {
// The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT
- if ((getFrom(&mp) != nodeDB.getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) {
+ if ((getFrom(&mp) != nodeDB->getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) {
if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) {
auto &p = mp.decoded;
- if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) {
+ if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') &&
+ (p.payload.bytes[2] == 0x00)) {
LOG_DEBUG("*** Legacy Request to send\n");
// Send the last 60 minutes of messages.
@@ -322,7 +334,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m
LOG_INFO("*** S&F - Busy. Try again shortly.\n");
meshtastic_MeshPacket *pr = allocReply();
pr->to = getFrom(&mp);
- pr->priority = meshtastic_MeshPacket_Priority_MIN;
+ pr->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
pr->want_ack = false;
pr->decoded.want_response = false;
pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
@@ -494,7 +506,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
break;
default:
- assert(0); // unexpected state
+ break; // no need to do anything
}
return true; // There's no need for others to look at this message.
}
@@ -543,6 +555,8 @@ StoreForwardModule::StoreForwardModule()
// send heartbeat advertising?
if (moduleConfig.store_forward.heartbeat)
this->heartbeat = moduleConfig.store_forward.heartbeat;
+ else
+ this->heartbeat = false;
// Popupate PSRAM with our data structures.
this->populatePSRAM();
diff --git a/src/modules/esp32/StoreForwardModule.h b/src/modules/esp32/StoreForwardModule.h
index cfa9945d5..0d2fb9fce 100644
--- a/src/modules/esp32/StoreForwardModule.h
+++ b/src/modules/esp32/StoreForwardModule.h
@@ -44,7 +44,7 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<
StoreForwardModule();
unsigned long lastHeartbeat = 0;
- uint32_t heartbeatInterval = default_broadcast_interval_secs;
+ uint32_t heartbeatInterval = 900;
/**
Update our local reference of when we last saw that node.
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 8c241a302..da1c204b8 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -2,6 +2,7 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
+#include "configuration.h"
#include "main.h"
#include "mesh/Channels.h"
#include "mesh/Router.h"
@@ -13,10 +14,11 @@
#endif
#include "mesh/generated/meshtastic/remote_hardware.pb.h"
#include "sleep.h"
-#if HAS_WIFI
+#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#include
#endif
+#include "Default.h"
#include
const int reconnectMax = 5;
@@ -77,8 +79,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
p->decoded.payload.size = jsonPayloadStr.length();
- meshtastic_MeshPacket *packet = packetPool.allocCopy(*p);
- service.sendToMesh(packet, RX_SRC_LOCAL);
+ service.sendToMesh(p, RX_SRC_LOCAL);
} else {
LOG_WARN("Received MQTT json payload too long, dropping\n");
}
@@ -135,7 +136,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
// Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message.
// We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node
// receives it when we get our own packet back. Then we'll stop our retransmissions.
- if (e.packet && getFrom(e.packet) == nodeDB.getNodeNum())
+ if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum())
routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index);
else
LOG_INFO("Ignoring downlink message we originally sent.\n");
@@ -186,10 +187,19 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
statusTopic = moduleConfig.mqtt.root + statusTopic;
cryptTopic = moduleConfig.mqtt.root + cryptTopic;
jsonTopic = moduleConfig.mqtt.root + jsonTopic;
+ mapTopic = moduleConfig.mqtt.root + mapTopic;
} else {
statusTopic = "msh" + statusTopic;
cryptTopic = "msh" + cryptTopic;
jsonTopic = "msh" + jsonTopic;
+ mapTopic = "msh" + mapTopic;
+ }
+
+ if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) {
+ map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision,
+ default_map_position_precision);
+ map_publish_interval_msecs = Default::getConfiguredOrDefaultMs(
+ moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs);
}
#ifdef HAS_NETWORKING
@@ -351,11 +361,13 @@ void MQTT::sendSubscriptions()
std::string topic = cryptTopic + channels.getGlobalId(i) + "/#";
LOG_INFO("Subscribing to %s\n", topic.c_str());
pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right?
+#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804
if (moduleConfig.mqtt.json_enabled == true) {
std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/#";
LOG_INFO("Subscribing to %s\n", topicDecoded.c_str());
pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right?
}
+#endif // ARCH_NRF52
}
}
#endif
@@ -363,38 +375,30 @@ void MQTT::sendSubscriptions()
bool MQTT::wantsLink() const
{
- bool hasChannel = false;
+ bool hasChannelorMapReport =
+ moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled());
- if (moduleConfig.mqtt.enabled) {
- // No need for link if no channel needed it
- size_t numChan = channels.getNumChannels();
- for (size_t i = 0; i < numChan; i++) {
- const auto &ch = channels.getByIndex(i);
- if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) {
- hasChannel = true;
- break;
- }
- }
- }
- if (hasChannel && moduleConfig.mqtt.proxy_to_client_enabled)
+ if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled)
return true;
#if HAS_WIFI
- return hasChannel && WiFi.isConnected();
+ return hasChannelorMapReport && WiFi.isConnected();
#endif
#if HAS_ETHERNET
- return hasChannel && Ethernet.linkStatus() == LinkON;
+ return hasChannelorMapReport && Ethernet.linkStatus() == LinkON;
#endif
return false;
}
int32_t MQTT::runOnce()
{
- if (!moduleConfig.mqtt.enabled)
+ if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()))
return disable();
bool wantConnection = wantsLink();
+ perhapsReportToMap();
+
// If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server
if (moduleConfig.mqtt.proxy_to_client_enabled) {
publishQueuedMessages();
@@ -450,6 +454,7 @@ void MQTT::publishQueuedMessages()
publish(topic.c_str(), bytes, numBytes, false);
+#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804
if (moduleConfig.mqtt.json_enabled) {
// handle json topic
auto jsonString = this->meshPacketToJson(env->packet);
@@ -460,6 +465,7 @@ void MQTT::publishQueuedMessages()
publish(topicJson.c_str(), jsonString.c_str(), false);
}
}
+#endif // ARCH_NRF52
mqttPool.release(env);
}
}
@@ -471,9 +477,9 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
auto &ch = channels.getByIndex(chIndex);
- if (&mp.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 &&
- (mp.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP ||
- mp.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) {
+ if (&mp_decoded.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 &&
+ (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP ||
+ mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) {
LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n");
return;
}
@@ -504,6 +510,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
publish(topic.c_str(), bytes, numBytes, false);
+#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804
if (moduleConfig.mqtt.json_enabled) {
// handle json topic
auto jsonString = this->meshPacketToJson((meshtastic_MeshPacket *)&mp_decoded);
@@ -514,7 +521,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
publish(topicJson.c_str(), jsonString.c_str(), false);
}
}
-
+#endif // ARCH_NRF52
} else {
LOG_INFO("MQTT not connected, queueing packet\n");
if (mqttQueue.numFree() == 0) {
@@ -531,6 +538,79 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
}
}
+void MQTT::perhapsReportToMap()
+{
+ if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly()))
+ return;
+
+ if (millis() - last_report_to_map < map_publish_interval_msecs) {
+ return;
+ } else {
+ if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
+ last_report_to_map = millis();
+ LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n");
+ return;
+ }
+
+ // Allocate ServiceEnvelope and fill it
+ meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed();
+ se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id
+ se->gateway_id = owner.id;
+
+ // Allocate MeshPacket and fill it
+ meshtastic_MeshPacket *mp = packetPool.allocZeroed();
+ mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
+ mp->from = nodeDB->getNodeNum();
+ mp->to = NODENUM_BROADCAST;
+ mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
+
+ // Fill MapReport message
+ meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
+ memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
+ memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
+ mapReport.role = config.device.role;
+ mapReport.hw_model = owner.hw_model;
+ strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
+ mapReport.region = config.lora.region;
+ mapReport.modem_preset = config.lora.modem_preset;
+ mapReport.has_default_channel = channels.hasDefaultChannel();
+
+ // Set position with precision (same as in PositionModule)
+ if (map_position_precision < 32 && map_position_precision > 0) {
+ mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
+ mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
+ mapReport.latitude_i += (1 << (31 - map_position_precision));
+ mapReport.longitude_i += (1 << (31 - map_position_precision));
+ } else {
+ mapReport.latitude_i = localPosition.latitude_i;
+ mapReport.longitude_i = localPosition.longitude_i;
+ }
+ mapReport.altitude = localPosition.altitude;
+ mapReport.position_precision = map_position_precision;
+
+ mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true);
+
+ // Encode MapReport message and set it to MeshPacket in ServiceEnvelope
+ mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes),
+ &meshtastic_MapReport_msg, &mapReport);
+ se->packet = mp;
+
+ // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
+ static uint8_t bytes[meshtastic_MeshPacket_size + 64];
+ size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se);
+
+ LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str());
+ publish(mapTopic.c_str(), bytes, numBytes, false);
+
+ // Release the allocated memory for ServiceEnvelope and MeshPacket
+ mqttPool.release(se);
+ packetPool.release(mp);
+
+ // Update the last report time
+ last_report_to_map = millis();
+ }
+}
+
// converts a downstream packet into a json message
std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
{
@@ -576,6 +656,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage);
msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization);
msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx);
+ msgPayload["uptime_seconds"] = new JSONValue((uint)decoded->variant.device_metrics.uptime_seconds);
} else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) {
msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature);
msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity);
@@ -650,6 +731,9 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
if ((int)decoded->VDOP) {
msgPayload["VDOP"] = new JSONValue((int)decoded->VDOP);
}
+ if ((int)decoded->precision_bits) {
+ msgPayload["precision_bits"] = new JSONValue((int)decoded->precision_bits);
+ }
jsonObj["payload"] = new JSONValue(msgPayload);
} else {
LOG_ERROR("Error decoding protobuf for position message!\n");
@@ -715,7 +799,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
// Lambda function for adding a long name to the route
auto addToRoute = [](JSONArray *route, NodeNum num) {
char long_name[40] = "Unknown";
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(num);
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num);
bool name_known = node ? node->has_user : false;
if (name_known)
memcpy(long_name, node->user.long_name, sizeof(long_name));
@@ -803,6 +887,8 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
if (mp->rx_snr != 0)
jsonObj["snr"] = new JSONValue((float)mp->rx_snr);
+ if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start)
+ jsonObj["hops_away"] = new JSONValue((uint)(mp->hop_start - mp->hop_limit));
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObj);
@@ -819,7 +905,7 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json)
// if "sender" is provided, avoid processing packets we uplinked
return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) &&
(json.find("from") != json.end()) && json["from"]->IsNumber() &&
- (json["from"]->AsNumber() == nodeDB.getNodeNum()) && // only accept message if the "from" is us
+ (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us
(json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type
(json.find("payload") != json.end()); // should have a payload
}
\ No newline at end of file
diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h
index 2b803e3fc..f2eb6b120 100644
--- a/src/mqtt/MQTT.h
+++ b/src/mqtt/MQTT.h
@@ -71,6 +71,10 @@ class MQTT : private concurrency::OSThread
void onClientProxyReceive(meshtastic_MqttClientProxyMessage msg);
+ bool isEnabled() { return this->enabled; };
+
+ void start() { setIntervalFromNow(0); };
+
protected:
PointerQueue mqttQueue;
@@ -79,10 +83,19 @@ class MQTT : private concurrency::OSThread
virtual int32_t runOnce() override;
private:
- std::string statusTopic = "/2/stat/";
- std::string cryptTopic = "/2/c/"; // msh/2/c/CHANNELID/NODEID
- std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID
- /** return true if we have a channel that wants uplink/downlink
+ std::string statusTopic = "/2/stat/"; // For "online"/"offline" message
+ std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID
+ std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID
+ std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages
+
+ // For map reporting (only applies when enabled)
+ const uint32_t default_map_position_precision = 14; // defaults to max. offset of ~1459m
+ const uint32_t default_map_publish_interval_secs = 60 * 15; // defaults to 15 minutes
+ uint32_t last_report_to_map = 0;
+ uint32_t map_position_precision = default_map_position_precision;
+ uint32_t map_publish_interval_msecs = default_map_publish_interval_secs * 1000;
+
+ /** return true if we have a channel that wants uplink/downlink or map reporting is enabled
*/
bool wantsLink() const;
@@ -102,6 +115,9 @@ class MQTT : private concurrency::OSThread
void publishStatus();
void publishQueuedMessages();
+ // Check if we should report unencrypted information about our node for consumption by a map
+ void perhapsReportToMap();
+
// returns true if this is a valid JSON envelope which we accept on downlink
bool isValidJsonEnvelope(JSONObject &json);
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 0b2a806c9..8f7e00461 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -1,7 +1,9 @@
-#include "NimbleBluetooth.h"
-#include "BluetoothCommon.h"
-#include "PowerFSM.h"
#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_BLUETOOTH
+#include "BluetoothCommon.h"
+#include "NimbleBluetooth.h"
+#include "PowerFSM.h"
+
#include "main.h"
#include "mesh/PhoneAPI.h"
#include "mesh/mesh-pb-constants.h"
@@ -104,14 +106,26 @@ static NimbleBluetoothFromRadioCallback *fromRadioCallbacks;
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\n");
- // Bluefruit.Advertising.stop();
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->stop();
+#endif
}
+// Proper shutdown for ESP32. Needs reboot to reverse.
+void NimbleBluetooth::deinit()
+{
+#ifdef ARCH_ESP32
+ LOG_INFO("Disable bluetooth until reboot\n");
+ NimBLEDevice::deinit();
+#endif
+}
+
+// Has initial setup been completed
bool NimbleBluetooth::isActive()
{
return bleServer;
@@ -227,3 +241,4 @@ void clearNVS()
ESP.restart();
#endif
}
+#endif
\ No newline at end of file
diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h
index 4080a7cbc..d1e347830 100644
--- a/src/nimble/NimbleBluetooth.h
+++ b/src/nimble/NimbleBluetooth.h
@@ -6,6 +6,7 @@ class NimbleBluetooth : BluetoothApi
public:
void setup();
void shutdown();
+ void deinit();
void clearBonds();
bool isActive();
bool isConnected();
@@ -16,5 +17,5 @@ class NimbleBluetooth : BluetoothApi
void startAdvertising();
};
-void setBluetoothEnable(bool on);
-void clearNVS();
+void setBluetoothEnable(bool enable);
+void clearNVS();
\ No newline at end of file
diff --git a/src/platform/esp32/SimpleAllocator.cpp b/src/platform/esp32/SimpleAllocator.cpp
index ed44722c5..63f3b02de 100644
--- a/src/platform/esp32/SimpleAllocator.cpp
+++ b/src/platform/esp32/SimpleAllocator.cpp
@@ -58,7 +58,7 @@ void *operator new(size_t sz) throw(std::bad_alloc)
void operator delete(void *ptr) throw()
{
if (activeAllocator)
- LOG_DEBUG("Warning: leaking an active allocator object\n"); // We don't properly handle this yet
+ LOG_WARN("Leaking an active allocator object\n"); // We don't properly handle this yet
else
free(ptr);
}
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index 7fab475f3..27088f86f 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -117,6 +117,8 @@
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER
#elif defined(TLORA_T3S3_V1)
#define HW_VENDOR meshtastic_HardwareModel_TLORA_T3_S3
+#elif defined(CDEBYTE_EORA_S3)
+#define HW_VENDOR meshtastic_HardwareModel_CDEBYTE_EORA_S3
#elif defined(BETAFPV_2400_TX)
#define HW_VENDOR meshtastic_HardwareModel_BETAFPV_2400_TX
#elif defined(NANO_G1_EXPLORER)
@@ -125,6 +127,10 @@
#define HW_VENDOR meshtastic_HardwareModel_BETAFPV_900_NANO_TX
#elif defined(PICOMPUTER_S3)
#define HW_VENDOR meshtastic_HardwareModel_PICOMPUTER_S3
+#elif defined(HELTEC_HT62)
+#define HW_VENDOR meshtastic_HardwareModel_HELTEC_HT62
+#elif defined(EBYTE_ESP32_S3)
+#define HW_VENDOR meshtastic_HardwareModel_EBYTE_ESP32_S3
#elif defined(ESP32_S3_PICO)
#define HW_VENDOR meshtastic_HardwareModel_ESP32_S3_PICO
#elif defined(SENSELORA_S3)
@@ -133,6 +139,10 @@
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_HT62
#elif defined(CHATTER_2)
#define HW_VENDOR meshtastic_HardwareModel_CHATTER_2
+#elif defined(STATION_G2)
+#define HW_VENDOR meshtastic_HardwareModel_STATION_G2
+#elif defined(UNPHONE)
+#define HW_VENDOR meshtastic_HardwareModel_UNPHONE
#endif
// -----------------------------------------------------------------------------
diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp
index c994eea48..2894a49fc 100644
--- a/src/platform/esp32/main-esp32.cpp
+++ b/src/platform/esp32/main-esp32.cpp
@@ -3,11 +3,14 @@
#include "esp_task_wdt.h"
#include "main.h"
-#if !defined(CONFIG_IDF_TARGET_ESP32S2)
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
+#include "BleOta.h"
#include "nimble/NimbleBluetooth.h"
#endif
-#include "BleOta.h"
+
+#if !MESHTASTIC_EXCLUDE_WIFI
#include "mesh/wifi/WiFiAPClient.h"
+#endif
#include "meshUtils.h"
#include "sleep.h"
@@ -18,23 +21,23 @@
#include
#include
-#if !defined(CONFIG_IDF_TARGET_ESP32S2)
-
-void setBluetoothEnable(bool on)
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
+void setBluetoothEnable(bool enable)
{
if (!isWifiAvailable() && config.bluetooth.enabled == true) {
if (!nimbleBluetooth) {
nimbleBluetooth = new NimbleBluetooth();
}
- if (on && !nimbleBluetooth->isActive()) {
+ if (enable && !nimbleBluetooth->isActive()) {
nimbleBluetooth->setup();
- } else if (!on) {
- nimbleBluetooth->shutdown();
}
+ // For ESP32, no way to recover from bluetooth shutdown without reboot
+ // BLE advertising automatically stops when MCU enters light-sleep(?)
+ // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse
}
}
#else
-void setBluetoothEnable(bool on) {}
+void setBluetoothEnable(bool enable) {}
void updateBatteryLevel(uint8_t level) {}
#endif
@@ -108,12 +111,16 @@ void esp32Setup()
preferences.putUInt("rebootCounter", rebootCounter);
preferences.end();
LOG_DEBUG("Number of Device Reboots: %d\n", rebootCounter);
+#if !MESHTASTIC_EXCLUDE_BLUETOOTH
String BLEOTA = BleOta::getOtaAppVersion();
if (BLEOTA.isEmpty()) {
LOG_DEBUG("No OTA firmware available\n");
} else {
LOG_DEBUG("OTA firmware version %s\n", BLEOTA.c_str());
}
+#else
+ LOG_DEBUG("No OTA firmware available\n");
+#endif
// enableModemSleep();
diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index dd81929c8..759cbb404 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -1,5 +1,6 @@
#include "NRF52Bluetooth.h"
#include "BluetoothCommon.h"
+#include "PowerFSM.h"
#include "configuration.h"
#include "main.h"
#include "mesh/PhoneAPI.h"
@@ -202,16 +203,16 @@ void setupMeshService(void)
toRadio.begin();
}
-// FIXME, turn off soft device access for debugging
-static bool isSoftDeviceAllowed = true;
static uint32_t configuredPasskey;
void NRF52Bluetooth::shutdown()
{
// Shutdown bluetooth for minimum power draw
LOG_INFO("Disable NRF52 bluetooth\n");
+ if (connectionHandle != 0) {
+ Bluefruit.disconnect(connectionHandle);
+ }
Bluefruit.Advertising.stop();
- Bluefruit.setTxPower(0); // Minimum power
}
bool NRF52Bluetooth::isConnected()
@@ -279,14 +280,19 @@ void NRF52Bluetooth::setup()
LOG_INFO("Configuring the Mesh bluetooth service\n");
setupMeshService();
- // Supposedly debugging works with soft device if you disable advertising
- if (isSoftDeviceAllowed) {
- // Setup the advertising packet(s)
- LOG_INFO("Setting up the advertising payload(s)\n");
- startAdv();
+ // Setup the advertising packet(s)
+ LOG_INFO("Setting up the advertising payload(s)\n");
+ startAdv();
- LOG_INFO("Advertising\n");
- }
+ LOG_INFO("Advertising\n");
+}
+
+void NRF52Bluetooth::resumeAdverising()
+{
+ Bluefruit.Advertising.restartOnDisconnect(true);
+ Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
+ Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
+ Bluefruit.Advertising.start(0);
}
/// Given a level between 0-100, update the BLE attribute
@@ -313,6 +319,7 @@ void NRF52Bluetooth::onConnectionSecured(uint16_t conn_handle)
bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request)
{
LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3);
+ powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
screen->startBluetoothPinScreen(configuredPasskey);
if (match_request) {
diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h
index 193e86cf8..fd27bbf04 100644
--- a/src/platform/nrf52/NRF52Bluetooth.h
+++ b/src/platform/nrf52/NRF52Bluetooth.h
@@ -8,6 +8,7 @@ class NRF52Bluetooth : BluetoothApi
public:
void setup();
void shutdown();
+ void resumeAdverising();
void clearBonds();
bool isConnected();
int getRssi();
@@ -17,4 +18,4 @@ class NRF52Bluetooth : BluetoothApi
void convertToUint8(uint8_t target[4], uint32_t source);
static bool onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request);
static void onPairingCompleted(uint16_t conn_handle, uint8_t auth_status);
-};
+};
\ No newline at end of file
diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp
index 63b014c46..ecffb745d 100644
--- a/src/platform/nrf52/main-nrf52.cpp
+++ b/src/platform/nrf52/main-nrf52.cpp
@@ -63,31 +63,35 @@ static void initBrownout()
// We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice
}
-static bool bleOn = false;
static const bool useSoftDevice = true; // Set to false for easier debugging
-void setBluetoothEnable(bool on)
+#if !MESHTASTIC_EXCLUDE_BLUETOOTH
+void setBluetoothEnable(bool enable)
{
- if (on != bleOn && config.bluetooth.enabled == true) {
- if (on) {
+ if (enable && config.bluetooth.enabled) {
+ if (!useSoftDevice) {
+ LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n");
+ } else {
if (!nrf52Bluetooth) {
- if (!useSoftDevice)
- LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n");
- else {
- nrf52Bluetooth = new NRF52Bluetooth();
- nrf52Bluetooth->setup();
+ LOG_DEBUG("Initializing NRF52 Bluetooth\n");
+ nrf52Bluetooth = new NRF52Bluetooth();
+ nrf52Bluetooth->setup();
- // We delay brownout init until after BLE because BLE starts soft device
- initBrownout();
- }
+ // We delay brownout init until after BLE because BLE starts soft device
+ initBrownout();
+ } else {
+ nrf52Bluetooth->resumeAdverising();
}
- } else if (nrf52Bluetooth) {
+ }
+ } else {
+ if (nrf52Bluetooth) {
nrf52Bluetooth->shutdown();
}
- bleOn = on;
}
}
-
+#else
+void setBluetoothEnable(bool enable) {}
+#endif
/**
* Override printf to use the SEGGER output library (note - this does not effect the printf method on the debug console)
*/
@@ -176,7 +180,7 @@ void cpuDeepSleep(uint32_t msecToWake)
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif
-#ifndef USE_EINK
+#ifdef AQ_SET_PIN
// RAK-12039 set pin for Air quality sensor
digitalWrite(AQ_SET_PIN, LOW);
#endif
diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index c8fcc3d13..a04c9c12c 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -15,12 +15,14 @@
#include