mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-29 06:00:33 +00:00
Compare commits
112 Commits
v2.5.20.4c
...
NextHopRou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5757b88c48 | ||
|
|
7eb77276cd | ||
|
|
7648391f91 | ||
|
|
4407d9e040 | ||
|
|
27fea5fc07 | ||
|
|
2f6cd02111 | ||
|
|
8c9947b05c | ||
|
|
50b7d6a0f7 | ||
|
|
c83ffd4911 | ||
|
|
9b46cb4ef0 | ||
|
|
01935ea35e | ||
|
|
495f69cf90 | ||
|
|
eb650a6adb | ||
|
|
7fdd262d55 | ||
|
|
d1fa27d353 | ||
|
|
8427072d79 | ||
|
|
4e2b47cc67 | ||
|
|
da1d78c882 | ||
|
|
7c4bf38647 | ||
|
|
96262b106c | ||
|
|
39e45d90e1 | ||
|
|
d70a9392af | ||
|
|
cea072288a | ||
|
|
4e8c4f0d55 | ||
|
|
4a6a0efcfd | ||
|
|
cb0519dd9c | ||
|
|
9db51a72a4 | ||
|
|
64def246ee | ||
|
|
1c8eb7ece3 | ||
|
|
447533aae5 | ||
|
|
1b457bcfbb | ||
|
|
ed07cc067a | ||
|
|
a3a295488c | ||
|
|
5c17afb2ac | ||
|
|
8cacdb65d6 | ||
|
|
3a34f8beaf | ||
|
|
d740934278 | ||
|
|
b370717dcd | ||
|
|
d9534cfc9d | ||
|
|
b91b66bc7a | ||
|
|
4c0e0b8471 | ||
|
|
b5cad2b65e | ||
|
|
cd8592ef4a | ||
|
|
78da8f6fc4 | ||
|
|
6a12760c3d | ||
|
|
30a31a3a13 | ||
|
|
2d42e1b2bc | ||
|
|
4747e73f37 | ||
|
|
10d553087c | ||
|
|
7649e70585 | ||
|
|
a14346bc4f | ||
|
|
fd56995764 | ||
|
|
dc4279e7fd | ||
|
|
766bd614a4 | ||
|
|
a3d9582b35 | ||
|
|
d5a8587deb | ||
|
|
5154e29b07 | ||
|
|
919660e005 | ||
|
|
c7d04d7064 | ||
|
|
4915a07c2a | ||
|
|
98b4a29ef9 | ||
|
|
41d0a39ba6 | ||
|
|
0952c861ae | ||
|
|
017bff8be7 | ||
|
|
e593d54743 | ||
|
|
6a29793f23 | ||
|
|
47116f65cd | ||
|
|
98719e4c62 | ||
|
|
bfc6a1940d | ||
|
|
360637c25d | ||
|
|
3ea2918f7f | ||
|
|
b229abc2b4 | ||
|
|
dbe520c3ab | ||
|
|
42d17b3322 | ||
|
|
3725319b4b | ||
|
|
17495e7dbf | ||
|
|
93bcee3aab | ||
|
|
71a90b3b78 | ||
|
|
be73b099a7 | ||
|
|
f37abe8f0f | ||
|
|
78bf1e192b | ||
|
|
70aa28c53c | ||
|
|
fbefce7e10 | ||
|
|
69f88b9fdc | ||
|
|
24ff7c0bfb | ||
|
|
bb64b1480b | ||
|
|
790801f8e7 | ||
|
|
28944adf20 | ||
|
|
aab973e81b | ||
|
|
e4c98185d2 | ||
|
|
01344835af | ||
|
|
9de8d5ae66 | ||
|
|
ba4220fe50 | ||
|
|
aae4443e25 | ||
|
|
6fe42ed4c5 | ||
|
|
2e303a33be | ||
|
|
913268b132 | ||
|
|
e91dcb4ec3 | ||
|
|
b8e01b4044 | ||
|
|
d4ef0cdba5 | ||
|
|
b456e34c6e | ||
|
|
25ec0514da | ||
|
|
3ba9ecbbfe | ||
|
|
ef2c6eed05 | ||
|
|
81f57b65e1 | ||
|
|
42757d847f | ||
|
|
c7293cf6f1 | ||
|
|
27a492adf8 | ||
|
|
9b1dd75549 | ||
|
|
44dc270c8a | ||
|
|
3776064b80 | ||
|
|
0d6729b9eb |
38
.github/workflows/build_native.yml
vendored
38
.github/workflows/build_native.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Build Native
|
||||
|
||||
on: workflow_call
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-native:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup native build
|
||||
id: base
|
||||
uses: ./.github/actions/setup-native
|
||||
|
||||
- name: Build Native
|
||||
run: bin/build-native.sh
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-native-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/meshtasticd_linux_x86_64
|
||||
bin/config-dist.yaml
|
||||
52
.github/workflows/build_raspbian.yml
vendored
52
.github/workflows/build_raspbian.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: Build Raspbian
|
||||
|
||||
on: workflow_call
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-raspbian:
|
||||
runs-on: [self-hosted, linux, ARM64]
|
||||
steps:
|
||||
- name: Install libbluetooth
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Upgrade python tools
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
pip install -U meshtastic --pre
|
||||
|
||||
- name: Upgrade platformio
|
||||
shell: bash
|
||||
run: |
|
||||
pio upgrade
|
||||
|
||||
- name: Build Raspbian
|
||||
run: bin/build-native.sh
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-raspbian-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/meshtasticd_linux_aarch64
|
||||
bin/config-dist.yaml
|
||||
52
.github/workflows/build_raspbian_armv7l.yml
vendored
52
.github/workflows/build_raspbian_armv7l.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: Build Raspbian Arm
|
||||
|
||||
on: workflow_call
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-raspbian-armv7l:
|
||||
runs-on: [self-hosted, linux, ARM]
|
||||
steps:
|
||||
- name: Install libbluetooth
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Upgrade python tools
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
pip install -U meshtastic --pre
|
||||
|
||||
- name: Upgrade platformio
|
||||
shell: bash
|
||||
run: |
|
||||
pio upgrade
|
||||
|
||||
- name: Build Raspbian
|
||||
run: bin/build-native.sh
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-raspbian-armv7l-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/meshtasticd_linux_armv7l
|
||||
bin/config-dist.yaml
|
||||
53
.github/workflows/main_matrix.yml
vendored
53
.github/workflows/main_matrix.yml
vendored
@@ -128,15 +128,6 @@ jobs:
|
||||
with:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
package-raspbian:
|
||||
uses: ./.github/workflows/package_raspbian.yml
|
||||
|
||||
package-raspbian-armv7l:
|
||||
uses: ./.github/workflows/package_raspbian_armv7l.yml
|
||||
|
||||
package-native:
|
||||
uses: ./.github/workflows/package_amd64.yml
|
||||
|
||||
build-debian-src:
|
||||
uses: ./.github/workflows/build_debian_src.yml
|
||||
with:
|
||||
@@ -144,6 +135,12 @@ jobs:
|
||||
build_location: local
|
||||
secrets: inherit
|
||||
|
||||
package-pio-deps-native:
|
||||
uses: ./.github/workflows/package_pio_deps.yml
|
||||
with:
|
||||
pio_env: native
|
||||
secrets: inherit
|
||||
|
||||
test-native:
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
@@ -158,7 +155,7 @@ jobs:
|
||||
docker-alpine-amd64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
@@ -289,13 +286,9 @@ jobs:
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
needs:
|
||||
[
|
||||
gather-artifacts,
|
||||
package-raspbian,
|
||||
package-raspbian-armv7l,
|
||||
package-native,
|
||||
build-debian-src,
|
||||
]
|
||||
- gather-artifacts
|
||||
- build-debian-src
|
||||
- package-pio-deps-native
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -324,13 +317,6 @@ jobs:
|
||||
body: |
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download deb files
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: meshtasticd_${{ steps.version.outputs.long }}_*.deb
|
||||
merge-multiple: true
|
||||
path: ./output
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -338,20 +324,27 @@ jobs:
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Zip source deb
|
||||
- name: Download native pio deps
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: platformio-deps-native-${{ steps.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output/pio-deps-native
|
||||
|
||||
- name: Zip linux sources
|
||||
working-directory: output
|
||||
run: zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
|
||||
run: |
|
||||
zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
|
||||
zip -9 -r ./platformio-deps-native-${{ steps.version.outputs.long }}.zip ./pio-deps-native
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Add deb files to release
|
||||
- name: Add linux sources to release
|
||||
run: |
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_arm64.deb
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_armhf.deb
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_amd64.deb
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-${{ steps.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
90
.github/workflows/package_amd64.yml
vendored
90
.github/workflows/package_amd64.yml
vendored
@@ -1,90 +0,0 @@
|
||||
name: Package Native
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-native:
|
||||
uses: ./.github/workflows/build_native.yml
|
||||
|
||||
package-native:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build-native
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
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@master
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: firmware-native-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: build .debpkg
|
||||
run: |
|
||||
mkdir -p .debpkg/DEBIAN
|
||||
mkdir -p .debpkg/usr/share/meshtasticd/web
|
||||
mkdir -p .debpkg/usr/sbin
|
||||
mkdir -p .debpkg/etc/meshtasticd
|
||||
mkdir -p .debpkg/etc/meshtasticd/config.d
|
||||
mkdir -p .debpkg/etc/meshtasticd/available.d
|
||||
mkdir -p .debpkg/usr/lib/systemd/system/
|
||||
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
|
||||
shopt -s dotglob nullglob
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
|
||||
gunzip .debpkg/usr/share/meshtasticd/web/ -r
|
||||
cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
|
||||
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
|
||||
chmod +x .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
|
||||
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
|
||||
chmod +x .debpkg/DEBIAN/conffiles
|
||||
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
|
||||
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
|
||||
chmod +x .debpkg/DEBIAN/preinst
|
||||
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
|
||||
chmod +x .debpkg/DEBIAN/postinst
|
||||
|
||||
- uses: jiro4989/build-deb-action@v3
|
||||
with:
|
||||
package: meshtasticd
|
||||
package_root: .debpkg
|
||||
maintainer: Jonathan Bennett
|
||||
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
|
||||
arch: amd64
|
||||
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
|
||||
desc: Native Linux Meshtastic binary.
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshtasticd_${{ steps.version.outputs.long }}_amd64.deb
|
||||
overwrite: true
|
||||
path: |
|
||||
./*.deb
|
||||
65
.github/workflows/package_pio_deps.yml
vendored
Normal file
65
.github/workflows/package_pio_deps.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Package PlatformIO Library Dependencies
|
||||
# trunk-ignore-all(checkov/CKV_GHA_7): Allow workflow_dispatch inputs for testing
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pio_env:
|
||||
description: PlatformIO environment to target
|
||||
required: true
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pio_env:
|
||||
description: PlatformIO environment to target
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
pkg-pio-libdeps:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
pip install platformio
|
||||
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Fetch libdeps
|
||||
shell: bash
|
||||
run: |-
|
||||
platformio pkg install -e ${{ inputs.pio_env }}
|
||||
platformio pkg install -e ${{ inputs.pio_env }} -t platformio/tool-scons@4.40502.0
|
||||
env:
|
||||
PLATFORMIO_LIBDEPS_DIR: pio/libdeps
|
||||
PLATFORMIO_PACKAGES_DIR: pio/packages
|
||||
PLATFORMIO_CORE_DIR: pio/core
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
include-hidden-files: true
|
||||
path: |
|
||||
pio/*
|
||||
90
.github/workflows/package_raspbian.yml
vendored
90
.github/workflows/package_raspbian.yml
vendored
@@ -1,90 +0,0 @@
|
||||
name: Package Raspbian
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-raspbian:
|
||||
uses: ./.github/workflows/build_raspbian.yml
|
||||
|
||||
package-raspbian:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build-raspbian
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
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@master
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: firmware-raspbian-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: build .debpkg
|
||||
run: |
|
||||
mkdir -p .debpkg/DEBIAN
|
||||
mkdir -p .debpkg/usr/share/meshtasticd/web
|
||||
mkdir -p .debpkg/usr/sbin
|
||||
mkdir -p .debpkg/etc/meshtasticd
|
||||
mkdir -p .debpkg/etc/meshtasticd/config.d
|
||||
mkdir -p .debpkg/etc/meshtasticd/available.d
|
||||
mkdir -p .debpkg/usr/lib/systemd/system/
|
||||
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
|
||||
shopt -s dotglob nullglob
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
|
||||
gunzip .debpkg/usr/share/meshtasticd/web/ -r
|
||||
cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
|
||||
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
|
||||
chmod +x .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
|
||||
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
|
||||
chmod +x .debpkg/DEBIAN/conffiles
|
||||
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
|
||||
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
|
||||
chmod +x .debpkg/DEBIAN/preinst
|
||||
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
|
||||
chmod +x .debpkg/DEBIAN/postinst
|
||||
|
||||
- uses: jiro4989/build-deb-action@v3
|
||||
with:
|
||||
package: meshtasticd
|
||||
package_root: .debpkg
|
||||
maintainer: Jonathan Bennett
|
||||
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
|
||||
arch: arm64
|
||||
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
|
||||
desc: Native Linux Meshtastic binary.
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshtasticd_${{ steps.version.outputs.long }}_arm64.deb
|
||||
overwrite: true
|
||||
path: |
|
||||
./*.deb
|
||||
90
.github/workflows/package_raspbian_armv7l.yml
vendored
90
.github/workflows/package_raspbian_armv7l.yml
vendored
@@ -1,90 +0,0 @@
|
||||
name: Package Raspbian
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-raspbian_armv7l:
|
||||
uses: ./.github/workflows/build_raspbian_armv7l.yml
|
||||
|
||||
package-raspbian_armv7l:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build-raspbian_armv7l
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
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@master
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: firmware-raspbian-armv7l-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: build .debpkg
|
||||
run: |
|
||||
mkdir -p .debpkg/DEBIAN
|
||||
mkdir -p .debpkg/usr/share/meshtasticd/web
|
||||
mkdir -p .debpkg/usr/sbin
|
||||
mkdir -p .debpkg/etc/meshtasticd
|
||||
mkdir -p .debpkg/etc/meshtasticd/config.d
|
||||
mkdir -p .debpkg/etc/meshtasticd/available.d
|
||||
mkdir -p .debpkg/usr/lib/systemd/system/
|
||||
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
|
||||
shopt -s dotglob nullglob
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
|
||||
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
|
||||
gunzip .debpkg/usr/share/meshtasticd/web/ -r
|
||||
cp release/meshtasticd_linux_armv7l .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
|
||||
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
|
||||
chmod +x .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
|
||||
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
|
||||
chmod +x .debpkg/DEBIAN/conffiles
|
||||
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
|
||||
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
|
||||
chmod +x .debpkg/DEBIAN/preinst
|
||||
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
|
||||
chmod +x .debpkg/DEBIAN/postinst
|
||||
|
||||
- uses: jiro4989/build-deb-action@v3
|
||||
with:
|
||||
package: meshtasticd
|
||||
package_root: .debpkg
|
||||
maintainer: Jonathan Bennett
|
||||
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
|
||||
arch: armhf
|
||||
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
|
||||
desc: Native Linux Meshtastic binary.
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: meshtasticd_${{ steps.version.outputs.long }}_armhf.deb
|
||||
overwrite: true
|
||||
path: |
|
||||
./*.deb
|
||||
12
.github/workflows/release_channels.yml
vendored
12
.github/workflows/release_channels.yml
vendored
@@ -37,9 +37,9 @@ jobs:
|
||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
secrets: inherit
|
||||
|
||||
# hook-copr:
|
||||
# uses: ./.github/workflows/hook_copr.yml
|
||||
# with:
|
||||
# copr_project: |-
|
||||
# ${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
# secrets: inherit
|
||||
hook-copr:
|
||||
uses: ./.github/workflows/hook_copr.yml
|
||||
with:
|
||||
copr_project: |-
|
||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
secrets: inherit
|
||||
|
||||
10
.trunk/configs/.prettierrc
Normal file
10
.trunk/configs/.prettierrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": "userPrefs.jsonc",
|
||||
"options": {
|
||||
"trailingComma": "none"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -38,7 +38,8 @@ USER root
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
&& mkdir -p /etc/meshtasticd/ssl
|
||||
|
||||
# Fetch compiled binary from the builder
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
|
||||
|
||||
@@ -29,7 +29,8 @@ USER root
|
||||
|
||||
RUN apk add libstdc++ libgpiod yaml-cpp libusb i2c-tools \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
&& mkdir -p /etc/meshtasticd/ssl
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
|
||||
|
||||
WORKDIR /var/lib/meshtasticd
|
||||
|
||||
@@ -11,9 +11,15 @@ build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-flto
|
||||
-Isrc/platform/stm32wl -g
|
||||
-DMESHTASTIC_MINIMIZE_BUILD
|
||||
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
-DMESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
-DMESHTASTIC_EXCLUDE_I2C
|
||||
-DMESHTASTIC_EXCLUDE_POWERMON
|
||||
-DMESHTASTIC_EXCLUDE_SCREEN
|
||||
-DMESHTASTIC_EXCLUDE_MQTT
|
||||
-DMESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
-DMESHTASTIC_EXCLUDE_PKI
|
||||
-DMESHTASTIC_EXCLUDE_GPS
|
||||
-DDEBUG_MUTE
|
||||
; -DVECT_TAB_OFFSET=0x08000000
|
||||
-DconfigUSE_CMSIS_RTOS_V2=1
|
||||
; -DSPI_MODE_0=SPI_MODE0
|
||||
|
||||
18
bin/build-firmware.sh
Normal file
18
bin/build-firmware.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
|
||||
|
||||
export PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
|
||||
if (echo $2 | grep -q "esp32"); then
|
||||
bin/build-esp32.sh $1
|
||||
elif (echo $2 | grep -q "nrf52"); then
|
||||
bin/build-nrf52.sh $1
|
||||
elif (echo $2 | grep -q "stm32"); then
|
||||
bin/build-stm32.sh $1
|
||||
elif (echo $2 | grep -q "rpi2040"); then
|
||||
bin/build-rpi2040.sh $1
|
||||
else
|
||||
echo "Unknown target $2"
|
||||
exit 1
|
||||
fi
|
||||
@@ -78,6 +78,8 @@ Lora:
|
||||
# TXen: x # TX and RX enable pins
|
||||
# RXen: x
|
||||
|
||||
# SX126X_MAX_POWER: 8 # Limit the output power to 8 dBm, useful for amped nodes
|
||||
|
||||
# spiSpeed: 2000000
|
||||
|
||||
### Set default/fallback gpio chip to use in /dev/. Defaults to 0.
|
||||
@@ -182,10 +184,12 @@ Logging:
|
||||
Webserver:
|
||||
# Port: 443 # Port for Webserver & Webservices
|
||||
# RootPath: /usr/share/meshtasticd/web # Root Dir of WebServer
|
||||
# SSLKey: /etc/meshtasticd/ssl/private_key.pem # Path to SSL Key, generated if not present
|
||||
# SSLCert: /etc/meshtasticd/ssl/certificate.pem # Path to SSL Certificate, generated if not present
|
||||
|
||||
General:
|
||||
MaxNodes: 200
|
||||
MaxMessageQueue: 100
|
||||
ConfigDirectory: /etc/meshtasticd/config.d/
|
||||
# MACAddress: AA:BB:CC:DD:EE:FF
|
||||
# MACAddressSource: eth0
|
||||
# MACAddressSource: eth0
|
||||
@@ -7,3 +7,6 @@ Lora:
|
||||
TXen: 13
|
||||
RXen: 12
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
# Only for E22-900M33S:
|
||||
# Limit the output power to 8 dBm
|
||||
# SX126X_MAX_POWER: 8
|
||||
@@ -102,7 +102,7 @@ pref_flags = []
|
||||
for pref in userPrefs:
|
||||
if userPrefs[pref].startswith("{"):
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
elif userPrefs[pref].replace(".", "").isdigit():
|
||||
elif userPrefs[pref].lstrip("-").replace(".", "").isdigit():
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
elif userPrefs[pref] == "true" or userPrefs[pref] == "false":
|
||||
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
|
||||
|
||||
52
boards/meshlink.json
Normal file
52
boards/meshlink.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DMESHLINK -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x00B3"],
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
],
|
||||
"usb_product": "MeshLink",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "meshlink",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "MeshLink",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.loraitalia.it",
|
||||
"vendor": "LoraItalia"
|
||||
}
|
||||
6
debian/changelog
vendored
6
debian/changelog
vendored
@@ -1,7 +1,9 @@
|
||||
meshtasticd (2.5.20.0) UNRELEASED; urgency=medium
|
||||
meshtasticd (2.5.22.0) UNRELEASED; urgency=medium
|
||||
|
||||
* Initial packaging
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- Austin Lane <github-actions[bot]@users.noreply.github.com> Wed, 15 Jan 2025 14:08:54 +0000
|
||||
-- Austin Lane <github-actions[bot]@users.noreply.github.com> Wed, 05 Feb 2025 01:10:33 +0000
|
||||
|
||||
2
debian/ci_pack_sdeb.sh
vendored
2
debian/ci_pack_sdeb.sh
vendored
@@ -11,7 +11,7 @@ platformio pkg install -e native -t platformio/tool-scons@4.40502.0
|
||||
tar -cf pio.tar pio/
|
||||
rm -rf pio
|
||||
# Download the latest meshtastic/web release build.tar to `web.tar`
|
||||
curl -L https://github.com/meshtastic/web/releases/download/latest/build.tar -o web.tar
|
||||
curl -L https://github.com/meshtastic/web/releases/latest/download/build.tar -o web.tar
|
||||
|
||||
package=$(dpkg-parsechangelog --show-field Source)
|
||||
|
||||
|
||||
3
debian/meshtasticd.dirs
vendored
3
debian/meshtasticd.dirs
vendored
@@ -1,4 +1,5 @@
|
||||
etc/meshtasticd
|
||||
etc/meshtasticd/config.d
|
||||
etc/meshtasticd/available.d
|
||||
usr/share/meshtasticd/web
|
||||
usr/share/meshtasticd/web
|
||||
etc/meshtasticd/ssl
|
||||
@@ -21,7 +21,7 @@ Summary: Meshtastic daemon for communicating with Meshtastic devices
|
||||
License: GPL-3.0
|
||||
URL: https://github.com/meshtastic/firmware
|
||||
Source0: {{{ git_dir_pack }}}
|
||||
Source1: https://github.com/meshtastic/web/releases/download/latest/build.tar
|
||||
Source1: https://github.com/meshtastic/web/releases/latest/download/build.tar
|
||||
|
||||
BuildRequires: systemd-rpm-macros
|
||||
BuildRequires: python3-devel
|
||||
@@ -72,6 +72,8 @@ install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.s
|
||||
# Install the web files under /usr/share/meshtasticd/web
|
||||
mkdir -p %{buildroot}%{_datadir}/meshtasticd/web
|
||||
cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
# Install default SSL storage directory (for web)
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
@@ -86,6 +88,7 @@ cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
%dir %{_datadir}/meshtasticd
|
||||
%dir %{_datadir}/meshtasticd/web
|
||||
%{_datadir}/meshtasticd/web/*
|
||||
%dir %{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
%changelog
|
||||
%autochangelog
|
||||
Submodule protobufs updated: 7f13df0e5f...068646653e
@@ -153,7 +153,7 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
|
||||
#endif
|
||||
#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX)
|
||||
pixels.fill(BUTTON2_COLOR, BUTTON1_COLOR_INDEX, 1);
|
||||
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
|
||||
#endif
|
||||
#endif
|
||||
pixels.show();
|
||||
|
||||
@@ -79,17 +79,17 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
|
||||
}
|
||||
if (color && logLevel != nullptr) {
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
|
||||
Print::write("\u001b[34m", 6);
|
||||
Print::write("\u001b[34m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
|
||||
Print::write("\u001b[32m", 6);
|
||||
Print::write("\u001b[32m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
|
||||
Print::write("\u001b[33m", 6);
|
||||
Print::write("\u001b[33m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
|
||||
Print::write("\u001b[31m", 6);
|
||||
Print::write("\u001b[31m", 5);
|
||||
}
|
||||
len = Print::write(printBuf, len);
|
||||
if (color && logLevel != nullptr) {
|
||||
Print::write("\u001b[0m", 5);
|
||||
Print::write("\u001b[0m", 4);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
@@ -107,15 +107,15 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
||||
// include the header
|
||||
if (color) {
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
|
||||
Print::write("\u001b[34m", 6);
|
||||
Print::write("\u001b[34m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
|
||||
Print::write("\u001b[32m", 6);
|
||||
Print::write("\u001b[32m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
|
||||
Print::write("\u001b[33m", 6);
|
||||
Print::write("\u001b[33m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
|
||||
Print::write("\u001b[31m", 6);
|
||||
Print::write("\u001b[31m", 5);
|
||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
|
||||
Print::write("\u001b[35m", 6);
|
||||
Print::write("\u001b[35m", 5);
|
||||
}
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
|
||||
@@ -393,4 +393,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
|
||||
break;
|
||||
}
|
||||
return std::string(formatted.get());
|
||||
}
|
||||
}
|
||||
@@ -6,28 +6,28 @@
|
||||
|
||||
void d_writeCommand(uint8_t c)
|
||||
{
|
||||
SPI1.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
if (PIN_EINK_DC >= 0)
|
||||
digitalWrite(PIN_EINK_DC, LOW);
|
||||
if (PIN_EINK_CS >= 0)
|
||||
digitalWrite(PIN_EINK_CS, LOW);
|
||||
SPI1.transfer(c);
|
||||
SPI.transfer(c);
|
||||
if (PIN_EINK_CS >= 0)
|
||||
digitalWrite(PIN_EINK_CS, HIGH);
|
||||
if (PIN_EINK_DC >= 0)
|
||||
digitalWrite(PIN_EINK_DC, HIGH);
|
||||
SPI1.endTransaction();
|
||||
SPI.endTransaction();
|
||||
}
|
||||
|
||||
void d_writeData(uint8_t d)
|
||||
{
|
||||
SPI1.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
if (PIN_EINK_CS >= 0)
|
||||
digitalWrite(PIN_EINK_CS, LOW);
|
||||
SPI1.transfer(d);
|
||||
SPI.transfer(d);
|
||||
if (PIN_EINK_CS >= 0)
|
||||
digitalWrite(PIN_EINK_CS, HIGH);
|
||||
SPI1.endTransaction();
|
||||
SPI.endTransaction();
|
||||
}
|
||||
|
||||
unsigned long d_waitWhileBusy(uint16_t busy_time)
|
||||
@@ -53,7 +53,7 @@ unsigned long d_waitWhileBusy(uint16_t busy_time)
|
||||
|
||||
void scanEInkDevice(void)
|
||||
{
|
||||
SPI1.begin();
|
||||
SPI.begin();
|
||||
d_writeCommand(0x22);
|
||||
d_writeData(0x83);
|
||||
d_writeCommand(0x20);
|
||||
@@ -62,6 +62,6 @@ void scanEInkDevice(void)
|
||||
LOG_DEBUG("EInk display found");
|
||||
else
|
||||
LOG_DEBUG("EInk display not found");
|
||||
SPI1.end();
|
||||
SPI.end();
|
||||
}
|
||||
#endif
|
||||
@@ -449,7 +449,22 @@ bool GPS::setup()
|
||||
if (!didSerialInit) {
|
||||
int msglen = 0;
|
||||
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
#ifdef TRACKER_T1000_E
|
||||
// add power up/down strategy, improve ag3335 detection success
|
||||
digitalWrite(PIN_GPS_EN, LOW);
|
||||
delay(500);
|
||||
digitalWrite(GPS_VRTC_EN, LOW);
|
||||
delay(1000);
|
||||
digitalWrite(GPS_VRTC_EN, HIGH);
|
||||
delay(500);
|
||||
digitalWrite(PIN_GPS_EN, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
#ifdef TRACKER_T1000_E
|
||||
if (probeTries < 5) {
|
||||
#else
|
||||
if (probeTries < 2) {
|
||||
#endif
|
||||
LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]);
|
||||
gnssModel = probe(serialSpeeds[speedSelect]);
|
||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
@@ -460,7 +475,11 @@ bool GPS::setup()
|
||||
}
|
||||
}
|
||||
// Rare Serial Speeds
|
||||
#ifdef TRACKER_T1000_E
|
||||
if (probeTries == 5) {
|
||||
#else
|
||||
if (probeTries == 2) {
|
||||
#endif
|
||||
LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
|
||||
gnssModel = probe(rareSerialSpeeds[speedSelect]);
|
||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
@@ -772,6 +791,9 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
||||
setPowerPMU(true); // Power (PMU): on
|
||||
writePinStandby(false); // Standby (pin): awake (not standby)
|
||||
setPowerUBLOX(true); // Standby (UBLOX): awake
|
||||
#ifdef GNSS_AIROHA
|
||||
lastFixStartMsec = 0;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case GPS_SOFTSLEEP:
|
||||
|
||||
@@ -140,6 +140,15 @@ bool EInkDisplay::connect()
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
||||
}
|
||||
#elif defined(MESHLINK)
|
||||
{
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
||||
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
||||
}
|
||||
#elif defined(RAK4630) || defined(MAKERPYTHON)
|
||||
{
|
||||
if (eink_found) {
|
||||
|
||||
@@ -238,7 +238,7 @@ void EInkDynamicDisplay::checkRateLimiting()
|
||||
|
||||
// Skip update: too soon for BACKGROUND
|
||||
if (frameFlags == BACKGROUND) {
|
||||
if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_BACKGROUND_SEC * 1000)) {
|
||||
if (Throttle::isWithinTimespanMs(previousRunMs, 30000)) {
|
||||
refresh = SKIPPED;
|
||||
reason = EXCEEDED_RATELIMIT_FULL;
|
||||
return;
|
||||
@@ -251,7 +251,7 @@ void EInkDynamicDisplay::checkRateLimiting()
|
||||
|
||||
// Skip update: too soon for RESPONSIVE
|
||||
if (frameFlags & RESPONSIVE) {
|
||||
if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000)) {
|
||||
if (Throttle::isWithinTimespanMs(previousRunMs, 1000)) {
|
||||
refresh = SKIPPED;
|
||||
reason = EXCEEDED_RATELIMIT_FAST;
|
||||
LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x", frameFlags);
|
||||
|
||||
@@ -123,7 +123,7 @@ static bool heartbeat = false;
|
||||
|
||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||
|
||||
/// Check if the display can render a string (detect special chars; emoji)
|
||||
// Check if the display can render a string (detect special chars; emoji)
|
||||
static bool haveGlyphs(const char *str)
|
||||
{
|
||||
#if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU) || defined(OLED_CS)
|
||||
@@ -162,11 +162,7 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
|
||||
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
#ifdef USERPREFS_SPLASH_TITLE
|
||||
const char *title = USERPREFS_SPLASH_TITLE;
|
||||
#else
|
||||
const char *title = "meshtastic.org";
|
||||
#endif
|
||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
@@ -185,6 +181,56 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
||||
}
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
|
||||
static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
static const uint8_t xbm[] = USERPREFS_OEM_IMAGE_DATA;
|
||||
display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2,
|
||||
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH,
|
||||
USERPREFS_OEM_IMAGE_HEIGHT, xbm);
|
||||
|
||||
switch (USERPREFS_OEM_FONT_SIZE) {
|
||||
case 0:
|
||||
display->setFont(FONT_SMALL);
|
||||
break;
|
||||
case 2:
|
||||
display->setFont(FONT_LARGE);
|
||||
break;
|
||||
default:
|
||||
display->setFont(FONT_MEDIUM);
|
||||
break;
|
||||
}
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
const char *title = USERPREFS_OEM_TEXT;
|
||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// Draw region in upper left
|
||||
if (upperMsg)
|
||||
display->drawString(x + 0, y + 0, upperMsg);
|
||||
|
||||
// Draw version and shortname in upper right
|
||||
char buf[25];
|
||||
snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display->drawString(x + SCREEN_WIDTH, y + 0, buf);
|
||||
screen->forceDisplay();
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
||||
}
|
||||
|
||||
static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Draw region in upper left
|
||||
const char *region = myRegion ? myRegion->name : NULL;
|
||||
drawOEMIconScreen(region, display, state, x, y);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
|
||||
{
|
||||
uint16_t x_offset = display->width() / 2;
|
||||
@@ -1400,9 +1446,9 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
|
||||
static char distStr[20];
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
strncpy(distStr, "? mi", sizeof(distStr)); // might not have location data
|
||||
strncpy(distStr, "? mi ?°", sizeof(distStr)); // might not have location data
|
||||
} else {
|
||||
strncpy(distStr, "? km", sizeof(distStr));
|
||||
strncpy(distStr, "? km ?°", sizeof(distStr));
|
||||
}
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
const char *fields[] = {username, lastStr, signalStr, distStr, NULL};
|
||||
@@ -1435,18 +1481,6 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
float d =
|
||||
GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0f m", d);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
||||
}
|
||||
|
||||
float bearingToOther =
|
||||
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
|
||||
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
|
||||
@@ -1454,6 +1488,22 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
if (!config.display.compass_north_top)
|
||||
bearingToOther -= myHeading;
|
||||
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
||||
|
||||
float bearingToOtherDegrees = (bearingToOther < 0) ? bearingToOther + 2 * PI : bearingToOther;
|
||||
bearingToOtherDegrees = bearingToOtherDegrees * 180 / PI;
|
||||
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0fft %.0f°", d * METERS_TO_FEET, bearingToOtherDegrees);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1fmi %.0f°", d * METERS_TO_FEET / MILES_TO_FEET,
|
||||
bearingToOtherDegrees);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0fm %.0f°", d, bearingToOtherDegrees);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1fkm %.0f°", d / 1000, bearingToOtherDegrees);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasNodeHeading) {
|
||||
@@ -1658,6 +1708,10 @@ void Screen::setup()
|
||||
// Set the utf8 conversion function
|
||||
dispdev->setFontTableLookupFunction(customFontTableLookup);
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
logo_timeout *= 2; // Double the time if we have a custom logo
|
||||
#endif
|
||||
|
||||
// Add frames.
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
|
||||
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
@@ -1803,6 +1857,22 @@ int32_t Screen::runOnce()
|
||||
showingBootScreen = false;
|
||||
}
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
static bool showingOEMBootScreen = true;
|
||||
if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) {
|
||||
LOG_INFO("Switch to OEM screen...");
|
||||
// Change frames.
|
||||
static FrameCallback bootOEMFrames[] = {drawOEMBootScreen};
|
||||
static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]);
|
||||
ui->setFrames(bootOEMFrames, bootOEMFrameCount);
|
||||
ui->update();
|
||||
#ifndef USE_EINK
|
||||
ui->update();
|
||||
#endif
|
||||
showingOEMBootScreen = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_WELCOME_UNSET
|
||||
if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
setWelcomeFrames();
|
||||
@@ -2578,13 +2648,12 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
display->drawString(x + 1, y, String("USB"));
|
||||
}
|
||||
|
||||
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
|
||||
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
|
||||
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
|
||||
if (config.display.heading_bold)
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
|
||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
|
||||
// if (config.display.heading_bold)
|
||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
|
||||
|
||||
// Line 2
|
||||
uint32_t currentMillis = millis();
|
||||
uint32_t seconds = currentMillis / 1000;
|
||||
uint32_t minutes = seconds / 60;
|
||||
@@ -2597,6 +2666,9 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
|
||||
display->setColor(WHITE);
|
||||
|
||||
// Setup string to assemble analogClock string
|
||||
std::string analogClock = "";
|
||||
|
||||
// Show uptime as days, hours, minutes OR seconds
|
||||
std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds);
|
||||
|
||||
@@ -2613,17 +2685,36 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||
|
||||
char timebuf[10];
|
||||
snprintf(timebuf, sizeof(timebuf), " %02d:%02d:%02d", hour, min, sec);
|
||||
uptime += timebuf;
|
||||
char timebuf[12];
|
||||
|
||||
if (config.display.use_12h_clock) {
|
||||
std::string meridiem = "am";
|
||||
if (hour >= 12) {
|
||||
if (hour > 12)
|
||||
hour -= 12;
|
||||
meridiem = "pm";
|
||||
}
|
||||
if (hour == 00) {
|
||||
hour = 12;
|
||||
}
|
||||
snprintf(timebuf, sizeof(timebuf), "%d:%02d:%02d%s", hour, min, sec, meridiem.c_str());
|
||||
} else {
|
||||
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", hour, min, sec);
|
||||
}
|
||||
analogClock += timebuf;
|
||||
}
|
||||
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, uptime.c_str());
|
||||
// Line 1
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||||
|
||||
// Line 2
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, analogClock.c_str());
|
||||
|
||||
// Display Channel Utilization
|
||||
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
|
||||
@@ -2662,13 +2753,14 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
|
||||
int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
||||
{
|
||||
// If auto carousel is disabled -> return 0 and skip new messages handling
|
||||
if (config.display.auto_screen_carousel_secs == 0)
|
||||
return 0;
|
||||
|
||||
// Handle focus change based on message type
|
||||
if (showingNormalScreen) {
|
||||
setFrames(packet->from == 0 ? FOCUS_PRESERVE : FOCUS_TEXTMESSAGE);
|
||||
// Outgoing message
|
||||
if (packet->from == 0)
|
||||
setFrames(FOCUS_PRESERVE); // Return to same frame (quietly hiding the rx text message frame)
|
||||
|
||||
// Incoming message
|
||||
else
|
||||
setFrames(FOCUS_TEXTMESSAGE); // Focus on the new message
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
42
src/main.cpp
42
src/main.cpp
@@ -115,10 +115,6 @@ AccelerometerThread *accelerometerThread = nullptr;
|
||||
AudioThread *audioThread = nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(TCXO_OPTIONAL)
|
||||
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this here so it can be changed further down.
|
||||
#endif
|
||||
|
||||
using namespace concurrency;
|
||||
|
||||
volatile static const char slipstreamTZString[] = USERPREFS_TZ_STRING;
|
||||
@@ -648,9 +644,9 @@ void setup()
|
||||
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
|
||||
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 we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
|
||||
router = new FloodingRouter();
|
||||
router = new NextHopRouter();
|
||||
#ifdef PIN_3V3_EN
|
||||
digitalWrite(PIN_3V3_EN, LOW);
|
||||
#endif
|
||||
@@ -928,13 +924,16 @@ void setup()
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
#endif
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
delete sxIf;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success");
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
@@ -942,29 +941,28 @@ void setup()
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// Try using the specified TCXO voltage
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %f V", tcxoVoltage);
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt
|
||||
// try using the specified TCXO voltage
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
delete sxIf;
|
||||
} else {
|
||||
LOG_WARN("SX1262 init success, TCXO, Vref %f V", tcxoVoltage);
|
||||
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instea
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref %f V", tcxoVoltage);
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if it fails, set the TCXO voltage back for the next radio search
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref %f V", tcxoVoltage);
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
|
||||
auth = bytesOut + numBytes;
|
||||
memcpy((uint8_t *)(auth + 8), &extraNonceTmp,
|
||||
sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp;
|
||||
LOG_INFO("Random nonce value: %d", extraNonceTmp);
|
||||
LOG_DEBUG("Random nonce value: %d", extraNonceTmp);
|
||||
if (remotePublic.size == 0) {
|
||||
LOG_DEBUG("Node %d or their public_key not found", toNode);
|
||||
return false;
|
||||
|
||||
@@ -13,7 +13,8 @@ FloodingRouter::FloodingRouter() {}
|
||||
ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p)
|
||||
{
|
||||
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
|
||||
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
||||
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
|
||||
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
||||
|
||||
return Router::send(p);
|
||||
}
|
||||
@@ -23,26 +24,17 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
|
||||
printPacket("Ignore dupe incoming msg", p);
|
||||
rxDupe++;
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
|
||||
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
|
||||
if (Router::cancelSending(p->from, p->id))
|
||||
txRelayCanceled++;
|
||||
}
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
|
||||
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
||||
}
|
||||
|
||||
/* If the original transmitter is doing retransmissions (hopStart equals hopLimit) for a reliable transmission, e.g., when
|
||||
the ACK got lost, we will handle the packet again to make sure it gets an ACK to its packet. */
|
||||
the ACK got lost, we will handle the packet again to make sure it gets an implicit ACK. */
|
||||
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
|
||||
if (isRepeated) {
|
||||
LOG_DEBUG("Repeated reliable tx");
|
||||
if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
|
||||
// FIXME - channel index should be used, but the packet is still encrypted here
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, 0, 0);
|
||||
}
|
||||
// Check if it's still in the Tx queue, if not, we have to relay it again
|
||||
if (!findInTxQueue(p->from, p->id))
|
||||
perhapsRebroadcast(p);
|
||||
} else {
|
||||
perhapsCancelDupe(p);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -51,13 +43,27 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
return Router::shouldFilterReceived(p);
|
||||
}
|
||||
|
||||
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
|
||||
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
|
||||
if (Router::cancelSending(p->from, p->id))
|
||||
txRelayCanceled++;
|
||||
}
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
|
||||
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
||||
}
|
||||
}
|
||||
|
||||
bool FloodingRouter::isRebroadcaster()
|
||||
{
|
||||
return config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE &&
|
||||
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
|
||||
}
|
||||
|
||||
bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
|
||||
if (p->id != 0) {
|
||||
@@ -72,13 +78,12 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
tosend->hop_limit = 2;
|
||||
}
|
||||
#endif
|
||||
tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
|
||||
|
||||
LOG_INFO("Rebroadcast received floodmsg");
|
||||
// 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
|
||||
Router::send(tosend);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
||||
}
|
||||
@@ -86,13 +91,12 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
LOG_DEBUG("Ignore 0 id broadcast");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
||||
{
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0);
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
||||
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
|
||||
if (isAckorReply && !isToUs(p) && !isBroadcast(p->to)) {
|
||||
// do not flood direct message that is ACKed or replied to
|
||||
LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "PacketHistory.h"
|
||||
#include "Router.h"
|
||||
|
||||
/**
|
||||
@@ -26,14 +25,11 @@
|
||||
Any entries in recentBroadcasts that are older than X seconds (longer than the
|
||||
max time a flood can take) will be discarded.
|
||||
*/
|
||||
class FloodingRouter : public Router, protected PacketHistory
|
||||
class FloodingRouter : public Router
|
||||
{
|
||||
private:
|
||||
bool isRebroadcaster();
|
||||
|
||||
/** Check if we should rebroadcast this packet, and do so if needed
|
||||
* @return true if rebroadcasted */
|
||||
bool perhapsRebroadcast(const meshtastic_MeshPacket *p);
|
||||
/* Check if we should rebroadcast this packet, and do so if needed */
|
||||
void perhapsRebroadcast(const meshtastic_MeshPacket *p);
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -62,4 +58,10 @@ class FloodingRouter : public Router, protected PacketHistory
|
||||
* Look for broadcasts we need to rebroadcast
|
||||
*/
|
||||
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
|
||||
|
||||
/* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
|
||||
void perhapsCancelDupe(const meshtastic_MeshPacket *p);
|
||||
|
||||
// Return true if we are a rebroadcaster
|
||||
bool isRebroadcaster();
|
||||
};
|
||||
@@ -20,12 +20,18 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
|
||||
// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
|
||||
#if ARCH_PORTDUINO
|
||||
#define LR1110_MAX_POWER settingsMap[lr1110_max_power]
|
||||
#endif
|
||||
#ifndef LR1110_MAX_POWER
|
||||
#define LR1110_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
// the 2.4G part maxes at 13dBm
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#define LR1120_MAX_POWER settingsMap[lr1120_max_power]
|
||||
#endif
|
||||
#ifndef LR1120_MAX_POWER
|
||||
#define LR1120_MAX_POWER 13
|
||||
#endif
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
std::vector<MeshModule *> *MeshModule::modules;
|
||||
|
||||
const meshtastic_MeshPacket *MeshModule::currentRequest;
|
||||
uint8_t MeshModule::numPeriodicModules = 0;
|
||||
|
||||
/**
|
||||
* If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow
|
||||
@@ -35,6 +36,15 @@ MeshModule::~MeshModule()
|
||||
modules->erase(it);
|
||||
}
|
||||
|
||||
// ⚠️ **Only call once** to set the initial delay before a module starts broadcasting periodically
|
||||
int32_t MeshModule::setStartDelay()
|
||||
{
|
||||
int32_t startDelay = MESHMODULE_MIN_BROADCAST_DELAY_MS + numPeriodicModules * MESHMODULE_BROADCAST_SPACING_MS;
|
||||
numPeriodicModules++;
|
||||
|
||||
return startDelay;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopLimit)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include <OLEDDisplayUi.h>
|
||||
#endif
|
||||
|
||||
#define MESHMODULE_MIN_BROADCAST_DELAY_MS 30 * 1000 // Min. delay after boot before sending first broadcast by any module
|
||||
#define MESHMODULE_BROADCAST_SPACING_MS 15 * 1000 // Initial spacing between broadcasts of different modules
|
||||
|
||||
/** handleReceived return enumeration
|
||||
*
|
||||
* Use ProcessMessage::CONTINUE to allows other modules to process a message.
|
||||
@@ -119,6 +122,12 @@ class MeshModule
|
||||
*/
|
||||
static const meshtastic_MeshPacket *currentRequest;
|
||||
|
||||
// We keep track of the number of modules that send a periodic broadcast to schedule them spaced out over time
|
||||
static uint8_t numPeriodicModules;
|
||||
|
||||
// Set the start delay for module that broadcasts periodically
|
||||
int32_t setStartDelay();
|
||||
|
||||
/**
|
||||
* If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling.
|
||||
*/
|
||||
|
||||
@@ -117,6 +117,19 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Attempt to find a packet from this queue. Return true if it was found. */
|
||||
bool MeshPacketQueue::find(NodeNum from, PacketId id)
|
||||
{
|
||||
for (auto it = queue.begin(); it != queue.end(); it++) {
|
||||
auto p = (*it);
|
||||
if (getFrom(p) == from && p->id == id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find a lower-priority packet in the queue and replace it with the provided one.
|
||||
* @return True if the replacement succeeded, false otherwise
|
||||
|
||||
@@ -37,4 +37,7 @@ class MeshPacketQueue
|
||||
|
||||
/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
|
||||
meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true);
|
||||
|
||||
/* Attempt to find a packet from this queue. Return true if it was found. */
|
||||
bool find(NodeNum from, PacketId id);
|
||||
};
|
||||
@@ -173,7 +173,9 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
p.from = 0; // We don't let phones assign nodenums to their sent messages
|
||||
p.from = 0; // We don't let clients assign nodenums to their sent messages
|
||||
p.next_hop = NO_NEXT_HOP_PREFERENCE; // We don't let clients assign next_hop to their sent messages
|
||||
p.relay_node = NO_RELAY_NODE; // We don't let clients assign relay_node to their sent messages
|
||||
|
||||
if (p.id == 0)
|
||||
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
||||
|
||||
@@ -40,6 +40,11 @@ enum RxSource {
|
||||
/// We normally just use max 3 hops for sending reliable messages
|
||||
#define HOP_RELIABLE 3
|
||||
|
||||
// For old firmware or when falling back to flooding, there is no next-hop preference
|
||||
#define NO_NEXT_HOP_PREFERENCE 0
|
||||
// For old firmware there is no relay node set
|
||||
#define NO_RELAY_NODE 0
|
||||
|
||||
typedef int ErrorCode;
|
||||
|
||||
/// Alloc and free packets to our global, ISR safe pool
|
||||
|
||||
272
src/mesh/NextHopRouter.cpp
Normal file
272
src/mesh/NextHopRouter.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
#include "NextHopRouter.h"
|
||||
|
||||
NextHopRouter::NextHopRouter() {}
|
||||
|
||||
PendingPacket::PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions)
|
||||
{
|
||||
packet = p;
|
||||
this->numRetransmissions = numRetransmissions - 1; // We subtract one, because we assume the user just did the first send
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet
|
||||
*/
|
||||
ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p)
|
||||
{
|
||||
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
|
||||
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
|
||||
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
||||
|
||||
p->next_hop = getNextHop(p->to, p->relay_node); // set the next hop
|
||||
LOG_DEBUG("Setting next hop for packet with dest %x to %x", p->to, p->next_hop);
|
||||
|
||||
// If it's from us, ReliableRouter already handles retransmissions if want_ack is set. If a next hop is set and hop limit is
|
||||
// not 0 or want_ack is set, start retransmissions
|
||||
if ((!isFromUs(p) || !p->want_ack) && p->next_hop != NO_NEXT_HOP_PREFERENCE && (p->hop_limit > 0 || p->want_ack))
|
||||
startRetransmission(packetPool.allocCopy(*p)); // start retransmission for relayed packet
|
||||
|
||||
return Router::send(p);
|
||||
}
|
||||
|
||||
bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
bool wasFallback = false;
|
||||
bool weWereNextHop = false;
|
||||
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
|
||||
printPacket("Ignore dupe incoming msg", p);
|
||||
rxDupe++;
|
||||
stopRetransmission(p->from, p->id);
|
||||
|
||||
// If it was a fallback to flooding, try to relay again
|
||||
if (wasFallback) {
|
||||
LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
|
||||
// Check if it's still in the Tx queue, if not, we have to relay it again
|
||||
if (!findInTxQueue(p->from, p->id))
|
||||
perhapsRelay(p);
|
||||
} else {
|
||||
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
|
||||
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
|
||||
if (isRepeated) {
|
||||
if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||
} else if (!weWereNextHop) {
|
||||
perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return Router::shouldFilterReceived(p);
|
||||
}
|
||||
|
||||
void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
||||
{
|
||||
NodeNum ourNodeNum = getNodeNum();
|
||||
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(ourNodeNum);
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
||||
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
|
||||
if (isAckorReply) {
|
||||
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
|
||||
// not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
|
||||
if (p->from != 0) {
|
||||
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
|
||||
if (origTx) {
|
||||
// Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from
|
||||
// the destination
|
||||
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
|
||||
(wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) {
|
||||
if (origTx->next_hop != p->relay_node) { // Not already set
|
||||
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
|
||||
origTx->next_hop = p->relay_node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isToUs(p)) {
|
||||
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
|
||||
// stop retransmission for the original packet
|
||||
stopRetransmission(p->to, p->decoded.request_id); // for original packet, from = to and id = request_id
|
||||
}
|
||||
}
|
||||
|
||||
perhapsRelay(p);
|
||||
|
||||
// handle the packet as normal
|
||||
Router::sniffReceived(p, c);
|
||||
}
|
||||
|
||||
/* Check if we should be relaying this packet if so, do so. */
|
||||
bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
|
||||
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
|
||||
if (isRebroadcaster()) {
|
||||
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
|
||||
LOG_INFO("Relaying received message coming from %x", p->relay_node);
|
||||
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
NextHopRouter::send(tosend);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next hop for a destination, given the relay node
|
||||
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
|
||||
*/
|
||||
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
|
||||
{
|
||||
// When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
|
||||
if (isBroadcast(to))
|
||||
return NO_NEXT_HOP_PREFERENCE;
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to);
|
||||
if (node && node->next_hop) {
|
||||
// We are careful not to return the relay node as the next hop
|
||||
if (node->next_hop != relay_node) {
|
||||
// LOG_DEBUG("Next hop for 0x%x is 0x%x", to, node->next_hop);
|
||||
return node->next_hop;
|
||||
} else
|
||||
LOG_WARN("Next hop for 0x%x is 0x%x, same as relayer; set no pref", to, node->next_hop);
|
||||
}
|
||||
return NO_NEXT_HOP_PREFERENCE;
|
||||
}
|
||||
|
||||
PendingPacket *NextHopRouter::findPendingPacket(GlobalPacketId key)
|
||||
{
|
||||
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
|
||||
if (old != pending.end()) {
|
||||
return &old->second;
|
||||
} else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*/
|
||||
bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
|
||||
{
|
||||
auto key = GlobalPacketId(from, id);
|
||||
return stopRetransmission(key);
|
||||
}
|
||||
|
||||
bool NextHopRouter::stopRetransmission(GlobalPacketId key)
|
||||
{
|
||||
auto old = findPendingPacket(key);
|
||||
if (old) {
|
||||
auto p = old->packet;
|
||||
/* 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_RELIABLE_RETX - 1) {
|
||||
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
|
||||
cancelSending(getFrom(p), p->id);
|
||||
// now free the pooled copy for retransmission too
|
||||
packetPool.release(p);
|
||||
}
|
||||
auto numErased = pending.erase(key);
|
||||
assert(numErased == 1);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
PendingPacket *NextHopRouter::startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx)
|
||||
{
|
||||
auto id = GlobalPacketId(p);
|
||||
auto rec = PendingPacket(p, numReTx);
|
||||
|
||||
stopRetransmission(getFrom(p), p->id);
|
||||
|
||||
setNextTx(&rec);
|
||||
pending[id] = rec;
|
||||
|
||||
return &pending[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*/
|
||||
int32_t NextHopRouter::doRetransmissions()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
int32_t d = INT32_MAX;
|
||||
|
||||
// FIXME, we should use a better datastructure rather than walking through this map.
|
||||
// for(auto el: pending) {
|
||||
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
|
||||
++nextIt; // we use this odd pattern because we might be deleting it...
|
||||
auto &p = it->second;
|
||||
|
||||
bool stillValid = true; // assume we'll keep this record around
|
||||
|
||||
// FIXME, handle 51 day rolloever here!!!
|
||||
if (p.nextTxMsec <= now) {
|
||||
if (p.numRetransmissions == 0) {
|
||||
if (isFromUs(p.packet)) {
|
||||
LOG_DEBUG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
|
||||
p.packet->id);
|
||||
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
|
||||
}
|
||||
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
|
||||
stopRetransmission(it->first);
|
||||
stillValid = false; // just deleted it
|
||||
} else {
|
||||
LOG_DEBUG("Sending retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
|
||||
p.packet->id, p.numRetransmissions);
|
||||
|
||||
if (!isBroadcast(p.packet->to)) {
|
||||
if (p.numRetransmissions == 1) {
|
||||
// Last retransmission, reset next_hop (fallback to FloodingRouter)
|
||||
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
|
||||
// Also reset it in the nodeDB
|
||||
meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to);
|
||||
if (sentTo) {
|
||||
LOG_INFO("Resetting next hop for packet with dest 0x%x\n", p.packet->to);
|
||||
sentTo->next_hop = NO_NEXT_HOP_PREFERENCE;
|
||||
}
|
||||
FloodingRouter::send(packetPool.allocCopy(*p.packet));
|
||||
} else {
|
||||
NextHopRouter::send(packetPool.allocCopy(*p.packet));
|
||||
}
|
||||
} else {
|
||||
// Note: we call the superclass version because we don't want to have our version of send() add a new
|
||||
// retransmission record
|
||||
FloodingRouter::send(packetPool.allocCopy(*p.packet));
|
||||
}
|
||||
|
||||
// Queue again
|
||||
--p.numRetransmissions;
|
||||
setNextTx(&p);
|
||||
}
|
||||
}
|
||||
|
||||
if (stillValid) {
|
||||
// Update our desired sleep delay
|
||||
int32_t t = p.nextTxMsec - now;
|
||||
|
||||
d = min(t, d);
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void NextHopRouter::setNextTx(PendingPacket *pending)
|
||||
{
|
||||
assert(iface);
|
||||
auto d = iface->getRetransmissionMsec(pending->packet);
|
||||
pending->nextTxMsec = millis() + d;
|
||||
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
|
||||
printPacket("", pending->packet);
|
||||
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
|
||||
}
|
||||
151
src/mesh/NextHopRouter.h
Normal file
151
src/mesh/NextHopRouter.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include "FloodingRouter.h"
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* An identifier for a globally unique message - a pair of the sending nodenum and the packet id assigned
|
||||
* to that message
|
||||
*/
|
||||
struct GlobalPacketId {
|
||||
NodeNum node;
|
||||
PacketId id;
|
||||
|
||||
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
|
||||
|
||||
explicit GlobalPacketId(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
node = getFrom(p);
|
||||
id = p->id;
|
||||
}
|
||||
|
||||
GlobalPacketId(NodeNum _from, PacketId _id)
|
||||
{
|
||||
node = _from;
|
||||
id = _id;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A packet queued for retransmission
|
||||
*/
|
||||
struct PendingPacket {
|
||||
meshtastic_MeshPacket *packet;
|
||||
|
||||
/** The next time we should try to retransmit this packet */
|
||||
uint32_t nextTxMsec = 0;
|
||||
|
||||
/** Starts at NUM_RETRANSMISSIONS -1 and counts down. Once zero it will be removed from the list */
|
||||
uint8_t numRetransmissions = 0;
|
||||
|
||||
PendingPacket() {}
|
||||
explicit PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions);
|
||||
};
|
||||
|
||||
class GlobalPacketIdHashFunction
|
||||
{
|
||||
public:
|
||||
size_t operator()(const GlobalPacketId &p) const { return (std::hash<NodeNum>()(p.node)) ^ (std::hash<PacketId>()(p.id)); }
|
||||
};
|
||||
|
||||
/*
|
||||
Router for direct messages, which only relays if it is the next hop for a packet. The next hop is set by the current
|
||||
relayer of a packet, which bases this on information from a previous successful delivery to the destination via flooding.
|
||||
Namely, in the PacketHistory, we keep track of (up to 3) relayers of a packet. When the ACK is delivered back to us via a node
|
||||
that also relayed the original packet, we use that node as next hop for the destination from then on. This makes sure that only
|
||||
when there’s a two-way connection, we assign a next hop. Both the ReliableRouter and NextHopRouter will do retransmissions (the
|
||||
NextHopRouter only 1 time). For the final retry, if no one actually relayed the packet, it will reset the next hop in order to
|
||||
fall back to the FloodingRouter again. Note that thus also intermediate hops will do a single retransmission if the intended
|
||||
next-hop didn’t relay, in order to fix changes in the middle of the route.
|
||||
*/
|
||||
class NextHopRouter : public FloodingRouter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
NextHopRouter();
|
||||
|
||||
/**
|
||||
* Send a packet
|
||||
* @return an error code
|
||||
*/
|
||||
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
|
||||
|
||||
/** Do our retransmission handling */
|
||||
virtual int32_t runOnce() override
|
||||
{
|
||||
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
|
||||
doRetransmissions();
|
||||
|
||||
int32_t r = FloodingRouter::runOnce();
|
||||
|
||||
// Also after calling runOnce there might be new packets to retransmit
|
||||
auto d = doRetransmissions();
|
||||
return min(d, r);
|
||||
}
|
||||
|
||||
// The number of retransmissions intermediate nodes will do (actually 1 less than this)
|
||||
constexpr static uint8_t NUM_INTERMEDIATE_RETX = 2;
|
||||
// The number of retransmissions the original sender will do
|
||||
constexpr static uint8_t NUM_RELIABLE_RETX = 3;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Pending retransmissions
|
||||
*/
|
||||
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
|
||||
|
||||
/**
|
||||
* Should this incoming filter be dropped?
|
||||
*
|
||||
* Called immediately on reception, before any further processing.
|
||||
* @return true to abandon the packet
|
||||
*/
|
||||
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
|
||||
|
||||
/**
|
||||
* Look for packets we need to relay
|
||||
*/
|
||||
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
|
||||
|
||||
/**
|
||||
* Try to find the pending packet record for this ID (or NULL if not found)
|
||||
*/
|
||||
PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); }
|
||||
PendingPacket *findPendingPacket(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
|
||||
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*
|
||||
* @return true if we found and removed a transmission with this ID
|
||||
*/
|
||||
bool stopRetransmission(NodeNum from, PacketId id);
|
||||
bool stopRetransmission(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*
|
||||
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
|
||||
*/
|
||||
int32_t doRetransmissions();
|
||||
|
||||
void setNextTx(PendingPacket *pending);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Get the next hop for a destination, given the relay node
|
||||
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
|
||||
*/
|
||||
uint8_t getNextHop(NodeNum to, uint8_t relay_node);
|
||||
|
||||
/** Check if we should be relaying this packet if so, do so.
|
||||
* @return true if we did relay */
|
||||
bool perhapsRelay(const meshtastic_MeshPacket *p);
|
||||
};
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "modules/NeighborInfoModule.h"
|
||||
#include <ErriezCRC32.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include <vector>
|
||||
@@ -408,7 +407,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
|
||||
rebootAtMsec = millis() + (5 * 1000);
|
||||
}
|
||||
|
||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3)) && defined(HAS_TFT)
|
||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3)) && HAS_TFT
|
||||
// as long as PhoneAPI shares BT and TFT app switch BT off
|
||||
config.bluetooth.enabled = false;
|
||||
if (moduleConfig.external_notification.nag_timeout == 60)
|
||||
@@ -1529,4 +1528,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co
|
||||
LOG_ERROR("A critical failure occurred, portduino is exiting");
|
||||
exit(2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ class NodeDB
|
||||
/// @return our node number
|
||||
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
|
||||
|
||||
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
|
||||
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
|
||||
|
||||
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
||||
// bool handleWantNodeNum(NodeNum n);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ PacketHistory::PacketHistory()
|
||||
/**
|
||||
* Update recentBroadcasts and return true if we have already seen this packet
|
||||
*/
|
||||
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate)
|
||||
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate, bool *wasFallback, bool *weWereNextHop)
|
||||
{
|
||||
if (p->id == 0) {
|
||||
LOG_DEBUG("Ignore message with zero id");
|
||||
@@ -27,6 +27,9 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
|
||||
r.id = p->id;
|
||||
r.sender = getFrom(p);
|
||||
r.rxTimeMsec = millis();
|
||||
r.next_hop = p->next_hop;
|
||||
r.relayed_by[0] = p->relay_node;
|
||||
// LOG_INFO("Add relayed_by 0x%x for id=0x%x", p->relay_node, r.id);
|
||||
|
||||
auto found = recentPackets.find(r);
|
||||
bool seenRecently = (found != recentPackets.end()); // found not equal to .end() means packet was seen recently
|
||||
@@ -40,14 +43,36 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
|
||||
|
||||
if (seenRecently) {
|
||||
LOG_DEBUG("Found existing packet record for fr=0x%x,to=0x%x,id=0x%x", p->from, p->to, p->id);
|
||||
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum());
|
||||
if (wasFallback) {
|
||||
// If it was seen with a next-hop not set to us and now it's NO_NEXT_HOP_PREFERENCE, and the relayer relayed already
|
||||
// before, it's a fallback to flooding. If we didn't already relay and the next-hop neither, we might need to handle
|
||||
// it now.
|
||||
if (found->sender != nodeDB->getNodeNum() && found->next_hop != NO_NEXT_HOP_PREFERENCE &&
|
||||
found->next_hop != ourRelayID && p->next_hop == NO_NEXT_HOP_PREFERENCE && wasRelayer(p->relay_node, found) &&
|
||||
!wasRelayer(ourRelayID, found) && !wasRelayer(found->next_hop, found)) {
|
||||
*wasFallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we were the next hop for this packet
|
||||
if (weWereNextHop) {
|
||||
*weWereNextHop = found->next_hop == ourRelayID;
|
||||
}
|
||||
}
|
||||
|
||||
if (withUpdate) {
|
||||
if (found != recentPackets.end()) { // delete existing to updated timestamp (re-insert)
|
||||
recentPackets.erase(found); // as unsorted_set::iterator is const (can't update timestamp - so re-insert..)
|
||||
if (found != recentPackets.end()) { // delete existing to updated timestamp and relayed_by (re-insert)
|
||||
// Add the existing relayed_by to the new record
|
||||
for (uint8_t i = 0; i < NUM_RELAYERS - 1; i++) {
|
||||
if (found->relayed_by[i])
|
||||
r.relayed_by[i + 1] = found->relayed_by[i];
|
||||
}
|
||||
r.next_hop = found->next_hop; // keep the original next_hop (such that we check whether we were originally asked)
|
||||
recentPackets.erase(found); // as unsorted_set::iterator is const (can't update - so re-insert..)
|
||||
}
|
||||
recentPackets.insert(r);
|
||||
printPacket("Add packet record", p);
|
||||
LOG_DEBUG("Add packet record fr=0x%x, id=0x%x", p->from, p->id);
|
||||
}
|
||||
|
||||
// Capacity is reerved, so only purge expired packets if recentPackets fills past 90% capacity
|
||||
@@ -75,4 +100,59 @@ void PacketHistory::clearExpiredRecentPackets()
|
||||
}
|
||||
|
||||
LOG_DEBUG("recentPackets size=%ld (after clearing expired packets)", recentPackets.size());
|
||||
}
|
||||
|
||||
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
|
||||
{
|
||||
if (relayer == 0)
|
||||
return false;
|
||||
|
||||
PacketRecord r = {.sender = sender, .id = id, .rxTimeMsec = 0, .next_hop = 0};
|
||||
auto found = recentPackets.find(r);
|
||||
|
||||
if (found == recentPackets.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return wasRelayer(relayer, found);
|
||||
}
|
||||
|
||||
/* Check if a certain node was a relayer of a packet in the history given iterator
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool PacketHistory::wasRelayer(const uint8_t relayer, std::unordered_set<PacketRecord, PacketRecordHashFunction>::iterator r)
|
||||
{
|
||||
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
|
||||
if (r->relayed_by[i] == relayer) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
||||
void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
|
||||
{
|
||||
PacketRecord r = {.sender = sender, .id = id, .rxTimeMsec = 0, .next_hop = 0};
|
||||
auto found = recentPackets.find(r);
|
||||
|
||||
if (found == recentPackets.end()) {
|
||||
return;
|
||||
}
|
||||
// Make a copy of the found record
|
||||
r.next_hop = found->next_hop;
|
||||
r.rxTimeMsec = found->rxTimeMsec;
|
||||
|
||||
// Only add the relayers that are not the one we want to remove
|
||||
uint8_t j = 0;
|
||||
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
|
||||
if (found->relayed_by[i] != relayer) {
|
||||
r.relayed_by[j] = found->relayed_by[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
recentPackets.erase(found);
|
||||
recentPackets.insert(r);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Router.h"
|
||||
#include "NodeDB.h"
|
||||
#include <unordered_set>
|
||||
|
||||
/// We clear our old flood record 10 minutes after we see the last of it
|
||||
@@ -10,13 +10,18 @@
|
||||
#define FLOOD_EXPIRE_TIME (10 * 60 * 1000L)
|
||||
#endif
|
||||
|
||||
#define NUM_RELAYERS \
|
||||
3 // Number of relayer we keep track of. Use 3 to be efficient with memory alignment of PacketRecord to 16 bytes
|
||||
|
||||
/**
|
||||
* A record of a recent message broadcast
|
||||
*/
|
||||
struct PacketRecord {
|
||||
NodeNum sender;
|
||||
PacketId id;
|
||||
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
|
||||
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
|
||||
uint8_t next_hop; // The next hop asked for this packet
|
||||
uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
|
||||
|
||||
bool operator==(const PacketRecord &p) const { return sender == p.sender && id == p.id; }
|
||||
};
|
||||
@@ -44,6 +49,20 @@ class PacketHistory
|
||||
* Update recentBroadcasts and return true if we have already seen this packet
|
||||
*
|
||||
* @param withUpdate if true and not found we add an entry to recentPackets
|
||||
* @param wasFallback if not nullptr, packet will be checked for fallback to flooding and value will be set to true if so
|
||||
* @param weWereNextHop if not nullptr, packet will be checked for us being the next hop and value will be set to true if so
|
||||
*/
|
||||
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true);
|
||||
};
|
||||
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true, bool *wasFallback = nullptr,
|
||||
bool *weWereNextHop = nullptr);
|
||||
|
||||
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
||||
|
||||
/* Check if a certain node was a relayer of a packet in the history given iterator
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool wasRelayer(const uint8_t relayer, std::unordered_set<PacketRecord, PacketRecordHashFunction>::iterator r);
|
||||
|
||||
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
||||
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "PhoneAPI.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "Router.h"
|
||||
#include "SPILock.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "main.h"
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include "PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#define RF95_MAX_POWER settingsMap[rf95_max_power]
|
||||
#endif
|
||||
#ifndef RF95_MAX_POWER
|
||||
#define RF95_MAX_POWER 20
|
||||
#endif
|
||||
@@ -337,4 +340,4 @@ bool RF95Interface::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -340,6 +340,10 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
||||
out += DEBUG_PORT.mt_sprintf(" via MQTT");
|
||||
if (p->hop_start != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start);
|
||||
if (p->next_hop != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" nextHop=0x%x", p->next_hop);
|
||||
if (p->relay_node != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" relay=0x%x", p->relay_node);
|
||||
if (p->priority != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority);
|
||||
|
||||
@@ -620,8 +624,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
|
||||
radioBuffer.header.to = p->to;
|
||||
radioBuffer.header.id = p->id;
|
||||
radioBuffer.header.channel = p->channel;
|
||||
radioBuffer.header.next_hop = 0; // *** For future use ***
|
||||
radioBuffer.header.relay_node = 0; // *** For future use ***
|
||||
radioBuffer.header.next_hop = p->next_hop;
|
||||
radioBuffer.header.relay_node = p->relay_node;
|
||||
if (p->hop_limit > HOP_MAX) {
|
||||
LOG_WARN("hop limit %d is too high, setting to %d", p->hop_limit, HOP_RELIABLE);
|
||||
p->hop_limit = HOP_RELIABLE;
|
||||
|
||||
@@ -38,10 +38,10 @@ 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
|
||||
// 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
|
||||
// Last byte of the NodeNum of the node that will relay/relayed this packet
|
||||
uint8_t relay_node;
|
||||
} PacketHeader;
|
||||
|
||||
@@ -155,6 +155,9 @@ class RadioInterface
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
virtual bool cancelSending(NodeNum from, PacketId id) { return false; }
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
virtual bool findInTxQueue(NodeNum from, PacketId id) { return false; }
|
||||
|
||||
// methods from radiohead
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
|
||||
@@ -222,6 +222,12 @@ bool RadioLibInterface::cancelSending(NodeNum from, PacketId id)
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
|
||||
{
|
||||
return txQueue.find(from, id);
|
||||
}
|
||||
|
||||
/** radio helper thread callback.
|
||||
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
|
||||
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.
|
||||
@@ -445,6 +451,9 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
mp->hop_start = (radioBuffer.header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
|
||||
mp->want_ack = !!(radioBuffer.header.flags & PACKET_FLAGS_WANT_ACK_MASK);
|
||||
mp->via_mqtt = !!(radioBuffer.header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
|
||||
// If hop_start is not set, next_hop and relay_node are invalid (firmware <2.3)
|
||||
mp->next_hop = mp->hop_start == 0 ? NO_NEXT_HOP_PREFERENCE : radioBuffer.header.next_hop;
|
||||
mp->relay_node = mp->hop_start == 0 ? NO_RELAY_NODE : radioBuffer.header.relay_node;
|
||||
|
||||
addReceiveMetadata(mp);
|
||||
|
||||
|
||||
@@ -135,6 +135,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
virtual bool cancelSending(NodeNum from, PacketId id) override;
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
|
||||
|
||||
private:
|
||||
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
|
||||
* doing the transmit */
|
||||
|
||||
@@ -23,7 +23,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
|
||||
}
|
||||
|
||||
auto copy = packetPool.allocCopy(*p);
|
||||
startRetransmission(copy);
|
||||
startRetransmission(copy, NUM_RELIABLE_RETX);
|
||||
}
|
||||
|
||||
/* If we have pending retransmissions, add the airtime of this packet to it, because during that time we cannot receive an
|
||||
@@ -35,7 +35,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
|
||||
}
|
||||
}
|
||||
|
||||
return FloodingRouter::send(p);
|
||||
return isBroadcast(p->to) ? FloodingRouter::send(p) : NextHopRouter::send(p);
|
||||
}
|
||||
|
||||
bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
@@ -73,7 +73,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
i->second.nextTxMsec += iface->getPacketTime(p);
|
||||
}
|
||||
|
||||
return FloodingRouter::shouldFilterReceived(p);
|
||||
return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,126 +138,5 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
||||
}
|
||||
|
||||
// handle the packet as normal
|
||||
FloodingRouter::sniffReceived(p, c);
|
||||
}
|
||||
|
||||
#define NUM_RETRANSMISSIONS 3
|
||||
|
||||
PendingPacket::PendingPacket(meshtastic_MeshPacket *p)
|
||||
{
|
||||
packet = p;
|
||||
numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send
|
||||
}
|
||||
|
||||
PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key)
|
||||
{
|
||||
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
|
||||
if (old != pending.end()) {
|
||||
return &old->second;
|
||||
} else
|
||||
return NULL;
|
||||
}
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*/
|
||||
bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id)
|
||||
{
|
||||
auto key = GlobalPacketId(from, id);
|
||||
return stopRetransmission(key);
|
||||
}
|
||||
|
||||
bool ReliableRouter::stopRetransmission(GlobalPacketId key)
|
||||
{
|
||||
auto old = findPendingPacket(key);
|
||||
if (old) {
|
||||
auto p = old->packet;
|
||||
/* 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) {
|
||||
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
|
||||
cancelSending(getFrom(p), p->id);
|
||||
}
|
||||
// now free the pooled copy for retransmission too
|
||||
packetPool.release(p);
|
||||
auto numErased = pending.erase(key);
|
||||
assert(numErased == 1);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
PendingPacket *ReliableRouter::startRetransmission(meshtastic_MeshPacket *p)
|
||||
{
|
||||
auto id = GlobalPacketId(p);
|
||||
auto rec = PendingPacket(p);
|
||||
|
||||
stopRetransmission(getFrom(p), p->id);
|
||||
|
||||
setNextTx(&rec);
|
||||
pending[id] = rec;
|
||||
|
||||
return &pending[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*/
|
||||
int32_t ReliableRouter::doRetransmissions()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
int32_t d = INT32_MAX;
|
||||
|
||||
// FIXME, we should use a better datastructure rather than walking through this map.
|
||||
// for(auto el: pending) {
|
||||
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
|
||||
++nextIt; // we use this odd pattern because we might be deleting it...
|
||||
auto &p = it->second;
|
||||
|
||||
bool stillValid = true; // assume we'll keep this record around
|
||||
|
||||
// FIXME, handle 51 day rollover here!!!
|
||||
if (p.nextTxMsec <= now) {
|
||||
if (p.numRetransmissions == 0) {
|
||||
LOG_DEBUG("Reliable send failed, return a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
|
||||
p.packet->id);
|
||||
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
|
||||
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
|
||||
stopRetransmission(it->first);
|
||||
stillValid = false; // just deleted it
|
||||
} else {
|
||||
LOG_DEBUG("Send reliable retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
|
||||
p.packet->id, p.numRetransmissions);
|
||||
|
||||
// Note: we call the superclass version because we don't want to have our version of send() add a new
|
||||
// retransmission record
|
||||
FloodingRouter::send(packetPool.allocCopy(*p.packet));
|
||||
|
||||
// Queue again
|
||||
--p.numRetransmissions;
|
||||
setNextTx(&p);
|
||||
}
|
||||
}
|
||||
|
||||
if (stillValid) {
|
||||
// Update our desired sleep delay
|
||||
int32_t t = p.nextTxMsec - now;
|
||||
|
||||
d = min(t, d);
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void ReliableRouter::setNextTx(PendingPacket *pending)
|
||||
{
|
||||
assert(iface);
|
||||
auto d = iface->getRetransmissionMsec(pending->packet);
|
||||
pending->nextTxMsec = millis() + d;
|
||||
LOG_DEBUG("Set next retransmission in %u msecs: ", d);
|
||||
printPacket("", pending->packet);
|
||||
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
|
||||
isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
|
||||
}
|
||||
@@ -1,61 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "FloodingRouter.h"
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* An identifier for a globally unique message - a pair of the sending nodenum and the packet id assigned
|
||||
* to that message
|
||||
*/
|
||||
struct GlobalPacketId {
|
||||
NodeNum node;
|
||||
PacketId id;
|
||||
|
||||
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
|
||||
|
||||
explicit GlobalPacketId(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
node = getFrom(p);
|
||||
id = p->id;
|
||||
}
|
||||
|
||||
GlobalPacketId(NodeNum _from, PacketId _id)
|
||||
{
|
||||
node = _from;
|
||||
id = _id;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A packet queued for retransmission
|
||||
*/
|
||||
struct PendingPacket {
|
||||
meshtastic_MeshPacket *packet;
|
||||
|
||||
/** The next time we should try to retransmit this packet */
|
||||
uint32_t nextTxMsec = 0;
|
||||
|
||||
/** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */
|
||||
uint8_t numRetransmissions = 0;
|
||||
|
||||
PendingPacket() {}
|
||||
explicit PendingPacket(meshtastic_MeshPacket *p);
|
||||
};
|
||||
|
||||
class GlobalPacketIdHashFunction
|
||||
{
|
||||
public:
|
||||
size_t operator()(const GlobalPacketId &p) const { return (std::hash<NodeNum>()(p.node)) ^ (std::hash<PacketId>()(p.id)); }
|
||||
};
|
||||
#include "NextHopRouter.h"
|
||||
|
||||
/**
|
||||
* This is a mixin that extends Router with the ability to do (one hop only) reliable message sends.
|
||||
*/
|
||||
class ReliableRouter : public FloodingRouter
|
||||
class ReliableRouter : public NextHopRouter
|
||||
{
|
||||
private:
|
||||
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
@@ -70,54 +21,14 @@ class ReliableRouter : public FloodingRouter
|
||||
*/
|
||||
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
|
||||
|
||||
/** Do our retransmission handling */
|
||||
virtual int32_t runOnce() override
|
||||
{
|
||||
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
|
||||
auto d = doRetransmissions();
|
||||
|
||||
int32_t r = FloodingRouter::runOnce();
|
||||
|
||||
return min(d, r);
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Look for acks/naks or someone retransmitting us
|
||||
*/
|
||||
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
|
||||
|
||||
/**
|
||||
* Try to find the pending packet record for this ID (or NULL if not found)
|
||||
*/
|
||||
PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); }
|
||||
PendingPacket *findPendingPacket(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* We hook this method so we can see packets before FloodingRouter says they should be discarded
|
||||
*/
|
||||
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
|
||||
|
||||
/**
|
||||
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
||||
*/
|
||||
PendingPacket *startRetransmission(meshtastic_MeshPacket *p);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*
|
||||
* @return true if we found and removed a transmission with this ID
|
||||
*/
|
||||
bool stopRetransmission(NodeNum from, PacketId id);
|
||||
bool stopRetransmission(GlobalPacketId p);
|
||||
|
||||
/**
|
||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||
*
|
||||
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
|
||||
*/
|
||||
int32_t doRetransmissions();
|
||||
|
||||
void setNextTx(PendingPacket *pending);
|
||||
};
|
||||
};
|
||||
@@ -249,6 +249,7 @@ 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);
|
||||
|
||||
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // set the relayer to us
|
||||
// If we are the original transmitter, set the hop limit with which we start
|
||||
if (isFromUs(p))
|
||||
p->hop_start = p->hop_limit;
|
||||
@@ -290,7 +291,18 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
bool Router::cancelSending(NodeNum from, PacketId id)
|
||||
{
|
||||
return iface ? iface->cancelSending(from, id) : false;
|
||||
if (iface && iface->cancelSending(from, id)) {
|
||||
// We are not a relayer of this packet anymore
|
||||
removeRelayer(nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum()), id, from);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
bool Router::findInTxQueue(NodeNum from, PacketId id)
|
||||
{
|
||||
return iface->findInTxQueue(from, id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
#include "PacketHistory.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
@@ -11,7 +12,7 @@
|
||||
/**
|
||||
* A mesh aware router that supports multiple interfaces.
|
||||
*/
|
||||
class Router : protected concurrency::OSThread
|
||||
class Router : protected concurrency::OSThread, protected PacketHistory
|
||||
{
|
||||
private:
|
||||
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
|
||||
@@ -50,6 +51,9 @@ class Router : protected concurrency::OSThread
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
bool cancelSending(NodeNum from, PacketId id);
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
bool findInTxQueue(NodeNum from, PacketId id);
|
||||
|
||||
/** Allocate and return a meshpacket which defaults as send to broadcast from the current node.
|
||||
* The returned packet is guaranteed to have a unique packet ID already assigned
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,9 @@ bool STM32WLE5JCInterface::init()
|
||||
{
|
||||
RadioLibInterface::init();
|
||||
|
||||
// https://github.com/Seeed-Studio/LoRaWan-E5-Node/blob/main/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.c
|
||||
setTCXOVoltage(1.7);
|
||||
|
||||
lora.setRfSwitchTable(rfswitch_pins, rfswitch_table);
|
||||
|
||||
if (power > STM32WLx_MAX_POWER) // This chip has lower power limits than some
|
||||
@@ -39,4 +42,4 @@ bool STM32WLE5JCInterface::init()
|
||||
return res == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
#endif // ARCH_STM32WL
|
||||
#endif // ARCH_STM32WL
|
||||
@@ -16,9 +16,6 @@ class STM32WLE5JCInterface : public SX126xInterface<STM32WLx>
|
||||
virtual bool init() override;
|
||||
};
|
||||
|
||||
// https://github.com/Seeed-Studio/LoRaWan-E5-Node/blob/main/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.c
|
||||
static const float tcxoVoltage = 1.7;
|
||||
|
||||
/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/
|
||||
* Wio-E5 module ONLY transmits through RFO_HP
|
||||
* Receive: PA4=1, PA5=0
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
|
||||
// specified (may be dangerous if using external PA and SX126x power config forgotten)
|
||||
#if ARCH_PORTDUINO
|
||||
#define SX126X_MAX_POWER settingsMap[sx126x_max_power]
|
||||
#endif
|
||||
#ifndef SX126X_MAX_POWER
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
@@ -50,22 +53,13 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
|
||||
tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
|
||||
if (settingsMap[sx126x_ant_sw_pin] != RADIOLIB_NC) {
|
||||
digitalWrite(settingsMap[sx126x_ant_sw_pin], HIGH);
|
||||
pinMode(settingsMap[sx126x_ant_sw_pin], OUTPUT);
|
||||
}
|
||||
// FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE
|
||||
#elif !defined(SX126X_DIO3_TCXO_VOLTAGE)
|
||||
float tcxoVoltage =
|
||||
0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
|
||||
// https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.h#L471C26-L471C104
|
||||
// (DIO3 is free to be used as an IRQ)
|
||||
#elif !defined(TCXO_OPTIONAL)
|
||||
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE;
|
||||
// (DIO3 is not free to be used as an IRQ)
|
||||
#endif
|
||||
if (tcxoVoltage == 0)
|
||||
if (tcxoVoltage == 0.0)
|
||||
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
|
||||
else
|
||||
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
|
||||
@@ -83,7 +77,7 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
|
||||
// \todo Display actual typename of the adapter, not just `SX126x`
|
||||
LOG_INFO("SX126x init result %d", res);
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
||||
return false;
|
||||
|
||||
LOG_INFO("Frequency set to %f", getFreq());
|
||||
@@ -342,4 +336,4 @@ template <typename T> bool SX126xInterface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -28,8 +28,11 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
||||
|
||||
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
|
||||
|
||||
void setTCXOVoltage(float voltage) { tcxoVoltage = voltage; }
|
||||
|
||||
protected:
|
||||
float currentLimit = 140; // Higher OCP limit for SX126x PA
|
||||
float tcxoVoltage = 0.0;
|
||||
|
||||
/**
|
||||
* Specific module instance
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#endif
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do
|
||||
#if ARCH_PORTDUINO
|
||||
#define SX128X_MAX_POWER settingsMap[sx128x_max_power]
|
||||
#endif
|
||||
#ifndef SX128X_MAX_POWER
|
||||
#define SX128X_MAX_POWER 13
|
||||
#endif
|
||||
@@ -315,4 +318,4 @@ template <typename T> bool SX128xInterface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -5,9 +5,6 @@
|
||||
#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 <RAK13800_W5100S.h>
|
||||
#include <SPI.h>
|
||||
@@ -72,12 +69,6 @@ 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
|
||||
|
||||
@@ -468,6 +468,9 @@ typedef struct _meshtastic_Config_DisplayConfig {
|
||||
bool wake_on_tap_or_motion;
|
||||
/* Indicates how to rotate or invert the compass output to accurate display on the display. */
|
||||
meshtastic_Config_DisplayConfig_CompassOrientation compass_orientation;
|
||||
/* If false (default), the device will display the time in 24-hour format on screen.
|
||||
If true, the device will display the time in 12-hour format on screen. */
|
||||
bool use_12h_clock;
|
||||
} meshtastic_Config_DisplayConfig;
|
||||
|
||||
/* Lora Config */
|
||||
@@ -690,7 +693,7 @@ extern "C" {
|
||||
#define meshtastic_Config_PowerConfig_init_default {0, 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, "", 0}
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
|
||||
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
|
||||
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
|
||||
#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}, 0, 0}
|
||||
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
|
||||
@@ -701,7 +704,7 @@ extern "C" {
|
||||
#define meshtastic_Config_PowerConfig_init_zero {0, 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, "", 0}
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
|
||||
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_zero {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}, 0, 0}
|
||||
#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
|
||||
@@ -765,6 +768,7 @@ extern "C" {
|
||||
#define meshtastic_Config_DisplayConfig_heading_bold_tag 9
|
||||
#define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
|
||||
#define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
|
||||
#define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
|
||||
#define meshtastic_Config_LoRaConfig_use_preset_tag 1
|
||||
#define meshtastic_Config_LoRaConfig_modem_preset_tag 2
|
||||
#define meshtastic_Config_LoRaConfig_bandwidth_tag 3
|
||||
@@ -907,7 +911,8 @@ X(a, STATIC, SINGULAR, UENUM, oled, 7) \
|
||||
X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \
|
||||
X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \
|
||||
X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \
|
||||
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11)
|
||||
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \
|
||||
X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12)
|
||||
#define meshtastic_Config_DisplayConfig_CALLBACK NULL
|
||||
#define meshtastic_Config_DisplayConfig_DEFAULT NULL
|
||||
|
||||
@@ -985,7 +990,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
|
||||
#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
|
||||
#define meshtastic_Config_BluetoothConfig_size 10
|
||||
#define meshtastic_Config_DeviceConfig_size 98
|
||||
#define meshtastic_Config_DisplayConfig_size 30
|
||||
#define meshtastic_Config_DisplayConfig_size 32
|
||||
#define meshtastic_Config_LoRaConfig_size 85
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
|
||||
#define meshtastic_Config_NetworkConfig_size 202
|
||||
|
||||
@@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
|
||||
#define meshtastic_LocalConfig_size 741
|
||||
#define meshtastic_LocalConfig_size 743
|
||||
#define meshtastic_LocalModuleConfig_size 699
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1775,4 +1775,4 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -347,7 +347,7 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
|
||||
bool health_screen_enabled;
|
||||
} meshtastic_ModuleConfig_TelemetryConfig;
|
||||
|
||||
/* TODO: REPLACE */
|
||||
/* Canned Messages Module Config */
|
||||
typedef struct _meshtastic_ModuleConfig_CannedMessageConfig {
|
||||
/* Enable the rotary encoder #1. This is a 'dumb' encoder sending pulses on both A and B pins while rotating. */
|
||||
bool rotary1_enabled;
|
||||
|
||||
@@ -65,6 +65,9 @@ mail: marchammermann@googlemail.com
|
||||
#define DEFAULT_REALM "default_realm"
|
||||
#define PREFIX ""
|
||||
|
||||
#define KEY_PATH settingsStrings[websslkeypath].c_str()
|
||||
#define CERT_PATH settingsStrings[websslcertpath].c_str()
|
||||
|
||||
struct _file_config configWeb;
|
||||
|
||||
// We need to specify some content-type mapping, so the resources get delivered with the
|
||||
@@ -384,13 +387,13 @@ char *read_file_into_string(const char *filename)
|
||||
int PiWebServerThread::CheckSSLandLoad()
|
||||
{
|
||||
// read certificate
|
||||
cert_pem = read_file_into_string("certificate.pem");
|
||||
cert_pem = read_file_into_string(CERT_PATH);
|
||||
if (cert_pem == NULL) {
|
||||
LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing");
|
||||
return 1;
|
||||
}
|
||||
// read private key
|
||||
key_pem = read_file_into_string("private_key.pem");
|
||||
key_pem = read_file_into_string(KEY_PATH);
|
||||
if (key_pem == NULL) {
|
||||
LOG_ERROR("ERROR file private_key can't be loaded or is missing");
|
||||
return 2;
|
||||
@@ -415,8 +418,8 @@ int PiWebServerThread::CreateSSLCertificate()
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Ope file to write private key file
|
||||
FILE *pkey_file = fopen("private_key.pem", "wb");
|
||||
// Open file to write private key file
|
||||
FILE *pkey_file = fopen(KEY_PATH, "wb");
|
||||
if (!pkey_file) {
|
||||
LOG_ERROR("Error opening private key file");
|
||||
return 3;
|
||||
@@ -426,18 +429,19 @@ int PiWebServerThread::CreateSSLCertificate()
|
||||
fclose(pkey_file);
|
||||
|
||||
// open Certificate file
|
||||
FILE *x509_file = fopen("certificate.pem", "wb");
|
||||
FILE *x509_file = fopen(CERT_PATH, "wb");
|
||||
if (!x509_file) {
|
||||
LOG_ERROR("Error opening cert");
|
||||
return 4;
|
||||
}
|
||||
// write cirtificate
|
||||
// write certificate
|
||||
PEM_write_X509(x509_file, x509);
|
||||
fclose(x509_file);
|
||||
|
||||
EVP_PKEY_free(pkey);
|
||||
LOG_INFO("Create SSL Key %s successful", KEY_PATH);
|
||||
X509_free(x509);
|
||||
LOG_INFO("Create SSL Cert -certificate.pem- succesfull ");
|
||||
LOG_INFO("Create SSL Cert %s successful", CERT_PATH);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
|
||||
#include "main.h"
|
||||
#include "mesh/api/WiFiServerAPI.h"
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
#include "target_specific.h"
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
@@ -111,12 +108,6 @@ static void onNetworkConnected()
|
||||
#endif
|
||||
APStartupComplete = true;
|
||||
}
|
||||
|
||||
// FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected'
|
||||
#ifndef MESHTASTIC_EXCLUDE_MQTT
|
||||
if (mqtt)
|
||||
mqtt->reconnect();
|
||||
#endif
|
||||
}
|
||||
|
||||
static int32_t reconnectWiFi()
|
||||
|
||||
@@ -162,7 +162,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
|
||||
case meshtastic_AdminMessage_set_module_config_tag:
|
||||
LOG_INFO("Client set module config");
|
||||
handleSetModuleConfig(r->set_module_config);
|
||||
if (!handleSetModuleConfig(r->set_module_config)) {
|
||||
myReply = allocErrorResponse(meshtastic_Routing_Error_BAD_REQUEST, &mp);
|
||||
}
|
||||
break;
|
||||
|
||||
case meshtastic_AdminMessage_set_channel_tag:
|
||||
@@ -648,15 +650,23 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
|
||||
saveChanges(changes, requiresReboot);
|
||||
}
|
||||
|
||||
void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
||||
bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
||||
{
|
||||
if (!hasOpenEditTransaction)
|
||||
disableBluetooth();
|
||||
switch (c.which_payload_variant) {
|
||||
case meshtastic_ModuleConfig_mqtt_tag:
|
||||
#if MESHTASTIC_EXCLUDE_MQTT
|
||||
LOG_WARN("Set module config: MESHTASTIC_EXCLUDE_MQTT is defined. Not setting MQTT config");
|
||||
return false;
|
||||
#else
|
||||
LOG_INFO("Set module config: MQTT");
|
||||
if (!MQTT::isValidConfig(c.payload_variant.mqtt)) {
|
||||
return false;
|
||||
}
|
||||
moduleConfig.has_mqtt = true;
|
||||
moduleConfig.mqtt = c.payload_variant.mqtt;
|
||||
#endif
|
||||
break;
|
||||
case meshtastic_ModuleConfig_serial_tag:
|
||||
LOG_INFO("Set module config: Serial");
|
||||
@@ -724,6 +734,7 @@ void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
||||
break;
|
||||
}
|
||||
saveChanges(SEGMENT_MODULECONFIG);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AdminModule::handleSetChannel(const meshtastic_Channel &cc)
|
||||
@@ -1160,4 +1171,4 @@ void disableBluetooth()
|
||||
nrf52Bluetooth->shutdown();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Obser
|
||||
void handleSetOwner(const meshtastic_User &o);
|
||||
void handleSetChannel(const meshtastic_Channel &cc);
|
||||
void handleSetConfig(const meshtastic_Config &c);
|
||||
void handleSetModuleConfig(const meshtastic_ModuleConfig &c);
|
||||
bool handleSetModuleConfig(const meshtastic_ModuleConfig &c);
|
||||
void handleSetChannel();
|
||||
void handleSetHamMode(const meshtastic_HamParameters &req);
|
||||
void handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg);
|
||||
|
||||
@@ -81,7 +81,7 @@ int32_t DetectionSensorModule::runOnce()
|
||||
}
|
||||
LOG_INFO("Detection Sensor Module: init");
|
||||
|
||||
return DELAYED_INTERVAL;
|
||||
return setStartDelay();
|
||||
}
|
||||
|
||||
// LOG_DEBUG("Detection Sensor Module: Current pin state: %i", digitalRead(moduleConfig.detection_sensor.monitor_pin));
|
||||
@@ -161,4 +161,4 @@ bool DetectionSensorModule::hasDetectionEvent()
|
||||
bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin);
|
||||
// LOG_DEBUG("Detection Sensor Module: Current state: %i", currentState);
|
||||
return (moduleConfig.detection_sensor.detection_trigger_type & 1) ? currentState : !currentState;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,8 @@ Will be used for broadcast.
|
||||
*/
|
||||
int32_t NeighborInfoModule::runOnce()
|
||||
{
|
||||
if (moduleConfig.neighbor_info.transmit_over_lora && !channels.isDefaultChannel(channels.getPrimaryIndex()) &&
|
||||
if (moduleConfig.neighbor_info.transmit_over_lora &&
|
||||
(!channels.isDefaultChannel(channels.getPrimaryIndex()) || !RadioInterface::uses_default_frequency_slot) &&
|
||||
airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) {
|
||||
sendNeighborInfo(NODENUM_BROADCAST, false);
|
||||
} else {
|
||||
|
||||
@@ -97,8 +97,9 @@ NodeInfoModule::NodeInfoModule()
|
||||
: ProtobufModule("nodeinfo", meshtastic_PortNum_NODEINFO_APP, &meshtastic_User_msg), concurrency::OSThread("NodeInfo")
|
||||
{
|
||||
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
|
||||
setIntervalFromNow(30 *
|
||||
1000); // Send our initial owner announcement 30 seconds after we start (to give network time to setup)
|
||||
|
||||
setIntervalFromNow(setStartDelay()); // Send our initial owner announcement 30 seconds
|
||||
// after we start (to give network time to setup)
|
||||
}
|
||||
|
||||
int32_t NodeInfoModule::runOnce()
|
||||
@@ -112,4 +113,4 @@ int32_t NodeInfoModule::runOnce()
|
||||
sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
|
||||
}
|
||||
return Default::getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs);
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,9 @@ PositionModule::PositionModule()
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
|
||||
setIntervalFromNow(60 * 1000);
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
|
||||
setIntervalFromNow(setStartDelay());
|
||||
}
|
||||
|
||||
// Power saving trackers should clear their position on startup to avoid waking up and sending a stale position
|
||||
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
|
||||
@@ -160,7 +161,8 @@ bool PositionModule::hasGPS()
|
||||
#endif
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
// Allocate a packet with our position data if we have one
|
||||
meshtastic_MeshPacket *PositionModule::allocPositionPacket()
|
||||
{
|
||||
if (precision == 0) {
|
||||
LOG_DEBUG("Skip location send because precision is set to 0!");
|
||||
@@ -262,7 +264,8 @@ meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
p.has_ground_speed = true;
|
||||
}
|
||||
|
||||
LOG_INFO("Position reply: time=%i lat=%i lon=%i", p.time, p.latitude_i, p.longitude_i);
|
||||
LOG_INFO("Position packet: time=%i lat=%i lon=%i", p.time, p.latitude_i, p.longitude_i);
|
||||
lastSentToMesh = millis();
|
||||
|
||||
// TAK Tracker devices should send their position in a TAK packet over the ATAK port
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
|
||||
@@ -271,6 +274,17 @@ meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
return allocDataProtobuf(p);
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
{
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND && lastSentToMesh &&
|
||||
Throttle::isWithinTimespanMs(lastSentToMesh, 3 * 60 * 1000)) {
|
||||
LOG_DEBUG("Skip Position reply since we sent it <3min ago");
|
||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
||||
return nullptr;
|
||||
}
|
||||
return allocPositionPacket();
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *PositionModule::allocAtakPli()
|
||||
{
|
||||
LOG_INFO("Send TAK PLI packet");
|
||||
@@ -333,9 +347,9 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha
|
||||
precision = 0;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *p = allocReply();
|
||||
meshtastic_MeshPacket *p = allocPositionPacket();
|
||||
if (p == nullptr) {
|
||||
LOG_DEBUG("allocReply returned a nullptr");
|
||||
LOG_DEBUG("allocPositionPacket returned a nullptr");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
meshtastic_MeshPacket *allocPositionPacket();
|
||||
struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition);
|
||||
meshtastic_MeshPacket *allocAtakPli();
|
||||
void trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate = false);
|
||||
@@ -62,6 +63,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
|
||||
void sendLostAndFoundText();
|
||||
bool hasQualityTimesource();
|
||||
bool hasGPS();
|
||||
uint32_t lastSentToMesh = 0; // Last time we sent our position to the mesh
|
||||
|
||||
const uint32_t minimumTimeThreshold =
|
||||
Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30);
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
SerialModule *serialModule;
|
||||
SerialModuleRadio *serialModuleRadio;
|
||||
|
||||
#if defined(TTGO_T_ECHO) || defined(CANARYONE)
|
||||
#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK)
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
|
||||
static Print *serialPrint = &Serial;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
@@ -158,7 +158,7 @@ int32_t SerialModule::runOnce()
|
||||
Serial.begin(baud);
|
||||
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
|
||||
}
|
||||
#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE)
|
||||
#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK)
|
||||
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
|
||||
#ifdef ARCH_RP2040
|
||||
Serial2.setFIFOSize(RX_BUFFER);
|
||||
@@ -214,7 +214,7 @@ int32_t SerialModule::runOnce()
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE)
|
||||
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK)
|
||||
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
|
||||
processWXSerial();
|
||||
|
||||
@@ -416,7 +416,7 @@ uint32_t SerialModule::getBaudRate()
|
||||
*/
|
||||
void SerialModule::processWXSerial()
|
||||
{
|
||||
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK)
|
||||
static unsigned int lastAveraged = 0;
|
||||
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
|
||||
static double dir_sum_sin = 0;
|
||||
|
||||
@@ -50,12 +50,12 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address;
|
||||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second =
|
||||
i2cScanner->fetchI2CBus(found.address);
|
||||
return 1000;
|
||||
return setStartDelay();
|
||||
}
|
||||
#endif
|
||||
return disable();
|
||||
}
|
||||
return 1000;
|
||||
return setStartDelay();
|
||||
}
|
||||
return disable();
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,7 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
|
||||
uptimeWrapCount = 0;
|
||||
uptimeLastMs = millis();
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
setIntervalFromNow(45 * 1000); // Wait until NodeInfo is sent
|
||||
setIntervalFromNow(setStartDelay()); // Wait until NodeInfo is sent
|
||||
}
|
||||
virtual bool wantUIFrame() { return false; }
|
||||
|
||||
@@ -62,4 +62,4 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
|
||||
|
||||
uint32_t uptimeWrapCount;
|
||||
uint32_t uptimeLastMs;
|
||||
};
|
||||
};
|
||||
@@ -107,8 +107,6 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
|
||||
if (moduleConfig.telemetry.environment_measurement_enabled) {
|
||||
LOG_INFO("Environment Telemetry: init");
|
||||
// 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
|
||||
#ifdef SENSECAP_INDICATOR
|
||||
result = indicatorSensor.runOnce();
|
||||
#endif
|
||||
@@ -171,7 +169,9 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
// 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
|
||||
return result == UINT32_MAX ? disable() : setStartDelay();
|
||||
} else {
|
||||
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
|
||||
if (!moduleConfig.telemetry.environment_measurement_enabled) {
|
||||
|
||||
@@ -62,7 +62,7 @@ int32_t HealthTelemetryModule::runOnce()
|
||||
if (max30102Sensor.hasSensor())
|
||||
result = max30102Sensor.runOnce();
|
||||
}
|
||||
return result;
|
||||
return result == UINT32_MAX ? disable() : setStartDelay();
|
||||
} else {
|
||||
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
|
||||
if (!moduleConfig.telemetry.health_measurement_enabled) {
|
||||
|
||||
@@ -65,7 +65,7 @@ int32_t PowerTelemetryModule::runOnce()
|
||||
if (max17048Sensor.hasSensor() && !max17048Sensor.isInitialized())
|
||||
result = max17048Sensor.runOnce();
|
||||
}
|
||||
return result;
|
||||
return result == UINT32_MAX ? disable() : setStartDelay();
|
||||
#else
|
||||
return disable();
|
||||
#endif
|
||||
|
||||
@@ -40,14 +40,14 @@ bool INA226Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
measurement->variant.environment_metrics.has_current = true;
|
||||
|
||||
// mV conversion to V
|
||||
measurement->variant.environment_metrics.voltage = ina226.getBusVoltage() / 1000;
|
||||
measurement->variant.environment_metrics.voltage = ina226.getBusVoltage();
|
||||
measurement->variant.environment_metrics.current = ina226.getCurrent_mA();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t INA226Sensor::getBusVoltageMv()
|
||||
{
|
||||
return lround(ina226.getBusVoltage());
|
||||
return lround(ina226.getBusVoltage() * 1000);
|
||||
}
|
||||
|
||||
int16_t INA226Sensor::getCurrentMa()
|
||||
|
||||
@@ -109,7 +109,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa
|
||||
void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination)
|
||||
{
|
||||
#ifdef DEBUG_PORT
|
||||
std::string route = "Route traced:";
|
||||
std::string route = "Route traced:\n";
|
||||
route += vformat("0x%x --> ", origin);
|
||||
for (uint8_t i = 0; i < r->route_count; i++) {
|
||||
if (i < r->snr_towards_count && r->snr_towards[i] != INT8_MIN)
|
||||
@@ -129,6 +129,7 @@ void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin,
|
||||
|
||||
// If there's a route back (or we are the destination as then the route is complete), print it
|
||||
if (r->route_back_count > 0 || origin == nodeDB->getNodeNum()) {
|
||||
route += "\n";
|
||||
if (r->snr_towards_count > 0 && origin == nodeDB->getNodeNum())
|
||||
route += vformat("(%.2fdB) 0x%x <-- ", (float)r->snr_back[r->snr_back_count - 1] / 4, origin);
|
||||
else
|
||||
|
||||
@@ -135,20 +135,6 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
screen->drawCompassNorth(display, compassX, compassY, myHeading);
|
||||
|
||||
// Distance to Waypoint
|
||||
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0f m", d);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
||||
}
|
||||
|
||||
// Compass bearing to waypoint
|
||||
float bearingToOther =
|
||||
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
|
||||
@@ -157,6 +143,25 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
if (!config.display.compass_north_top)
|
||||
bearingToOther -= myHeading;
|
||||
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
||||
|
||||
float bearingToOtherDegrees = (bearingToOther < 0) ? bearingToOther + 2*PI : bearingToOther;
|
||||
bearingToOtherDegrees = bearingToOtherDegrees * 180 / PI;
|
||||
|
||||
// Distance to Waypoint
|
||||
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0fft %.0f°", d * METERS_TO_FEET, bearingToOtherDegrees);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1fmi %.0f°", d * METERS_TO_FEET / MILES_TO_FEET, bearingToOtherDegrees);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0fm %.0f°", d, bearingToOtherDegrees);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1fkm %.0f°", d / 1000, bearingToOtherDegrees);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// If our node doesn't have position
|
||||
@@ -166,9 +171,9 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
|
||||
// ? in the distance field
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
|
||||
strncpy(distStr, "? mi", sizeof(distStr));
|
||||
strncpy(distStr, "? mi ?°", sizeof(distStr));
|
||||
else
|
||||
strncpy(distStr, "? km", sizeof(distStr));
|
||||
strncpy(distStr, "? km ?°", sizeof(distStr));
|
||||
}
|
||||
|
||||
// Draw compass circle
|
||||
|
||||
@@ -41,6 +41,7 @@ MQTT *mqtt;
|
||||
namespace
|
||||
{
|
||||
constexpr int reconnectMax = 5;
|
||||
constexpr uint16_t mqttPort = 1883;
|
||||
|
||||
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
|
||||
static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid
|
||||
@@ -217,6 +218,7 @@ bool isPrivateIpAddress(const IPAddress &ip)
|
||||
{.network = 169u << 24 | 254 << 16, .mask = 0xffff0000}, // 169.254.0.0/16
|
||||
{.network = 10u << 24, .mask = 0xff000000}, // 10.0.0.0/8
|
||||
{.network = 127u << 24 | 1, .mask = 0xffffffff}, // 127.0.0.1/32
|
||||
{.network = 100u << 24 | 64 << 16, .mask = 0xffc00000}, // 100.64.0.0/10
|
||||
};
|
||||
const uint32_t addr = ntohl(ip);
|
||||
for (const auto &cidrRange : privateCidrRanges) {
|
||||
@@ -244,6 +246,11 @@ std::pair<String, uint16_t> parseHostAndPort(String server, uint16_t port = 0)
|
||||
}
|
||||
return std::make_pair(std::move(server), port);
|
||||
}
|
||||
|
||||
bool isDefaultServer(const String &host)
|
||||
{
|
||||
return host.length() == 0 || host == default_mqtt_address;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length)
|
||||
@@ -323,7 +330,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
|
||||
}
|
||||
|
||||
String host = parseHostAndPort(moduleConfig.mqtt.address).first;
|
||||
isConfiguredForDefaultServer = host.length() == 0 || host == default_mqtt_address;
|
||||
isConfiguredForDefaultServer = isDefaultServer(host);
|
||||
IPAddress ip;
|
||||
isMqttServerAddressPrivate = ip.fromString(host.c_str()) && isPrivateIpAddress(ip);
|
||||
|
||||
@@ -407,40 +414,32 @@ void MQTT::reconnect()
|
||||
}
|
||||
#if HAS_NETWORKING
|
||||
// Defaults
|
||||
int serverPort = 1883;
|
||||
int serverPort = mqttPort;
|
||||
const char *serverAddr = default_mqtt_address;
|
||||
const char *mqttUsername = default_mqtt_username;
|
||||
const char *mqttPassword = default_mqtt_password;
|
||||
MQTTClient *clientConnection = mqttClient.get();
|
||||
|
||||
if (*moduleConfig.mqtt.address) {
|
||||
serverAddr = moduleConfig.mqtt.address;
|
||||
mqttUsername = moduleConfig.mqtt.username;
|
||||
mqttPassword = moduleConfig.mqtt.password;
|
||||
}
|
||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO) && !defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
if (moduleConfig.mqtt.tls_enabled) {
|
||||
// change default for encrypted to 8883
|
||||
try {
|
||||
serverPort = 8883;
|
||||
wifiSecureClient.setInsecure();
|
||||
|
||||
pubSub.setClient(wifiSecureClient);
|
||||
LOG_INFO("Use TLS-encrypted session");
|
||||
clientConnection = &wifiSecureClient;
|
||||
} catch (const std::exception &e) {
|
||||
LOG_ERROR("MQTT ERROR: %s", e.what());
|
||||
}
|
||||
} else {
|
||||
LOG_INFO("Use non-TLS-encrypted session");
|
||||
pubSub.setClient(*mqttClient);
|
||||
}
|
||||
#else
|
||||
pubSub.setClient(*mqttClient);
|
||||
#endif
|
||||
#elif HAS_NETWORKING
|
||||
pubSub.setClient(*mqttClient);
|
||||
#endif
|
||||
|
||||
std::pair<String, uint16_t> hostAndPort = parseHostAndPort(serverAddr, serverPort);
|
||||
serverAddr = hostAndPort.first.c_str();
|
||||
serverPort = hostAndPort.second;
|
||||
@@ -450,13 +449,14 @@ void MQTT::reconnect()
|
||||
LOG_INFO("Connect directly to MQTT server %s, port: %d, username: %s, password: %s", serverAddr, serverPort, mqttUsername,
|
||||
mqttPassword);
|
||||
|
||||
pubSub.setClient(*clientConnection);
|
||||
bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword);
|
||||
if (connected) {
|
||||
LOG_INFO("MQTT connected");
|
||||
enabled = true; // Start running background process again
|
||||
runASAP = true;
|
||||
reconnectCount = 0;
|
||||
isMqttServerAddressPrivate = isPrivateIpAddress(mqttClient->remoteIP());
|
||||
isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP());
|
||||
|
||||
publishNodeInfo();
|
||||
sendSubscriptions();
|
||||
@@ -567,6 +567,23 @@ int32_t MQTT::runOnce()
|
||||
return 30000;
|
||||
}
|
||||
|
||||
bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config)
|
||||
{
|
||||
String host;
|
||||
uint16_t port;
|
||||
std::tie(host, port) = parseHostAndPort(config.address, mqttPort);
|
||||
const bool defaultServer = isDefaultServer(host);
|
||||
if (defaultServer && config.tls_enabled) {
|
||||
LOG_ERROR("Invalid MQTT config: TLS was enabled, but the default server does not support TLS");
|
||||
return false;
|
||||
}
|
||||
if (defaultServer && port != mqttPort) {
|
||||
LOG_ERROR("Invalid MQTT config: Unsupported port '%d' for the default MQTT server", port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MQTT::publishNodeInfo()
|
||||
{
|
||||
// TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA)
|
||||
|
||||
@@ -47,10 +47,6 @@ class MQTT : private concurrency::OSThread
|
||||
*/
|
||||
void onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex);
|
||||
|
||||
/** Attempt to connect to server if necessary
|
||||
*/
|
||||
void reconnect();
|
||||
|
||||
bool isConnectedDirectly();
|
||||
|
||||
bool publish(const char *topic, const char *payload, bool retained);
|
||||
@@ -65,6 +61,8 @@ class MQTT : private concurrency::OSThread
|
||||
|
||||
bool isUsingDefaultServer() { return isConfiguredForDefaultServer; }
|
||||
|
||||
static bool isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config);
|
||||
|
||||
protected:
|
||||
struct QueueEntry {
|
||||
std::string topic;
|
||||
@@ -115,6 +113,10 @@ class MQTT : private concurrency::OSThread
|
||||
*/
|
||||
bool wantsLink() const;
|
||||
|
||||
/** Attempt to connect to server if necessary
|
||||
*/
|
||||
void reconnect();
|
||||
|
||||
/** Tell the server what subscriptions we want (based on channels.downlink_enabled)
|
||||
*/
|
||||
void sendSubscriptions();
|
||||
|
||||
@@ -127,4 +127,4 @@
|
||||
#if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA)
|
||||
// No serial ports on this board - ONLY use segger in memory console
|
||||
#define USE_SEGGER
|
||||
#endif
|
||||
#endif
|
||||
@@ -304,6 +304,11 @@ void cpuDeepSleep(uint32_t msecToWake)
|
||||
nrf_gpio_cfg_default(WB_I2C1_SDA);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef MESHLINK
|
||||
#ifdef PIN_WD_EN
|
||||
digitalWrite(PIN_WD_EN, LOW);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HELTEC_MESH_NODE_T114
|
||||
nrf_gpio_cfg_default(PIN_GPS_PPS);
|
||||
|
||||
@@ -369,6 +369,12 @@ bool loadConfig(const char *configPath)
|
||||
}
|
||||
}
|
||||
|
||||
settingsMap[sx126x_max_power] = yamlConfig["Lora"]["SX126X_MAX_POWER"].as<int>(22);
|
||||
settingsMap[sx128x_max_power] = yamlConfig["Lora"]["SX128X_MAX_POWER"].as<int>(13);
|
||||
settingsMap[lr1110_max_power] = yamlConfig["Lora"]["LR1110_MAX_POWER"].as<int>(22);
|
||||
settingsMap[lr1120_max_power] = yamlConfig["Lora"]["LR1120_MAX_POWER"].as<int>(13);
|
||||
settingsMap[rf95_max_power] = yamlConfig["Lora"]["RF95_MAX_POWER"].as<int>(20);
|
||||
|
||||
settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as<bool>(false);
|
||||
settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<float>(0) * 1000;
|
||||
if (settingsMap[dio3_tcxo_voltage] == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<bool>(false)) {
|
||||
@@ -518,7 +524,12 @@ bool loadConfig(const char *configPath)
|
||||
|
||||
if (yamlConfig["Webserver"]) {
|
||||
settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
|
||||
settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as<std::string>("");
|
||||
settingsStrings[webserverrootpath] =
|
||||
(yamlConfig["Webserver"]["RootPath"]).as<std::string>("/usr/share/meshtasticd/web");
|
||||
settingsStrings[websslkeypath] =
|
||||
(yamlConfig["Webserver"]["SSLKey"]).as<std::string>("/etc/meshtasticd/ssl/private_key.pem");
|
||||
settingsStrings[websslcertpath] =
|
||||
(yamlConfig["Webserver"]["SSLCert"]).as<std::string>("/etc/meshtasticd/ssl/certificate.pem");
|
||||
}
|
||||
|
||||
if (yamlConfig["General"]) {
|
||||
@@ -569,4 +580,4 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,11 @@ enum configNames {
|
||||
sx126x_ant_sw_pin,
|
||||
sx126x_ant_sw_line,
|
||||
sx126x_ant_sw_gpiochip,
|
||||
sx126x_max_power,
|
||||
sx128x_max_power,
|
||||
lr1110_max_power,
|
||||
lr1120_max_power,
|
||||
rf95_max_power,
|
||||
dio2_as_rf_switch,
|
||||
dio3_tcxo_voltage,
|
||||
use_rf95,
|
||||
@@ -76,6 +81,8 @@ enum configNames {
|
||||
webserver,
|
||||
webserverport,
|
||||
webserverrootpath,
|
||||
websslkeypath,
|
||||
websslcertpath,
|
||||
maxtophone,
|
||||
maxnodes,
|
||||
ascii_logs,
|
||||
@@ -94,4 +101,4 @@ int initGPIOPin(int pinNum, std::string gpioChipname, int line);
|
||||
bool loadConfig(const char *configPath);
|
||||
static bool ends_with(std::string_view str, std::string_view suffix);
|
||||
void getMacAddr(uint8_t *dmac);
|
||||
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
|
||||
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
|
||||
@@ -139,6 +139,12 @@ bool SimRadio::cancelSending(NodeNum from, PacketId id)
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
bool SimRadio::findInTxQueue(NodeNum from, PacketId id)
|
||||
{
|
||||
return txQueue.find(from, id);
|
||||
}
|
||||
|
||||
void SimRadio::onNotify(uint32_t notification)
|
||||
{
|
||||
switch (notification) {
|
||||
|
||||
@@ -33,6 +33,9 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
virtual bool cancelSending(NodeNum from, PacketId id) override;
|
||||
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
|
||||
|
||||
/**
|
||||
* Start waiting to receive a message
|
||||
*
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#define OCV_ARRAY 1400, 1300, 1280, 1270, 1260, 1250, 1240, 1230, 1210, 1150, 1000
|
||||
#elif defined(CELL_TYPE_LTO)
|
||||
#define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500
|
||||
#elif defined(TRACKER_T1000_E)
|
||||
#define OCV_ARRAY 4190, 4078, 4017, 3969, 3887, 3818, 3798, 3791, 3766, 3712, 3100
|
||||
#else // LiIon
|
||||
#define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include <sstream>
|
||||
#include <stdio.h>
|
||||
@@ -851,18 +850,26 @@ std::string JSONValue::StringifyString(const std::string &str)
|
||||
str_out += "\\r";
|
||||
} else if (chr == '\t') {
|
||||
str_out += "\\t";
|
||||
} else if (chr < ' ' || chr > 126) {
|
||||
str_out += "\\u";
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int value = (chr >> 12) & 0xf;
|
||||
if (value >= 0 && value <= 9)
|
||||
str_out += (char)('0' + value);
|
||||
else if (value >= 10 && value <= 15)
|
||||
str_out += (char)('A' + (value - 10));
|
||||
chr <<= 4;
|
||||
}
|
||||
} else if (chr < 0x20 || chr == 0x7F) {
|
||||
char buf[7];
|
||||
snprintf(buf, sizeof(buf), "\\u%04x", chr);
|
||||
str_out += buf;
|
||||
} else if (chr < 0x80) {
|
||||
str_out += chr;
|
||||
} else {
|
||||
str_out += chr;
|
||||
size_t remain = str.end() - iter - 1;
|
||||
if ((chr & 0xE0) == 0xC0 && remain >= 1) {
|
||||
++iter;
|
||||
str_out += *iter;
|
||||
} else if ((chr & 0xF0) == 0xE0 && remain >= 2) {
|
||||
str_out += *(++iter);
|
||||
str_out += *(++iter);
|
||||
} else if ((chr & 0xF8) == 0xF0 && remain >= 3) {
|
||||
str_out += *(++iter);
|
||||
str_out += *(++iter);
|
||||
str_out += *(++iter);
|
||||
}
|
||||
}
|
||||
|
||||
++iter;
|
||||
|
||||
@@ -245,6 +245,9 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
|
||||
#ifdef PIN_3V3_EN
|
||||
digitalWrite(PIN_3V3_EN, LOW);
|
||||
#endif
|
||||
#ifdef PIN_WD_EN
|
||||
digitalWrite(PIN_WD_EN, LOW);
|
||||
#endif
|
||||
#endif
|
||||
ledBlink.set(false);
|
||||
|
||||
@@ -530,4 +533,4 @@ void enableLoraInterrupt()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -242,6 +242,7 @@ class MQTTUnitTest : public MQTT
|
||||
mqttClient.release();
|
||||
delete pubsub;
|
||||
}
|
||||
using MQTT::reconnect;
|
||||
int queueSize() { return mqttQueue.numUsed(); }
|
||||
void reportToMap(std::optional<uint32_t> precision = std::nullopt)
|
||||
{
|
||||
@@ -488,7 +489,7 @@ void test_reconnectProxyDoesNotReconnectMqtt(void)
|
||||
moduleConfig.mqtt.proxy_to_client_enabled = true;
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
mqtt->reconnect();
|
||||
unitTest->reconnect();
|
||||
|
||||
TEST_ASSERT_FALSE(pubsub->connected_);
|
||||
}
|
||||
@@ -799,6 +800,38 @@ void test_customMqttRoot(void)
|
||||
[] { return pubsub->subscriptions_.count("custom/2/e/test/+") && pubsub->subscriptions_.count("custom/2/e/PKI/+"); }));
|
||||
}
|
||||
|
||||
// Empty configuration is valid.
|
||||
void test_configurationEmptyIsValid(void)
|
||||
{
|
||||
meshtastic_ModuleConfig_MQTTConfig config;
|
||||
|
||||
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
|
||||
}
|
||||
|
||||
// Configuration with the default server is valid.
|
||||
void test_configWithDefaultServer(void)
|
||||
{
|
||||
meshtastic_ModuleConfig_MQTTConfig config = {.address = default_mqtt_address};
|
||||
|
||||
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
|
||||
}
|
||||
|
||||
// Configuration with the default server and port 8888 is invalid.
|
||||
void test_configWithDefaultServerAndInvalidPort(void)
|
||||
{
|
||||
meshtastic_ModuleConfig_MQTTConfig config = {.address = default_mqtt_address ":8888"};
|
||||
|
||||
TEST_ASSERT_FALSE(MQTT::isValidConfig(config));
|
||||
}
|
||||
|
||||
// Configuration with the default server and tls_enabled = true is invalid.
|
||||
void test_configWithDefaultServerAndInvalidTLSEnabled(void)
|
||||
{
|
||||
meshtastic_ModuleConfig_MQTTConfig config = {.tls_enabled = true};
|
||||
|
||||
TEST_ASSERT_FALSE(MQTT::isValidConfig(config));
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
initializeTestEnvironment();
|
||||
@@ -842,6 +875,10 @@ void setup()
|
||||
RUN_TEST(test_enabled);
|
||||
RUN_TEST(test_disabled);
|
||||
RUN_TEST(test_customMqttRoot);
|
||||
RUN_TEST(test_configurationEmptyIsValid);
|
||||
RUN_TEST(test_configWithDefaultServer);
|
||||
RUN_TEST(test_configWithDefaultServerAndInvalidPort);
|
||||
RUN_TEST(test_configWithDefaultServerAndInvalidTLSEnabled);
|
||||
exit(UNITY_END());
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -29,9 +29,13 @@
|
||||
// "USERPREFS_FIXED_GPS_LON": "2.294508368",
|
||||
// "USERPREFS_LORACONFIG_CHANNEL_NUM": "31",
|
||||
// "USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST",
|
||||
// "USERPREFS_SPLASH_TITLE": "DEFCONtastic",
|
||||
"USERPREFS_TZ_STRING": "tzplaceholder "
|
||||
// "USERPREFS_USE_ADMIN_KEY_0": "{ 0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c }",
|
||||
// "USERPREFS_USE_ADMIN_KEY_1": "{}",
|
||||
// "USERPREFS_USE_ADMIN_KEY_2": "{}"
|
||||
}
|
||||
// "USERPREFS_USE_ADMIN_KEY_2": "{}",
|
||||
// "USERPREFS_OEM_TEXT": "Caterham Car Club",
|
||||
// "USERPREFS_OEM_FONT_SIZE": "0",
|
||||
// "USERPREFS_OEM_IMAGE_WIDTH": "50",
|
||||
// "USERPREFS_OEM_IMAGE_HEIGHT": "28",
|
||||
// "USERPREFS_OEM_IMAGE_DATA": "{ 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, 0x18, 0x1F, 0xF0, 0x67, 0x00, 0x00, 0x00, 0x30, 0x1F, 0xF8, 0x33, 0x00, 0x00, 0x00, 0x30, 0x00, 0xFC, 0x31, 0x00, 0x00, 0x00, 0x60, 0x00, 0xFE, 0x18, 0x00, 0x00, 0x00, 0x60, 0x00, 0x7E, 0x18, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x0C, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x80, 0x81, 0x1F, 0x06, 0x00, 0x00, 0x00, 0x80, 0xC1, 0x0F, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x8F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xC7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00}"
|
||||
}
|
||||
|
||||
BIN
variants/diy/nrf52_promicro_diy_tcxo/E80_RSSI_per_case.webp
Normal file
BIN
variants/diy/nrf52_promicro_diy_tcxo/E80_RSSI_per_case.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
107
variants/diy/nrf52_promicro_diy_tcxo/readme.md
Normal file
107
variants/diy/nrf52_promicro_diy_tcxo/readme.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Notes
|
||||
|
||||
## General
|
||||
|
||||
The pinout is contained in the variant.h file, and a [generic schematic](./Schematic_Pro-Micro_Pinouts%202024-12-14.pdf) is located in this directory.
|
||||
|
||||
### Note on DIO2, RXEN, TXEN, and RF switching
|
||||
|
||||
Several modules require external switching between transmit (Tx) and receive (Rx). This can be achieved using several methods:
|
||||
|
||||
1. Link the TXEN pin on the radio module to DIO2 on the same module, and then connect RXEN on the radio module to pin 0.17 on the Pro-Micro.
|
||||
2. Use DIO2 to drive a logic inverter, so that when DIO2 is `high`, RXEN is `low`, and vice versa.
|
||||
3. Use DIO2 to drive a pair of MOSFETs or transistors to supply the same function.
|
||||
|
||||
RXEN is not required to be connected if the selected module already has internal RF switching, or if external RF switching logic is already applied.
|
||||
Also worth noting that the Seeed WIO SX1262 in particular only has RXEN exposed (marked RF_SW) and has the DIO2-TXEN link internally.
|
||||
|
||||
<details>
|
||||
|
||||
<summary> The table of known modules is at the bottom of the variant.h, and reproduced here for convenience. </summary>
|
||||
|
||||
| Mfr | Module | TCXO | RF Switch | Notes |
|
||||
| ------------ | ---------------- | ---- | --------- | ------------------------------------- |
|
||||
| Ebyte | E22-900M22S | Yes | Ext | |
|
||||
| Ebyte | E22-900MM22S | No | Ext | |
|
||||
| Ebyte | E22-900M30S | Yes | Ext | |
|
||||
| Ebyte | E22-900M33S | Yes | Ext | MAX_POWER must be set to 8 for this |
|
||||
| Ebyte | E220-900M22S | No | Ext | LLCC68, looks like DIO3 not connected |
|
||||
| AI-Thinker | RA-01SH | No | Int | SX1262 |
|
||||
| Heltec | HT-RA62 | Yes | Int | |
|
||||
| NiceRF | Lora1262 | yes | Int | |
|
||||
| Waveshare | Core1262-HF | yes | Ext | |
|
||||
| Waveshare | LoRa Node Module | yes | Int | |
|
||||
| Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed |
|
||||
| AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** |
|
||||
| RF Solutions | RFM95 | No | Int | Untested |
|
||||
| Ebyte | E80-900M2213S | Yes | Int | LR1121 radio |
|
||||
|
||||
</details>
|
||||
|
||||
## LR1121 modules - E80 is the default
|
||||
|
||||
The E80 from CDEbyte is the most obtainable module at present, and has been selected as the default option.
|
||||
|
||||
Naturally, CDEbyte have chosen to ignore the generic Semtech impelementation of the RF switching logic and have supplied confusing and contradictory documentation, which is explained below.
|
||||
|
||||
tl;dr: The E80 is chosen as the default. **If you wish to use another module, the table in `rfswitch.h` must be adjusted accordingly.**
|
||||
|
||||
### E80 switching - the saga
|
||||
|
||||
The CDEbyte implementation of the LR1121 is contained in their E80 module. As stated above, CDEbyte have chosen to ignore the generic Semtech implementation of the RF switching logic and have their own table, which is located at the bottom of the page [here](https://www.cdebyte.com/products/E80-900M2213S/2#Pin), and reflected on page 6 of their user manual, and reproduced below:
|
||||
|
||||
| DIO5/RFSW0 | DIO6/RFSW1 | RF status |
|
||||
| ---------- | ---------- | ----------------------------- |
|
||||
| 0 | 0 | RX |
|
||||
| 0 | 1 | TX (Sub-1GHz low power mode) |
|
||||
| 1 | 0 | TX (Sub-1GHz high power mode) |
|
||||
| 1 | 1 | TX(2.4GHz) |
|
||||
|
||||
However, looking at the sample code they provide on page 9, the values would be:
|
||||
|
||||
| DIO5/RFSW0 | DIO6/RFSW1 | RF status |
|
||||
| ---------- | ---------- | ----------------------------- |
|
||||
| 0 | 1 | RX |
|
||||
| 1 | 1 | TX (Sub-1GHz low power mode) |
|
||||
| 1 | 0 | TX (Sub-1GHz high power mode) |
|
||||
| 0 | 0 | TX(2.4GHz) |
|
||||
|
||||
The Semtech default, the values are (taken from [here](https://github.com/Lora-net/SWSD006/blob/v2.6.1/lib/app_subGHz_config_lr11xx.c#L145-L154)):
|
||||
|
||||
<details>
|
||||
|
||||
```cpp
|
||||
.rfswitch = {
|
||||
.enable = LR11XX_SYSTEM_RFSW0_HIGH | LR11XX_SYSTEM_RFSW1_HIGH | LR11XX_SYSTEM_RFSW2_HIGH,
|
||||
.standby = 0,
|
||||
.rx = LR11XX_SYSTEM_RFSW0_HIGH,
|
||||
.tx = LR11XX_SYSTEM_RFSW0_HIGH | LR11XX_SYSTEM_RFSW1_HIGH,
|
||||
.tx_hp = LR11XX_SYSTEM_RFSW1_HIGH,
|
||||
.tx_hf = 0,
|
||||
.gnss = LR11XX_SYSTEM_RFSW2_HIGH,
|
||||
.wifi = 0,
|
||||
},
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
| DIO5/RFSW0 | DIO6/RFSW1 | RF status |
|
||||
| ---------- | ---------- | ----------------------------- |
|
||||
| 1 | 0 | RX |
|
||||
| 1 | 1 | TX (Sub-1GHz low power mode) |
|
||||
| 0 | 1 | TX (Sub-1GHz high power mode) |
|
||||
| 0 | 0 | TX(2.4GHz) |
|
||||
|
||||
It is evident from the tables above that there is no real consistency to those provided by Ebyte.
|
||||
|
||||
#### An experiment
|
||||
|
||||
Tests were conducted in each of the three configurations between a known-good SX1262 and an E80, passing packets in both directions and recording the reported RSSI. The E80 was set at 22db and 14db to activate the high and low power settings respectively. The results are shown in the chart below.
|
||||
|
||||

|
||||
|
||||
## Conclusion
|
||||
|
||||
The RF switching is based on the code example given. Logically, this shows the DIO5 and DIO6 are swapped compared to the reference design.
|
||||
|
||||
If future DIYers wish to use an alternative module, the table in `rfswitch.h` must be adjusted accordingly.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user