Compare commits

..
247 changed files with 3255 additions and 6946 deletions
+1 -1
View File
@@ -100,7 +100,7 @@ runs:
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}
overwrite: true
+3 -3
View File
@@ -4,16 +4,16 @@
- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
is appreciated." This will allow other devs to potentially save you time by not accidentally duplicating work etc...
is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
- Please do not check in files that don't have real changes
- Please do not reformat lines that you didn't have to change the code on
- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the Linux version),
- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the linux version),
because it automatically follows our indentation rules and its auto reformatting will not cause spurious changes to lines.
- If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description.
- If your other co-developers have comments on your PR please tweak as needed.
- Please also enable "Allow edits by maintainers".
- Please do not submit untested code.
- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and community members can help test your changes.
- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and commnunity members can help test your changes.
- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord
## 🤝 Attestations
+2 -2
View File
@@ -42,7 +42,7 @@ jobs:
sudo mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v7
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
id: gpg
@@ -64,7 +64,7 @@ jobs:
PKG_VERSION: ${{ steps.version.outputs.deb }}
- name: Store binaries as an artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
overwrite: true
+2 -2
View File
@@ -111,7 +111,7 @@ jobs:
echo "</details>" >> $GITHUB_STEP_SUMMARY
- name: Store binaries as an artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
id: upload-firmware
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
@@ -127,7 +127,7 @@ jobs:
release/device-*.bat
- name: Store manifests as an artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
id: upload-manifest
with:
name: manifest-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
+4 -4
View File
@@ -98,7 +98,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
path: ./
pattern: firmware-*-*
@@ -111,7 +111,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -127,7 +127,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
pattern: firmware-*-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -146,7 +146,7 @@ jobs:
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
overwrite: true
+1 -1
View File
@@ -35,8 +35,8 @@ jobs:
series:
- jammy # 22.04 LTS
- noble # 24.04 LTS
- plucky # 25.04
- questing # 25.10
- resolute # 26.04 LTS
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: ppa:meshtastic/daily
+5 -5
View File
@@ -60,16 +60,16 @@ jobs:
- name: Docker login
if: ${{ inputs.push }}
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
username: meshtastic
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Docker setup
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Sanitize platform string
id: sanitize_platform
@@ -78,7 +78,7 @@ jobs:
- name: Docker tag
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: meshtastic/meshtasticd
tags: |
@@ -86,7 +86,7 @@ jobs:
flavor: latest=false
- name: Docker build and push
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
id: docker_variant
with:
context: .
+3 -3
View File
@@ -139,14 +139,14 @@ jobs:
id: tags
- name: Docker login
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
username: meshtastic
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
- name: Docker meta (Debian)
id: meta_debian
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: meshtastic/meshtasticd
tags: |
@@ -167,7 +167,7 @@ jobs:
- name: Docker meta (Alpine)
id: meta_alpine
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: meshtastic/meshtasticd
tags: |
+14 -13
View File
@@ -96,6 +96,7 @@ jobs:
pio_platform: ${{ matrix.check.platform }}
pio_env: ${{ matrix.check.board }}
pio_target: check
pio_opts: --fail-on-defect=low
build:
needs: [setup, version]
@@ -177,7 +178,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -187,7 +188,7 @@ jobs:
run: ls -R
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -205,7 +206,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -224,7 +225,7 @@ jobs:
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -250,13 +251,13 @@ jobs:
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
fetch-depth: 0
- name: Download the current manifests
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
path: ./manifests-new/
pattern: manifest-*
merge-multiple: true
- name: Upload combined manifests for later commit and global stats crunching.
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
id: upload-manifest
with:
name: manifests-${{ github.sha }}
@@ -324,14 +325,14 @@ jobs:
body: ${{ steps.release_notes.outputs.notes }}
- name: Download source deb
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -355,7 +356,7 @@ jobs:
}' > firmware-${{ needs.version.outputs.long }}.json
- name: Save Release manifest artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: manifest-${{ needs.version.outputs.long }}
overwrite: true
@@ -396,7 +397,7 @@ jobs:
with:
python-version: 3.x
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -413,7 +414,7 @@ jobs:
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -454,14 +455,14 @@ jobs:
python-version: 3.x
- name: Get firmware artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Get manifest artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
pattern: manifest-${{ needs.version.outputs.long }}
path: ./publish
+9 -9
View File
@@ -147,7 +147,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -160,7 +160,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -176,7 +176,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -195,7 +195,7 @@ jobs:
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -235,14 +235,14 @@ jobs:
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -292,7 +292,7 @@ jobs:
with:
python-version: 3.x
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -309,7 +309,7 @@ jobs:
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -347,7 +347,7 @@ jobs:
with:
python-version: 3.x
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
+1 -1
View File
@@ -58,7 +58,7 @@ jobs:
id: version
- name: Download artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
+1 -11
View File
@@ -54,19 +54,9 @@ jobs:
PLATFORMIO_LIBDEPS_DIR: pio/libdeps
PLATFORMIO_PACKAGES_DIR: pio/packages
PLATFORMIO_CORE_DIR: pio/core
PLATFORMIO_SETTING_ENABLE_TELEMETRY: 0
PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL: 3650
PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD: 10240
- name: Mangle platformio cache
# Add "1" to epoch-timestamps of all downloads in the cache.
# This is a hack to prevent internet access at build-time.
run: |
cp pio/core/.cache/downloads/usage.db pio/core/.cache/downloads/usage.db.bak
jq -c 'with_entries(.value |= (. | tostring + "1" | tonumber))' pio/core/.cache/downloads/usage.db.bak > pio/core/.cache/downloads/usage.db
- name: Store binaries as an artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
overwrite: true
+2 -2
View File
@@ -46,7 +46,7 @@ jobs:
sudo apt-get install -y dput
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v7
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
id: gpg
@@ -60,7 +60,7 @@ jobs:
id: version
- name: Download artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
- name: Download test artifacts
if: needs.native-tests.result != 'skipped'
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
merge-multiple: true
+1 -1
View File
@@ -23,8 +23,8 @@ jobs:
series:
- jammy # 22.04 LTS
- noble # 24.04 LTS
- plucky # 25.04
- questing # 25.10
- resolute # 26.04 LTS
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: |-
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
# step 3
- name: save report as pipeline artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: report.sarif
overwrite: true
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Stale PR+Issues
uses: actions/stale@v10.2.0
uses: actions/stale@v10.1.1
with:
days-before-stale: 45
stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
+7 -7
View File
@@ -59,7 +59,7 @@ jobs:
id: version
- name: Save coverage information
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}
@@ -94,7 +94,7 @@ jobs:
- name: Save test results
if: always() # run this step even if previous step failed
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
overwrite: true
@@ -108,7 +108,7 @@ jobs:
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
- name: Save coverage information
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}
@@ -137,20 +137,20 @@ jobs:
id: version
- name: Download test artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.6.0
uses: dorny/test-reporter@v2.5.0
with:
name: PlatformIO Tests
path: testreport.xml
reporter: java-junit
- name: Download coverage artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}
path: code-coverage-report
@@ -163,7 +163,7 @@ jobs:
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
- name: Save Code Coverage Report
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: code-coverage-report-${{ steps.version.outputs.long }}
path: code-coverage-report
+11 -11
View File
@@ -4,24 +4,24 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.7.5
ref: v1.7.4
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.507
- renovate@43.57.0
- checkov@3.2.501
- renovate@43.10.3
- prettier@3.8.1
- trufflehog@3.93.7
- trufflehog@3.93.3
- yamllint@1.38.0
- bandit@1.9.4
- trivy@0.69.3
- bandit@1.9.3
- trivy@0.69.1
- taplo@0.10.0
- ruff@0.15.5
- isort@8.0.1
- markdownlint@0.48.0
- ruff@0.15.1
- isort@7.0.0
- markdownlint@0.47.0
- oxipng@10.1.0
- svgo@4.0.1
- actionlint@1.7.11
- svgo@4.0.0
- actionlint@1.7.10
- flake8@7.3.0
- hadolint@2.14.0
- shfmt@3.6.0
-26
View File
@@ -1,26 +0,0 @@
# Lightweight container for running native PlatformIO tests on non-Linux hosts
FROM python:3.14-slim-trixie
ENV DEBIAN_FRONTEND=noninteractive
ENV PIP_ROOT_USER_ACTION=ignore
# hadolint ignore=DL3008
RUN apt-get update && apt-get install --no-install-recommends -y \
g++ git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir platformio==6.1.19 \
&& useradd --create-home --shell /usr/sbin/nologin meshtastic
WORKDIR /firmware
RUN chown -R meshtastic:meshtastic /firmware
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD platformio --version || exit 1
USER meshtastic
# Run tests by default; override with docker run args for specific filters
CMD ["platformio", "test", "-e", "coverage", "-v"]
+1 -1
View File
@@ -23,4 +23,4 @@ for BOARD in $BOARDS; do
CHECK="${CHECK} -e ${BOARD}"
done
pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=low --fail-on-defect=medium --fail-on-defect=high
pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=medium --fail-on-defect=high
-2
View File
@@ -187,7 +187,6 @@ Logging:
LogLevel: info # debug, info, warn, error
# TraceFile: /var/log/meshtasticd.json
# JSONFile: /packets.json # File location for JSON output of decoded packets
# JSONFileRotate: 60 # Rotate JSON file every N minutes, or 0 for no rotation
# JSONFilter: position # filter for packets to save to JSON file
# AsciiLogs: true # default if not specified is !isatty() on stdout
@@ -215,4 +214,3 @@ General:
AvailableDirectory: /etc/meshtasticd/available.d/
# MACAddress: AA:BB:CC:DD:EE:FF
# MACAddressSource: eth0
# APIPort: 4403
@@ -6,9 +6,6 @@ Lora:
Reset: 16 # IO4
Busy: 24 # IO5
# Ant_sw: 13 # IO3
Enable_Pins:
- 12
- 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
@@ -4,8 +4,5 @@ Lora:
Reset: 24 # IO4
Busy: 19 # IO5
# Ant_sw: 23 # IO3
Enable_Pins:
- 26
- 23
spidev: spidev0.1
# CS: 7
@@ -1,16 +0,0 @@
Lora:
### RAK13300in Slot 1
Module: sx1262
IRQ: 22 #IO6
Reset: 16 # IO4
Busy: 24 # IO5
# Ant_sw: 13 # IO3
Enable_Pins:
- 12
- 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: 8
TX_GAIN_LORA: [8]
@@ -1,12 +0,0 @@
Lora:
### RAK13300in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
# Ant_sw: 23 # IO3
Enable_Pins:
- 26
- 23
spidev: spidev0.1
# CS: 7
TX_GAIN_LORA: [8]
+1 -5
View File
@@ -5,11 +5,7 @@ Lora:
IRQ: 22 #IO6
Reset: 16 # IO4
Busy: 24 # IO5
Enable_Pins:
- 12
- 13
# Ant_sw: 13 # IO3
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# GPIO_DETECT_PA: 13
TX_GAIN_LORA: [8]
-16
View File
@@ -1,16 +0,0 @@
# For use with Armbian luckfox-lyra-ultra-w
# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat
# 1 Watt Lyra Ultra hat
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
CS: 10
IRQ: 5
Busy: 11
Reset: 9
RXen: 14
spidev: spidev0.0 #pins are (CS=10, CLK=8, MOSI=6, MISO=7)
spiSpeed: 2000000
-17
View File
@@ -1,17 +0,0 @@
# For use with Armbian luckfox-lyra-ultra-w
# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat
# 2 Watt Lyra Ultra hat
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 8
CS: 10
IRQ: 5
Busy: 11
Reset: 9
RXen: 14
spidev: spidev0.0 #pins are (CS=10, CLK=8, MOSI=6, MISO=7)
spiSpeed: 2000000
@@ -1,25 +0,0 @@
# For use with Armbian luckfox-lyra // luckfox-lyra-plus
# Enable overlay 'luckfox-lyra-plus-spi0-cs0_rmio13-spidev' with armbian-config
# Waveshare LoRa HAT for Raspberry Pi Pico
# https://www.waveshare.com/wiki/Pico-LoRa-SX1262
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
spidev: spidev0.0
CS: # GPIO0_B5
pin: 13
gpiochip: 0
line: 13
IRQ: # GPIO1_C2
pin: 50
gpiochip: 1
line: 18
Busy: # GPIO0_B4
pin: 12
gpiochip: 0
line: 12
Reset: # GPIO0_A2
pin: 2
gpiochip: 0
line: 2
+2 -1
View File
@@ -13,7 +13,8 @@ Lora:
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 22
# Reduce output power to improve EMI
TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7]
NUM_PA_POINTS: 22
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
# Note: This module integrates an additional PA to achieve higher output power.
# The 'power' parameter here does not represent the actual RF output.
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm).
+2 -1
View File
@@ -13,7 +13,8 @@ Lora:
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 22
# Reduce output power to improve EMI
TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7]
NUM_PA_POINTS: 22
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
# Note: This module integrates an additional PA to achieve higher output power.
# The 'power' parameter here does not represent the actual RF output.
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm).
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env bash
# Run native PlatformIO tests inside Docker (for macOS / non-Linux hosts).
#
# Usage:
# ./bin/test-native-docker.sh # run all native tests
# ./bin/test-native-docker.sh -f test_transmit_history # run specific test filter
# ./bin/test-native-docker.sh --rebuild # force rebuild the image
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
IMAGE_NAME="meshtastic-native-test"
REBUILD=false
EXTRA_ARGS=()
for arg in "$@"; do
if [[ "$arg" == "--rebuild" ]]; then
REBUILD=true
else
EXTRA_ARGS+=("$arg")
fi
done
if $REBUILD || ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
echo "Building test image (first run may take a few minutes)..."
docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/Dockerfile.test" "$ROOT_DIR"
fi
# Disable BUILD_EPOCH to avoid full rebuilds between test runs (matches CI)
sed_cmd='s/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/'
# Default: run all tests. Pass extra args (e.g. -f test_transmit_history) through.
if [[ ${#EXTRA_ARGS[@]} -eq 0 ]]; then
CMD=("platformio" "test" "-e" "coverage" "-v")
else
CMD=("platformio" "test" "-e" "coverage" "-v" "${EXTRA_ARGS[@]}")
fi
exec docker run --rm \
-v "$ROOT_DIR:/src:ro" \
"$IMAGE_NAME" \
bash -c "rm -rf /tmp/fw-test && cp -a /src /tmp/fw-test && cd /tmp/fw-test && sed -i '${sed_cmd}' platformio.ini && ${CMD[*]}"
-40
View File
@@ -1,40 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_ESP32S3_DEV",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "LilyGo Mini-Epaper-S3 (4 MB Flash, 2MB PSRAM)",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://www.lilygo.cc",
"vendor": "LilyGo"
}
+1 -1
View File
@@ -12,6 +12,6 @@ Ex:
- `logo_320x480.png`
- `logo_320x240.png`
This file is copied to `data/boot/logo.png` before filesystem image compilation.
This file is copied to `data/boot/logo.png` before filesytem image compilation.
For additional examples see the [`event/defcon33` branch](https://github.com/meshtastic/firmware/tree/event/defcon33).
-3
View File
@@ -3,9 +3,6 @@ export DEBEMAIL="jbennett@incomsystems.biz"
export PLATFORMIO_LIBDEPS_DIR=pio/libdeps
export PLATFORMIO_PACKAGES_DIR=pio/packages
export PLATFORMIO_CORE_DIR=pio/core
export PLATFORMIO_SETTING_ENABLE_TELEMETRY=0
export PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650
export PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240
# Download libraries to `pio`
platformio pkg install -e native-tft
+1 -4
View File
@@ -9,10 +9,7 @@
PIO_ENV:=\
PLATFORMIO_CORE_DIR=pio/core \
PLATFORMIO_LIBDEPS_DIR=pio/libdeps \
PLATFORMIO_PACKAGES_DIR=pio/packages \
PLATFORMIO_SETTING_ENABLE_TELEMETRY=0 \
PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650 \
PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240
PLATFORMIO_PACKAGES_DIR=pio/packages
# Raspbian armhf builds should be compatible with armv6-hardfloat
# https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-1/#rpi1-compiler-flags
-625
View File
@@ -1,625 +0,0 @@
# BLE Mesh Implementation Plan
## Overview
Add BLE as a mesh transport alongside LoRa, enabling Meshtastic nodes to relay packets over Bluetooth Low Energy. Follow the same handler pattern used by UDP multicast ([UdpMulticastHandler](src/mesh/udp/UdpMulticastHandler.h)) — a standalone handler class with `onSend()` hooked into `Router::send()` and received packets injected via `router->enqueueReceivedMessage()`. No changes to `RadioInterface` or Router architecture needed.
BLE mesh is useful for:
- Dense indoor deployments where LoRa range is excessive
- Extending mesh reach through devices that lack LoRa hardware
- Hybrid LoRa+BLE networks (gateway nodes bridge both transports)
- Lower latency links between nearby nodes
---
## Architecture
### Pattern: Same as UDP Multicast
```
Router::send()
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
iface->send(p) udpHandler->onSend(p) bleMeshHandler->onSend(p)
(LoRa) (UDP) (BLE mesh)
Router::enqueueReceivedMessage()
▲
┌───────────────────┼───────────────────┐
│ │ │
LoRa RX ISR udpHandler->onReceive() bleMeshHandler->onReceive()
```
The existing UDP pattern ([Router.cpp:378-382](src/mesh/Router.cpp#L378-L382)):
```cpp
#if HAS_UDP_MULTICAST
if (udpHandler && config.network.enabled_protocols & ...) {
udpHandler->onSend(const_cast<meshtastic_MeshPacket *>(p));
}
#endif
```
BLE mesh hooks in identically, right next to it.
---
## Phase 1: Protobuf & Configuration
### 1.1 Transport mechanism enum
**File:** protobufs repo (`mesh.proto`)
Add a new transport type alongside the existing ones:
```protobuf
enum TransportMechanism {
// ... existing 0-7 ...
TRANSPORT_BLE_MESH = 8;
}
```
### 1.2 BLE Mesh configuration
Add BLE mesh config to the device config protobuf:
```protobuf
message BleMeshConfig {
bool enabled = 1;
// Operating mode
enum Mode {
OFF = 0;
ADVERTISE_AND_SCAN = 1; // connectionless (extended adv)
GATT = 2; // connection-based
HYBRID = 3; // both
}
Mode mode = 2;
// Scan/advertising interval in ms (latency vs power tradeoff)
uint32_t interval_ms = 3; // default: 500ms
// TX power level (platform-mapped)
int8_t tx_power = 4; // dBm
// Max BLE mesh peers to track
uint32_t max_peers = 5; // default: 8
}
```
---
## Phase 2: BLE Mesh Handler — Core Class
### 2.1 Base handler (platform-agnostic)
**New file:** `src/mesh/BLEMeshHandler.h`
Follow the `UdpMulticastHandler` pattern — a simple class, not a `RadioInterface`:
```cpp
#pragma once
#if HAS_BLE_MESH
#include "configuration.h"
#include "mesh/Router.h"
class BLEMeshHandler {
public:
BLEMeshHandler() : isRunning(false) {}
virtual void start() = 0;
virtual void stop() = 0;
// Called from Router::send() — broadcast packet over BLE
virtual bool onSend(const meshtastic_MeshPacket *mp) = 0;
protected:
bool isRunning;
// Shared RX path: decode and enqueue into router
void deliverToRouter(const uint8_t *data, size_t len) {
meshtastic_MeshPacket mp;
if (!pb_decode_from_bytes(data, len, &meshtastic_MeshPacket_msg, &mp))
return;
if (mp.which_payload_variant != meshtastic_MeshPacket_encrypted_tag)
return;
mp.transport_mechanism =
meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH;
mp.rx_snr = 0;
mp.rx_rssi = 0;
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp);
if (router)
router->enqueueReceivedMessage(p.release());
}
// Shared TX path: encode packet to bytes
size_t encodeForBLE(const meshtastic_MeshPacket *mp, uint8_t *buf, size_t bufLen) {
return pb_encode_to_bytes(buf, bufLen, &meshtastic_MeshPacket_msg, mp);
}
};
#endif // HAS_BLE_MESH
```
### 2.2 Wire format
Same as UDP multicast — protobuf-encoded `meshtastic_MeshPacket` (already encrypted). This means:
- Packets are encoded with `pb_encode_to_bytes()` on TX
- Decoded with `pb_decode_from_bytes()` on RX
- Encryption already applied by Router before `onSend()` is called
- Same deduplication via `PacketHistory` (keyed on `sender, id`)
### 2.3 Echo prevention
Same pattern as UDP ([UdpMulticastHandler.h:102](src/mesh/udp/UdpMulticastHandler.h#L102)):
```cpp
if (mp->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH) {
LOG_ERROR("Attempt to send BLE-sourced packet over BLE");
return false;
}
```
---
## Phase 3: ESP32 Implementation (NimBLE)
### Platform: ESP32, ESP32-S3, ESP32-C3, ESP32-C6
**New files:** `src/nimble/NimbleBLEMesh.h` / `NimbleBLEMesh.cpp`
```cpp
#pragma once
#if HAS_BLE_MESH && defined(ARCH_ESP32)
#include "mesh/BLEMeshHandler.h"
#include "nimble/NimbleBluetooth.h"
class NimbleBLEMesh : public BLEMeshHandler {
public:
void start() override;
void stop() override;
bool onSend(const meshtastic_MeshPacket *mp) override;
private:
// --- Advertising mode ---
void startScanning();
void stopScanning();
static void onScanResult(const ble_gap_disc_desc *desc, void *arg);
void broadcastViaAdv(const uint8_t *data, size_t len);
// --- GATT mode ---
void setupMeshService();
static int onMeshCharWrite(uint16_t conn_handle,
const ble_gatt_error *error,
ble_gatt_attr *attr, void *arg);
void sendViaGATT(const uint8_t *data, size_t len);
// --- Peer tracking ---
struct BLEPeer {
NodeNum nodeNum;
ble_addr_t addr;
int8_t rssi;
uint32_t lastSeenMs;
uint16_t connHandle; // 0 if not connected
};
std::vector<BLEPeer> peers;
void updatePeer(const ble_addr_t &addr, NodeNum nodeNum, int8_t rssi);
void pruneStale();
};
#endif
```
### 3.1 Advertising mode (connectionless)
Encode mesh packets into BLE extended advertisements with Meshtastic manufacturer data.
**TX flow:**
1. `onSend()` called from `Router::send()`
2. Encode packet via `encodeForBLE()`
3. Pack into extended advertisement manufacturer data
4. Broadcast for configurable number of intervals
**RX flow:**
1. NimBLE scan callback fires
2. Check manufacturer ID for Meshtastic magic
3. Extract protobuf bytes
4. Call `deliverToRouter()`
**Advertisement structure:**
```
AD Type: 0xFF (Manufacturer Specific)
Company ID: 0x????? (Meshtastic BLE SIG ID or 0xFFFF)
Mesh Version: 1 byte
Protobuf payload: variable (up to ~244 bytes with extended adv)
```
**Key NimBLE APIs:**
- `ble_gap_ext_adv_set_data()` / `ble_gap_ext_adv_start()` — extended adv TX
- `ble_gap_disc()` or `ble_gap_ext_disc()` — scanning
- Scan callback receives `ble_gap_disc_desc` or `ble_gap_ext_disc_desc`
### 3.2 GATT mode (connection-based)
Add a new GATT service for mesh relay, separate from the phone API service:
**New Mesh Relay Service:**
```
Mesh Relay Service UUID: (new, distinct from phone service in BluetoothCommon.h)
├── MeshTX (WRITE_NO_RSP) — peers write mesh packets to us
├── MeshRX (NOTIFY) — we notify peers of new mesh packets
└── MeshPeer (READ) — our NodeNum for discovery
```
**TX:** Write to MeshTX on all connected peers (broadcast) or specific next-hop peer (directed).
**RX:** MeshTX write callback → `deliverToRouter()`.
### 3.3 Coexistence with phone BLE
Both services registered on the same NimBLE host:
- Phone API service: existing UUIDs ([BluetoothCommon.h](src/BluetoothCommon.h))
- Mesh relay service: new UUIDs
- Separate connection handles for phone vs mesh peers
- Use extended adv set 0 for phone, set 1 for mesh (NimBLE supports multiple adv sets)
- Reserve 1 connection for phone, remaining for mesh peers
### 3.4 ESP32 variant considerations
| Variant | BLE | Extended Adv | Max Conn | Strategy |
| -------- | --- | ------------ | -------- | --------------------------------------- |
| ESP32 | 4.2 | No | 9 | GATT-only (legacy adv too small at 31B) |
| ESP32-S3 | 5.0 | Yes | 9 | Full adv+GATT |
| ESP32-C3 | 5.0 | Yes | 3 | Adv preferred (limited connections) |
| ESP32-C6 | 5.0 | Yes | 9 | Full adv+GATT |
---
## Phase 4: nRF52 Implementation (SoftDevice/Bluefruit)
### Platform: nRF52840 (primary), nRF52833
**New files:** `src/platform/nrf52/NRF52BLEMesh.h` / `NRF52BLEMesh.cpp`
```cpp
#pragma once
#if HAS_BLE_MESH && defined(ARCH_NRF52)
#include "mesh/BLEMeshHandler.h"
#include <bluefruit.h>
class NRF52BLEMesh : public BLEMeshHandler {
public:
void start() override;
void stop() override;
bool onSend(const meshtastic_MeshPacket *mp) override;
private:
// Advertising mode
void startScanning();
static void onScanCallback(ble_gap_evt_adv_report_t *report);
void broadcastViaAdv(const uint8_t *data, size_t len);
// GATT mode
BLEService meshService;
BLECharacteristic meshTxChar;
BLECharacteristic meshRxChar;
void setupMeshService();
static void onMeshWrite(uint16_t connHandle, BLECharacteristic *chr,
uint8_t *data, uint16_t len);
void sendViaGATT(const uint8_t *data, size_t len);
// Peers
struct BLEPeer {
NodeNum nodeNum;
ble_gap_addr_t addr;
int8_t rssi;
uint32_t lastSeenMs;
uint16_t connHandle;
};
std::vector<BLEPeer> peers;
};
#endif
```
### 4.1 Advertising mode
nRF52840 supports BLE 5.0 extended advertisements (up to 247 bytes).
**Bluefruit APIs:**
- `Bluefruit.Scanner.setRxCallback()` — scan result callback
- `Bluefruit.Scanner.start()` / `.stop()` — control scanning
- `Bluefruit.Advertising` — for standard adv
- Raw SoftDevice `sd_ble_gap_adv_set_configure()` if Bluefruit doesn't expose extended adv data
**Implementation:**
- Use Bluefruit Scanner for RX (filter by Meshtastic manufacturer ID)
- For TX, either use Bluefruit Advertising API or drop to raw SoftDevice for extended adv payloads
### 4.2 GATT mode
Same service structure as ESP32, using Bluefruit GATT APIs:
```cpp
meshService = BLEService(MESH_RELAY_SERVICE_UUID);
meshTxChar = BLECharacteristic(MESH_TX_CHAR_UUID);
meshTxChar.setProperties(CHR_PROPS_WRITE_WO_RESP);
meshTxChar.setWriteCallback(onMeshWrite);
meshTxChar.setMaxLen(512);
meshRxChar = BLECharacteristic(MESH_RX_CHAR_UUID);
meshRxChar.setProperties(CHR_PROPS_NOTIFY);
meshRxChar.setMaxLen(512);
```
**Peer connections:**
- nRF52840 SoftDevice S140 supports up to 20 concurrent connections
- Use `Bluefruit.Central` for outbound connections to mesh peers
- Bluefruit supports simultaneous peripheral (phone) + central (mesh peers)
### 4.3 Coexistence with phone BLE
Existing phone service ([NRF52Bluetooth.cpp](src/platform/nrf52/NRF52Bluetooth.cpp)) uses peripheral role:
- Keep phone API service as-is
- Add mesh relay service alongside it
- Scanner shared between peer discovery and mesh RX (filter by service UUID or manufacturer data)
- SoftDevice connection budget: 1 peripheral (phone) + N central (mesh peers)
### 4.4 nRF52 extras
| Feature | nRF52840 | nRF52833 |
| ---------------------- | -------- | -------- |
| Extended Adv | Yes | Yes |
| Max Connections | 20 | 20 |
| RAM per connection | ~1.8KB | ~1.8KB |
| Coded PHY (Long Range) | Yes | Yes |
**Coded PHY:** BLE Coded PHY (125kbps) provides ~4x range vs 1Mbps PHY. Could be used for extended-range BLE mesh links as a configuration option.
---
## Phase 5: Router Integration
### 5.1 Hook into Router::send()
**File:** [src/mesh/Router.cpp](src/mesh/Router.cpp) — add right next to UDP handler:
```cpp
#if HAS_BLE_MESH
if (bleMeshHandler && config.bluetooth.mesh_enabled) {
bleMeshHandler->onSend(p);
}
#endif
```
### 5.2 Global handler instance
**File:** [src/main.h](src/main.h):
```cpp
#ifdef HAS_BLE_MESH
#include "mesh/BLEMeshHandler.h"
extern BLEMeshHandler *bleMeshHandler;
#endif
```
**File:** [src/main.cpp](src/main.cpp):
```cpp
#ifdef HAS_BLE_MESH
BLEMeshHandler *bleMeshHandler = nullptr;
#endif
// In setup():
#ifdef HAS_BLE_MESH
#if defined(ARCH_ESP32)
bleMeshHandler = new NimbleBLEMesh();
#elif defined(ARCH_NRF52)
bleMeshHandler = new NRF52BLEMesh();
#endif
bleMeshHandler->start();
#endif
```
### 5.3 NODENUM_BROADCAST_NO_LORA
Already reserved ([MeshTypes.h:13-14](src/mesh/MeshTypes.h#L13-L14)) and already dropped by LoRa ([RadioLibInterface.cpp:170](src/mesh/RadioLibInterface.cpp#L170)). BLE mesh handler should still send packets addressed to `NODENUM_BROADCAST_NO_LORA` — this is its purpose.
### 5.4 Feature flag
**File:** `src/configuration.h` or per-variant `platformio.ini`:
```cpp
#define HAS_BLE_MESH 1 // Enable BLE mesh transport
```
Disabled by default. Enabled per-board in variant configs for boards with sufficient BLE capability.
---
## Phase 6: Peer Discovery & Management
### 6.1 Discovery methods
1. **Passive scanning**: Listen for Meshtastic manufacturer data in BLE advertisements
2. **Service discovery**: Scan for Mesh Relay Service UUID (GATT mode)
3. **NodeDB integration**: Update `meshtastic_NodeInfoLite` with BLE reachability when a peer is discovered
### 6.2 Peer table
```cpp
struct BLEMeshPeer {
NodeNum nodeNum;
// Platform-specific BLE address (ble_addr_t on ESP32, ble_gap_addr_t on nRF52)
uint8_t addr[6];
uint8_t addrType;
int8_t rssi;
uint32_t lastSeenMs;
uint16_t connHandle; // 0 = not connected
uint8_t failedConnAttempts;
};
```
Max peers: configurable (default 8), bounded by platform connection limits.
### 6.3 Connection policy (GATT mode)
- Auto-connect if RSSI > threshold and slots available
- Disconnect stale peers (no traffic for N minutes)
- Prefer advertising for broadcasts, GATT for directed/reliable
- Exponential backoff on failed connection attempts
---
## Phase 7: Power Management
### 7.1 Scan duty cycling
```
Active scanning: ~15mA (nRF52), ~90mA (ESP32)
Advertising: ~5mA peak per event
Idle: ~0mA additional
Default strategy:
- Scan window: 50ms every 500ms (10% duty cycle)
- ~1.5mA average on nRF52, ~9mA on ESP32
- Configurable via BleMeshConfig.interval_ms
```
### 7.2 Sleep integration
- Light sleep: pause scanning, continue advertising at reduced rate
- Deep sleep: disable BLE mesh entirely
- Hook into existing power management observers
### 7.3 Role-based behavior
| Device Role | BLE Mesh Behavior |
| ----------- | ------------------------------------------- |
| CLIENT | Scan on-demand, advertise when sending |
| CLIENT_MUTE | Receive only, no rebroadcast |
| ROUTER | Continuous scan+advertise, max connections |
| ROUTER_LATE | Delayed rebroadcast (same as LoRa behavior) |
| SENSOR | Minimal: advertise telemetry only |
---
## Phase 8: Testing & Validation
### 8.1 Unit tests
- BLE wire format encode/decode roundtrip
- PacketHistory deduplication across LoRa + BLE
- Peer table management (add, prune, limits)
- Echo prevention (don't re-send BLE-sourced packets over BLE)
### 8.2 Integration tests
- Two ESP32 nodes: BLE-only mesh (no LoRa)
- Two nRF52 nodes: BLE-only mesh
- Three nodes: multi-hop BLE relay
- Hybrid: Node A (LoRa+BLE) <-> Node B (BLE only) <-> Node C (LoRa+BLE)
- Phone API + BLE mesh coexistence
- Power consumption profiling
### 8.3 Compatibility matrix
| Test | ESP32 | ESP32-S3 | ESP32-C3 | ESP32-C6 | nRF52840 |
| ------------- | ----------- | -------- | -------- | -------- | -------- |
| Adv mode | GATT only\* | Yes | Yes | Yes | Yes |
| GATT mode | Yes | Yes | Yes | Yes | Yes |
| Phone coexist | Yes | Yes | Yes | Yes | Yes |
| Multi-hop | Yes | Yes | Yes | Yes | Yes |
\*ESP32 (BLE 4.2): legacy adv limited to 31B, use GATT-only mode
---
## Implementation Order
### Milestone 1: Foundation
1. Protobuf additions (`TRANSPORT_BLE_MESH`, `BleMeshConfig`)
2. `BLEMeshHandler` base class
3. Router hook (alongside UDP handler)
4. Feature flag + main.cpp wiring
### Milestone 2: ESP32 MVP
5. `NimbleBLEMesh` — advertising mode on ESP32-S3
6. Phone BLE coexistence validation
7. Basic two-node BLE mesh test
### Milestone 3: nRF52 MVP
8. `NRF52BLEMesh` — advertising mode
9. Cross-platform interop (ESP32 <-> nRF52 BLE mesh)
### Milestone 4: GATT Mode
10. GATT service + connection management for ESP32
11. GATT service + connection management for nRF52
12. Peer discovery & table management
### Milestone 5: Polish
13. Power management integration
14. Full test matrix
15. ESP32 (BLE 4.2) GATT-only fallback
16. Admin UI / config in phone apps
---
## Risks & Mitigations
| Risk | Impact | Mitigation |
| -------------------------------- | ------------------------- | ---------------------------------------------------------------------------- |
| BLE scan power drain | Battery life | Duty cycling, role-based policy |
| Phone BLE drops during mesh scan | UX regression | Separate adv sets, careful interleaving |
| NimBLE/Bluefruit API differences | Platform code duplication | Thin platform subclasses, shared base logic |
| No extended adv on ESP32 (4.2) | Can't fit packets in adv | GATT-only fallback |
| BLE range (~30-100m) | Coverage gaps | Hybrid LoRa+BLE; Coded PHY on nRF52 |
| Packet storms in dense BLE mesh | Saturation | Existing flood mitigation (PacketHistory, hop_limit, role-based rebroadcast) |
---
## Files to Create
| File | Purpose |
| ------------------------------------- | --------------------------------------------- |
| `src/mesh/BLEMeshHandler.h` | Base handler class (like UdpMulticastHandler) |
| `src/nimble/NimbleBLEMesh.h` | ESP32 BLE mesh header |
| `src/nimble/NimbleBLEMesh.cpp` | ESP32 NimBLE implementation |
| `src/platform/nrf52/NRF52BLEMesh.h` | nRF52 BLE mesh header |
| `src/platform/nrf52/NRF52BLEMesh.cpp` | nRF52 Bluefruit implementation |
## Files to Modify
| File | Change |
| --------------------------------------- | -------------------------------------------------- |
| `src/mesh/Router.cpp` | Add `bleMeshHandler->onSend()` next to UDP handler |
| `src/main.h` | Declare `extern BLEMeshHandler *bleMeshHandler` |
| `src/main.cpp` | Instantiate + start BLE mesh handler |
| `src/configuration.h` | `HAS_BLE_MESH` feature flag |
| Protobufs (`mesh.proto`) | `TRANSPORT_BLE_MESH` enum + `BleMeshConfig` |
| `src/nimble/NimbleBluetooth.cpp` | Coexistence (adv set separation) |
| `src/platform/nrf52/NRF52Bluetooth.cpp` | Coexistence (scanner sharing) |
| Platform `variant.h` / `platformio.ini` | Enable per-board |
+4 -4
View File
@@ -138,7 +138,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
adafruit/Adafruit BME280 Library@2.3.0
# renovate: datasource=custom.pio depName=Adafruit DPS310 packageName=adafruit/library/Adafruit DPS310
adafruit/Adafruit DPS310@1.1.6
adafruit/Adafruit DPS310@1.1.5
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
adafruit/Adafruit MCP9808 Library@2.0.2
# renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library
@@ -150,7 +150,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
adafruit/Adafruit LIS3DH@1.3.0
# renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0
adafruit/Adafruit AHTX0@2.0.6
adafruit/Adafruit AHTX0@2.0.5
# renovate: datasource=custom.pio depName=Adafruit LSM6DS packageName=adafruit/library/Adafruit LSM6DS
adafruit/Adafruit LSM6DS@4.7.4
# renovate: datasource=custom.pio depName=Adafruit TSL2591 packageName=adafruit/library/Adafruit TSL2591 Library
@@ -158,7 +158,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=EmotiBit MLX90632 packageName=emotibit/library/EmotiBit MLX90632
emotibit/EmotiBit MLX90632@1.0.8
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
adafruit/Adafruit MLX90614 Library@2.1.6
adafruit/Adafruit MLX90614 Library@2.1.5
# renovate: datasource=git-refs depName=INA3221 packageName=https://github.com/sgtwilko/INA3221 gitBranch=FixOverflow
https://github.com/sgtwilko/INA3221/archive/bb03d7e9bfcc74fc798838a54f4f99738f29fc6a.zip
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
@@ -180,7 +180,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
dfrobot/DFRobot_BMM150@1.0.0
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
adafruit/Adafruit TSL2561@1.1.3
adafruit/Adafruit TSL2561@1.1.2
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE
wollewald/BH1750_WE@1.1.10
+3 -1
View File
@@ -51,7 +51,9 @@ class AudioThread : public concurrency::OSThread
i2sRtttl = nullptr;
}
rtttlFile = nullptr;
if (rtttlFile != nullptr) {
rtttlFile = nullptr;
}
setCPUFast(false);
#ifdef T_LORA_PAGER
+2 -8
View File
@@ -38,10 +38,7 @@ static bool isPowered()
return true;
#endif
bool isRouter = ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
? 1
: 0);
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
// We assume routers might be powered all the time, but from a low current (solar) source
@@ -265,10 +262,7 @@ Fsm powerFSM(&stateBOOT);
void PowerFSM_setup()
{
bool isRouter = ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
? 1
: 0);
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
bool hasPower = isPowered();
LOG_INFO("PowerFSM init, USB power=%d", hasPower ? 1 : 0);
-2
View File
@@ -35,8 +35,6 @@ void consoleInit()
#if defined(SERIAL_HAS_ON_RECEIVE)
// onReceive does only exist for HardwareSerial not for USB CDC serial
Port.onReceive([sc]() { sc->rxInt(); });
#else
(void)sc;
#endif
DEBUG_PORT.rpInit(); // Simply sets up semaphore
}
+6 -11
View File
@@ -1,29 +1,24 @@
#pragma once
#include <functional>
#include <utility>
#include "concurrency/OSThread.h"
namespace concurrency
{
/**
* @brief Periodically invoke a callback.
* Supports both legacy function pointers and modern callables.
* @brief Periodically invoke a callback. This just provides C-style callback conventions
* rather than a virtual function - FIXME, remove?
*/
class Periodic : public OSThread
{
int32_t (*callback)();
public:
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
Periodic(const char *name, int32_t (*cb)()) : OSThread(name), callback(cb) {}
Periodic(const char *name, std::function<int32_t()> cb) : OSThread(name), callback(std::move(cb)) {}
Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {}
protected:
int32_t runOnce() override { return callback ? callback() : 0; }
private:
std::function<int32_t()> callback;
int32_t runOnce() override { return callback(); }
};
} // namespace concurrency
-12
View File
@@ -149,12 +149,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
#endif
#ifdef USE_KCT8103L_PA
// Power Amps are often non-linear, so we can use an array of values for the power curve
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 7
#endif
#ifdef RAK13302
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
@@ -169,10 +163,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define TX_GAIN_LORA 0
#endif
#ifndef HAS_LORA_FEM
#define HAS_LORA_FEM 0
#endif
// -----------------------------------------------------------------------------
// Feature toggles
// -----------------------------------------------------------------------------
@@ -271,8 +261,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BHI260AP_ADDR 0x28
#define BMM150_ADDR 0x13
#define DA217_ADDR 0x26
#define BMI270_ADDR 0x68
#define BMI270_ADDR_ALT 0x69
// -----------------------------------------------------------------------------
// LED
+2 -2
View File
@@ -37,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150, BMI270};
return firstOfOrNONE(10, types);
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150};
return firstOfOrNONE(9, types);
}
ScanI2C::FoundDevice ScanI2C::firstAQI() const
+1 -3
View File
@@ -89,12 +89,10 @@ class ScanI2C
DA217,
CHSC6X,
CST226SE,
BMI270,
SEN5X,
SFA30,
CW2015,
SCD30,
ADS1115
SCD30
} DeviceType;
// typedef uint8_t DeviceAddress;
+6 -20
View File
@@ -652,8 +652,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, and SEN5X_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR
case ICM20948_ADDR: // same as BMX160_ADDR and SEN5X_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
// ICM20948 Register check
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
#ifdef HAS_ICM20948
type = ICM20948;
@@ -664,14 +665,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = ICM20948;
logFoundDevice("ICM20948", (uint8_t)addr.address);
break;
} else if (registerValue == 0x24) {
type = BMI270;
logFoundDevice("BMI270", (uint8_t)addr.address);
break;
} else if (registerValue == 0xD8) { // BMX160 chip ID at register 0x00
type = BMX160;
logFoundDevice("BMX160", (uint8_t)addr.address);
break;
} else {
String prod = "";
prod = readSEN5xProductName(i2cBus, addr.address);
@@ -725,18 +718,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
if (len == 5 && memcmp(expectedInfo, info, len) == 0) {
LOG_INFO("NXP SE050 crypto chip found");
type = NXP_SE050;
break;
}
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
if (registerValue == 0x8583 || registerValue == 0x8580) {
type = ADS1115;
logFoundDevice("ADS1115 ADC", (uint8_t)addr.address);
break;
} else {
LOG_INFO("FT6336U touchscreen found");
type = FT6336U;
}
LOG_INFO("FT6336U touchscreen found");
type = FT6336U;
break;
}
+6 -6
View File
@@ -93,7 +93,7 @@ static const char *getGPSPowerStateString(GPSPowerState state)
#ifdef PIN_GPS_SWITCH
// If we have a hardware switch, define a periodic watcher outside of the GPS runOnce thread, since this can be sleeping
// indefinitely
// idefinitely
int lastState = LOW;
bool firstrun = true;
@@ -586,14 +586,14 @@ bool GPS::setup()
_serial_gps->write("$PMTK301,2*2E\r\n");
delay(250);
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
// Set the initial configuration of the device - these _should_ work for most AT6558 devices
// Set the intial configuration of the device - these _should_ work for most AT6558 devices
msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
_serial_gps->write(UBXscratch, msglen);
if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
LOG_WARN("ATGM336H: Could not set Config");
}
// Set the update frequency to 1Hz
// Set the update frequence to 1Hz
msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
_serial_gps->write(UBXscratch, msglen);
if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
@@ -700,7 +700,7 @@ bool GPS::setup()
} else { // 8,9
LOG_INFO("GPS+SBAS+GLONASS+Galileo configured");
}
// Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next
// Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next
// commands for the M8 it tends to be more... 1 sec should be enough ;>)
delay(1000);
}
@@ -733,7 +733,7 @@ bool GPS::setup()
SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersave for GPS", 500);
SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
// For M8 we want to enable NMEA version 4.10 so we can see the additional sats.
// For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats.
if (gnssModel == GNSS_MODEL_UBLOX8) {
clearBuffer();
SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500);
@@ -1211,7 +1211,7 @@ int32_t GPS::runOnce()
return disable(); // This should trigger when we have a fixed position, and get that first position
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
// if not awake we can run super infrequently (once every 5 secs?) to see if we need to wake.
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
}
+5 -5
View File
@@ -12,7 +12,7 @@ GeoCoord::GeoCoord(int32_t lat, int32_t lon, int32_t alt) : _latitude(lat), _lon
GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt)
{
// Change decimal representation to int32_t. I.e., 12.345 becomes 123450000
// Change decimial representation to int32_t. I.e., 12.345 becomes 123450000
_latitude = int32_t(lat * 1e+7);
_longitude = int32_t(lon * 1e+7);
GeoCoord::setCoords();
@@ -20,7 +20,7 @@ GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt)
GeoCoord::GeoCoord(double lat, double lon, int32_t alt) : _altitude(alt)
{
// Change decimal representation to int32_t. I.e., 12.345 becomes 123450000
// Change decimial representation to int32_t. I.e., 12.345 becomes 123450000
_latitude = int32_t(lat * 1e+7);
_longitude = int32_t(lon * 1e+7);
GeoCoord::setCoords();
@@ -467,10 +467,10 @@ int32_t GeoCoord::bearingTo(const GeoCoord &pointB)
}
/**
* Create a new point based on the passed-in point
* Create a new point bassed on the passed in poin
* Ported from http://www.edwilliams.org/avform147.htm#LL
* @param bearing
* The bearing in radians
* The bearing in raidans
* @param range_meters
* range in meters
* @return GeoCoord object of point at bearing and range from initial point
@@ -593,4 +593,4 @@ double GeoCoord::toRadians(double deg)
double GeoCoord::toDegrees(double r)
{
return r * 180 / PI;
}
}
+2 -2
View File
@@ -223,7 +223,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
// This delta value works on all platforms
timeStartMsec = now;
zeroOffsetSecs = tv->tv_sec;
// If this platform has a settable RTC, set it
// If this platform has a setable RTC, set it
#ifdef RV3028_RTC
if (rtc_found.address == RV3028_RTC) {
Melopero_RV3028 rtc;
@@ -402,7 +402,7 @@ time_t gm_mktime(const struct tm *tm)
#if !MESHTASTIC_EXCLUDE_TZ
time_t result = 0;
// First, get us to the start of tm->year, by calculating the number of days since the Unix epoch.
// First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
int year = 1900 + tm->tm_year; // tm_year is years since 1900
int year_minus_one = year - 1;
int days_before_this_year = 0;
+1 -1
View File
@@ -37,7 +37,7 @@ static const uint8_t _message_CAS_CFG_RATE_1HZ[] = {
// CFG-NAVX (0x06, 0x07)
// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system
// Quirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used.
static const uint8_t _message_CAS_CFG_NAVX_CONF[] = {
0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings
+6 -6
View File
@@ -57,7 +57,7 @@ static const uint8_t _message_CFG_PM2[] PROGMEM = {
0x00, 0x00, 0x00, 0x00 // 0x64, 0x40, 0x01, 0x00 // reserved 11
};
// Constellation setup, none required for Neo-6
// Constallation setup, none required for Neo-6
// For Neo-7 GPS & SBAS
static const uint8_t _message_GNSS_7[] = {
@@ -157,7 +157,7 @@ static const uint8_t _message_NAVX5[] = {
0x00, 0x00, 0x00, 0x00, // Reserved 9
0x00, // Reserved 10
0x00, // Reserved 11
0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true)
0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
0x01, // useAOP (AssistNow Autonomous configuration) = 1 (enabled)
0x00, // Reserved 12
0x00, // Reserved 13
@@ -185,7 +185,7 @@ static const uint8_t _message_NAVX5_8[] = {
0x00, // Reserved 4
0x00, 0x00, // Reserved 5
0x00, 0x00, // Reserved 6
0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true)
0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
0x01, // aopCfg (AssistNow Autonomous configuration) = 1 (enabled)
0x00, 0x00, // Reserved 7
0x00, 0x00, // aopOrbMaxErr = 0 to reset to firmware default
@@ -314,7 +314,7 @@ static const uint8_t _message_DISABLE_TXT_INFO[] = {
// This command applies to M8 products
static const uint8_t _message_PMS[] = {
0x00, // Version (0)
0x03, // Power setup value 3 = Agressive 1Hz
0x03, // Power setup value 3 = Agresssive 1Hz
0x00, 0x00, // period: not applicable, set to 0
0x00, 0x00, // onTime: not applicable, set to 0
0x00, 0x00 // reserved, generated by u-center
@@ -337,7 +337,7 @@ static const uint8_t _message_SAVE_10[] = {
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
// BBR will survive a restart, and power off for a while, but modules with small backup
// batteries or super caps will not retain the config for a long power off time.
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast acquisition after
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after
// sleep
// VALSET Commands for M10
@@ -462,7 +462,7 @@ Default GNSS configuration is: GPS, Galileo, BDS B1l, with QZSS and SBAS enabled
The PMREQ puts the receiver to sleep and wakeup re-acquires really fast and seems to not need
the PM config. Lets try without it.
PMREQ sort of works with SBAS, but the awake time is too short to re-acquire any SBAS sats.
The definition of "Got Fix" doesn't seem to include SBAS. Much more too this...
The defination of "Got Fix" doesn't seem to include SBAS. Much more too this...
Even if it was, it can take minutes (up to 12.5),
even under good sat visibility conditions to re-acquire the SBAS data.
+1 -12
View File
@@ -101,7 +101,7 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
return true;
}
// End the update process - virtual method, overridden in derived class
// End the update process - virtual method, overriden in derived class
void EInkDisplay::endUpdate()
{
// Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
@@ -259,17 +259,6 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#elif defined(MINI_EPAPER_S3)
spi1 = new SPIClass(HSPI);
spi1->begin(PIN_SPI1_SCK, PIN_SPI1_MISO, PIN_SPI1_MOSI, PIN_EINK_CS);
// Create GxEPD2 objects
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);
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(1);
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
// Detect display model, before starting SPI
+1 -2
View File
@@ -93,8 +93,7 @@ class EInkDisplay : public OLEDDisplay
SPIClass *hspi = NULL;
#endif
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) || \
defined(MINI_EPAPER_S3)
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
SPIClass *spi1 = NULL;
#endif
+3 -3
View File
@@ -95,7 +95,7 @@ void EInkDynamicDisplay::adjustRefreshCounters()
// Trigger the display update by calling base class
bool EInkDynamicDisplay::update()
{
// Determine the refresh mode to use, and start the update
// Detemine the refresh mode to use, and start the update
bool refreshApproved = determineMode();
if (refreshApproved) {
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
@@ -317,7 +317,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x", frameFlags);
}
// Have too many fast-refreshes occurred consecutively, since last full refresh?
// Have too many fast-refreshes occured consecutively, since last full refresh?
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
{
// If a decision was already reached, don't run the check
@@ -561,4 +561,4 @@ void EInkDynamicDisplay::awaitRefresh()
}
#endif // HAS_EINK_ASYNCFULL
#endif // USE_EINK_DYNAMICDISPLAY
#endif // USE_EINK_DYNAMICDISPLAY
+2 -15
View File
@@ -433,15 +433,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif
// some screens seem to need a kick in the pants to turn back on
#if defined(MUZI_BASE) || defined(M5STACK_CARDPUTER_ADV)
#if defined(MUZI_BASE)
dispdev->init();
dispdev->setBrightness(brightness);
dispdev->flipScreenVertically();
dispdev->resetDisplay();
#ifdef SCREEN_12V_ENABLE
digitalWrite(SCREEN_12V_ENABLE, HIGH);
#endif
delay(100);
#endif
#if !ARCH_PORTDUINO
@@ -465,11 +462,9 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
ui->init();
#endif
#if defined(USE_ST7789) && defined(VTFT_LEDA)
#ifdef VTFT_CTRL
#ifdef USE_ST7789
pinMode(VTFT_CTRL, OUTPUT);
digitalWrite(VTFT_CTRL, LOW);
#endif
ui->init();
#ifdef ESP_PLATFORM
analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT);
@@ -511,12 +506,8 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#ifdef USE_ST7789
SPI1.end();
#if defined(ARCH_ESP32)
#ifdef VTFT_LEDA
pinMode(VTFT_LEDA, ANALOG);
#endif
#ifdef VTFT_CTRL
pinMode(VTFT_CTRL, ANALOG);
#endif
pinMode(ST7789_RESET, ANALOG);
pinMode(ST7789_RS, ANALOG);
pinMode(ST7789_NSS, ANALOG);
@@ -884,10 +875,6 @@ int32_t Screen::runOnce()
break;
case Cmd::STOP_ALERT_FRAME:
NotificationRenderer::pauseBanner = false;
// Return from one-off alert mode back to regular frames.
if (!showingNormalScreen && NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
setFrames();
}
break;
case Cmd::STOP_BOOT_SCREEN:
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
-4
View File
@@ -765,11 +765,7 @@ class Screen : public concurrency::OSThread
DebugInfo debugInfo;
/// Display device
#ifdef USE_ST7789
ST7789Spi *dispdev;
#else
OLEDDisplay *dispdev;
#endif
/// UI helper for rendering to frames and switching between them
OLEDDisplayUi *ui;
+2 -2
View File
@@ -1209,8 +1209,8 @@ void TFTDisplay::display(bool fromBlank)
bool somethingChanged = false;
// Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
colorTftMesh = __builtin_bswap16(TFT_MESH);
colorTftBlack = __builtin_bswap16(TFT_BLACK);
colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8);
colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8);
y = 0;
while (y < displayHeight) {
+3 -3
View File
@@ -43,7 +43,7 @@ InputEvent NotificationRenderer::inEvent;
int8_t NotificationRenderer::curSelected = 0;
char NotificationRenderer::alertBannerMessage[256] = {0};
uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever
uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are selectable options
uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are seelctable options
const char **NotificationRenderer::optionsArrayPtr = nullptr;
const int *NotificationRenderer::optionsEnumPtr = nullptr;
std::function<void(int)> NotificationRenderer::alertBannerCallback = NULL;
@@ -95,7 +95,7 @@ void NotificationRenderer::resetBanner()
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
alertBannerOptions = 0; // last x lines are selectable options
alertBannerOptions = 0; // last x lines are seelctable options
optionsArrayPtr = nullptr;
optionsEnumPtr = nullptr;
alertBannerCallback = NULL;
@@ -781,4 +781,4 @@ void NotificationRenderer::showKeyboardMessagePopupWithTitle(const char *title,
}
} // namespace graphics
#endif
#endif
+1 -1
View File
@@ -22,7 +22,7 @@ class NotificationRenderer
static uint32_t alertBannerUntil; // 0 is a special case meaning forever
static const char **optionsArrayPtr;
static const int *optionsEnumPtr;
static uint8_t alertBannerOptions; // last x lines are selectable options
static uint8_t alertBannerOptions; // last x lines are seelctable options
static std::function<void(int)> alertBannerCallback;
static uint32_t numDigits;
static uint32_t currentNumber;
+6
View File
@@ -12,6 +12,7 @@
#include "graphics/TimeFormatters.h"
#include "graphics/images.h"
#include "main.h"
#include "modules/CannedMessageModule.h"
#include "target_specific.h"
#include <OLEDDisplay.h>
#include <RTC.h>
@@ -1422,6 +1423,11 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
const int totalWidth = (pageEnd - pageStart) * iconSize + (pageEnd - pageStart - 1) * spacing;
const int xStart = (SCREEN_WIDTH - totalWidth) / 2;
// Suppress nav overlay entirely
if (cannedMessageModule && cannedMessageModule->isFreeTextUIActive()) {
return;
}
bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
@@ -37,8 +37,8 @@ class DEPG0213BNS800 : public SSD16XX
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
void finalizeUpdate() override; // Only overridden for a slight optimization
void finalizeUpdate() override; // Only overriden for a slight optimization
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -35,8 +35,8 @@ class DEPG0290BNS800 : public SSD16XX
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
void finalizeUpdate() override; // Only overridden for a slight optimization
void finalizeUpdate() override; // Only overriden for a slight optimization
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+1 -1
View File
@@ -19,7 +19,7 @@ void setupNicheGraphics()
SPIClass *hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
// Setup EInk driver
// Setup Enk driver
Drivers::EInk *driver = new Drivers::DEPG0290BNS800;
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY);
+2 -2
View File
@@ -3,7 +3,7 @@
/*
Base class for InkHUD applets
Must be overridden
Must be overriden
An applet is one "program" which may show info on the display.
@@ -208,4 +208,4 @@ class Applet : public GFX
}; // namespace NicheGraphics::InkHUD
#endif
#endif
@@ -525,7 +525,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
}
// Determine the conversion factor between metres, and pixels on screen
// May be overridden by derived applet, if custom scale required (fixed map size?)
// May be overriden by derived applet, if custom scale required (fixed map size?)
void InkHUD::MapApplet::calculateMapScale()
{
// Aspect ratio of map and screen
@@ -555,4 +555,4 @@ void InkHUD::MapApplet::drawCross(int16_t x, int16_t y, uint8_t size)
drawLine(x0, y1, x1, y0, BLACK);
}
#endif
#endif
@@ -120,26 +120,9 @@ void InkHUD::NodeListApplet::onRender(bool full)
// Draw the main node list
// ========================
// Leave a small gutter between long-name text and right-side card content
constexpr uint8_t rightContentGap = 2;
// Truncate with trailing "...", sized using the current font.
auto ellipsizeToWidth = [this](std::string text, uint16_t maxWidth) {
constexpr const char *ellipsis = "...";
const uint16_t ellipsisW = getTextWidth(ellipsis);
uint16_t textW = getTextWidth(text);
if (maxWidth == 0)
return std::string();
if (textW <= maxWidth)
return text;
if (ellipsisW > maxWidth)
return std::string();
while (!text.empty() && (textW + ellipsisW > maxWidth)) {
text.pop_back();
textW = getTextWidth(text);
}
return text + ellipsis;
};
// Imaginary vertical line dividing left-side and right-side info
// Long-name will crop here
const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
// Y value (top) of the current card. Increases as we draw.
uint16_t cardTopY = headerDivY + padDivH;
@@ -158,7 +141,7 @@ void InkHUD::NodeListApplet::onRender(bool full)
SignalStrength &signal = card->signal;
std::string longName; // handled below
std::string shortName; // handled below
std::string distance; // handled below
std::string distance; // handled below;
const uint8_t &hopsAway = card->hopsAway;
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
@@ -202,49 +185,41 @@ void InkHUD::NodeListApplet::onRender(bool full)
setFont(fontMedium);
printAt(0, lineAY, shortName, LEFT, MIDDLE);
// Right-side labels and long name are rendered in small font.
// Print the distance
setFont(fontSmall);
uint16_t rightContentW = 0;
printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE);
// Bottom row right: distance.
if (!distance.empty()) {
rightContentW = std::max(rightContentW, getTextWidth(distance));
printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE);
}
// Top row right: direct-link signal only.
// If we have a direct connection to the node, draw the signal indicator
if (hopsAway == 0 && signal != SIGNAL_UNKNOWN) {
uint16_t signalW = getTextWidth("Xkm"); // Indicator width tuned to a short right-side label
uint16_t signalW = getTextWidth("Xkm"); // Indicator should be similar width to distance label
uint16_t signalH = fontMedium.lineHeight() * 0.75;
int16_t signalY = lineAY + (fontMedium.lineHeight() / 2) - (fontMedium.lineHeight() * 0.75);
int16_t signalX = width() - signalW;
rightContentW = std::max(rightContentW, signalW);
drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
} else if (hopsAway != CardInfo::HOPS_UNKNOWN) {
std::string hopString = to_string(hopsAway) + (hopsAway == 1 ? " Hop" : " Hops");
rightContentW = std::max(rightContentW, getTextWidth(hopString));
}
// Otherwise, print "hops away" info, if available
else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
std::string hopString = to_string(node->hops_away);
hopString += " Hop";
if (node->hops_away != 1)
hopString += "s"; // Append s for "Hops", rather than "Hop"
printAt(width() - 1, lineAY, hopString, RIGHT, MIDDLE);
}
// Give long names as much room as possible while still avoiding right side signal and hop space
const uint16_t longNameMaxW =
(rightContentW + rightContentGap < width()) ? (width() - rightContentW - rightContentGap) : 0;
const std::string longNameShown = ellipsizeToWidth(longName, longNameMaxW);
// Print the long name, cropping to prevent overflow onto the right-side info
setCrop(0, 0, dividerX - 1, height());
printAt(0, lineBY, longName, LEFT, MIDDLE);
// Safety crop
setCrop(0, cardTopY, longNameMaxW, cardH);
printAt(0, lineBY, longNameShown, LEFT, MIDDLE);
resetCrop();
// Draw separator between cards
const int16_t separatorY = cardTopY + cardH - 1;
if (separatorY < height() - 1 && (card + 1) != cards.end()) {
for (int16_t xSep = 0; xSep < width(); xSep += 2)
drawPixel(xSep, separatorY, BLACK);
}
// GFX effect: "hatch" the right edge of longName area
// If a longName has been cropped, it will appear to fade out,
// creating a soft barrier with the right-side info
const int16_t hatchLeft = dividerX - 1 - (fontSmall.lineHeight());
const int16_t hatchWidth = fontSmall.lineHeight();
hatchRegion(hatchLeft, cardTopY, hatchWidth, cardH, 2, WHITE);
// Prepare to draw the next card
resetCrop();
cardTopY += cardH;
// Once we've run out of screen, stop drawing cards
@@ -313,4 +288,4 @@ void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t
}
}
#endif
#endif
@@ -65,10 +65,10 @@ class NodeListApplet : public Applet, public MeshModule
// Card Dimensions
// - for rendering and for maxCards calc
uint8_t cardMarginH = 1; // Gap between cards (minimal to fit more rows)
uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
uint16_t cardH = fontMedium.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
};
} // namespace NicheGraphics::InkHUD
#endif
#endif
@@ -6,7 +6,7 @@ using namespace NicheGraphics;
InkHUD::BatteryIconApplet::BatteryIconApplet()
{
alwaysRender = true; // render every time the screen is updated
alwaysRender = true; // render everytime the screen is updated
// Show at boot, if user has previously enabled the feature
if (settings->optionalFeatures.batteryIcon)
@@ -91,4 +91,4 @@ void InkHUD::BatteryIconApplet::onRender(bool full)
drawRect(sliceL, sliceT, sliceW, sliceH, BLACK);
}
#endif
#endif
@@ -2,7 +2,7 @@
/*
System Applet to render an on-screen keyboard
System Applet to render an on-screeen keyboard
*/
@@ -45,7 +45,7 @@ void InkHUD::LogoApplet::onRender(bool full)
int16_t logoCY = Y(0.5 - 0.05);
// Invert colors if black-on-white
// Used during shutdown, to report display health
// Used during shutdown, to resport display health
// Todo: handle this in InkHUD::Renderer instead
if (inverted) {
fillScreen(BLACK);
@@ -186,4 +186,4 @@ int32_t InkHUD::LogoApplet::runOnce()
return OSThread::disable();
}
#endif
#endif
@@ -2028,7 +2028,7 @@ void InkHUD::MenuApplet::sendText(NodeNum dest, ChannelIndex channel, const char
service->sendToMesh(p, RX_SRC_LOCAL, true); // Send to mesh, cc to phone
}
// Free up any heap memory we'd used while selecting / sending canned messages
// Free up any heap mmemory we'd used while selecting / sending canned messages
void InkHUD::MenuApplet::freeCannedMessageResources()
{
cm.selectedMessageItem = nullptr;
@@ -73,7 +73,7 @@ ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshP
}
} else {
// For non-local packets, this applet only reacts to favorited nodes.
const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
if (!sender || !sender->is_favorite)
return ProcessMessage::CONTINUE;
@@ -7,7 +7,7 @@ Displays a thread-view of incoming and outgoing message for a specific channel
The channel for this applet is set in the constructor,
when the applet is added to WindowManager in the setupNicheGraphics method.
Several messages are saved to flash at shutdown, to preserve applet between reboots.
Several messages are saved to flash at shutdown, to preseve applet between reboots.
This class has its own internal method for saving and loading to fs, which interacts directly with the FSCommon layer.
If the amount of flash usage is unacceptable, we could keep these in RAM only.
@@ -55,4 +55,4 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule
} // namespace NicheGraphics::InkHUD
#endif
#endif
+2 -2
View File
@@ -53,7 +53,7 @@ class Renderer : protected concurrency::OSThread
uint16_t height();
private:
// Make attempts to render / update, once triggered by requestUpdate or forceUpdate
// Make attemps to render / update, once triggered by requestUpdate or forceUpdate
int32_t runOnce() override;
// Apply the display rotation to handled pixels
@@ -95,4 +95,4 @@ class Renderer : protected concurrency::OSThread
} // namespace NicheGraphics::InkHUD
#endif
#endif
+2 -2
View File
@@ -649,7 +649,7 @@ void InkHUD::WindowManager::refocusTile()
}
}
// Search for any applets which believe they are foreground, but no longer have a valid tile
// Seach for any applets which believe they are foreground, but no longer have a valid tile
// Tidies up after layout changes at runtime
void InkHUD::WindowManager::findOrphanApplets()
{
@@ -679,4 +679,4 @@ void InkHUD::WindowManager::findOrphanApplets()
}
}
#endif
#endif
+1 -1
View File
@@ -733,7 +733,7 @@ To add support for additional encodings, add to the `AppletFont::Encodings` enum
#### Custom Line Height
Some fonts may have a handful of especially tall characters, especially extended-ASCII fonts with diacritics. Ideally, the font should be modified to help resolve this, but if the problem remains, manual offsets to the automatically determined line height can be specified in the constructor.
Some fonts may have a handful of especially tall characters, especially extended-ASCII fonts with diacritcs. Ideally, the font should be modified to help resolve this, but if the problem remains, manual offsets to the automatically determined line height can be specified in the constructor.
```cpp
// -2 px of padding above, +1 px of padding below
+2 -2
View File
@@ -59,7 +59,7 @@ void TwoButton::stop()
}
// Attempt to resolve a GPIO pin for the user button, honoring userPrefs.jsonc and device settings
// This helper method isn't used by the TwoButton class itself, it could be moved elsewhere.
// This helper method isn't used by the TweButton class itself, it could be moved elsewhere.
// Intention is to pass this value to TwoButton::setWiring in the setupNicheGraphics method.
uint8_t TwoButton::getUserButtonPin()
{
@@ -308,4 +308,4 @@ int TwoButton::afterLightSleep(esp_sleep_wakeup_cause_t cause)
#endif
#endif
#endif
+4 -5
View File
@@ -271,13 +271,12 @@ int32_t ButtonThread::runOnce()
break;
} // end multipress event
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediately.
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
// Require press started after boot holdoff to avoid phantom shutdown from floating pins
if (millis() > 30000 && buttonPressStartTime > 30000 && _longLongPress != INPUT_BROKER_NONE &&
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
(millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
evt.inputEvent = _longLongPress;
this->notifyObservers(&evt);
@@ -347,4 +346,4 @@ int ButtonThread::afterLightSleep(esp_sleep_wakeup_cause_t cause)
void ButtonThread::storeClickCount()
{
multipressClickCount = userButton.getNumberClicks();
}
}
-200
View File
@@ -1,200 +0,0 @@
#if defined(M5STACK_CARDPUTER_ADV)
#include "CardputerKeyboard.h"
#include "main.h"
#define _TCA8418_COLS 8
#define _TCA8418_ROWS 7
#define _TCA8418_NUM_KEYS 56
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
using Key = TCA8418KeyboardBase::TCA8418Key;
constexpr uint8_t modifierFnKey = 2;
constexpr uint8_t modifierFn = 0b0010;
constexpr uint8_t modifierCtrlKey = 3;
constexpr uint8_t modifierShiftKey = 6;
constexpr uint8_t modifierShift = 0b0001;
constexpr uint8_t modifierOptKey = 7;
constexpr uint8_t modifierAltKey = 11;
// Num chars per key, Modulus for rotating through characters
static uint8_t CardputerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
static unsigned char CardputerTapMap[_TCA8418_NUM_KEYS][3] = {{'`', '~', Key::ESC},
{Key::TAB, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{'1', '!', 0x00},
{'q', 'Q', Key::REBOOT},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{'2', '@', 0x00},
{'w', 'W', 0x00},
{'a', 'A', 0x00},
{0x00, 0x00, 0x00},
{'3', '#', 0x00},
{'e', 'E', 0x00},
{'s', 'S', 0x00},
{'z', 'Z', 0x00},
{'4', '$', 0x00},
{'r', 'R', 0x00},
{'d', 'D', 0x00},
{'x', 'X', 0x00},
{'5', '%', 0x00},
{'t', 'T', 0x00},
{'f', 'F', 0x00},
{'c', 'C', 0x00},
{'6', '^', 0x00},
{'y', 'Y', 0x00},
{'g', 'G', Key::GPS_TOGGLE},
{'v', 'V', 0x00},
{'7', '&', 0x00},
{'u', 'U', 0x00},
{'h', 'H', 0x00},
{'b', 'B', Key::BT_TOGGLE},
{'8', '*', 0x00},
{'i', 'I', 0x00},
{'j', 'J', 0x00},
{'n', 'N', 0x00},
{'9', '(', 0x00},
{'o', 'O', 0x00},
{'k', 'K', 0x00},
{'m', 'M', Key::MUTE_TOGGLE},
{'0', ')', 0x00},
{'p', 'P', Key::SEND_PING},
{'l', 'L', 0x00},
{',', '<', Key::LEFT},
{'_', '-', 0x00},
{'[', '{', 0x00},
{';', ':', Key::UP},
{'.', '>', Key::DOWN},
{'=', '+', 0x00},
{']', '}', 0x00},
{'\'', '"', 0x00},
{'/', '?', Key::RIGHT},
{Key::BSP, 0x00, 0x00},
{'\\', '|', 0x00},
{Key::SELECT, 0x00, 0x00},
{' ', ' ', ' '}};
CardputerKeyboard::CardputerKeyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
last_tap(0L), char_idx(0), tap_interval(0)
{
reset();
}
void CardputerKeyboard::reset(void)
{
TCA8418KeyboardBase::reset();
}
// handle multi-key presses (shift and alt)
void CardputerKeyboard::trigger()
{
uint8_t count = keyCount();
if (count == 0)
return;
for (uint8_t i = 0; i < count; ++i) {
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
uint8_t key = k & 0x7F;
if (k & 0x80) {
pressed(key);
} else {
released();
state = Idle;
}
}
}
void CardputerKeyboard::pressed(uint8_t key)
{
if (state == Init || state == Busy) {
return;
}
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
modifierFlag = 0;
}
uint8_t next_key = 0;
int row = (key - 1) / 10;
int col = (key - 1) % 10;
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
return; // Invalid key
}
next_key = row * _TCA8418_COLS + col;
state = Held;
uint32_t now = millis();
tap_interval = now - last_tap;
updateModifierFlag(next_key);
if (isModifierKey(next_key)) {
last_modifier_time = now;
}
if (tap_interval < 0) {
last_tap = 0;
state = Busy;
return;
}
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
char_idx = 0;
} else {
char_idx += 1;
}
last_key = next_key;
last_tap = now;
}
void CardputerKeyboard::released()
{
if (state != Held) {
return;
}
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
last_key = -1;
state = Idle;
return;
}
uint32_t now = millis();
last_tap = now;
if (inputBroker->menuMode && modifierFlag == 0) {
if (last_key == 0 || last_key == 43 || last_key == 46 || last_key == 47 ||
last_key == 51) { // esc, left, up, down, right key
modifierFlag = modifierFn;
}
}
queueEvent(CardputerTapMap[last_key][modifierFlag % CardputerTapMod[last_key]]);
if (isModifierKey(last_key) == false)
modifierFlag = 0;
}
void CardputerKeyboard::updateModifierFlag(uint8_t key)
{
if (key == modifierShiftKey) {
modifierFlag ^= modifierShift;
} else if (key == modifierFnKey) {
modifierFlag ^= modifierFn;
}
}
bool CardputerKeyboard::isModifierKey(uint8_t key)
{
return (key == modifierShiftKey || key == modifierFnKey);
}
#endif
-26
View File
@@ -1,26 +0,0 @@
#include "TCA8418KeyboardBase.h"
class CardputerKeyboard : public TCA8418KeyboardBase
{
public:
CardputerKeyboard();
void reset(void);
void trigger(void) override;
virtual ~CardputerKeyboard() {}
protected:
void pressed(uint8_t key) override;
void released(void) override;
void updateModifierFlag(uint8_t key);
bool isModifierKey(uint8_t key);
private:
uint8_t modifierFlag;
uint32_t last_modifier_time;
int8_t last_key;
int8_t next_key;
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
};
+1 -16
View File
@@ -100,28 +100,13 @@ void InputBroker::processInputEventQueue()
int InputBroker::handleInputEvent(const InputEvent *event)
{
#if HAS_SCREEN
bool screenWasOff = false;
if (screen) {
screenWasOff = !screen->isScreenOn();
}
#endif
powerFSM.trigger(EVENT_INPUT);
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule &&
moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) {
externalNotificationModule->stopNow();
// If this turns off a notification, don't further process the event
return 0;
}
#if HAS_SCREEN
if (screen && screenWasOff) {
// If the screen was off, it is in the process of turning on, and we just drop the event
return 0;
}
#endif
this->notifyObservers(event);
return 0;
}
-1
View File
@@ -70,7 +70,6 @@ class InputBroker : public Observable<const InputEvent *>
public:
InputBroker();
bool menuMode = true;
void registerSource(Observable<const InputEvent *> *source);
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
+2 -2
View File
@@ -177,7 +177,7 @@ void MPR121Keyboard::reset()
delay(20);
writeRegister(_MPR121_REG_CONFIG2, 0x21);
delay(20);
// Enter run mode by setting partial filter calibration tracking, disable proximity detection, enable 12 channels
// Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels
writeRegister(_MPR121_REG_ELECTRODE_CONFIG,
ECR_CALIBRATION_TRACK_FROM_FULL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH);
delay(100);
@@ -430,4 +430,4 @@ void MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value)
if (writeCallback) {
writeCallback(m_addr, data[0], &(data[1]), 1);
}
}
}
+2 -2
View File
@@ -59,7 +59,7 @@ int32_t SeesawRotary::runOnce()
wasPressed = currentlyPressed;
int32_t new_position = ss.getEncoderPosition();
// did we move around?
// did we move arounde?
if (encoder_position != new_position) {
if (encoder_position == 0 && new_position != 1) {
e.inputEvent = INPUT_BROKER_ALT_PRESS;
@@ -80,4 +80,4 @@ int32_t SeesawRotary::runOnce()
return 50;
}
#endif
#endif
+1 -1
View File
@@ -88,7 +88,7 @@ void TCA8418Keyboard::pressed(uint8_t key)
// Check if the key is the same as the last one or if the time interval has passed
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
char_idx = 0; // Reset char index if new key or long press
should_backspace = false; // don't backspace on new key
should_backspace = false; // dont backspace on new key
} else {
char_idx += 1; // Cycle through characters if same key pressed
should_backspace = true; // allow backspace on same key
+2 -2
View File
@@ -43,7 +43,7 @@ int32_t TouchScreenBase::runOnce()
// process touch events
int16_t x, y;
bool touched = getTouch(x, y);
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turning off the screen
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turing off the screen
touched = false;
if (touched) {
this->setInterval(20);
@@ -123,7 +123,7 @@ int32_t TouchScreenBase::runOnce()
}
}
#else
// fire TAP event when no 2nd tap occurred within time
// fire TAP event when no 2nd tap occured within time
if (_tapped) {
_tapped = false;
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
+1 -5
View File
@@ -7,8 +7,6 @@
#include "TDeckProKeyboard.h"
#elif defined(T_LORA_PAGER)
#include "TLoraPagerKeyboard.h"
#elif defined(M5STACK_CARDPUTER_ADV)
#include "CardputerKeyboard.h"
#elif defined(HACKADAY_COMMUNICATOR)
#include "HackadayCommunicatorKeyboard.h"
#else
@@ -24,8 +22,6 @@ KbI2cBase::KbI2cBase(const char *name)
TCAKeyboard(*(new TDeckProKeyboard()))
#elif defined(T_LORA_PAGER)
TCAKeyboard(*(new TLoraPagerKeyboard()))
#elif defined(M5STACK_CARDPUTER_ADV)
TCAKeyboard(*(new CardputerKeyboard()))
#elif defined(HACKADAY_COMMUNICATOR)
TCAKeyboard(*(new HackadayCommunicatorKeyboard()))
#else
@@ -491,7 +487,7 @@ int32_t KbI2cBase::runOnce()
e.kbchar = 0;
break;
case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker))
// toggle modifiers button.
// toggle moddifiers button.
is_sym = !is_sym;
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the
+3 -33
View File
@@ -9,7 +9,6 @@
#include "PowerMon.h"
#include "RadioLibInterface.h"
#include "ReliableRouter.h"
#include "TransmitHistory.h"
#include "airtime.h"
#include "buzz.h"
#include "power/PowerHAL.h"
@@ -147,15 +146,6 @@ extern void tftSetup(void);
UdpMulticastHandler *udpHandler = nullptr;
#endif
#if HAS_BLE_MESH
BLEMeshHandler *bleMeshHandler = nullptr;
#if defined(ARCH_NRF52)
#include "platform/nrf52/NRF52BLEMesh.h"
#elif defined(ARCH_ESP32)
#include "platform/esp32/ESP32BLEMesh.h"
#endif
#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
@@ -256,6 +246,7 @@ uint32_t timeLastPowered = 0;
static OSThread *powerFSMthread;
OSThread *ambientLightingThread;
RadioInterface *rIf = NULL;
RadioLibHal *RadioLibHAL = NULL;
/**
@@ -398,8 +389,8 @@ void setup()
#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
#ifndef SENSECAP_INDICATOR
// use PSRAM for malloc calls > 2048 bytes
heap_caps_malloc_extmem_enable(2048);
// use PSRAM for malloc calls > 256 bytes
heap_caps_malloc_extmem_enable(256);
#endif
#endif
@@ -713,9 +704,6 @@ void setup()
// We do this as early as possible because this loads preferences from flash
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
// Initialize transmit history to persist broadcast throttle timers across reboots
TransmitHistory::getInstance()->loadFromDisk();
#if HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
tftSetup();
@@ -893,17 +881,6 @@ void setup()
udpHandler->start();
}
#endif
#endif
#if HAS_BLE_MESH
LOG_DEBUG("Start BLE mesh handler");
#if defined(ARCH_NRF52)
bleMeshHandler = new NRF52BLEMesh();
#elif defined(ARCH_ESP32)
bleMeshHandler = new ESP32BLEMesh();
#endif
if (bleMeshHandler)
bleMeshHandler->start();
#endif
service = new MeshService();
service->init();
@@ -1148,13 +1125,6 @@ void loop()
lastRadioMissedIrqPoll = millis();
RadioLibInterface::instance->pollMissedIrqs();
}
// Periodic AGC reset — warm sleep + recalibrate to prevent stuck AGC gain
static uint32_t lastAgcReset;
if (!Throttle::isWithinTimespanMs(lastAgcReset, AGC_RESET_INTERVAL_MS)) {
lastAgcReset = millis();
RadioLibInterface::instance->resetAGC();
}
}
#ifdef DEBUG_STACK
-5
View File
@@ -62,11 +62,6 @@ extern AudioThread *audioThread;
extern UdpMulticastHandler *udpHandler;
#endif
#if HAS_BLE_MESH
#include "mesh/BLEMeshHandler.h"
extern BLEMeshHandler *bleMeshHandler;
#endif
// Global Screen singleton.
extern graphics::Screen *screen;
-63
View File
@@ -1,63 +0,0 @@
#pragma once
#if HAS_BLE_MESH
#include "MeshTypes.h"
#include "Router.h"
#include "mesh-pb-constants.h"
// Meshtastic BLE mesh manufacturer data identifier
// Using 0xFFFF (reserved for testing) until a real BLE SIG company ID is assigned
#define BLE_MESH_COMPANY_ID 0xFFFF
#define BLE_MESH_PROTOCOL_VERSION 1
// TODO: Add TRANSPORT_BLE_MESH = 8 to mesh.proto TransportMechanism enum and regenerate.
// Until then, define it here so the code compiles.
#ifndef meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH
#define meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH ((meshtastic_MeshPacket_TransportMechanism)8)
#endif
class BLEMeshHandler
{
public:
BLEMeshHandler() : isRunning(false) {}
virtual ~BLEMeshHandler() {}
virtual void start() = 0;
virtual void stop() = 0;
virtual void onBluetoothReady() {}
// Called from Router::send() to broadcast packet over BLE mesh
virtual bool onSend(const meshtastic_MeshPacket *mp) = 0;
protected:
bool isRunning;
// Decode received BLE mesh data and enqueue into router
void deliverToRouter(const uint8_t *data, size_t len)
{
meshtastic_MeshPacket mp = meshtastic_MeshPacket_init_zero;
bool decoded = pb_decode_from_bytes(data, len, &meshtastic_MeshPacket_msg, &mp);
if (!decoded || mp.which_payload_variant != meshtastic_MeshPacket_encrypted_tag)
return;
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH;
mp.pki_encrypted = false;
mp.public_key.size = 0;
memset(mp.public_key.bytes, 0, sizeof(mp.public_key.bytes));
mp.rx_snr = 0;
mp.rx_rssi = 0;
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp);
if (p && router)
router->enqueueReceivedMessage(p.release());
}
// Encode a mesh packet for BLE transmission, returns encoded length
size_t encodeForBLE(const meshtastic_MeshPacket *mp, uint8_t *buf, size_t bufLen)
{
return pb_encode_to_bytes(buf, bufLen, &meshtastic_MeshPacket_msg, mp);
}
};
#endif // HAS_BLE_MESH
+7 -3
View File
@@ -22,8 +22,6 @@ const char *Channels::serialChannel = "serial";
const char *Channels::mqttChannel = "mqtt";
#endif
meshtastic_Channel dummyChannel = {.index = -1};
uint8_t xorHash(const uint8_t *p, size_t len)
{
uint8_t code = 0;
@@ -311,7 +309,13 @@ meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex)
return *ch;
} else {
LOG_ERROR("Invalid channel index %d > %d, malformed packet received?", chIndex, channelFile.channels_count);
return dummyChannel;
static meshtastic_Channel *ch = (meshtastic_Channel *)malloc(sizeof(meshtastic_Channel));
memset(ch, 0, sizeof(meshtastic_Channel));
// ch.index -1 means we don't know the channel locally and need to look it up by settings.name
// not sure this is handled right everywhere
ch->index = -1;
return *ch;
}
}
+2 -4
View File
@@ -38,13 +38,11 @@ uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultVa
uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes)
{
// If we are a router, we don't scale the value. It's already significantly higher.
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER)
return getConfiguredOrDefaultMs(configured, defaultValue);
// Additionally if we're a tracker or sensor, we want priority to send position and telemetry
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_TRACKER,
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER))
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_TRACKER))
return getConfiguredOrDefaultMs(configured, defaultValue);
return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes);
+15 -35
View File
@@ -1,8 +1,5 @@
#pragma once
#include <MeshRadio.h>
#include <NodeDB.h>
#include <RadioInterface.h>
#include <cmath>
#include <cstdint>
#include <meshUtils.h>
#define ONE_DAY 24 * 60 * 60
@@ -45,10 +42,7 @@
#define default_mqtt_tls_enabled false
#define IF_ROUTER(routerVal, normalVal) \
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || \
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) \
? (routerVal) \
: (normalVal))
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal))
class Default
{
@@ -69,39 +63,25 @@ class Default
if (numOnlineNodes <= 40) {
return 1.0;
} else {
// Resolve SF and BW from preset or manual config
// When use_preset is true, config.lora.spread_factor and bandwidth may be 0
// because applyModemConfig() sets them on RadioInterface, not on config.lora
float bwKHz;
uint8_t sf;
uint8_t cr;
if (config.lora.use_preset) {
modemPresetToParams(config.lora.modem_preset, false, bwKHz, sf, cr);
} else {
sf = config.lora.spread_factor;
bwKHz = bwCodeToKHz(config.lora.bandwidth);
}
// Guard against invalid values
sf = clampSpreadFactor(sf);
bwKHz = clampBandwidthKHz(bwKHz);
// throttlingFactor = 2^SF / (BW_in_kHz * scaling_divisor)
// With scaling_divisor=100:
// In SF11 and BW=250khz (longfast), this gives 0.08192 rather than the original 0.075
// In SF10 and BW=250khz (mediumslow), this gives 0.04096 rather than the original 0.04
// In SF9 and BW=250khz (mediumfast), this gives 0.02048 rather than the original 0.02
// In SF7 and BW=250khz (shortfast), this gives 0.00512 rather than the original 0.01
float throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 100.0f);
float throttlingFactor = 0.075;
if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW)
throttlingFactor = 0.04;
else if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST)
throttlingFactor = 0.02;
else if (config.lora.use_preset &&
IS_ONE_OF(config.lora.modem_preset, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW))
throttlingFactor = 0.01;
#if USERPREFS_EVENT_MODE
// If we are in event mode, scale down the throttling factor by 4
throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 25.0f);
// If we are in event mode, scale down the throttling factor
throttlingFactor = 0.04;
#endif
// Scaling up traffic based on number of nodes over 40
int nodesOverForty = (numOnlineNodes - 40);
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by throttle factor
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
}
}
};
};
-32
View File
@@ -299,38 +299,6 @@ template <typename T> bool LR11x0Interface<T>::isActivelyReceiving()
RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED);
}
#ifdef LR11X0_AGC_RESET
template <typename T> void LR11x0Interface<T>::resetAGC()
{
// Safety: don't reset mid-packet
if (sendingPacket != NULL || (isReceiving && isActivelyReceiving()))
return;
LOG_DEBUG("LR11x0 AGC reset: warm sleep + Calibrate(0x3F)");
// 1. Warm sleep — powers down the analog frontend, resetting AGC state
lora.sleep(true, 0);
// 2. Wake to RC standby for stable calibration
lora.standby(RADIOLIB_LR11X0_STANDBY_RC, true);
// 3. Calibrate all blocks (PLL, ADC, image, RC oscillators)
// calibrate() is protected on LR11x0, so use raw SPI (same as internal implementation)
uint8_t calData = RADIOLIB_LR11X0_CALIBRATE_ALL;
module.SPIwriteStream(RADIOLIB_LR11X0_CMD_CALIBRATE, &calData, 1, true, true);
// 4. Re-calibrate image rejection for actual operating frequency
// Calibrate(0x3F) defaults to 902-928 MHz which is wrong for other regions.
lora.calibrateImageRejection(getFreq() - 4.0f, getFreq() + 4.0f);
// 5. Re-apply RX boosted gain mode
lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
// 6. Resume receiving
startReceive();
}
#endif
template <typename T> bool LR11x0Interface<T>::sleep()
{
// \todo Display actual typename of the adapter, not just `LR11x0`
-4
View File
@@ -27,10 +27,6 @@ template <class T> class LR11x0Interface : public RadioLibInterface
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
#ifdef LR11X0_AGC_RESET
void resetAGC() override;
#endif
protected:
/**
* Specific module instance
-228
View File
@@ -1,228 +0,0 @@
#if HAS_LORA_FEM
#include "LoRaFEMInterface.h"
#if defined(ARCH_ESP32)
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#endif
LoRaFEMInterface loraFEMInterface;
void LoRaFEMInterface::init(void)
{
setLnaCanControl(false); // Default is uncontrollable
#ifdef HELTEC_V4
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
delay(1);
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD);
pinMode(LORA_KCT8103L_PA_CSD, INPUT); // detect which FEM is used
delay(1);
if (digitalRead(LORA_KCT8103L_PA_CSD) == HIGH) {
// FEM is KCT8103L
fem_type = KCT8103L_PA;
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX);
pinMode(LORA_KCT8103L_PA_CSD, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
pinMode(LORA_KCT8103L_PA_CTX, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
setLnaCanControl(true);
} else if (digitalRead(LORA_KCT8103L_PA_CSD) == LOW) {
// FEM is GC1109
fem_type = GC1109_PA;
// LORA_GC1109_PA_EN and LORA_KCT8103L_PA_CSD are the same pin and do not need to be repeatedly turned off and held.
// rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
pinMode(LORA_GC1109_PA_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else {
fem_type = OTHER_FEM_TYPES;
}
#elif defined(USE_GC1109_PA)
fem_type = GC1109_PA;
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
#if defined(ARCH_ESP32)
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_TX_EN);
#endif
delay(1);
pinMode(LORA_GC1109_PA_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#elif defined(USE_KCT8103L_PA)
fem_type = KCT8103L_PA;
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
#if defined(ARCH_ESP32)
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD);
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX);
#endif
delay(1);
pinMode(LORA_KCT8103L_PA_CSD, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
pinMode(LORA_KCT8103L_PA_CTX, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
setLnaCanControl(true);
#endif
}
void LoRaFEMInterface::setSleepModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
/*
* Do not switch the power on and off frequently.
* After turning off LORA_GC1109_PA_EN, the power consumption has dropped to the uA level.
*/
digitalWrite(LORA_GC1109_PA_EN, LOW);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else if (fem_type == KCT8103L_PA) {
// shutdown the PA
digitalWrite(LORA_KCT8103L_PA_CSD, LOW);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, LOW);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#elif defined(USE_KCT8103L_PA)
// shutdown the PA
digitalWrite(LORA_KCT8103L_PA_CSD, LOW);
#endif
}
void LoRaFEMInterface::setTxModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
#elif defined(USE_KCT8103L_PA)
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
#endif
}
void LoRaFEMInterface::setRxModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#elif defined(USE_KCT8103L_PA)
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
#endif
}
void LoRaFEMInterface::setRxModeEnableWhenMCUSleep(void)
{
#ifdef HELTEC_V4
// Keep GC1109 FEM powered during deep sleep so LNA remains active for RX wake.
// Set PA_POWER and PA_EN HIGH (overrides SX126xInterface::sleep() shutdown),
// then latch with RTC hold so the state survives deep sleep.
digitalWrite(LORA_PA_POWER, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_PA_POWER, HIGH);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
#if defined(ARCH_ESP32)
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
#endif
#elif defined(USE_KCT8103L_PA)
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX);
#endif
}
void LoRaFEMInterface::setLNAEnable(bool enabled)
{
lna_enabled = enabled;
}
int8_t LoRaFEMInterface::powerConversion(int8_t loraOutputPower)
{
#ifdef HELTEC_V4
const uint16_t gc1109_tx_gain[] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7};
const uint16_t kct8103l_tx_gain[] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7};
const uint16_t *tx_gain;
uint16_t tx_gain_num;
if (fem_type == GC1109_PA) {
tx_gain = gc1109_tx_gain;
tx_gain_num = sizeof(gc1109_tx_gain) / sizeof(gc1109_tx_gain[0]);
} else if (fem_type == KCT8103L_PA) {
tx_gain = kct8103l_tx_gain;
tx_gain_num = sizeof(kct8103l_tx_gain) / sizeof(kct8103l_tx_gain[0]);
} else {
return loraOutputPower;
}
#else
#ifdef ARCH_PORTDUINO
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
uint16_t tx_gain_num = portduino_config.num_pa_points;
#else
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
uint16_t tx_gain_num = NUM_PA_POINTS;
#endif
#endif
for (int radio_dbm = 0; radio_dbm < tx_gain_num; radio_dbm++) {
if (((radio_dbm + tx_gain[radio_dbm]) > loraOutputPower) ||
((radio_dbm == (tx_gain_num - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= loraOutputPower))) {
// we've exceeded the power limit, or hit the max we can do
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", loraOutputPower, tx_gain[radio_dbm]);
loraOutputPower -= tx_gain[radio_dbm];
break;
}
}
return loraOutputPower;
}
#endif
-30
View File
@@ -1,30 +0,0 @@
#pragma once
#if HAS_LORA_FEM
#include "configuration.h"
#include <stdint.h>
typedef enum { GC1109_PA, KCT8103L_PA, OTHER_FEM_TYPES } LoRaFEMType;
class LoRaFEMInterface
{
public:
LoRaFEMInterface() {}
virtual ~LoRaFEMInterface() {}
void init(void);
void setSleepModeEnable(void);
void setTxModeEnable(void);
void setRxModeEnable(void);
void setRxModeEnableWhenMCUSleep(void);
void setLNAEnable(bool enabled);
int8_t powerConversion(int8_t loraOutputPower);
bool isLnaCanControl(void) { return lna_can_control; }
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
private:
LoRaFEMType fem_type;
bool lna_enabled = false;
bool lna_can_control = false;
};
extern LoRaFEMInterface loraFEMInterface;
#endif
-12
View File
@@ -106,18 +106,6 @@ class MeshModule
/* We allow modules to ignore a request without sending an error if they have a specific reason for it. */
bool ignoreRequest = false;
/**
* Check if the current request is a multi-hop broadcast. Modules should call this in allocReply()
* and return NULL to prevent reply storms from broadcast requests that have already been relayed.
*/
bool isMultiHopBroadcastRequest()
{
if (currentRequest && isBroadcast(currentRequest->to) && currentRequest->hop_limit < currentRequest->hop_start) {
return true;
}
return false;
}
/** If a bound channel name is set, we will only accept received packets that come in on that channel.
* A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface
* are allowed on any channel (this lets the local user do anything).

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