diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 30af24bd2..f546d4cfd 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,7 +1,7 @@
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
-FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12
+FROM mcr.microsoft.com/devcontainers/cpp:2-debian-13
USER root
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index bf1c50982..e3f076ce0 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -8,7 +8,7 @@
"features": {
"ghcr.io/devcontainers/features/python:1": {
"installTools": true,
- "version": "latest"
+ "version": "3.14"
}
},
"customizations": {
diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml
index f611908ee..a71ddfc4d 100644
--- a/.github/actions/build-variant/action.yml
+++ b/.github/actions/build-variant/action.yml
@@ -100,7 +100,7 @@ runs:
id: version
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml
index f6c1fd80c..80f5c6855 100644
--- a/.github/actions/setup-base/action.yml
+++ b/.github/actions/setup-base/action.yml
@@ -5,7 +5,7 @@ runs:
using: composite
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
diff --git a/.github/workflows/build_debian_src.yml b/.github/workflows/build_debian_src.yml
index 7f3f8b672..d7d26f0e8 100644
--- a/.github/workflows/build_debian_src.yml
+++ b/.github/workflows/build_debian_src.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
@@ -64,7 +64,7 @@ jobs:
PKG_VERSION: ${{ steps.version.outputs.deb }}
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
overwrite: true
diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml
index 2ef67405a..9ac84c23e 100644
--- a/.github/workflows/build_firmware.yml
+++ b/.github/workflows/build_firmware.yml
@@ -19,8 +19,10 @@ jobs:
pio-build:
name: build-${{ inputs.platform }}
runs-on: ubuntu-24.04
+ outputs:
+ artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
@@ -54,7 +56,8 @@ jobs:
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
+ id: upload
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
overwrite: true
diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
deleted file mode 100644
index f5352b3c4..000000000
--- a/.github/workflows/build_one_arch.yml
+++ /dev/null
@@ -1,500 +0,0 @@
-name: Build One Arch
-
-on:
- workflow_dispatch:
- inputs:
- arch:
- type: choice
- options:
- - esp32
- - esp32s3
- - esp32c3
- - esp32c6
- - nrf52840
- - rp2040
- - rp2350
- - stm32
- - native
-
-jobs:
- setup:
- strategy:
- fail-fast: false
- runs-on: ubuntu-24.04
- steps:
- - uses: actions/checkout@v5
- - uses: actions/setup-python@v6
- with:
- python-version: 3.x
- cache: pip
- - run: pip install -U platformio
- - name: Generate matrix
- id: jsonStep
- run: |
- if [[ "$GITHUB_HEAD_REF" == "" ]]; then
- TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra)
- else
- TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
- fi
- echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
- echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
- outputs:
- esp32: ${{ steps.jsonStep.outputs.esp32 }}
- esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
- esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
- esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
- nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
- rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
- rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
- stm32: ${{ steps.jsonStep.outputs.stm32 }}
- check: ${{ steps.jsonStep.outputs.check }}
-
- version:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v5
- - name: Get release version string
- run: |
- echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
- echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
- id: version
- env:
- BUILD_LOCATION: local
- outputs:
- long: ${{ steps.version.outputs.long }}
- deb: ${{ steps.version.outputs.deb }}
-
- build-esp32:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32
-
- build-esp32s3:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32s3
-
- build-esp32c3:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32c3
-
- build-esp32c6:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32c6
-
- build-nrf52840:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: nrf52840
-
- build-rp2040:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: rp2040
-
- build-rp2350:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: rp2350
-
- build-stm32:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: stm32
-
- build-debian-src:
- if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
- uses: ./.github/workflows/build_debian_src.yml
- with:
- series: UNRELEASED
- build_location: local
- secrets: inherit
-
- package-pio-deps-native-tft:
- if: ${{ inputs.arch == 'native' }}
- uses: ./.github/workflows/package_pio_deps.yml
- with:
- pio_env: native-tft
- secrets: inherit
-
- test-native:
- if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
- uses: ./.github/workflows/test_native.yml
-
- docker-deb-amd64:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
-
- docker-deb-amd64-tft:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
- pio_env: native-tft
-
- docker-alp-amd64:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: alpine
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
-
- docker-alp-amd64-tft:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: alpine
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
- pio_env: native-tft
-
- docker-deb-arm64:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/arm64
- runs-on: ubuntu-24.04-arm
- push: false
-
- docker-deb-armv7:
- if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/arm/v7
- runs-on: ubuntu-24.04-arm
- push: false
-
- gather-artifacts:
- permissions:
- contents: write
- pull-requests: write
- strategy:
- fail-fast: false
- matrix:
- arch:
- - esp32
- - esp32s3
- - esp32c3
- - esp32c6
- - nrf52840
- - rp2040
- - rp2350
- - stm32
- runs-on: ubuntu-latest
- needs:
- [
- version,
- build-esp32,
- build-esp32s3,
- build-esp32c3,
- build-esp32c6,
- build-nrf52840,
- build-rp2040,
- build-rp2350,
- build-stm32,
- ]
- steps:
- - name: Checkout code
- uses: actions/checkout@v5
- with:
- ref: ${{github.event.pull_request.head.ref}}
- repository: ${{github.event.pull_request.head.repo.full_name}}
-
- - uses: actions/download-artifact@v5
- with:
- path: ./
- pattern: firmware-${{inputs.arch}}-*
- merge-multiple: true
-
- - name: Display structure of downloaded files
- run: ls -R
-
- - name: Move files up
- run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
-
- - name: Repackage in single firmware zip
- uses: actions/upload-artifact@v4
- with:
- name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
- overwrite: true
- path: |
- ./firmware-*.bin
- ./firmware-*.uf2
- ./firmware-*.hex
- ./firmware-*-ota.zip
- ./device-*.sh
- ./device-*.bat
- ./littlefs-*.bin
- ./bleota*bin
- ./Meshtastic_nRF52_factory_erase*.uf2
- retention-days: 30
-
- - uses: actions/download-artifact@v5
- with:
- name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
- merge-multiple: true
- path: ./output
-
- # For diagnostics
- - name: Show artifacts
- run: ls -lR
-
- - name: Device scripts permissions
- run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
-
- - name: Zip firmware
- run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
-
- - name: Repackage in single elfs zip
- uses: actions/upload-artifact@v4
- with:
- name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
- overwrite: true
- path: ./*.elf
- retention-days: 30
-
- - uses: scruplelesswizard/comment-artifact@main
- if: ${{ github.event_name == 'pull_request' }}
- with:
- name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
- description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
- github-token: ${{ secrets.GITHUB_TOKEN }}
-
- release-artifacts:
- runs-on: ubuntu-latest
- if: ${{ github.event_name == 'workflow_dispatch' }}
- outputs:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- needs:
- - version
- - gather-artifacts
- - build-debian-src
- - package-pio-deps-native-tft
- steps:
- - name: Checkout
- uses: actions/checkout@v5
-
- - name: Setup Python
- uses: actions/setup-python@v6
- with:
- python-version: 3.x
-
- - name: Create release
- uses: softprops/action-gh-release@v2
- id: create_release
- with:
- draft: true
- prerelease: true
- name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
- tag_name: v${{ needs.version.outputs.long }}
- body: |
- Autogenerated by github action, developer should edit as required before publishing...
-
- - name: Download source deb
- uses: actions/download-artifact@v5
- 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@v5
- with:
- pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
- merge-multiple: true
- path: ./output/pio-deps-native-tft
-
- - name: Zip Linux sources
- working-directory: output
- run: |
- zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
- zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
-
- # For diagnostics
- - name: Display structure of downloaded files
- run: ls -lR
-
- - name: Add Linux sources to GtiHub Release
- # Only run when targeting master branch with workflow_dispatch
- if: ${{ github.ref_name == 'master' }}
- run: |
- gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
- gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- release-firmware:
- strategy:
- fail-fast: false
- matrix:
- arch:
- - esp32
- - esp32s3
- - esp32c3
- - esp32c6
- - nrf52840
- - rp2040
- - rp2350
- - stm32
- runs-on: ubuntu-latest
- if: ${{ github.event_name == 'workflow_dispatch' }}
- needs: [release-artifacts, version]
- steps:
- - name: Checkout
- uses: actions/checkout@v5
-
- - name: Setup Python
- uses: actions/setup-python@v6
- with:
- python-version: 3.x
-
- - uses: actions/download-artifact@v5
- with:
- pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
- merge-multiple: true
- path: ./output
-
- - name: Display structure of downloaded files
- run: ls -lR
-
- - name: Device scripts permissions
- run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
-
- - name: Zip firmware
- run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
-
- - uses: actions/download-artifact@v5
- with:
- name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
- merge-multiple: true
- path: ./elfs
-
- - name: Zip debug elfs
- run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
-
- # For diagnostics
- - name: Display structure of downloaded files
- run: ls -lR
-
- - name: Add bins and debug elfs to GitHub Release
- # Only run when targeting master branch with workflow_dispatch
- if: ${{ github.ref_name == 'master' }}
- run: |
- gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
- gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- publish-firmware:
- runs-on: ubuntu-24.04
- if: ${{ github.event_name == 'workflow_dispatch' }}
- needs: [release-firmware, version]
- env:
- targets: |-
- esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
- steps:
- - name: Checkout
- uses: actions/checkout@v5
-
- - name: Setup Python
- uses: actions/setup-python@v6
- with:
- python-version: 3.x
-
- - uses: actions/download-artifact@v5
- with:
- pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
- merge-multiple: true
- path: ./publish
-
- - name: Publish firmware to meshtastic.github.io
- uses: peaceiris/actions-gh-pages@v4
- env:
- # On event/* branches, use the event name as the destination prefix
- DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
- with:
- deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
- external_repository: meshtastic/meshtastic.github.io
- publish_branch: master
- publish_dir: ./publish
- destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
- keep_files: true
- user_name: github-actions[bot]
- user_email: github-actions[bot]@users.noreply.github.com
- commit_message: ${{ needs.version.outputs.long }}
- enable_jekyll: true
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
index 3c83ce960..e4b332a06 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.yml
@@ -3,6 +3,7 @@ name: Build One Target
on:
workflow_dispatch:
inputs:
+ # trunk-ignore(checkov/CKV_GHA_7)
arch:
type: choice
options:
@@ -14,16 +15,17 @@ on:
- rp2040
- rp2350
- stm32
- - native
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
-
# find-target:
# type: boolean
# default: true
# description: 'Find the available targets'
+
+permissions: read-all
+
jobs:
find-targets:
if: ${{ inputs.target == '' }}
@@ -39,10 +41,9 @@ jobs:
- rp2040
- rp2350
- stm32
-
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -51,19 +52,19 @@ jobs:
- name: Generate matrix
id: jsonStep
run: |
- TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra)
+ TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "Targets:" >> $GITHUB_STEP_SUMMARY
- echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
+ echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY
version:
if: ${{ inputs.target != '' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -75,108 +76,29 @@ jobs:
long: ${{ steps.version.outputs.long }}
deb: ${{ steps.version.outputs.deb }}
- build-arch:
+ build:
if: ${{ inputs.target != '' && inputs.arch != 'native' }}
needs: [version]
- strategy:
- fail-fast: false
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ inputs.target }}
platform: ${{ inputs.arch }}
- build-debian-src:
- if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }}
- uses: ./.github/workflows/build_debian_src.yml
- with:
- series: UNRELEASED
- build_location: local
- secrets: inherit
-
- package-pio-deps-native-tft:
- if: ${{ inputs.arch == 'native' }}
- uses: ./.github/workflows/package_pio_deps.yml
- with:
- pio_env: native-tft
- secrets: inherit
-
- test-native:
- if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
- uses: ./.github/workflows/test_native.yml
-
- docker-deb-amd64:
- if: ${{ inputs.target != '' && inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
-
- docker-deb-amd64-tft:
- if: ${{ inputs.target != '' && inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
- pio_env: native-tft
-
- docker-alp-amd64:
- if: ${{ inputs.target != '' && inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: alpine
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
-
- docker-alp-amd64-tft:
- if: ${{ inputs.target != '' && inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: alpine
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
- pio_env: native-tft
-
- docker-deb-arm64:
- if: ${{ inputs.target != '' && inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/arm64
- runs-on: ubuntu-24.04-arm
- push: false
-
- docker-deb-armv7:
- if: ${{ inputs.target != '' && inputs.arch == 'native' }}
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/arm/v7
- runs-on: ubuntu-24.04-arm
- push: false
-
gather-artifacts:
permissions:
contents: write
pull-requests: write
- strategy:
- fail-fast: false
runs-on: ubuntu-latest
- needs: [version, build-arch]
+ needs: [version, build]
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
path: ./
pattern: firmware-*-*
@@ -189,7 +111,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -205,7 +127,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
pattern: firmware-*-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -224,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@v4
+ uses: actions/upload-artifact@v5
with:
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
overwrite: true
@@ -237,159 +159,3 @@ jobs:
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}
-
- release-artifacts:
- runs-on: ubuntu-latest
- if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
- outputs:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- needs:
- - version
- - gather-artifacts
- - build-debian-src
- - package-pio-deps-native-tft
- steps:
- - name: Checkout
- uses: actions/checkout@v5
-
- - name: Setup Python
- uses: actions/setup-python@v6
- with:
- python-version: 3.x
-
- - name: Create release
- uses: softprops/action-gh-release@v2
- id: create_release
- with:
- draft: true
- prerelease: true
- name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
- tag_name: v${{ needs.version.outputs.long }}
- body: |
- Autogenerated by github action, developer should edit as required before publishing...
-
- - name: Download source deb
- uses: actions/download-artifact@v5
- 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@v5
- with:
- pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
- merge-multiple: true
- path: ./output/pio-deps-native-tft
-
- - name: Zip Linux sources
- working-directory: output
- run: |
- zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
- zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
-
- # For diagnostics
- - name: Display structure of downloaded files
- run: ls -lR
-
- - name: Add Linux sources to GtiHub Release
- # Only run when targeting master branch with workflow_dispatch
- if: ${{ github.ref_name == 'master' }}
- run: |
- gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
- gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- release-firmware:
- strategy:
- fail-fast: false
- runs-on: ubuntu-latest
- if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
- needs: [release-artifacts, version]
- steps:
- - name: Checkout
- uses: actions/checkout@v5
-
- - name: Setup Python
- uses: actions/setup-python@v6
- with:
- python-version: 3.x
-
- - uses: actions/download-artifact@v5
- with:
- pattern: firmware-*-${{ needs.version.outputs.long }}
- merge-multiple: true
- path: ./output
-
- - name: Display structure of downloaded files
- run: ls -lR
-
- - name: Device scripts permissions
- run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
-
- - name: Zip firmware
- run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
-
- - uses: actions/download-artifact@v5
- with:
- pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
- merge-multiple: true
- path: ./elfs
-
- - name: Zip debug elfs
- run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs
-
- # For diagnostics
- - name: Display structure of downloaded files
- run: ls -lR
-
- - name: Add bins and debug elfs to GitHub Release
- # Only run when targeting master branch with workflow_dispatch
- if: ${{ github.ref_name == 'master' }}
- run: |
- gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
- gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- publish-firmware:
- runs-on: ubuntu-24.04
- if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }}
- needs: [release-firmware, version]
- env:
- targets: |-
- esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
- steps:
- - name: Checkout
- uses: actions/checkout@v5
-
- - name: Setup Python
- uses: actions/setup-python@v6
- with:
- python-version: 3.x
-
- - uses: actions/download-artifact@v5
- with:
- pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
- merge-multiple: true
- path: ./publish
-
- - name: Publish firmware to meshtastic.github.io
- uses: peaceiris/actions-gh-pages@v4
- env:
- # On event/* branches, use the event name as the destination prefix
- DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
- with:
- deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
- external_repository: meshtastic/meshtastic.github.io
- publish_branch: master
- publish_dir: ./publish
- destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
- keep_files: true
- user_name: github-actions[bot]
- user_email: github-actions[bot]@users.noreply.github.com
- commit_message: ${{ needs.version.outputs.long }}
- enable_jekyll: true
diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml
index 26a9cff18..8d19af894 100644
--- a/.github/workflows/docker_build.yml
+++ b/.github/workflows/docker_build.yml
@@ -47,7 +47,7 @@ jobs:
runs-on: ${{ inputs.runs-on }}
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
diff --git a/.github/workflows/docker_manifest.yml b/.github/workflows/docker_manifest.yml
index 20b9ceee6..396ddb68e 100644
--- a/.github/workflows/docker_manifest.yml
+++ b/.github/workflows/docker_manifest.yml
@@ -83,7 +83,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
diff --git a/.github/workflows/hook_copr.yml b/.github/workflows/hook_copr.yml
index 2204cc02c..eb4ebc57b 100644
--- a/.github/workflows/hook_copr.yml
+++ b/.github/workflows/hook_copr.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{ github.ref }}
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index f61e314a7..38373a2fc 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -27,58 +27,39 @@ on:
jobs:
setup:
- if: github.repository == 'meshtastic/firmware'
strategy:
- fail-fast: false
+ fail-fast: true
matrix:
arch:
- - esp32
- - esp32s3
- - esp32c3
- - esp32c6
- - nrf52840
- - rp2040
- - rp2350
- - stm32
+ - all
- check
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- - name: Uncomment build epoch
- shell: bash
- run: |
- sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- name: Generate matrix
id: jsonStep
run: |
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else
- TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
+ TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
fi
- echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
- echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+ echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
+ echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
+ echo "$TARGETS" >> $GITHUB_STEP_SUMMARY
outputs:
- esp32: ${{ steps.jsonStep.outputs.esp32 }}
- esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
- esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
- esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
- nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
- rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
- rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
- stm32: ${{ steps.jsonStep.outputs.stm32 }}
+ all: ${{ steps.jsonStep.outputs.all }}
check: ${{ steps.jsonStep.outputs.check }}
version:
- if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -94,105 +75,30 @@ jobs:
needs: setup
strategy:
fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.check) }}
+ matrix:
+ check: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Build base
id: base
uses: ./.github/actions/setup-base
- - name: Check ${{ matrix.board }}
- run: bin/check-all.sh ${{ matrix.board }}
+ - name: Check ${{ matrix.check.board }}
+ run: bin/check-all.sh ${{ matrix.check.board }}
- build-esp32:
+ build:
needs: [setup, version]
strategy:
fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+ matrix:
+ build: ${{ fromJson(needs.setup.outputs.all) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32
-
- build-esp32s3:
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32s3
-
- build-esp32c3:
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32c3
-
- build-esp32c6:
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32c6
-
- build-nrf52840:
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: nrf52840
-
- build-rp2040:
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: rp2040
-
- build-rp2350:
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: rp2350
-
- build-stm32:
- needs: [setup, version]
- strategy:
- fail-fast: false
- matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: stm32
+ pio_env: ${{ matrix.build.board }}
+ platform: ${{ matrix.build.platform }}
build-debian-src:
if: github.repository == 'meshtastic/firmware'
@@ -203,7 +109,7 @@ jobs:
secrets: inherit
package-pio-deps-native-tft:
- if: ${{ github.event_name == 'workflow_dispatch' }}
+ if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
@@ -213,60 +119,26 @@ jobs:
if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
uses: ./.github/workflows/test_native.yml
- docker-deb-amd64:
- if: github.repository == 'meshtastic/firmware'
+ docker:
+ strategy:
+ fail-fast: false
+ matrix:
+ distro: [debian, alpine]
+ platform: [linux/amd64, linux/arm64, linux/arm/v7]
+ pio_env: [native, native-tft]
+ exclude:
+ - distro: alpine
+ platform: linux/arm/v7
+ - pio_env: native-tft
+ platform: linux/arm64
+ - pio_env: native-tft
+ platform: linux/arm/v7
uses: ./.github/workflows/docker_build.yml
with:
- distro: debian
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
-
- docker-deb-amd64-tft:
- if: github.repository == 'meshtastic/firmware'
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
- pio_env: native-tft
-
- docker-alp-amd64:
- if: github.repository == 'meshtastic/firmware'
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: alpine
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
-
- docker-alp-amd64-tft:
- if: github.repository == 'meshtastic/firmware'
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: alpine
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
- pio_env: native-tft
-
- docker-deb-arm64:
- if: github.repository == 'meshtastic/firmware'
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/arm64
- runs-on: ubuntu-24.04-arm
- push: false
-
- docker-deb-armv7:
- if: github.repository == 'meshtastic/firmware'
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/arm/v7
- runs-on: ubuntu-24.04-arm
+ distro: ${{ matrix.distro }}
+ platform: ${{ matrix.platform }}
+ runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
+ pio_env: ${{ matrix.pio_env }}
push: false
gather-artifacts:
@@ -288,26 +160,15 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
- needs:
- [
- version,
- build-esp32,
- build-esp32s3,
- build-esp32c3,
- build-esp32c6,
- build-nrf52840,
- build-rp2040,
- build-rp2350,
- build-stm32,
- ]
+ needs: [version, build]
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -320,7 +181,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -336,7 +197,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -355,7 +216,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@v4
+ uses: actions/upload-artifact@v5
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
overwrite: true
@@ -381,7 +242,7 @@ jobs:
- package-pio-deps-native-tft
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
@@ -400,14 +261,14 @@ jobs:
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v6
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@v5
+ uses: actions/download-artifact@v6
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -450,14 +311,14 @@ jobs:
needs: [release-artifacts, version]
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -474,7 +335,7 @@ jobs:
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
merge-multiple: true
@@ -505,14 +366,14 @@ jobs:
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index 7f397ce18..154b230c7 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -7,27 +7,17 @@ on:
# Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
merge_group:
-env:
- FAIL_FAST_PER_ARCH: true
-
jobs:
setup:
strategy:
fail-fast: true
matrix:
arch:
- - esp32
- - esp32s3
- - esp32c3
- - esp32c6
- - nrf52840
- - rp2040
- - rp2350
- - stm32
+ - all
- check
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -39,25 +29,18 @@ jobs:
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else
- TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
+ TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
fi
- echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
- echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+ echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
+ echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
outputs:
- esp32: ${{ steps.jsonStep.outputs.esp32 }}
- esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
- esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
- esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
- nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
- rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
- rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
- stm32: ${{ steps.jsonStep.outputs.stm32 }}
+ all: ${{ steps.jsonStep.outputs.all }}
check: ${{ steps.jsonStep.outputs.check }}
version:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -73,105 +56,29 @@ jobs:
needs: setup
strategy:
fail-fast: true
- matrix: ${{ fromJson(needs.setup.outputs.check) }}
+ matrix:
+ check: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Build base
id: base
uses: ./.github/actions/setup-base
- - name: Check ${{ matrix.board }}
- run: bin/check-all.sh ${{ matrix.board }}
+ - name: Check ${{ matrix.check.board }}
+ run: bin/check-all.sh ${{ matrix.check.board }}
- build-esp32:
+ build:
needs: [setup, version]
strategy:
- fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
- matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+ matrix:
+ build: ${{ fromJson(needs.setup.outputs.all) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32
-
- build-esp32s3:
- needs: [setup, version]
- strategy:
- fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
- matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32s3
-
- build-esp32c3:
- needs: [setup, version]
- strategy:
- fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
- matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32c3
-
- build-esp32c6:
- needs: [setup, version]
- strategy:
- fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
- matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: esp32c6
-
- build-nrf52840:
- needs: [setup, version]
- strategy:
- fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
- matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: nrf52840
-
- build-rp2040:
- needs: [setup, version]
- strategy:
- fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
- matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: rp2040
-
- build-rp2350:
- needs: [setup, version]
- strategy:
- fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
- matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: rp2350
-
- build-stm32:
- needs: [setup, version]
- strategy:
- fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
- matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
- uses: ./.github/workflows/build_firmware.yml
- with:
- version: ${{ needs.version.outputs.long }}
- pio_env: ${{ matrix.board }}
- platform: stm32
+ pio_env: ${{ matrix.build.board }}
+ platform: ${{ matrix.build.platform }}
build-debian-src:
if: github.repository == 'meshtastic/firmware'
@@ -192,54 +99,26 @@ jobs:
if: ${{ !contains(github.ref_name, 'event/') }}
uses: ./.github/workflows/test_native.yml
- docker-deb-amd64:
+ docker:
+ strategy:
+ fail-fast: false
+ matrix:
+ distro: [debian, alpine]
+ platform: [linux/amd64, linux/arm64, linux/arm/v7]
+ pio_env: [native, native-tft]
+ exclude:
+ - distro: alpine
+ platform: linux/arm/v7
+ - pio_env: native-tft
+ platform: linux/arm64
+ - pio_env: native-tft
+ platform: linux/arm/v7
uses: ./.github/workflows/docker_build.yml
with:
- distro: debian
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
-
- docker-deb-amd64-tft:
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
- pio_env: native-tft
-
- docker-alp-amd64:
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: alpine
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
-
- docker-alp-amd64-tft:
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: alpine
- platform: linux/amd64
- runs-on: ubuntu-24.04
- push: false
- pio_env: native-tft
-
- docker-deb-arm64:
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/arm64
- runs-on: ubuntu-24.04-arm
- push: false
-
- docker-deb-armv7:
- uses: ./.github/workflows/docker_build.yml
- with:
- distro: debian
- platform: linux/arm/v7
- runs-on: ubuntu-24.04-arm
+ distro: ${{ matrix.distro }}
+ platform: ${{ matrix.platform }}
+ runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
+ pio_env: ${{ matrix.pio_env }}
push: false
gather-artifacts:
@@ -260,26 +139,15 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
- needs:
- [
- version,
- build-esp32,
- build-esp32s3,
- build-esp32c3,
- build-esp32c6,
- build-nrf52840,
- build-rp2040,
- build-rp2350,
- build-stm32,
- ]
+ needs: [version, build]
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -292,7 +160,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -308,7 +176,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -327,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@v4
+ uses: actions/upload-artifact@v5
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
overwrite: true
@@ -353,7 +221,7 @@ jobs:
- package-pio-deps-native-tft
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
@@ -372,14 +240,14 @@ jobs:
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v6
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@v5
+ uses: actions/download-artifact@v6
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -422,14 +290,14 @@ jobs:
needs: [release-artifacts, version]
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -446,7 +314,7 @@ jobs:
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
merge-multiple: true
@@ -477,14 +345,14 @@ jobs:
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v6
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index f26073ec4..045e94895 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1
@@ -31,7 +31,7 @@ jobs:
pull-requests: write # For trunk to create PRs
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Trunk Upgrade
uses: trunk-io/trunk-action/upgrade@v1
diff --git a/.github/workflows/package_obs.yml b/.github/workflows/package_obs.yml
index 4c547eadc..2b202ed95 100644
--- a/.github/workflows/package_obs.yml
+++ b/.github/workflows/package_obs.yml
@@ -34,7 +34,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
@@ -58,7 +58,7 @@ jobs:
id: version
- name: Download artifacts
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v6
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
diff --git a/.github/workflows/package_pio_deps.yml b/.github/workflows/package_pio_deps.yml
index d8ff6e631..cb10a79f3 100644
--- a/.github/workflows/package_pio_deps.yml
+++ b/.github/workflows/package_pio_deps.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
@@ -56,7 +56,7 @@ jobs:
PLATFORMIO_CORE_DIR: pio/core
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
overwrite: true
diff --git a/.github/workflows/package_ppa.yml b/.github/workflows/package_ppa.yml
index aece730a0..2e3278041 100644
--- a/.github/workflows/package_ppa.yml
+++ b/.github/workflows/package_ppa.yml
@@ -32,7 +32,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
@@ -60,7 +60,7 @@ jobs:
id: version
- name: Download artifacts
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v6
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml
index 4e285852d..048186538 100644
--- a/.github/workflows/pr_tests.yml
+++ b/.github/workflows/pr_tests.yml
@@ -40,7 +40,7 @@ jobs:
checks: write
pull-requests: write
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
submodules: recursive
@@ -50,7 +50,7 @@ jobs:
- name: Download test artifacts
if: needs.native-tests.result != 'skipped'
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v6
with:
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
merge-multiple: true
diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml
index d5d642db4..f21b13ee1 100644
--- a/.github/workflows/release_channels.yml
+++ b/.github/workflows/release_channels.yml
@@ -60,7 +60,10 @@ jobs:
shell: bash
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
+ with:
+ # Always use master branch for version bumps
+ ref: master
- name: Setup Python
uses: actions/setup-python@v6
diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
index 96c993cba..d044f9038 100644
--- a/.github/workflows/sec_sast_semgrep_cron.yml
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -21,7 +21,7 @@ jobs:
steps:
# step 1
- name: clone application source code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
# step 2
- name: full scan
@@ -33,7 +33,7 @@ jobs:
# step 3
- name: save report as pipeline artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: report.sarif
overwrite: true
@@ -41,7 +41,7 @@ jobs:
# step 4
- name: publish code scanning alerts
- uses: github/codeql-action/upload-sarif@v3
+ uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: report.sarif
category: semgrep
diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml
index e93b2ae8b..e9b4108a1 100644
--- a/.github/workflows/sec_sast_semgrep_pull.yml
+++ b/.github/workflows/sec_sast_semgrep_pull.yml
@@ -13,7 +13,7 @@ jobs:
steps:
# step 1
- name: clone application source code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
fetch-depth: 0
diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml
index a80619e90..11ba59386 100644
--- a/.github/workflows/stale_bot.yml
+++ b/.github/workflows/stale_bot.yml
@@ -20,5 +20,7 @@ jobs:
uses: actions/stale@v10.1.0
with:
days-before-stale: 45
- exempt-issue-labels: pinned,3.0
- exempt-pr-labels: pinned,3.0
+ 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.
+ close-issue-message: This issue has not had any comment since the last notice. It has been closed automatically. If this is incorrect, or the issue becomes relevant again, please request that it is reopened.
+ exempt-issue-labels: pinned,3.0,triaged,backlog
+ exempt-pr-labels: pinned,3.0,triaged,backlog
diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml
index 6b788f4c7..a2328022e 100644
--- a/.github/workflows/test_native.yml
+++ b/.github/workflows/test_native.yml
@@ -14,7 +14,7 @@ jobs:
name: Native Simulator Tests
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -40,7 +40,7 @@ jobs:
- name: Integration test
run: |
- .pio/build/coverage/program &
+ .pio/build/coverage/program -s &
PID=$!
timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
echo "Simulator started, launching python test..."
@@ -59,7 +59,7 @@ jobs:
id: version
- name: Save coverage information
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}.zip
@@ -70,7 +70,7 @@ jobs:
name: Native PlatformIO Tests
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -94,7 +94,7 @@ jobs:
- name: Save test results
if: always() # run this step even if previous step failed
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
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@v4
+ uses: actions/upload-artifact@v5
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}.zip
@@ -127,7 +127,7 @@ jobs:
- platformio-tests
if: always()
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -137,20 +137,20 @@ jobs:
id: version
- name: Download test artifacts
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v6
with:
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
merge-multiple: true
- name: Test Report
- uses: dorny/test-reporter@v2.1.1
+ uses: dorny/test-reporter@v2.2.0
with:
name: PlatformIO Tests
path: testreport.xml
reporter: java-junit
- name: Download coverage artifacts
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v6
with:
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
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@v4
+ uses: actions/upload-artifact@v5
with:
name: code-coverage-report-${{ steps.version.outputs.long }}.zip
path: code-coverage-report
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 942659348..4a97853e2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: test-runner
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
# - uses: actions/setup-python@v5
# with:
@@ -47,9 +47,9 @@ jobs:
pio upgrade
- name: Setup Node
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
- name: Setup pnpm
uses: pnpm/action-setup@v4
diff --git a/.github/workflows/trunk_annotate_pr.yml b/.github/workflows/trunk_annotate_pr.yml
index 23dcf8d09..59ab25c28 100644
--- a/.github/workflows/trunk_annotate_pr.yml
+++ b/.github/workflows/trunk_annotate_pr.yml
@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1
diff --git a/.github/workflows/trunk_check.yml b/.github/workflows/trunk_check.yml
index 41731d491..874374fe0 100644
--- a/.github/workflows/trunk_check.yml
+++ b/.github/workflows/trunk_check.yml
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1
diff --git a/.github/workflows/trunk_format_pr.yml b/.github/workflows/trunk_format_pr.yml
index 51082fc5f..8fa0cc1eb 100644
--- a/.github/workflows/trunk_format_pr.yml
+++ b/.github/workflows/trunk_format_pr.yml
@@ -15,7 +15,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml
index c06e06b0a..af0557fda 100644
--- a/.github/workflows/update_protobufs.yml
+++ b/.github/workflows/update_protobufs.yml
@@ -11,7 +11,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
submodules: true
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 74b850b64..1fd8790f2 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -4,31 +4,31 @@ cli:
plugins:
sources:
- id: trunk
- ref: v1.7.2
+ ref: v1.7.3
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - checkov@3.2.473
- - renovate@41.132.5
+ - checkov@3.2.492
+ - renovate@42.5.4
- prettier@3.6.2
- - trufflehog@3.90.8
+ - trufflehog@3.90.13
- yamllint@1.37.1
- bandit@1.8.6
- - trivy@0.67.0
+ - trivy@0.67.2
- taplo@0.10.0
- - ruff@0.13.2
- - isort@6.1.0
+ - ruff@0.14.4
+ - isort@7.0.0
- markdownlint@0.45.0
- oxipng@9.1.5
- svgo@4.0.0
- - actionlint@1.7.7
+ - actionlint@1.7.8
- flake8@7.3.0
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- - black@25.9.0
+ - black@25.11.0
- git-diff-check
- - gitleaks@8.28.0
+ - gitleaks@8.29.0
- clang-format@16.0.3
ignore:
- linters: [ALL]
diff --git a/Dockerfile b/Dockerfile
index b1e151ac7..111dd69fc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
-FROM python:3.13-slim-trixie AS builder
+FROM python:3.14-slim-trixie AS builder
ARG PIO_ENV=native
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC
diff --git a/README.md b/README.md
index a53fe9646..f34bf1839 100644
--- a/README.md
+++ b/README.md
@@ -37,4 +37,3 @@ Join our community and help improve Meshtastic! 🚀
## Stats

-
diff --git a/alpine.Dockerfile b/alpine.Dockerfile
index 670736241..bdee57d79 100644
--- a/alpine.Dockerfile
+++ b/alpine.Dockerfile
@@ -3,12 +3,13 @@
# trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
-FROM python:3.13-alpine3.22 AS builder
+FROM python:3.14-alpine3.22 AS builder
ARG PIO_ENV=native
ENV PIP_ROOT_USER_ACTION=ignore
RUN apk --no-cache add \
- bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
+ bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
+ libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev \
&& rm -rf /var/cache/apk/* \
@@ -40,8 +41,8 @@ LABEL org.opencontainers.image.title="Meshtastic" \
USER root
RUN apk --no-cache add \
- shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
- libx11 libinput libxkbcommon \
+ shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
+ i2c-tools libuv libx11 libinput libxkbcommon \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d \
diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index d2c933461..810c9780e 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -31,6 +31,7 @@ build_flags =
-DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL
-DAXP_DEBUG_PORT=Serial
-DCONFIG_BT_NIMBLE_ENABLED
+ -DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
@@ -56,7 +57,7 @@ lib_deps =
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
- https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip
+ https://github.com/lewisxhe/XPowersLib/archive/v0.3.1.zip
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini
index 1afb9b547..7b06f4cd8 100644
--- a/arch/esp32/esp32c6.ini
+++ b/arch/esp32/esp32c6.ini
@@ -28,7 +28,7 @@ lib_deps =
${environmental_extra.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
- lewisxhe/XPowersLib@0.3.0
+ lewisxhe/XPowersLib@0.3.1
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index 36effe017..e60d47ce7 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -7,7 +7,7 @@ extends = arduino_base
platform_packages =
; our custom Git version until they merge our PR
# TODO renovate
- platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#e13f5820002a4fb2a5e6754b42ace185277e5adf
+ platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39
; Don't renovate toolchain-gccarmnoneeabi
platformio/toolchain-gccarmnoneeabi@~1.90301.0
diff --git a/arch/nrf52/nrf52840.ini b/arch/nrf52/nrf52840.ini
index 5e846b3b7..e13443152 100644
--- a/arch/nrf52/nrf52840.ini
+++ b/arch/nrf52/nrf52840.ini
@@ -8,7 +8,7 @@ lib_deps =
${environmental_base.lib_deps}
${environmental_extra.lib_deps}
# renovate: datasource=git-refs depName=Kongduino-Adafruit_nRFCrypto packageName=https://github.com/Kongduino/Adafruit_nRFCrypto gitBranch=master
- https://github.com/Kongduino/Adafruit_nRFCrypto/archive/5f838d2709461a2c981f642917aa50254a25c14c.zip
+ https://github.com/Kongduino/Adafruit_nRFCrypto/archive/8cde7189b5ead9dcd49f72601b43b969c0bbc06e.zip
; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board.
diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini
index f3fd00de7..bce06f907 100644
--- a/arch/portduino/portduino.ini
+++ b/arch/portduino/portduino.ini
@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
- https://github.com/meshtastic/platform-native/archive/d3f6e339534233c7217818867368767590ce549e.zip
+ https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip
framework = arduino
build_src_filter =
diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini
index 8b7d256b3..7732533c9 100644
--- a/arch/stm32/stm32.ini
+++ b/arch/stm32/stm32.ini
@@ -37,6 +37,9 @@ build_flags =
-DRADIOLIB_EXCLUDE_LR11X0=1
-DHAL_DAC_MODULE_ONLY
-DHAL_RNG_MODULE_ENABLED
+ -Wl,--wrap=__assert_func
+ -Wl,--wrap=strerror
+ -Wl,--wrap=_tzset_unlocked_r
build_src_filter =
${arduino_base.build_src_filter} - - - - - - - - - - - - - -
diff --git a/bin/device-install.bat b/bin/device-install.bat
index 9c206d718..519073b08 100755
--- a/bin/device-install.bat
+++ b/bin/device-install.bat
@@ -15,12 +15,12 @@ SET "LOGCOUNTER=0"
SET "BPS_RESET=0"
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
-SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv"
+SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv heltec-v4"
SET "C3=esp32c3"
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
-SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv"
+SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv heltec-v4"
GOTO getopts
:help
diff --git a/bin/device-install.sh b/bin/device-install.sh
index ede75bbba..69e4794ba 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -31,21 +31,23 @@ MUIDB_8MB=(
"seeed-sensecap-indicator"
)
BIGDB_16MB=(
- "t-deck"
- "mesh-tab"
- "t-energy-s3"
"dreamcatcher"
- "ESP32-S3-Pico"
- "m5stack-cores3"
- "station-g2"
- "t-eth-elite"
- "tlora-pager"
- "t-watch-s3"
"elecrow-adv"
+ "ESP32-S3-Pico"
+ "heltec-v4"
+ "m5stack-cores3"
+ "mesh-tab"
+ "station-g2"
+ "t-deck"
+ "t-energy-s3"
+ "t-eth-elite"
+ "t-watch-s3"
+ "tlora-pager"
)
S3_VARIANTS=(
"s3"
"-v3"
+ "-v4"
"t-deck"
"wireless-paper"
"wireless-tracker"
diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py
index aaa76aa45..b4c18c05b 100755
--- a/bin/generate_ci_matrix.py
+++ b/bin/generate_ci_matrix.py
@@ -1,28 +1,32 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""Generate the CI matrix."""
+import argparse
import json
-import sys
-import random
import re
from platformio.project.config import ProjectConfig
-options = sys.argv[1:]
+parser = argparse.ArgumentParser(description="Generate the CI matrix")
+parser.add_argument("platform", help="Platform to build for")
+parser.add_argument(
+ "--level",
+ choices=["extra", "pr"],
+ nargs="*",
+ default=[],
+ help="Board level to build for (omit for full release boards)",
+)
+args = parser.parse_args()
outlist = []
-if len(options) < 1:
- print(json.dumps(outlist))
- exit(1)
-
cfg = ProjectConfig.get_instance()
pio_envs = cfg.envs()
# Gather all PlatformIO environments for filtering later
all_envs = []
for pio_env in pio_envs:
- env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
+ env_build_flags = cfg.get(f"env:{pio_env}", "build_flags")
env_platform = None
for flag in env_build_flags:
# Extract the platform from the build flags
@@ -37,36 +41,35 @@ for pio_env in pio_envs:
exit(1)
# Store env details as a dictionary, and add to 'all_envs' list
env = {
- 'name': pio_env,
- 'platform': env_platform,
- 'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
- 'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
+ "ci": {"board": pio_env, "platform": env_platform},
+ "board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
+ "board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
}
all_envs.append(env)
# Filter outputs based on options
# Check is mutually exclusive with other options (except 'pr')
-if "check" in options:
+if "check" in args.platform:
for env in all_envs:
- if env['board_check']:
- if "pr" in options:
- if env['board_level'] == 'pr':
- outlist.append(env['name'])
+ if env["board_check"]:
+ if "pr" in args.level:
+ if env["board_level"] == "pr":
+ outlist.append(env["ci"])
else:
- outlist.append(env['name'])
+ outlist.append(env["ci"])
# Filter (non-check) builds by platform
else:
for env in all_envs:
- if options[0] == env['platform']:
+ if args.platform == env["ci"]["platform"] or args.platform == "all":
# Always include board_level = 'pr'
- if env['board_level'] == 'pr':
- outlist.append(env['name'])
+ if env["board_level"] == "pr":
+ outlist.append(env["ci"])
# Include board_level = 'extra' when requested
- elif "extra" in options and env['board_level'] == "extra":
- outlist.append(env['name'])
+ elif "extra" in args.level and env["board_level"] == "extra":
+ outlist.append(env["ci"])
# If no board level is specified, include in release builds (not PR)
- elif "pr" not in options and not env['board_level']:
- outlist.append(env['name'])
+ elif "pr" not in args.level and not env["board_level"]:
+ outlist.append(env["ci"])
# Return as a JSON list
print(json.dumps(outlist))
diff --git a/bin/kill-github-actions.sh b/bin/kill-github-actions.sh
new file mode 100755
index 000000000..f71047c5e
--- /dev/null
+++ b/bin/kill-github-actions.sh
@@ -0,0 +1,116 @@
+#!/bin/bash
+
+# Script to cancel all running GitHub Actions workflows
+# Requires GitHub CLI (gh) to be installed and authenticated
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Function to print colored output
+print_status() {
+ echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}[WARN]${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# Check if gh CLI is installed
+if ! command -v gh &> /dev/null; then
+ print_error "GitHub CLI (gh) is not installed. Please install it first:"
+ echo " brew install gh"
+ echo " Or visit: https://cli.github.com/"
+ exit 1
+fi
+
+# Check if authenticated
+if ! gh auth status &> /dev/null; then
+ print_error "GitHub CLI is not authenticated. Please run:"
+ echo " gh auth login"
+ exit 1
+fi
+
+# Get repository info
+REPO=$(gh repo view --json owner,name -q '.owner.login + "/" + .name')
+if [[ -z "$REPO" ]]; then
+ print_error "Could not determine repository. Make sure you're in a GitHub repository."
+ exit 1
+fi
+
+print_status "Working with repository: $REPO"
+
+# Get all active workflows (both queued and in-progress)
+print_status "Fetching active workflows (queued and in-progress)..."
+QUEUED_WORKFLOWS=$(gh run list --status queued --json databaseId,displayTitle,headBranch,status --limit 100)
+IN_PROGRESS_WORKFLOWS=$(gh run list --status in_progress --json databaseId,displayTitle,headBranch,status --limit 100)
+
+# Combine both lists
+ALL_WORKFLOWS=$(echo "$QUEUED_WORKFLOWS $IN_PROGRESS_WORKFLOWS" | jq -s 'add | unique_by(.databaseId)')
+
+if [[ "$ALL_WORKFLOWS" == "[]" ]]; then
+ print_status "No active workflows found."
+ exit 0
+fi
+
+# Parse and display active workflows
+echo
+print_warning "Found active workflows:"
+echo "$ALL_WORKFLOWS" | jq -r '.[] | " - \(.displayTitle) (Branch: \(.headBranch), Status: \(.status), ID: \(.databaseId))"'
+
+echo
+read -p "Do you want to cancel ALL these workflows? (y/N): " -n 1 -r
+echo
+
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ print_status "Cancelled by user."
+ exit 0
+fi
+
+# Cancel each workflow
+print_status "Cancelling workflows..."
+CANCELLED_COUNT=0
+FAILED_COUNT=0
+
+while IFS= read -r WORKFLOW_ID; do
+ if [[ -n "$WORKFLOW_ID" ]]; then
+ print_status "Cancelling workflow ID: $WORKFLOW_ID"
+ if gh run cancel "$WORKFLOW_ID" 2>/dev/null; then
+ ((CANCELLED_COUNT++))
+ else
+ print_error "Failed to cancel workflow ID: $WORKFLOW_ID"
+ ((FAILED_COUNT++))
+ fi
+ fi
+done < <(echo "$ALL_WORKFLOWS" | jq -r '.[].databaseId')
+
+echo
+print_status "Summary:"
+echo " - Cancelled: $CANCELLED_COUNT workflows"
+if [[ $FAILED_COUNT -gt 0 ]]; then
+ echo " - Failed: $FAILED_COUNT workflows"
+fi
+
+print_status "Done!"
+
+# Optional: Show remaining active workflows
+echo
+print_status "Checking for any remaining active workflows..."
+REMAINING_QUEUED=$(gh run list --status queued --json databaseId --limit 10)
+REMAINING_IN_PROGRESS=$(gh run list --status in_progress --json databaseId --limit 10)
+REMAINING_ALL=$(echo "$REMAINING_QUEUED $REMAINING_IN_PROGRESS" | jq -s 'add | unique_by(.databaseId)')
+
+if [[ "$REMAINING_ALL" == "[]" ]]; then
+ print_status "All workflows successfully cancelled."
+else
+ REMAINING_COUNT=$(echo "$REMAINING_ALL" | jq '. | length')
+ print_warning "Still $REMAINING_COUNT workflows active (may take a moment to update status)"
+fi
\ No newline at end of file
diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 3505f1940..243edca0c 100644
--- a/bin/org.meshtastic.meshtasticd.metainfo.xml
+++ b/bin/org.meshtastic.meshtasticd.metainfo.xml
@@ -87,6 +87,18 @@
+
+ https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.16
+
+
+ https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15
+
+
+ https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14
+
+
+ https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13
+
https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12
diff --git a/bin/web.version b/bin/web.version
index e46a05b19..ba5c9fca6 100644
--- a/bin/web.version
+++ b/bin/web.version
@@ -1 +1 @@
-2.6.4
\ No newline at end of file
+2.6.7
\ No newline at end of file
diff --git a/boards/ThinkNode-M3.json b/boards/ThinkNode-M3.json
new file mode 100644
index 000000000..ff21e046a
--- /dev/null
+++ b/boards/ThinkNode-M3.json
@@ -0,0 +1,53 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "nrf52840_s140_v6.ld"
+ },
+ "core": "nRF5",
+ "cpu": "cortex-m4",
+ "extra_flags": "-DNRF52840_XXAA",
+ "f_cpu": "64000000L",
+ "hwids": [
+ ["0x239A", "0x4405"],
+ ["0x239A", "0x0029"],
+ ["0x239A", "0x002A"]
+ ],
+ "usb_product": "elecrow_eink",
+ "mcu": "nrf52840",
+ "variant": "ELECROW-ThinkNode-M3",
+ "variants_dir": "variants",
+ "bsp": {
+ "name": "adafruit"
+ },
+ "softdevice": {
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
+ "sd_fwid": "0x00B6"
+ },
+ "bootloader": {
+ "settings_addr": "0xFF000"
+ }
+ },
+ "connectivity": ["bluetooth"],
+ "debug": {
+ "jlink_device": "nRF52840_xxAA",
+ "onboard_tools": ["jlink"],
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
+ },
+ "frameworks": ["arduino"],
+ "name": "elecrow nrf",
+ "upload": {
+ "maximum_ram_size": 248832,
+ "maximum_size": 815104,
+ "speed": 115200,
+ "protocol": "nrfutil",
+ "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
+ "use_1200bps_touch": true,
+ "require_upload_port": true,
+ "wait_for_upload_port": true
+ },
+ "url": "",
+ "vendor": "ELECROW"
+}
diff --git a/boards/ThinkNode-M6.json b/boards/ThinkNode-M6.json
new file mode 100644
index 000000000..9fe324ec2
--- /dev/null
+++ b/boards/ThinkNode-M6.json
@@ -0,0 +1,53 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "nrf52840_s140_v6.ld"
+ },
+ "core": "nRF5",
+ "cpu": "cortex-m4",
+ "extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA",
+ "f_cpu": "64000000L",
+ "hwids": [
+ ["0x239A", "0x4405"],
+ ["0x239A", "0x0029"],
+ ["0x239A", "0x002A"]
+ ],
+ "usb_product": "elecrow_thinknode_m6",
+ "mcu": "nrf52840",
+ "variant": "ELECROW-ThinkNode-M6",
+ "variants_dir": "variants",
+ "bsp": {
+ "name": "adafruit"
+ },
+ "softdevice": {
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
+ "sd_fwid": "0x00B6"
+ },
+ "bootloader": {
+ "settings_addr": "0xFF000"
+ }
+ },
+ "connectivity": ["bluetooth"],
+ "debug": {
+ "jlink_device": "nRF52840_xxAA",
+ "onboard_tools": ["jlink"],
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
+ },
+ "frameworks": ["arduino"],
+ "name": "ELECROW ThinkNode M6",
+ "upload": {
+ "maximum_ram_size": 248832,
+ "maximum_size": 815104,
+ "speed": 115200,
+ "protocol": "nrfutil",
+ "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
+ "use_1200bps_touch": true,
+ "require_upload_port": true,
+ "wait_for_upload_port": true
+ },
+ "url": "https://www.elecrow.com/thinknode-m6-outdoor-solar-power-for-lora-powered-by-nrf52840-supports-gps.html",
+ "vendor": "ELECROW"
+}
diff --git a/boards/muzi-base.json b/boards/muzi-base.json
new file mode 100644
index 000000000..5f65c0dc8
--- /dev/null
+++ b/boards/muzi-base.json
@@ -0,0 +1,56 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "nrf52840_s140_v6.ld"
+ },
+ "core": "nRF5",
+ "cpu": "cortex-m4",
+ "extra_flags": "-DARDUINO_NRF52840_MUZI_BASE -DNRF52840_XXAA",
+ "f_cpu": "64000000L",
+ "hwids": [["0x239A", "0xcafe"]],
+ "mcu": "nrf52840",
+ "variant": "muzi-base",
+ "variants_dir": "variants",
+ "bsp": {
+ "name": "adafruit"
+ },
+ "softdevice": {
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
+ "sd_fwid": "0x00B6"
+ },
+ "bootloader": {
+ "settings_addr": "0xFF000"
+ }
+ },
+ "connectivity": ["bluetooth"],
+ "debug": {
+ "jlink_device": "nRF52840_xxAA",
+ "onboard_tools": ["jlink"],
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
+ },
+ "frameworks": ["arduino"],
+ "name": "Muzi Base",
+ "url": "https://muzi.works/",
+ "vendor": "MuziWorks",
+ "upload": {
+ "maximum_ram_size": 248832,
+ "maximum_size": 815104,
+ "speed": 115200,
+ "protocol": "nrfutil",
+ "protocols": [
+ "jlink",
+ "nrfjprog",
+ "nrfutil",
+ "blackmagic",
+ "cmsis-dap",
+ "mbed",
+ "stlink"
+ ],
+ "use_1200bps_touch": true,
+ "require_upload_port": true,
+ "wait_for_upload_port": true
+ }
+}
diff --git a/debian/changelog b/debian/changelog
index 8bd053b25..5a0f543eb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,28 @@
+meshtasticd (2.7.16.0) unstable; urgency=medium
+
+ * Version 2.7.16
+
+ -- GitHub Actions Wed, 19 Nov 2025 16:12:32 +0000
+
+
+meshtasticd (2.7.15.0) unstable; urgency=medium
+
+ * Version 2.7.15
+
+ -- GitHub Actions Thu, 13 Nov 2025 12:31:57 +0000
+
+meshtasticd (2.7.14.0) unstable; urgency=medium
+
+ * Version 2.7.14
+
+ -- GitHub Actions Mon, 03 Nov 2025 16:11:31 +0000
+
+meshtasticd (2.7.13.0) unstable; urgency=medium
+
+ * Version 2.7.13
+
+ -- GitHub Actions Sat, 11 Oct 2025 15:27:28 +0000
+
meshtasticd (2.7.12.0) unstable; urgency=medium
[ Austin Lane ]
diff --git a/debian/control b/debian/control
index 761383a5c..679a444c9 100644
--- a/debian/control
+++ b/debian/control
@@ -3,6 +3,7 @@ Section: misc
Priority: optional
Maintainer: Austin Lane
Build-Depends: debhelper-compat (= 13),
+ libc6-dev (>= 2.38) | libbsd-dev,
lsb-release,
tar,
gzip,
diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg
index eb4ab5ae7..b9152c4a3 100644
--- a/meshtasticd.spec.rpkg
+++ b/meshtasticd.spec.rpkg
@@ -33,6 +33,7 @@ BuildRequires: python3dist(grpcio[protobuf])
BuildRequires: python3dist(grpcio-tools)
BuildRequires: git-core
BuildRequires: gcc-c++
+BuildRequires: (glibc-devel >= 2.38) or pkgconfig(libbsd-overlay)
BuildRequires: pkgconfig(yaml-cpp)
BuildRequires: pkgconfig(libgpiod)
BuildRequires: pkgconfig(bluez)
@@ -49,6 +50,13 @@ BuildRequires: pkgconfig(x11)
BuildRequires: pkgconfig(libinput)
BuildRequires: pkgconfig(xkbcommon-x11)
+# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
+%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
+BuildRequires: glibc-devel >= 2.38
+%else
+BuildRequires: pkgconfig(libbsd-overlay)
+%endif
+
Requires: systemd-udev
%description
diff --git a/platformio.ini b/platformio.ini
index b6d6733e3..d6ff155e4 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -62,7 +62,7 @@ monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
- https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
+ https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -70,7 +70,7 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
- https://github.com/meshtastic/ArduinoThread/archive/7c3ee9e1951551b949763b1f5280f8db1fa4068d.zip
+ https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip
# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
nanopb/Nanopb@0.4.91
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
@@ -115,12 +115,13 @@ lib_deps =
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
- jgromes/RadioLib@7.3.0
+ # jgromes/RadioLib@7.4.0
+ https://github.com/jgromes/RadioLib/archive/536c7267362e2c1345be7054ba45e503252975ff.zip
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
- https://github.com/meshtastic/device-ui/archive/505ffadaa7a931df5dc8153229b719a07bbb028c.zip
+ https://github.com/meshtastic/device-ui/archive/28167c67dfd13015a0b5eef1828f95fe8e3ab7c3.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -164,7 +165,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
mprograms/QMC5883LCompass@1.2.3
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
- dfrobot/DFRobot_RTU@1.0.3
+ dfrobot/DFRobot_RTU@1.0.6
# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226
@@ -176,11 +177,13 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
adafruit/Adafruit LTR390 Library@1.1.2
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
- adafruit/Adafruit PCT2075@1.0.5
+ adafruit/Adafruit PCT2075@1.0.6
# 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.2
+ # renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/BH1750_WE@^1.1.10
+ wollewald/BH1750_WE@^1.1.10
; (not included in native / portduino)
[environmental_extra]
diff --git a/protobufs b/protobufs
index a1b8c3d17..52fa252f1 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit a1b8c3d171445b2eebfd4b5bd1e4876f3bbed605
+Subproject commit 52fa252f1e01be87ad2f7ab17ceef7882b2a4a93
diff --git a/src/NodeStatus.h b/src/NodeStatus.h
index 60d1bdd98..550f6254a 100644
--- a/src/NodeStatus.h
+++ b/src/NodeStatus.h
@@ -14,16 +14,16 @@ class NodeStatus : public Status
CallbackObserver statusObserver =
CallbackObserver(this, &NodeStatus::updateStatus);
- uint8_t numOnline = 0;
- uint8_t numTotal = 0;
+ uint16_t numOnline = 0;
+ uint16_t numTotal = 0;
- uint8_t lastNumTotal = 0;
+ uint16_t lastNumTotal = 0;
public:
bool forceUpdate = false;
NodeStatus() { statusType = STATUS_TYPE_NODE; }
- NodeStatus(uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false) : Status()
+ NodeStatus(uint16_t numOnline, uint16_t numTotal, bool forceUpdate = false) : Status()
{
this->forceUpdate = forceUpdate;
this->numOnline = numOnline;
@@ -34,11 +34,11 @@ class NodeStatus : public Status
void observe(Observable *source) { statusObserver.observe(source); }
- uint8_t getNumOnline() const { return numOnline; }
+ uint16_t getNumOnline() const { return numOnline; }
- uint8_t getNumTotal() const { return numTotal; }
+ uint16_t getNumTotal() const { return numTotal; }
- uint8_t getLastNumTotal() const { return lastNumTotal; }
+ uint16_t getLastNumTotal() const { return lastNumTotal; }
bool matches(const NodeStatus *newStatus) const
{
@@ -56,7 +56,7 @@ class NodeStatus : public Status
numTotal = newStatus->getNumTotal();
}
if (isDirty || newStatus->forceUpdate) {
- LOG_DEBUG("Node status update: %d online, %d total", numOnline, numTotal);
+ LOG_DEBUG("Node status update: %u online, %u total", numOnline, numTotal);
onNewStatus.notifyObservers(this);
}
return 0;
diff --git a/src/Power.cpp b/src/Power.cpp
index 7de82b8d6..0ac89670f 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -194,7 +194,7 @@ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level se
#ifdef BATTERY_PIN
-static void adcEnable()
+void battery_adcEnable()
{
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
#ifdef ADC_USE_PULLUP
@@ -214,7 +214,7 @@ static void adcEnable()
#endif
}
-static void adcDisable()
+static void battery_adcDisable()
{
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
#ifdef ADC_USE_PULLUP
@@ -278,6 +278,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
break;
}
}
+#if defined(BATTERY_CHARGING_INV)
+ // bit of trickery to show 99% up until the charge finishes
+ if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99)
+ battery_SOC = 99;
+#endif
return clamp((int)(battery_SOC), 0, 100);
}
@@ -320,7 +325,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
uint32_t raw = 0;
float scaled = 0;
- adcEnable();
+ battery_adcEnable();
#ifdef ARCH_ESP32 // ADC block for espressif platforms
raw = espAdcRead();
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
@@ -332,7 +337,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
raw = raw / BATTERY_SENSE_SAMPLES;
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
#endif
- adcDisable();
+ battery_adcDisable();
if (!initial_read_done) {
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
@@ -455,6 +460,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
// if it's not HIGH - check the battery
#endif
+#elif defined(MUZI_BASE)
+ return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
#endif
return getBattVoltage() > chargingVolt;
}
@@ -470,6 +477,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
#endif
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
+#elif defined(BATTERY_CHARGING_INV)
+ return !digitalRead(BATTERY_CHARGING_INV);
#else
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
if (hasINA()) {
@@ -562,6 +571,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
config.power.device_battery_ina_address) {
if (!ina226Sensor.isInitialized())
return ina226Sensor.runOnce() > 0;
+ return ina226Sensor.isRunning();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
config.power.device_battery_ina_address) {
if (!ina260Sensor.isInitialized())
@@ -691,7 +701,26 @@ bool Power::setup()
#ifdef NRF_APM
found = true;
#endif
-
+#ifdef EXT_PWR_DETECT
+ attachInterrupt(
+ EXT_PWR_DETECT,
+ []() {
+ power->setIntervalFromNow(0);
+ runASAP = true;
+ BaseType_t higherWake = 0;
+ },
+ CHANGE);
+#endif
+#ifdef BATTERY_CHARGING_INV
+ attachInterrupt(
+ BATTERY_CHARGING_INV,
+ []() {
+ power->setIntervalFromNow(0);
+ runASAP = true;
+ BaseType_t higherWake = 0;
+ },
+ CHANGE);
+#endif
enabled = found;
low_voltage_counter = 0;
@@ -748,6 +777,8 @@ void Power::shutdown()
if (screen) {
#ifdef T_DECK_PRO
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
+#elif USE_EINK
+ screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
#else
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
#endif
@@ -828,8 +859,11 @@ void Power::readPowerStatus()
// Notify any status instances that are observing us
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
- LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
- powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
+ if (millis() > lastLogTime + 50 * 1000) {
+ LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
+ powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
+ lastLogTime = millis();
+ }
newStatus.notifyObservers(&powerStatus2);
#ifdef DEBUG_HEAP
if (lastheap != memGet.getFreeHeap()) {
@@ -892,13 +926,8 @@ void Power::readPowerStatus()
low_voltage_counter++;
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
if (low_voltage_counter > 10) {
-#ifdef ARCH_NRF52
- // We can't trigger deep sleep on NRF52, it's freezing the board
- LOG_DEBUG("Low voltage detected, but not trigger deep sleep");
-#else
LOG_INFO("Low voltage detected, trigger deep sleep");
powerFSM.trigger(EVENT_LOW_BATTERY);
-#endif
}
} else {
low_voltage_counter = 0;
@@ -1538,4 +1567,4 @@ bool Power::meshSolarInit()
{
return false;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index fad0fb92f..dd2acb599 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -50,6 +50,7 @@ void consolePrintf(const char *format, ...)
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
{
+ api_type = TYPE_SERIAL;
assert(!console);
console = this;
canWrite = false; // We don't send packets to our port until it has talked to us first
diff --git a/src/configuration.h b/src/configuration.h
index e67f0b8e5..d37269995 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -33,6 +33,9 @@ along with this program. If not, see .
#include "pcf8563.h"
#endif
+/* Offer chance for variant-specific defines */
+#include "variant.h"
+
// -----------------------------------------------------------------------------
// Version
// -----------------------------------------------------------------------------
@@ -123,6 +126,11 @@ along with this program. If not, see .
#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 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
+#endif
+
// Default system gain to 0 if not defined
#ifndef TX_GAIN_LORA
#define TX_GAIN_LORA 0
@@ -220,6 +228,7 @@ along with this program. If not, see .
#define ICM20948_ADDR_ALT 0x68
#define BHI260AP_ADDR 0x28
#define BMM150_ADDR 0x13
+#define DA217_ADDR 0x26
// -----------------------------------------------------------------------------
// LED
@@ -242,6 +251,7 @@ along with this program. If not, see .
// -----------------------------------------------------------------------------
#define FT6336U_ADDR 0x48
#define CST328_ADDR 0x1A
+#define CHSC6X_ADDR 0x2E
// -----------------------------------------------------------------------------
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
@@ -260,9 +270,6 @@ along with this program. If not, see .
// convert 24-bit color to 16-bit (56K)
#define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
-/* Step #1: offer chance for variant-specific defines */
-#include "variant.h"
-
#if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE)
// Older variant.h files might not be defining this value, so stay with the old default
#define VEXT_ON_VALUE LOW
@@ -389,6 +396,13 @@ along with this program. If not, see .
#define HAS_RGB_LED
#endif
+#ifndef LED_STATE_OFF
+#define LED_STATE_OFF 0
+#endif
+#ifndef LED_STATE_ON
+#define LED_STATE_ON 1
+#endif
+
// default mapping of pins
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
#define ALT_BUTTON_PIN PIN_BUTTON2
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 2e602338c..55980face 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -82,7 +82,10 @@ class ScanI2C
BHI260AP,
BMM150,
TSL2561,
- DRV2605
+ DRV2605,
+ BH1750,
+ DA217,
+ CHSC6X
} DeviceType;
// typedef uint8_t DeviceAddress;
diff --git a/src/detect/ScanI2CConsumer.cpp b/src/detect/ScanI2CConsumer.cpp
new file mode 100644
index 000000000..a70fa5398
--- /dev/null
+++ b/src/detect/ScanI2CConsumer.cpp
@@ -0,0 +1,16 @@
+#include "ScanI2CConsumer.h"
+#include
+
+static std::forward_list ScanI2CConsumers;
+
+ScanI2CConsumer::ScanI2CConsumer()
+{
+ ScanI2CConsumers.push_front(this);
+}
+
+void ScanI2CCompleted(ScanI2C *i2cScanner)
+{
+ for (ScanI2CConsumer *consumer : ScanI2CConsumers) {
+ consumer->i2cScanFinished(i2cScanner);
+ }
+}
\ No newline at end of file
diff --git a/src/detect/ScanI2CConsumer.h b/src/detect/ScanI2CConsumer.h
new file mode 100644
index 000000000..fd97f7edc
--- /dev/null
+++ b/src/detect/ScanI2CConsumer.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "ScanI2C.h"
+#include
+
+class ScanI2CConsumer
+{
+ public:
+ ScanI2CConsumer();
+ virtual void i2cScanFinished(ScanI2C *i2cScanner) = 0;
+};
+
+void ScanI2CCompleted(ScanI2C *i2cScanner);
\ No newline at end of file
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 6df3f8be1..bcf49286e 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -106,6 +106,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
if (i2cBus->available())
i2cBus->read();
}
+ LOG_DEBUG("Register value: 0x%x", value);
return value;
}
@@ -377,13 +378,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
- registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
- if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c || registerValue == 0xc8d) {
- type = SHT4X;
- logFoundDevice("SHT4X", (uint8_t)addr.address);
- } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
+ if (registerValue == 0x5449) {
type = OPT3001;
logFoundDevice("OPT3001", (uint8_t)addr.address);
+ } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
+ type = SHT4X;
+ logFoundDevice("SHT4X", (uint8_t)addr.address);
} else {
type = SHT31;
logFoundDevice("SHT31", (uint8_t)addr.address);
@@ -464,8 +465,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
- SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
+ case TCA9555_ADDR:
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 1);
+ if (registerValue == 0x13) {
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
+ if (registerValue == 0x81) {
+ type = DA217;
+ logFoundDevice("DA217", (uint8_t)addr.address);
+ } else {
+ type = TCA9555;
+ logFoundDevice("TCA9555", (uint8_t)addr.address);
+ }
+ } else {
+ type = TCA9555;
+ logFoundDevice("TCA9555", (uint8_t)addr.address);
+ }
+ break;
case TSL25911_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
if (registerValue == 0x50) {
@@ -484,7 +500,26 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
- SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
+ SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
+ case LTR553ALS_ADDR:
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
+ if (registerValue == 0x92) { // LTR553ALS Part ID
+ type = LTR553ALS;
+ logFoundDevice("LTR553ALS", (uint8_t)addr.address);
+ } else {
+ // Test BH1750 - send power on command
+ i2cBus->beginTransmission(addr.address);
+ i2cBus->write(0x01); // Power On command
+ uint8_t bh1750_error = i2cBus->endTransmission();
+ if (bh1750_error == 0) {
+ type = BH1750;
+ logFoundDevice("BH1750", (uint8_t)addr.address);
+ } else {
+ LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
+ }
+ }
+ break;
+
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
@@ -512,6 +547,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case ICM20948_ADDR: // same as BMX160_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
+#ifdef HAS_ICM20948
+ type = ICM20948;
+ logFoundDevice("ICM20948", (uint8_t)addr.address);
+ break;
+#endif
if (registerValue == 0xEA) {
type = ICM20948;
logFoundDevice("ICM20948", (uint8_t)addr.address);
@@ -580,7 +620,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
scanPort(port, nullptr, 0);
}
-TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
+TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address)
{
if (address.port == ScanI2C::I2CPort::WIRE) {
return &Wire;
diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h
index 6988091ad..c5b791920 100644
--- a/src/detect/ScanI2CTwoWire.h
+++ b/src/detect/ScanI2CTwoWire.h
@@ -23,12 +23,12 @@ class ScanI2CTwoWire : public ScanI2C
ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override;
- TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const;
-
bool exists(ScanI2C::DeviceType) const override;
size_t countDevices() const override;
+ static TwoWire *fetchI2CBus(ScanI2C::DeviceAddress);
+
protected:
FoundDevice firstOfOrNONE(size_t, DeviceType[]) const override;
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 0487d1fea..a61a71dde 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -38,14 +38,16 @@ template std::size_t array_count(const T (&)[N])
return N;
}
-#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
-#if defined(GPS_SERIAL_PORT)
-HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
-#else
-HardwareSerial *GPS::_serial_gps = &Serial1;
+#ifndef GPS_SERIAL_PORT
+#define GPS_SERIAL_PORT Serial1
#endif
+
+#if defined(ARCH_NRF52)
+Uart *GPS::_serial_gps = &GPS_SERIAL_PORT;
+#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
+HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
#elif defined(ARCH_RP2040)
-SerialUART *GPS::_serial_gps = &Serial1;
+SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT;
#else
HardwareSerial *GPS::_serial_gps = nullptr;
#endif
@@ -240,6 +242,9 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
buffer[bytesRead] = b;
bytesRead++;
if ((bytesRead == 767) || (b == '\r')) {
+#ifdef GPS_DEBUG
+ LOG_DEBUG(debugmsg.c_str());
+#endif
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
#ifdef GPS_DEBUG
LOG_DEBUG("Found: %s", message); // Log the found message
@@ -247,9 +252,6 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
return GNSS_RESPONSE_OK;
} else {
bytesRead = 0;
-#ifdef GPS_DEBUG
- LOG_DEBUG(debugmsg.c_str());
-#endif
}
}
}
@@ -494,22 +496,10 @@ bool GPS::setup()
if (!didSerialInit) {
int msglen = 0;
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
-#ifdef TRACKER_T1000_E
- // add power up/down strategy, improve ag3335 detection success
- digitalWrite(PIN_GPS_EN, LOW);
- delay(500);
- digitalWrite(GPS_VRTC_EN, LOW);
- delay(1000);
- digitalWrite(GPS_VRTC_EN, HIGH);
- delay(500);
- digitalWrite(PIN_GPS_EN, HIGH);
- delay(1000);
-#endif
if (probeTries < GPS_PROBETRIES) {
- LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]);
gnssModel = probe(serialSpeeds[speedSelect]);
if (gnssModel == GNSS_MODEL_UNKNOWN) {
- if (++speedSelect == array_count(serialSpeeds)) {
+ if (currentStep == 0 && ++speedSelect == array_count(serialSpeeds)) {
speedSelect = 0;
++probeTries;
}
@@ -518,10 +508,9 @@ bool GPS::setup()
// Rare Serial Speeds
#ifndef CONFIG_IDF_TARGET_ESP32C6
if (probeTries == GPS_PROBETRIES) {
- LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
gnssModel = probe(rareSerialSpeeds[speedSelect]);
if (gnssModel == GNSS_MODEL_UNKNOWN) {
- if (++speedSelect == array_count(rareSerialSpeeds)) {
+ if (currentStep == 0 && ++speedSelect == array_count(rareSerialSpeeds)) {
LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE);
return true;
}
@@ -1033,7 +1022,7 @@ void GPS::down()
LOG_DEBUG("%us until next search", sleepTime / 1000);
// If update interval less than 10 seconds, no attempt to sleep
- if (updateInterval <= 10 * 1000UL || sleepTime == 0)
+ if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS || sleepTime == 0)
setPowerState(GPS_IDLE);
else {
@@ -1094,7 +1083,7 @@ int32_t GPS::runOnce()
return disable();
}
if (!setup())
- return 2000; // Setup failed, re-run in two seconds
+ return currentDelay; // Setup failed, re-run in two seconds
// We have now loaded our saved preferences from flash
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
@@ -1104,6 +1093,29 @@ int32_t GPS::runOnce()
publishUpdate();
}
+ // ======================== GPS_ACTIVE state ========================
+ // In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages.
+ // We use the following logic to determine when to update the local position
+ // or time by running GPS::publishUpdate.
+ // Note: Local position update is asynchronous to position broadcast. We
+ // generally run this state every gps_update_interval seconds, and in most cases
+ // gps_update_interval is faster than the position broadcast interval so there's a
+ // fresh position ready when the device wants to broadcast one on the mesh.
+ //
+ // 1. Got a time for the first time --> set the time, don't publish.
+ // 2. Got a lock for the first time
+ // --> If gps_update_interval is <= 10s --> publishUpdate
+ // --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
+ // 3. Got a lock after turning back on
+ // --> If gps_update_interval is <= 10s --> publishUpdate
+ // --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
+ // 4. Hold has expired
+ // --> If we have a time and a location --> publishUpdate
+ // --> down()
+ // 5. Search time has expired
+ // --> If we have a time and a location --> publishUpdate
+ // --> If we had a location before but don't now --> publishUpdate
+ // --> down()
if (whileActive()) {
// if we have received valid NMEA claim we are connected
setConnected();
@@ -1113,55 +1125,81 @@ int32_t GPS::runOnce()
if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue())
up();
- // If we've already set time from the GPS, no need to ask the GPS
- bool gotTime = (getRTCQuality() >= RTCQualityGPS);
- if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
- gotTime = true;
- shouldPublish = true;
- }
-
+ // quality of the previous fix. We set it to 0 when we go down, so it's a way
+ // to check if we're getting a lock after being GPS_OFF.
uint8_t prev_fixQual = fixQual;
- bool gotLoc = lookForLocation();
- if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
- LOG_DEBUG("hasValidLocation RISING EDGE");
- hasValidLocation = true;
- shouldPublish = true;
- // Hold for 20secs after getting a lock to download ephemeris etc
- fixHoldEnds = millis() + 20000;
- }
- if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on.
- fixHoldEnds = millis() + 20000;
- shouldPublish = true; // Publish immediately, since next publish is at end of hold
- }
+ if (powerState == GPS_ACTIVE) {
+ // if gps_update_interval is <=10s, GPS never goes off, so we treat that differently
+ uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
- bool tooLong = scheduling.searchedTooLong();
- if (tooLong)
- LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
+ // 1. Got a time for the first time
+ bool gotTime = (getRTCQuality() >= RTCQualityGPS);
+ if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
+ gotTime = true;
+ }
- // Once we get a location we no longer desperately want an update
- if ((gotLoc && gotTime) || tooLong) {
+ // 2. Got a lock for the first time, or 3. Got a lock after turning back on
+ bool gotLoc = lookForLocation();
+ if (gotLoc) {
+#ifdef GPS_DEBUG
+ if (!hasValidLocation) { // declare that we have location ASAP
+ LOG_DEBUG("hasValidLocation RISING EDGE");
+ }
+#endif
+ if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) {
+ hasValidLocation = true;
+ shouldPublish = true;
+ } else if (!hasValidLocation || prev_fixQual == 0 || (fixHoldEnds + GPS_THREAD_INTERVAL) < millis()) {
+ hasValidLocation = true;
+ // Hold for up to 20secs after getting a lock to download ephemeris etc
+ uint32_t holdTime = updateInterval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS;
+ if (holdTime > GPS_FIX_HOLD_MAX_MS)
+ holdTime = GPS_FIX_HOLD_MAX_MS;
+ fixHoldEnds = millis() + holdTime;
+#ifdef GPS_DEBUG
+ LOG_DEBUG("Holding for %ums after lock", holdTime);
+#endif
+ }
+ }
+
+ bool tooLong = scheduling.searchedTooLong();
if (tooLong && !gotLoc) {
+ LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
// we didn't get a location during this ack window, therefore declare loss of lock
if (hasValidLocation) {
- LOG_DEBUG("hasValidLocation FALLING EDGE");
- }
- p = meshtastic_Position_init_default;
- hasValidLocation = false;
- }
- if (millis() > fixHoldEnds) {
- shouldPublish = true; // publish our update at the end of the lock hold
- publishUpdate();
- down();
+ p = meshtastic_Position_init_default;
+ hasValidLocation = false;
+ shouldPublish = true;
#ifdef GPS_DEBUG
- } else {
+ LOG_DEBUG("hasValidLocation FALLING EDGE");
+#endif
+ }
+ }
+
+ // Hold has expired , Search time has expired, we got a time only, or we never needed to hold.
+ bool holdExpired = (fixHoldEnds != 0 && millis() > fixHoldEnds);
+ if (shouldPublish || tooLong || holdExpired) {
+ if (gotTime && hasValidLocation) {
+ shouldPublish = true;
+ }
+ if (shouldPublish) {
+ fixHoldEnds = 0;
+ publishUpdate();
+ }
+
+ // There's a chance we just got a time, so keep going to see if we can get a location too
+ if (tooLong || holdExpired) {
+ down();
+ }
+
+#ifdef GPS_DEBUG
+ } else if (fixHoldEnds != 0) {
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
#endif
}
}
-
- // If state has changed do a publish
- publishUpdate();
+ // ===================== end GPS_ACTIVE state ========================
if (config.position.fixed_position == true && hasValidLocation)
return disable(); // This should trigger when we have a fixed position, and get that first position
@@ -1218,163 +1256,215 @@ static const char *DETECTED_MESSAGE = "%s detected";
GnssModel_t GPS::probe(int serialSpeed)
{
+ uint8_t buffer[768] = {0};
+
+ switch (currentStep) {
+ case 0: {
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
- _serial_gps->end();
- _serial_gps->begin(serialSpeed);
+ _serial_gps->end();
+ _serial_gps->begin(serialSpeed);
#elif defined(ARCH_RP2040)
- _serial_gps->end();
- _serial_gps->setFIFOSize(256);
- _serial_gps->begin(serialSpeed);
+ _serial_gps->end();
+ _serial_gps->setFIFOSize(256);
+ _serial_gps->begin(serialSpeed);
#else
- if (_serial_gps->baudRate() != serialSpeed) {
- LOG_DEBUG("Set Baud to %i", serialSpeed);
- _serial_gps->updateBaudRate(serialSpeed);
- }
+ if (_serial_gps->baudRate() != serialSpeed) {
+ LOG_DEBUG("Set GPS Baud to %i", serialSpeed);
+ _serial_gps->updateBaudRate(serialSpeed);
+ }
#endif
- memset(&ublox_info, 0, sizeof(ublox_info));
- uint8_t buffer[768] = {0};
- delay(100);
+ memset(&ublox_info, 0, sizeof(ublox_info));
+ delay(100);
- // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
- _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
- delay(20);
- // Close NMEA sequences on Ublox
- _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
- _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
- _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
- delay(20);
- // Close NMEA sequences on CM121
- _serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
- _serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
- _serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
- delay(20);
+#if defined(PIN_GPS_RESET) && PIN_GPS_RESET != -1
+ digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
+ delay(10);
+ digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
- // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
- std::vector unicore = {
- {"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
- PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
-
- std::vector atgm = {
- {"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
- /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
- {"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
- PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
-
- /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
- _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
- _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
- _serial_gps->write("$PAIR513*3D\r\n"); // save configuration
- std::vector airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
- {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
- {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
- PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
-
- PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
- PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
-
- // Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
- _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
- delay(20);
- std::vector mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
- {"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
- {"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
- {"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
-
- PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
-
- uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
- UBXChecksum(cfg_rate, sizeof(cfg_rate));
- clearBuffer();
- _serial_gps->write(cfg_rate, sizeof(cfg_rate));
- // Check that the returned response class and message ID are correct
- GPS_RESPONSE response = getACK(0x06, 0x08, 750);
- if (response == GNSS_RESPONSE_NONE) {
- LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
+ // attempt to detect the chip based on boot messages
+ std::vector passive_detect = {
+ {"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
+ {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
+ {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352},
+ {"UC6580", "UC6580", GNSS_MODEL_UC6580},
+ // as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now.
+ /*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/};
+ GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed);
+ if (detectedDriver != GNSS_MODEL_UNKNOWN) {
+ return detectedDriver;
+ }
+#endif
+ // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
+ _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
+ delay(20);
+ // Close NMEA sequences on Ublox
+ _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
+ _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
+ _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
+ delay(20);
+ // Close NMEA sequences on CM121
+ _serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
+ _serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
+ _serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
+ currentDelay = 20;
+ currentStep = 1;
return GNSS_MODEL_UNKNOWN;
- } else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
- LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
}
+ case 1: {
- memset(buffer, 0, sizeof(buffer));
- uint8_t _message_MONVER[8] = {
- 0xB5, 0x62, // Sync message for UBX protocol
- 0x0A, 0x04, // Message class and ID (UBX-MON-VER)
- 0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
- 0x00, 0x00 // Checksum
- };
- // Get Ublox gnss module hardware and software info
- UBXChecksum(_message_MONVER, sizeof(_message_MONVER));
- clearBuffer();
- _serial_gps->write(_message_MONVER, sizeof(_message_MONVER));
+ // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
+ std::vector unicore = {
+ {"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
+ PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
+ currentDelay = 20;
+ currentStep = 2;
+ return GNSS_MODEL_UNKNOWN;
+ }
+ case 2: {
+ std::vector atgm = {
+ {"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
+ /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
+ {"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
+ PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
+ currentDelay = 20;
+ currentStep = 3;
+ return GNSS_MODEL_UNKNOWN;
+ }
+ case 3: {
+ /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
+ _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
+ _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
+ _serial_gps->write("$PAIR513*3D\r\n"); // save configuration
+ std::vector airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
+ {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
+ {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
+ PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
+ currentDelay = 20;
+ currentStep = 4;
+ return GNSS_MODEL_UNKNOWN;
+ }
+ case 4: {
+ PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
+ PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
+ currentDelay = 20;
+ currentStep = 5;
+ return GNSS_MODEL_UNKNOWN;
+ }
+ case 5: {
- uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
- if (len) {
- uint16_t position = 0;
- for (int i = 0; i < 30; i++) {
- ublox_info.swVersion[i] = buffer[position];
- position++;
- }
- for (int i = 0; i < 10; i++) {
- ublox_info.hwVersion[i] = buffer[position];
- position++;
- }
+ // Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
+ _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
+ delay(20);
+ std::vector mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
+ {"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
+ {"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
+ {"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
- while (len >= position + 30) {
- for (int i = 0; i < 30; i++) {
- ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
- position++;
- }
- ublox_info.extensionNo++;
- if (ublox_info.extensionNo > 9)
- break;
- }
-
- LOG_DEBUG("Module Info : ");
- LOG_DEBUG("Soft version: %s", ublox_info.swVersion);
- LOG_DEBUG("Hard version: %s", ublox_info.hwVersion);
- LOG_DEBUG("Extensions:%d", ublox_info.extensionNo);
- for (int i = 0; i < ublox_info.extensionNo; i++) {
- LOG_DEBUG(" %s", ublox_info.extension[i]);
+ PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
+ currentDelay = 20;
+ currentStep = 6;
+ return GNSS_MODEL_UNKNOWN;
+ }
+ case 6: {
+ uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
+ UBXChecksum(cfg_rate, sizeof(cfg_rate));
+ clearBuffer();
+ _serial_gps->write(cfg_rate, sizeof(cfg_rate));
+ // Check that the returned response class and message ID are correct
+ GPS_RESPONSE response = getACK(0x06, 0x08, 750);
+ if (response == GNSS_RESPONSE_NONE) {
+ LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
+ currentDelay = 2000;
+ currentStep = 0;
+ return GNSS_MODEL_UNKNOWN;
+ } else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
+ LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
}
memset(buffer, 0, sizeof(buffer));
+ uint8_t _message_MONVER[8] = {
+ 0xB5, 0x62, // Sync message for UBX protocol
+ 0x0A, 0x04, // Message class and ID (UBX-MON-VER)
+ 0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
+ 0x00, 0x00 // Checksum
+ };
+ // Get Ublox gnss module hardware and software info
+ UBXChecksum(_message_MONVER, sizeof(_message_MONVER));
+ clearBuffer();
+ _serial_gps->write(_message_MONVER, sizeof(_message_MONVER));
- // tips: extensionNo field is 0 on some 6M GNSS modules
- for (int i = 0; i < ublox_info.extensionNo; ++i) {
- if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
- strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
- } else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
- char *ptr = nullptr;
- memset(buffer, 0, sizeof(buffer));
- strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
- LOG_DEBUG("Protocol Version:%s", (char *)buffer);
- if (strlen((char *)buffer)) {
- ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10);
- LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version);
- } else {
- ublox_info.protocol_version = 0;
+ uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
+ if (len) {
+ uint16_t position = 0;
+ for (int i = 0; i < 30; i++) {
+ ublox_info.swVersion[i] = buffer[position];
+ position++;
+ }
+ for (int i = 0; i < 10; i++) {
+ ublox_info.hwVersion[i] = buffer[position];
+ position++;
+ }
+
+ while (len >= position + 30) {
+ for (int i = 0; i < 30; i++) {
+ ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
+ position++;
+ }
+ ublox_info.extensionNo++;
+ if (ublox_info.extensionNo > 9)
+ break;
+ }
+
+ LOG_DEBUG("Module Info : ");
+ LOG_DEBUG("Soft version: %s", ublox_info.swVersion);
+ LOG_DEBUG("Hard version: %s", ublox_info.hwVersion);
+ LOG_DEBUG("Extensions:%d", ublox_info.extensionNo);
+ for (int i = 0; i < ublox_info.extensionNo; i++) {
+ LOG_DEBUG(" %s", ublox_info.extension[i]);
+ }
+
+ memset(buffer, 0, sizeof(buffer));
+
+ // tips: extensionNo field is 0 on some 6M GNSS modules
+ for (int i = 0; i < ublox_info.extensionNo; ++i) {
+ if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
+ strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
+ } else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
+ char *ptr = nullptr;
+ memset(buffer, 0, sizeof(buffer));
+ strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
+ LOG_DEBUG("Protocol Version:%s", (char *)buffer);
+ if (strlen((char *)buffer)) {
+ ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10);
+ LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version);
+ } else {
+ ublox_info.protocol_version = 0;
+ }
}
}
- }
- if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
- LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
- return GNSS_MODEL_UBLOX6;
- } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
- LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
- return GNSS_MODEL_UBLOX7;
- } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
- LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
- return GNSS_MODEL_UBLOX8;
- } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
- LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
- return GNSS_MODEL_UBLOX9;
- } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
- LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
- return GNSS_MODEL_UBLOX10;
+ if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
+ return GNSS_MODEL_UBLOX6;
+ } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
+ return GNSS_MODEL_UBLOX7;
+ } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
+ return GNSS_MODEL_UBLOX8;
+ } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
+ return GNSS_MODEL_UBLOX9;
+ } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
+ return GNSS_MODEL_UBLOX10;
+ }
}
}
+ }
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
+ currentDelay = 2000;
+ currentStep = 0;
return GNSS_MODEL_UNKNOWN;
}
@@ -1403,12 +1493,12 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
-#ifdef GPS_DEBUG
- LOG_DEBUG(response);
-#endif
// check if we can see our chips
for (const auto &chipInfo : responseMap) {
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
+#ifdef GPS_DEBUG
+ LOG_DEBUG(response);
+#endif
LOG_INFO("%s detected", chipInfo.chipName.c_str());
delete[] response; // Cleanup before return
return chipInfo.driver;
@@ -1416,6 +1506,9 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
+#ifdef GPS_DEBUG
+ LOG_DEBUG(response);
+#endif
// Reset the response buffer for the next potential message
responseLen = 0;
response[0] = '\0';
@@ -1434,10 +1527,7 @@ GPS *GPS::createGps()
int8_t _rx_gpio = config.position.rx_gpio;
int8_t _tx_gpio = config.position.tx_gpio;
int8_t _en_gpio = config.position.gps_en_gpio;
-#if HAS_GPS && !defined(ARCH_ESP32)
- _rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags.
- _tx_gpio = 1;
-#endif
+
#if defined(GPS_RX_PIN)
if (!_rx_gpio)
_rx_gpio = GPS_RX_PIN;
@@ -1502,8 +1592,6 @@ GPS *GPS::createGps()
#ifdef PIN_GPS_RESET
pinMode(PIN_GPS_RESET, OUTPUT);
- digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
- delay(10);
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
#endif
@@ -1513,16 +1601,28 @@ GPS *GPS::createGps()
_serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256
#endif
-// ESP32 has a special set of parameters vs other arduino ports
-#if defined(ARCH_ESP32)
LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio);
LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio);
+
+// ESP32 has a special set of parameters vs other arduino ports
+#if defined(ARCH_ESP32)
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
#elif defined(ARCH_RP2040)
+ _serial_gps->setPinout(new_gps->tx_gpio, new_gps->rx_gpio);
_serial_gps->setFIFOSize(256);
_serial_gps->begin(GPS_BAUDRATE);
-#else
+#elif defined(ARCH_NRF52)
+ _serial_gps->setPins(new_gps->rx_gpio, new_gps->tx_gpio);
_serial_gps->begin(GPS_BAUDRATE);
+#elif defined(ARCH_STM32WL)
+ _serial_gps->setTx(new_gps->tx_gpio);
+ _serial_gps->setRx(new_gps->rx_gpio);
+ _serial_gps->begin(GPS_BAUDRATE);
+#elif defined(ARCH_PORTDUINO)
+ // Portduino can't set the GPS pins directly.
+ _serial_gps->begin(GPS_BAUDRATE);
+#else
+#error Unsupported architecture!
#endif
}
return new_gps;
@@ -1589,8 +1689,12 @@ bool GPS::lookForLocation()
#ifndef TINYGPS_OPTION_NO_STATISTICS
if (reader.failedChecksum() > lastChecksumFailCount) {
- LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
- reader.failedChecksum());
+// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them.
+#ifndef GPS_DEBUG
+ if (reader.failedChecksum() > 4)
+#endif
+ LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
+ reader.failedChecksum());
lastChecksumFailCount = reader.failedChecksum();
}
#endif
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index cba767460..59cee7113 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -16,6 +16,9 @@
#define GPS_EN_ACTIVE 1
#endif
+static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
+static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;
+
typedef enum {
GNSS_MODEL_ATGM336H,
GNSS_MODEL_MTK,
@@ -151,6 +154,8 @@ class GPS : private concurrency::OSThread
TinyGPSPlus reader;
uint8_t fixQual = 0; // fix quality from GPGGA
uint32_t lastChecksumFailCount = 0;
+ uint8_t currentStep = 0;
+ int32_t currentDelay = 2000;
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
// (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
@@ -173,8 +178,6 @@ class GPS : private concurrency::OSThread
*/
bool hasValidLocation = false; // default to false, until we complete our first read
- bool isInPowersave = false;
-
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
bool hasGPS = false; // Do we have a GPS we are talking to
@@ -191,6 +194,8 @@ class GPS : private concurrency::OSThread
/** If !NULL we will use this serial port to construct our GPS */
#if defined(ARCH_RP2040)
static SerialUART *_serial_gps;
+#elif defined(ARCH_NRF52)
+ static Uart *_serial_gps;
#else
static HardwareSerial *_serial_gps;
#endif
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 665a9aaa3..1122f0a51 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -112,7 +112,11 @@ RTCSetResult readFromRTC()
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
uint32_t now = millis();
+#ifdef MUZI_BASE
+ ArtronShop_RX8130CE rtc(&Wire1);
+#else
ArtronShop_RX8130CE rtc(&Wire);
+#endif
tm t;
if (rtc.getTime(&t)) {
tv.tv_sec = gm_mktime(&t);
@@ -245,7 +249,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
}
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
+#ifdef MUZI_BASE
+ ArtronShop_RX8130CE rtc(&Wire1);
+#else
ArtronShop_RX8130CE rtc(&Wire);
+#endif
tm *t = gmtime(&tv->tv_sec);
if (rtc.setTime(*t)) {
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
@@ -310,7 +318,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
- LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+ LOG_WARN("Ignore time (%lu) before build epoch (%lu)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
@@ -319,7 +327,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
// Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
- LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
+ LOG_WARN("Ignore time (%lu) too far in the future (build epoch: %lu, max allowed: %lu)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 31c211019..dc9806156 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -435,6 +435,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif
+#if defined(MUZI_BASE)
+ dispdev->init();
+ dispdev->setBrightness(brightness);
+ dispdev->flipScreenVertically();
+ dispdev->resetDisplay();
+ digitalWrite(SCREEN_12V_ENABLE, HIGH);
+ delay(100);
+#endif
#if !ARCH_PORTDUINO
dispdev->displayOn();
#endif
@@ -443,7 +451,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
if (uiconfig.screen_brightness == 1)
digitalWrite(PIN_EINK_EN, HIGH);
#elif defined(PCA_PIN_EINK_EN)
- if (uiconfig.screen_brightness == 1)
+ if (uiconfig.screen_brightness > 0)
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
#endif
@@ -484,6 +492,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#endif
dispdev->displayOff();
+
+#ifdef SCREEN_12V_ENABLE
+ digitalWrite(SCREEN_12V_ENABLE, LOW);
+#endif
#ifdef USE_ST7789
SPI1.end();
#if defined(ARCH_ESP32)
@@ -534,7 +546,7 @@ void Screen::setup()
static_cast(dispdev)->setDetected(model);
#endif
-#ifdef USE_SH1107_128_64
+#if defined(USE_SH1107_128_64) || defined(USE_SH1107)
static_cast(dispdev)->setSubtype(7);
#endif
@@ -542,6 +554,9 @@ void Screen::setup()
// Apply custom RGB color (e.g. Heltec T114/T190)
static_cast(dispdev)->setRGB(TFT_MESH);
#endif
+#if defined(MUZI_BASE)
+ dispdev->delayPoweron = true;
+#endif
// === Initialize display and UI system ===
ui->init();
@@ -1428,6 +1443,9 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
}
nodeDB->updateGUI = false;
break;
+ case STATUS_TYPE_POWER:
+ forceDisplay(true);
+ break;
}
return 0;
@@ -1487,7 +1505,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
strcpy(banner, "Alert Received");
}
screen->showSimpleBanner(banner, 3000);
- } else if (!channel.settings.mute) {
+ } else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
@@ -1503,7 +1521,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
screen->showSimpleBanner(banner, 1500);
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
- (!isBroadcast(packet->to) && isToUs(p))) {
+ (!isBroadcast(packet->to) && isToUs(packet))) {
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet contains an alert and alert bell buzzer is enabled
// - packet is a non-broadcast that is addressed to this node
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 74b8d7c5d..375bc2805 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -249,6 +249,8 @@ class Screen : public concurrency::OSThread
bool isOverlayBannerShowing();
+ bool isScreenOn() { return screenOn; }
+
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
char ourId[5];
diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index dcaa5d69b..1645789a7 100644
--- a/src/graphics/SharedUIDisplay.cpp
+++ b/src/graphics/SharedUIDisplay.cpp
@@ -1,6 +1,10 @@
-#include "graphics/SharedUIDisplay.h"
+#include "configuration.h"
+#if HAS_SCREEN
+#include "MeshService.h"
#include "RTC.h"
+#include "draw/NodeListRenderer.h"
#include "graphics/ScreenFonts.h"
+#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/UIRenderer.h"
#include "main.h"
#include "meshtastic/config.pb.h"
@@ -396,6 +400,43 @@ const int *getTextPositions(OLEDDisplay *display)
return textPositions;
}
+// *************************
+// * Common Footer Drawing *
+// *************************
+void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
+{
+ bool drawConnectionState = false;
+ if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI ||
+ service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET ||
+ service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) {
+ drawConnectionState = true;
+ }
+
+ if (drawConnectionState) {
+ if (isHighResolution) {
+ const int scale = 2;
+ const int bytesPerRow = (connection_icon_width + 7) / 8;
+ int iconX = 0;
+ int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
+
+ for (int yy = 0; yy < connection_icon_height; ++yy) {
+ const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
+ for (int xx = 0; xx < connection_icon_width; ++xx) {
+ const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
+ const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
+ if (byteVal & bitMask) {
+ display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
+ }
+ }
+ }
+
+ } else {
+ display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
+ connection_icon);
+ }
+ }
+}
+
bool isAllowedPunctuation(char c)
{
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
@@ -423,3 +464,4 @@ std::string sanitizeString(const std::string &input)
}
} // namespace graphics
+#endif
\ No newline at end of file
diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h
index e1a7c6383..b51dfea36 100644
--- a/src/graphics/SharedUIDisplay.h
+++ b/src/graphics/SharedUIDisplay.h
@@ -52,6 +52,9 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
bool show_date = false);
+// Shared battery/time/mail header
+void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y);
+
const int *getTextPositions(OLEDDisplay *display);
bool isAllowedPunctuation(char c);
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 0663602d9..87593b0d4 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -422,7 +422,57 @@ static LGFX *tft = nullptr;
#elif defined(ST7789_CS)
#include // Graphics and font library for ST7735 driver chip
+#ifdef HELTEC_V4_TFT
+#include "chsc6x.h"
+#include "lgfx/v1/Touch.hpp"
+namespace lgfx
+{
+inline namespace v1
+{
+class TOUCH_CHSC6X : public ITouch
+{
+ public:
+ TOUCH_CHSC6X(void)
+ {
+ _cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
+ _cfg.x_min = 0;
+ _cfg.x_max = 240;
+ _cfg.y_min = 0;
+ _cfg.y_max = 320;
+ };
+ bool init(void) override
+ {
+ if (chsc6xTouch == nullptr) {
+ chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
+ }
+ chsc6xTouch->chsc6x_init();
+ return true;
+ };
+
+ uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override
+ {
+ uint16_t raw_x, raw_y;
+ if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) {
+ tp[0].x = 320 - 1 - raw_y;
+ tp[0].y = 240 - 1 - raw_x;
+ tp[0].size = 1;
+ tp[0].id = 1;
+ return 1;
+ }
+ tp[0].size = 0;
+ return 0;
+ };
+
+ void wakeup(void) override{};
+ void sleep(void) override{};
+
+ private:
+ chsc6x *chsc6xTouch = nullptr;
+};
+} // namespace v1
+} // namespace lgfx
+#endif
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7789 _panel_instance;
@@ -431,6 +481,8 @@ class LGFX : public lgfx::LGFX_Device
#if HAS_TOUCHSCREEN
#if defined(T_WATCH_S3) || defined(ELECROW)
lgfx::Touch_FT5x06 _touch_instance;
+#elif defined(HELTEC_V4_TFT)
+ lgfx::TOUCH_CHSC6X _touch_instance;
#else
lgfx::Touch_GT911 _touch_instance;
#endif
@@ -464,9 +516,9 @@ class LGFX : public lgfx::LGFX_Device
{ // Set the display panel control.
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
- cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
- cfg.pin_rst = -1; // Pin number where RST is connected (-1 = disable)
- cfg.pin_busy = -1; // Pin number where BUSY is connected (-1 = disable)
+ cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
+ cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable)
+ cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable)
// The following setting values ​​are general initial values ​​for each panel, so please comment out any
// unknown items and try them.
diff --git a/src/graphics/TimeFormatters.cpp b/src/graphics/TimeFormatters.cpp
index 47036078b..0a1c23341 100644
--- a/src/graphics/TimeFormatters.cpp
+++ b/src/graphics/TimeFormatters.cpp
@@ -101,3 +101,23 @@ void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
else
snprintf(timeStr, maxLength, "unknown age");
}
+
+void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs)
+{
+ uint32_t days = uptimeMillis / 86400000;
+ uint32_t hours = (uptimeMillis % 86400000) / 3600000;
+ uint32_t mins = (uptimeMillis % 3600000) / 60000;
+ uint32_t secs = (uptimeMillis % 60000) / 1000;
+
+ if (days) {
+ snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours);
+ } else if (hours) {
+ snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins);
+ } else if (!includeSecs) {
+ snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins);
+ } else if (mins) {
+ snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs);
+ } else {
+ snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs);
+ }
+}
diff --git a/src/graphics/TimeFormatters.h b/src/graphics/TimeFormatters.h
index b3d8413a2..f86c6725c 100644
--- a/src/graphics/TimeFormatters.h
+++ b/src/graphics/TimeFormatters.h
@@ -24,3 +24,10 @@ bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int
* @param maxLength Maximum length of the resulting string buffer
*/
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
+
+/**
+ * Get a compact human-readable string that only shows the largest non-zero time components.
+ * For example, 0 days 1 hour 2 minutes will display as "1h 2m" but 1 day 2 hours 3 minutes
+ * will display as "1d 2h".
+ */
+void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs = false);
diff --git a/src/graphics/VirtualKeyboard.cpp b/src/graphics/VirtualKeyboard.cpp
index 84d5551cb..8062a0338 100644
--- a/src/graphics/VirtualKeyboard.cpp
+++ b/src/graphics/VirtualKeyboard.cpp
@@ -1,5 +1,6 @@
-#include "VirtualKeyboard.h"
#include "configuration.h"
+#if HAS_SCREEN
+#include "VirtualKeyboard.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
@@ -736,3 +737,4 @@ bool VirtualKeyboard::isTimedOut() const
}
} // namespace graphics
+#endif
\ No newline at end of file
diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index 751db8d88..cc6a70957 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -194,17 +194,12 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0;
-#ifdef T_WATCH_S3
- if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
- graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
- }
-#endif
-
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
char timeString[16];
int hour = 0;
int minute = 0;
int second = 0;
+
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
@@ -215,11 +210,11 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
}
bool isPM = hour >= 12;
- // hour = hour > 12 ? hour - 12 : hour;
if (config.display.use_12h_clock) {
hour %= 12;
- if (hour == 0)
+ if (hour == 0) {
hour = 12;
+ }
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
} else {
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
@@ -229,24 +224,56 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
char secondString[8];
snprintf(secondString, sizeof(secondString), "%02d", second);
-#ifdef T_WATCH_S3
- float scale = 1.5;
-#elif defined(CHATTER_2)
- float scale = 1.1;
-#else
- float scale = 0.75;
- if (isHighResolution) {
- scale = 1.5;
- }
-#endif
+ static bool scaleInitialized = false;
+ static float scale = 0.75f;
+ static float segmentWidth = SEGMENT_WIDTH * 0.75f;
+ static float segmentHeight = SEGMENT_HEIGHT * 0.75f;
- uint16_t segmentWidth = SEGMENT_WIDTH * scale;
- uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
+ if (!scaleInitialized) {
+ float screenwidth_target_ratio = 0.80f; // Target 80% of display width (adjustable)
+ float max_scale = 3.5f; // Safety limit to avoid runaway scaling
+ float step = 0.05f; // Step increment per iteration
+
+ float target_width = display->getWidth() * screenwidth_target_ratio;
+ float target_height =
+ display->getHeight() -
+ (isHighResolution
+ ? 46
+ : 33); // Be careful adjusting this number, we have to account for header and the text under the time
+
+ float calculated_width_size = 0.0f;
+ float calculated_height_size = 0.0f;
+
+ while (true) {
+ segmentWidth = SEGMENT_WIDTH * scale;
+ segmentHeight = SEGMENT_HEIGHT * scale;
+
+ calculated_width_size = segmentHeight + ((segmentWidth + (segmentHeight * 2) + 4) * 4);
+ calculated_height_size = segmentHeight + ((segmentHeight + (segmentHeight * 2) + 4) * 2);
+
+ if (calculated_width_size >= target_width || calculated_height_size >= target_height || scale >= max_scale) {
+ break;
+ }
+
+ scale += step;
+ }
+
+ // If we overshot width, back off one step and recompute segment sizes
+ if (calculated_width_size > target_width || calculated_height_size > target_height) {
+ scale -= step;
+ segmentWidth = SEGMENT_WIDTH * scale;
+ segmentHeight = SEGMENT_HEIGHT * scale;
+ }
+
+ scaleInitialized = true;
+ }
+
+ size_t len = strlen(timeString);
// calculate hours:minutes string width
- uint16_t timeStringWidth = strlen(timeString) * 5;
+ uint16_t timeStringWidth = len * 5; // base spacing between characters
- for (uint8_t i = 0; i < strlen(timeString); i++) {
+ for (size_t i = 0; i < len; i++) {
char character = timeString[i];
if (character == ':') {
@@ -257,19 +284,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
}
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
-
uint16_t startingHourMinuteTextX = hourMinuteTextX;
- uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
+ uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2) + 2;
// iterate over characters in hours:minutes string and draw segmented characters
- for (uint8_t i = 0; i < strlen(timeString); i++) {
+ for (size_t i = 0; i < len; i++) {
char character = timeString[i];
if (character == ':') {
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
hourMinuteTextX += segmentHeight + 6;
+ if (scale >= 2.0f) {
+ hourMinuteTextX += (uint16_t)(4.5f * scale);
+ }
} else {
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
@@ -279,34 +308,27 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
hourMinuteTextX += 5;
}
- // draw seconds string
+ // draw seconds string + AM/PM
display->setFont(FONT_SMALL);
int xOffset = (isHighResolution) ? 0 : -1;
if (hour >= 10) {
xOffset += (isHighResolution) ? 32 : 18;
}
- int yOffset = (isHighResolution) ? 3 : 1;
-#ifdef SENSECAP_INDICATOR
- yOffset -= 3;
-#endif
-#ifdef T_DECK
- yOffset -= 5;
-#endif
+
if (config.display.use_12h_clock) {
- display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
- isPM ? "pm" : "am");
+ display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - 1, isPM ? "pm" : "am");
}
#ifndef USE_EINK
xOffset = (isHighResolution) ? 18 : 10;
- display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
+ if (scale >= 2.0f) {
+ xOffset -= (int)(4.5f * scale);
+ }
+ display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - 1,
secondString);
#endif
-}
-void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
-{
- display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
+ graphics::drawCommonFooter(display, x, y);
}
// Draw an analog clock
@@ -319,11 +341,6 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0;
-#ifdef T_WATCH_S3
- if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
- drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
- }
-#endif
// clock face center coordinates
int16_t centerX = display->getWidth() / 2;
int16_t centerY = display->getHeight() / 2;
@@ -516,6 +533,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->drawLine(centerX, centerY, secondX, secondY);
#endif
}
+ graphics::drawCommonFooter(display, x, y);
}
} // namespace ClockRenderer
diff --git a/src/graphics/draw/ClockRenderer.h b/src/graphics/draw/ClockRenderer.h
index c8ba62868..eace26cf5 100644
--- a/src/graphics/draw/ClockRenderer.h
+++ b/src/graphics/draw/ClockRenderer.h
@@ -24,7 +24,6 @@ void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int heig
// UI elements for clock displays
// void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
-void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
} // namespace ClockRenderer
diff --git a/src/graphics/draw/CompassRenderer.cpp b/src/graphics/draw/CompassRenderer.cpp
index 0e5a1d727..629949ffd 100644
--- a/src/graphics/draw/CompassRenderer.cpp
+++ b/src/graphics/draw/CompassRenderer.cpp
@@ -1,3 +1,5 @@
+#include "configuration.h"
+#if HAS_SCREEN
#include "CompassRenderer.h"
#include "NodeDB.h"
#include "UIRenderer.h"
@@ -135,3 +137,4 @@ uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
} // namespace CompassRenderer
} // namespace graphics
+#endif
\ No newline at end of file
diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index 60abd661e..79c1e7e61 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -3,6 +3,7 @@
#include "../Screen.h"
#include "DebugRenderer.h"
#include "FSCommon.h"
+#include "MeshService.h"
#include "NodeDB.h"
#include "Throttle.h"
#include "UIRenderer.h"
@@ -10,6 +11,7 @@
#include "gps/RTC.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TimeFormatters.h"
#include "graphics/images.h"
#include "main.h"
#include "mesh/Channels.h"
@@ -223,6 +225,8 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
+ graphics::drawCommonFooter(display, x, y);
+
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
@@ -503,6 +507,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
chUtilPercentage);
#endif
+ graphics::drawCommonFooter(display, x, y);
}
// ****************************
@@ -642,27 +647,48 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
- display->drawString(nameX, getTextPositions(display)[line], appversionstr);
-#if !defined(M5STACK_UNITC6L)
- if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
- line += 1;
+ display->drawString(nameX, getTextPositions(display)[line++], appversionstr);
+
+ if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
char uptimeStr[32] = "";
- uint32_t uptime = millis() / 1000;
- uint32_t days = uptime / 86400;
- uint32_t hours = (uptime % 86400) / 3600;
- uint32_t mins = (uptime % 3600) / 60;
- // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
- if (days)
- snprintf(uptimeStr, sizeof(uptimeStr), " Up: %ud %uh", days, hours);
- else if (hours)
- snprintf(uptimeStr, sizeof(uptimeStr), " Up: %uh %um", hours, mins);
- else
- snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
+ getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
textWidth = display->getStringWidth(uptimeStr);
nameX = (SCREEN_WIDTH - textWidth) / 2;
- display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
+ display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
}
-#endif
+
+ if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show API state if the screen can show it
+ char api_state[32] = "";
+ const char *clientWord = nullptr;
+
+ // Determine if narrow or wide screen
+ if (isHighResolution) {
+ clientWord = "Client";
+ } else {
+ clientWord = "App";
+ }
+ snprintf(api_state, sizeof(api_state), "No %ss Connected", clientWord);
+
+ if (service->api_state == service->STATE_BLE) {
+ snprintf(api_state, sizeof(api_state), "%s Connected (BLE)", clientWord);
+ } else if (service->api_state == service->STATE_WIFI) {
+ snprintf(api_state, sizeof(api_state), "%s Connected (WiFi)", clientWord);
+ } else if (service->api_state == service->STATE_SERIAL) {
+ snprintf(api_state, sizeof(api_state), "%s Connected (Serial)", clientWord);
+ } else if (service->api_state == service->STATE_PACKET) {
+ snprintf(api_state, sizeof(api_state), "%s Connected (Internal)", clientWord);
+ } else if (service->api_state == service->STATE_HTTP) {
+ snprintf(api_state, sizeof(api_state), "%s Connected (HTTP)", clientWord);
+ } else if (service->api_state == service->STATE_ETH) {
+ snprintf(api_state, sizeof(api_state), "%s Connected (Ethernet)", clientWord);
+ }
+ if (api_state[0] != '\0') {
+ display->drawString((SCREEN_WIDTH - display->getStringWidth(api_state)) / 2, getTextPositions(display)[line++],
+ api_state);
+ }
+ }
+
+ graphics::drawCommonFooter(display, x, y);
}
// ****************************
@@ -694,4 +720,4 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1
} // namespace DebugRenderer
} // namespace graphics
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index c93e34545..bd647c3d8 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -119,6 +119,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
auto changes = SEGMENT_CONFIG;
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
+#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) {
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
@@ -139,6 +140,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
}
+#endif
config.lora.tx_enabled = true;
initRegion();
if (myRegion->dutyCycle < 100) {
@@ -515,7 +517,7 @@ void menuHandler::homeBaseMenu()
}
saveUIConfig();
#elif defined(PCA_PIN_EINK_EN)
- if (uiconfig.screen_brightness == 1) {
+ if (uiconfig.screen_brightness > 0) {
uiconfig.screen_brightness = 0;
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
} else {
@@ -574,21 +576,16 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu()
{
- enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
+ enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications;
-#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
- defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
- optionsArray[options] = "Screen Options";
+ optionsArray[options] = "Display Options";
optionsEnumArray[options++] = ScreenOptions;
-#endif
- optionsArray[options] = "Frame Visiblity Toggle";
- optionsEnumArray[options++] = FrameToggles;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Bluetooth";
#else
@@ -626,9 +623,6 @@ void menuHandler::systemBaseMenu()
} else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow();
- } else if (selected == FrameToggles) {
- menuHandler::menuQueue = menuHandler::FrameToggles;
- screen->runNow();
} else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow();
@@ -762,19 +756,52 @@ void menuHandler::nodeListMenu()
screen->showOverlayBanner(bannerOptions);
}
+void menuHandler::nodeNameLengthMenu()
+{
+ enum OptionsNumbers { Back, Long, Short };
+ static const char *optionsArray[] = {"Back", "Long", "Short"};
+ BannerOverlayOptions bannerOptions;
+ bannerOptions.message = "Node Name Length";
+ bannerOptions.optionsArrayPtr = optionsArray;
+ bannerOptions.optionsCount = 3;
+ bannerOptions.bannerCallback = [](int selected) -> void {
+ if (selected == Long) {
+ // Set names to long
+ LOG_INFO("Setting names to long");
+ config.display.use_long_node_name = true;
+ } else if (selected == Short) {
+ // Set names to short
+ LOG_INFO("Setting names to short");
+ config.display.use_long_node_name = false;
+ } else if (selected == Back) {
+ menuQueue = screen_options_menu;
+ screen->runNow();
+ }
+ };
+ bannerOptions.InitialSelected = config.display.use_long_node_name == true ? 1 : 2;
+ screen->showOverlayBanner(bannerOptions);
+}
+
void menuHandler::resetNodeDBMenu()
{
- static const char *optionsArray[] = {"Back", "Confirm"};
+ static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Confirm Reset NodeDB";
bannerOptions.optionsArrayPtr = optionsArray;
- bannerOptions.optionsCount = 2;
+ bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
- if (selected == 1) {
+ if (selected == 1 || selected == 2) {
disableBluetooth();
+ screen->setFrames(Screen::FOCUS_DEFAULT);
+ }
+ if (selected == 1) {
LOG_INFO("Initiate node-db reset");
nodeDB->resetNodes();
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
+ } else if (selected == 2) {
+ LOG_INFO("Initiate node-db reset but keeping favorites");
+ nodeDB->resetNodes(1);
+ rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
};
screen->showOverlayBanner(bannerOptions);
@@ -910,7 +937,9 @@ void menuHandler::BluetoothToggleMenu()
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
- if (selected == 1 || selected == 2) {
+ if (selected == 0)
+ return;
+ else if (selected != (config.bluetooth.enabled ? 1 : 2)) {
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
@@ -1304,11 +1333,16 @@ void menuHandler::screenOptionsMenu()
hasSupportBrightness = false;
#endif
- enum optionsNumbers { Back, Brightness, ScreenColor };
- static const char *optionsArray[4] = {"Back"};
- static int optionsEnumArray[4] = {Back};
+ enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor, FrameToggles, DisplayUnits };
+ static const char *optionsArray[5] = {"Back"};
+ static int optionsEnumArray[5] = {Back};
int options = 1;
+#if defined(T_DECK) || defined(T_LORA_PAGER)
+ optionsArray[options] = "Show Long/Short Name";
+ optionsEnumArray[options++] = NodeNameLength;
+#endif
+
// Only show brightness for B&W displays
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
@@ -1321,8 +1355,14 @@ void menuHandler::screenOptionsMenu()
optionsEnumArray[options++] = ScreenColor;
#endif
+ optionsArray[options] = "Frame Visibility Toggle";
+ optionsEnumArray[options++] = FrameToggles;
+
+ optionsArray[options] = "Display Units";
+ optionsEnumArray[options++] = DisplayUnits;
+
BannerOverlayOptions bannerOptions;
- bannerOptions.message = "Screen Options";
+ bannerOptions.message = "Display Options";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -1333,6 +1373,15 @@ void menuHandler::screenOptionsMenu()
} else if (selected == ScreenColor) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
+ } else if (selected == NodeNameLength) {
+ menuHandler::menuQueue = menuHandler::node_name_length_menu;
+ screen->runNow();
+ } else if (selected == FrameToggles) {
+ menuHandler::menuQueue = menuHandler::FrameToggles;
+ screen->runNow();
+ } else if (selected == DisplayUnits) {
+ menuHandler::menuQueue = menuHandler::DisplayUnits;
+ screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
@@ -1431,6 +1480,8 @@ void menuHandler::FrameToggles_menu()
lora,
clock,
show_favorites,
+ show_telemetry,
+ show_power,
enumEnd
};
static const char *optionsArray[enumEnd] = {"Finish"};
@@ -1469,6 +1520,12 @@ void menuHandler::FrameToggles_menu()
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
optionsEnumArray[options++] = show_favorites;
+ optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Telemetry" : "Show Telemetry";
+ optionsEnumArray[options++] = show_telemetry;
+
+ optionsArray[options] = moduleConfig.telemetry.power_screen_enabled ? "Hide Power" : "Show Power";
+ optionsEnumArray[options++] = show_power;
+
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Show/Hide Frames";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -1523,6 +1580,42 @@ void menuHandler::FrameToggles_menu()
screen->toggleFrameVisibility("show_favorites");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
+ } else if (selected == show_telemetry) {
+ moduleConfig.telemetry.environment_screen_enabled = !moduleConfig.telemetry.environment_screen_enabled;
+ menuHandler::menuQueue = menuHandler::FrameToggles;
+ screen->runNow();
+ } else if (selected == show_power) {
+ moduleConfig.telemetry.power_screen_enabled = !moduleConfig.telemetry.power_screen_enabled;
+ menuHandler::menuQueue = menuHandler::FrameToggles;
+ screen->runNow();
+ }
+ };
+ screen->showOverlayBanner(bannerOptions);
+}
+
+void menuHandler::DisplayUnits_menu()
+{
+ enum optionsNumbers { Back, MetricUnits, ImperialUnits };
+
+ static const char *optionsArray[] = {"Back", "Metric", "Imperial"};
+ BannerOverlayOptions bannerOptions;
+ bannerOptions.message = " Select display units";
+ bannerOptions.optionsArrayPtr = optionsArray;
+ bannerOptions.optionsCount = 3;
+ if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
+ bannerOptions.InitialSelected = 2;
+ else
+ bannerOptions.InitialSelected = 1;
+ bannerOptions.bannerCallback = [](int selected) -> void {
+ if (selected == MetricUnits) {
+ config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC;
+ service->reloadConfig(SEGMENT_CONFIG);
+ } else if (selected == ImperialUnits) {
+ config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL;
+ service->reloadConfig(SEGMENT_CONFIG);
+ } else {
+ menuHandler::menuQueue = menuHandler::screen_options_menu;
+ screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@@ -1594,6 +1687,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case brightness_picker:
BrightnessPickerMenu();
break;
+ case node_name_length_menu:
+ nodeNameLengthMenu();
+ break;
case reboot_menu:
rebootMenu();
break;
@@ -1639,6 +1735,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case FrameToggles:
FrameToggles_menu();
break;
+ case DisplayUnits:
+ DisplayUnits_menu();
+ break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;
@@ -1653,4 +1752,4 @@ void menuHandler::saveUIConfig()
} // namespace graphics
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 47dbb2543..a611b7c9d 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -43,7 +43,9 @@ class menuHandler
key_verification_final_prompt,
trace_route_menu,
throttle_message,
- FrameToggles
+ node_name_length_menu,
+ FrameToggles,
+ DisplayUnits
};
static screenMenus menuQueue;
@@ -85,7 +87,9 @@ class menuHandler
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
+ static void nodeNameLengthMenu();
static void FrameToggles_menu();
+ static void DisplayUnits_menu();
static void textMessageMenu();
private:
diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp
index 6971826de..da6ec7abc 100644
--- a/src/graphics/draw/MessageRenderer.cpp
+++ b/src/graphics/draw/MessageRenderer.cpp
@@ -213,6 +213,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
#else
display->drawString(center_text, getTextPositions(display)[2], messageString);
#endif
+ graphics::drawCommonFooter(display, x, y);
return;
}
@@ -423,6 +424,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Draw header at the end to sort out overlapping elements
graphics::drawCommonHeader(display, x, y, titleStr);
#endif
+ graphics::drawCommonFooter(display, x, y);
}
std::vector generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp
index 7d6a38dd3..1a36a6188 100644
--- a/src/graphics/draw/NodeListRenderer.cpp
+++ b/src/graphics/draw/NodeListRenderer.cpp
@@ -53,28 +53,56 @@ static int scrollIndex = 0;
// Utility Functions
// =============================
-const char *getSafeNodeName(meshtastic_NodeInfoLite *node)
+const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node)
{
+ const char *name = NULL;
static char nodeName[16] = "?";
- if (node->has_user && strlen(node->user.short_name) > 0) {
- bool valid = true;
- const char *name = node->user.short_name;
- for (size_t i = 0; i < strlen(name); i++) {
- uint8_t c = (uint8_t)name[i];
- if (c < 32 || c > 126) {
- valid = false;
- break;
- }
- }
- if (valid) {
- strncpy(nodeName, name, sizeof(nodeName) - 1);
- nodeName[sizeof(nodeName) - 1] = '\0';
+ if (config.display.use_long_node_name == true) {
+ if (node->has_user && strlen(node->user.long_name) > 0) {
+ name = node->user.long_name;
} else {
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
}
+ } else {
+ if (node->has_user && strlen(node->user.short_name) > 0) {
+ name = node->user.short_name;
+ } else {
+ snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
+ }
+ }
+
+ // Use sanitizeString() function and copy directly into nodeName
+ std::string sanitized_name = sanitizeString(name ? name : "");
+
+ if (!sanitized_name.empty()) {
+ strncpy(nodeName, sanitized_name.c_str(), sizeof(nodeName) - 1);
+ nodeName[sizeof(nodeName) - 1] = '\0';
} else {
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
}
+
+ if (config.display.use_long_node_name == true) {
+ int availWidth = (SCREEN_WIDTH / 2) - 65;
+ if (availWidth < 0)
+ availWidth = 0;
+
+ size_t origLen = strlen(nodeName);
+ while (nodeName[0] && display->getStringWidth(nodeName) > availWidth) {
+ nodeName[strlen(nodeName) - 1] = '\0';
+ }
+
+ // If we actually truncated, append "..." (ensure space remains in buffer)
+ if (strlen(nodeName) < origLen) {
+ size_t len = strlen(nodeName);
+ size_t maxLen = sizeof(nodeName) - 4; // 3 for "..." and 1 for '\0'
+ if (len > maxLen) {
+ nodeName[maxLen] = '\0';
+ len = maxLen;
+ }
+ strcat(nodeName, "...");
+ }
+ }
+
return nodeName;
}
@@ -141,7 +169,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
- const char *nodeName = getSafeNodeName(node);
+ const char *nodeName = getSafeNodeName(display, node);
char timeStr[10];
uint32_t seconds = sinceLastSeen(node);
@@ -186,7 +214,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
int barsXOffset = columnWidth - barsOffset;
- const char *nodeName = getSafeNodeName(node);
+ const char *nodeName = getSafeNodeName(display, node);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -230,7 +258,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
- const char *nodeName = getSafeNodeName(node);
+ const char *nodeName = getSafeNodeName(display, node);
char distStr[10] = "";
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -325,7 +353,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
// Adjust max text width depending on column and screen width
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
- const char *nodeName = getSafeNodeName(node);
+ const char *nodeName = getSafeNodeName(display, node);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -356,11 +384,11 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
float bearingToNode = RAD_TO_DEG * bearing;
float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
- float angle = relativeBearing * DEG_TO_RAD;
// Shrink size by 2px
int size = FONT_HEIGHT_SMALL - 5;
CompassRenderer::drawArrowToNode(display, centerX, centerY, size, relativeBearing);
/*
+ float angle = relativeBearing * DEG_TO_RAD;
float halfSize = size / 2.0;
// Point of the arrow
@@ -397,6 +425,12 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
{
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
+ bool locationScreen = false;
+
+ if (strcmp(title, "Bearings") == 0)
+ locationScreen = true;
+ else if (strcmp(title, "Distance") == 0)
+ locationScreen = true;
#if defined(M5STACK_UNITC6L)
int columnWidth = display->getWidth();
#else
@@ -412,7 +446,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int totalEntries = nodeDB->getNumMeshNodes();
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
-
+ int numskipped = 0;
int visibleNodeRows = totalRowsAvailable;
#if defined(M5STACK_UNITC6L)
int totalColumns = 1;
@@ -432,6 +466,10 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int rowCount = 0;
for (int i = startIndex; i < endIndex; ++i) {
+ if (locationScreen && !nodeDB->getMeshNodeByIndex(i)->has_position) {
+ numskipped++;
+ continue;
+ }
int xPos = x + (col * columnWidth);
int yPos = y + yOffset;
renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth);
@@ -454,6 +492,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
}
}
+ // This should correct the scrollbar
+ totalEntries -= numskipped;
+
#if !defined(M5STACK_UNITC6L)
// Draw column separator
if (shownCount > 0) {
@@ -464,6 +505,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
#endif
const int scrollStartY = y + 3;
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
+ graphics::drawCommonFooter(display, x, y);
}
// =============================
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index ff8cd20c5..c50fe5cf1 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -11,6 +11,7 @@
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TimeFormatters.h"
#include "graphics/images.h"
#include "main.h"
#include "target_specific.h"
@@ -383,17 +384,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
- uint32_t uptime = node->device_metrics.uptime_seconds;
- uint32_t days = uptime / 86400;
- uint32_t hours = (uptime % 86400) / 3600;
- uint32_t mins = (uptime % 3600) / 60;
- // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
- if (days)
- snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %ud %uh", days, hours);
- else if (hours)
- snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %uh %um", hours, mins);
- else
- snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
+ getUptimeStr(node->device_metrics.uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr));
}
if (uptimeStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
@@ -552,6 +543,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// else show nothing
}
#endif
+ graphics::drawCommonFooter(display, x, y);
}
// ****************************
@@ -563,6 +555,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
+ meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
// === Header ===
#if defined(M5STACK_UNITC6L)
@@ -590,18 +583,8 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#endif
char uptimeStr[32] = "";
- uint32_t uptime = millis() / 1000;
- uint32_t days = uptime / 86400;
- uint32_t hours = (uptime % 86400) / 3600;
- uint32_t mins = (uptime % 3600) / 60;
- // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
#if !defined(M5STACK_UNITC6L)
- if (days)
- snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
- else if (hours)
- snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
- else
- snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
+ getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
#endif
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
@@ -740,7 +723,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int yOffset = (isHighResolution) ? 0 : 5;
std::string longNameStr;
- meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
longNameStr = sanitizeString(ourNode->user.long_name);
}
@@ -771,6 +753,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
}
#endif
+ graphics::drawCommonFooter(display, x, y);
}
// Start Functions to write date/time to the screen
@@ -1000,24 +983,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
const char *displayLine = ""; // Initialize to empty string by default
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
- bool usePhoneGPS = (ourNode && nodeDB->hasValidPosition(ourNode) &&
- config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED);
-
- if (usePhoneGPS) {
- // Phone-provided GPS is active
- displayLine = "Phone GPS";
- int yOffset = (isHighResolution) ? 3 : 1;
- if (isHighResolution) {
- NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
- imgSatellite_height, imgSatellite, display);
- } else {
- display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
- imgSatellite);
- }
- int xOffset = (isHighResolution) ? 6 : 0;
- display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
- } else if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
- // GPS disabled / not present
+ if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
if (config.position.fixed_position) {
displayLine = "Fixed GPS";
} else {
@@ -1063,36 +1029,17 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
// === Second Row: Last GPS Fix ===
if (gpsStatus->getLastFixMillis() > 0) {
- uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
- uint32_t days = delta / 86400;
- uint32_t hours = (delta % 86400) / 3600;
- uint32_t mins = (delta % 3600) / 60;
- uint32_t secs = delta % 60;
-
- char buf[32];
+ uint32_t delta = millis() - gpsStatus->getLastFixMillis();
+ char uptimeStr[32];
#if defined(USE_EINK)
// E-Ink: skip seconds, show only days/hours/mins
- if (days > 0) {
- snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
- } else if (hours > 0) {
- snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
- } else {
- snprintf(buf, sizeof(buf), "Last: %um", mins);
- }
+ getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false);
#else
// Non E-Ink: include seconds where useful
- if (days > 0) {
- snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
- } else if (hours > 0) {
- snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
- } else if (mins > 0) {
- snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
- } else {
- snprintf(buf, sizeof(buf), "Last: %us", secs);
- }
+ getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true);
#endif
- display->drawString(0, getTextPositions(display)[line++], buf);
+ display->drawString(0, getTextPositions(display)[line++], uptimeStr);
} else {
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
}
@@ -1108,9 +1055,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// === Final Row: Altitude ===
char altitudeLine[32] = {0};
- int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
- ? ourNode->position.altitude
- : geoCoord.getAltitude();
+ int32_t alt = geoCoord.getAltitude();
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET);
} else {
@@ -1202,6 +1147,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
}
#endif
#endif // HAS_GPS
+ graphics::drawCommonFooter(display, x, y);
}
#ifdef USERPREFS_OEM_TEXT
@@ -1286,7 +1232,13 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
if (totalIcons == 0)
return;
- const size_t iconsPerPage = (SCREEN_WIDTH + spacing) / (iconSize + spacing);
+ const int navPadding = isHighResolution ? 24 : 12; // padding per side
+
+ int usableWidth = SCREEN_WIDTH - (navPadding * 2);
+ if (usableWidth < iconSize)
+ usableWidth = iconSize;
+
+ const size_t iconsPerPage = usableWidth / (iconSize + spacing);
const size_t currentPage = currentFrame / iconsPerPage;
const size_t pageStart = currentPage * iconsPerPage;
const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons);
@@ -1357,6 +1309,47 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setColor(WHITE);
}
}
+
+ // Compact arrow drawer
+ auto drawArrow = [&](bool rightSide) {
+ display->setColor(WHITE);
+
+ const int offset = isHighResolution ? 3 : 1;
+ const int halfH = rectHeight / 2;
+
+ const int top = (y - 2) + (rectHeight - halfH) / 2;
+ const int bottom = top + halfH - 1;
+ const int midY = top + (halfH / 2);
+
+ const int maxW = 4;
+
+ // Determine left X coordinate
+ int baseX = rightSide ? (rectX + rectWidth + offset) : // right arrow
+ (rectX - offset - 1); // left arrow
+
+ for (int yy = top; yy <= bottom; yy++) {
+ int dist = abs(yy - midY);
+ int lineW = maxW - (dist * maxW / (halfH / 2));
+ if (lineW < 1)
+ lineW = 1;
+
+ if (rightSide) {
+ display->drawHorizontalLine(baseX, yy, lineW);
+ } else {
+ display->drawHorizontalLine(baseX - lineW + 1, yy, lineW);
+ }
+ }
+ };
+ // Right arrow
+ if (pageEnd < totalIcons) {
+ drawArrow(true);
+ }
+
+ // Left arrow
+ if (pageStart > 0) {
+ drawArrow(false);
+ }
+
// Knock the corners off the square
display->setColor(BLACK);
display->drawRect(rectX, y - 2, 1, 1);
@@ -1391,4 +1384,4 @@ std::string UIRenderer::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t mi
} // namespace graphics
-#endif // HAS_SCREEN
\ No newline at end of file
+#endif // HAS_SCREEN
diff --git a/src/graphics/emotes.cpp b/src/graphics/emotes.cpp
index e1a105d20..bed2b7b7c 100644
--- a/src/graphics/emotes.cpp
+++ b/src/graphics/emotes.cpp
@@ -1,3 +1,5 @@
+#include "configuration.h"
+#if HAS_SCREEN
#include "emotes.h"
namespace graphics
@@ -16,6 +18,8 @@ const Emote emotes[] = {
{"\U0001F642", Slightly_Smiling, Slightly_Smiling_width, Slightly_Smiling_height}, // 🙂 Slightly Smiling Face
{"\U0001F609", Winking_Face, Winking_Face_width, Winking_Face_height}, // 😉 Winking Face
{"\U0001F601", Grinning_Smiling_Eyes, Grinning_Smiling_Eyes_width, Grinning_Smiling_Eyes_height}, // � Grinning Smiling Eyes
+ {"\U0001F60D", Heart_eyes, Heart_eyes_width, Heart_eyes_height}, // � Heart Eyes
+ {"\U0001F970", heart_smile, heart_smile_width, heart_smile_height}, // 🥰 Smiling Face with Hearts
// --- Question/Alert ---
{"\u2753", question, question_width, question_height}, // � Question Mark
@@ -28,11 +32,15 @@ const Emote emotes[] = {
{"\U0001F605", haha, haha_width, haha_height}, // 😅 Smiling with Sweat
{"\U0001F604", Grinning_SmilingEyes2, Grinning_SmilingEyes2_width,
Grinning_SmilingEyes2_height}, // 😄 Grinning Face with Smiling Eyes
+ {"\U0001F62D", Loudly_Crying_Face, Loudly_Crying_Face_width, Loudly_Crying_Face_height}, // 😠Loudly Crying Face
// --- Gestures and People ---
- {"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
- {"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠Cowboy Hat Face
- {"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
+ {"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
+ {"\u270C\uFE0F", peace_sign, peace_sign_width, peace_sign_height}, // ✌� Victory Hand
+ {"\U0001F596", vulcan_salute, vulcan_salute_width, vulcan_salute_height}, // 🖖 Vulcan Salute
+ {"\U0001F64F", Praying, Praying_width, Praying_height}, // � Praying Hands
+ {"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠Cowboy Hat Face
+ {"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
// --- Weather ---
{"\u2600", sun, sun_width, sun_height}, // ☀ Sun (without variation selector)
@@ -43,8 +51,12 @@ const Emote emotes[] = {
// --- Misc Faces ---
{"\U0001F608", devil, devil_width, devil_height}, // 😈 Smiling Face with Horns
+ {"\U0001F921", clown, clown_width, clown_height}, // 🤡 Clown Face
+ {"\U0001F916", robo, robo_width, robo_height}, // 🤖 Robot Face
// --- Hearts (Multiple Unicode Aliases) ---
+ {"\u2665", heart, heart_width, heart_height}, // ♥ Black Heart Suit
+ {"\u2665\uFE0F", heart, heart_width, heart_height}, // ♥� Black Heart Suit (emoji presentation)
{"\u2764\uFE0F", heart, heart_width, heart_height}, // �� Red Heart
{"\U0001F9E1", heart, heart_width, heart_height}, // 🧡 Orange Heart
{"\U00002763", heart, heart_width, heart_height}, // � Heart Exclamation
@@ -55,223 +67,167 @@ const Emote emotes[] = {
{"\U0001F498", heart, heart_width, heart_height}, // 💘 Heart with Arrow
// --- Objects ---
- {"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
- {"\U0001F514", bell_icon, bell_icon_width, bell_icon_height} // 🔔 Bell
+ {"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
+ {"\U0001F514", bell_icon, bell_icon_width, bell_icon_height}, // 🔔 Bell
+ {"\U0001F36A", cookie, cookie_width, cookie_height}, // � Cookie
+ {"\U0001F525", Fire, Fire_width, Fire_height}, // 🔥 Fire
+ {"\u2728", Sparkles, Sparkles_width, Sparkles_height}, // ✨ Sparkles
+ {"\U0001F573\uFE0F", hole, hole_width, hole_height}, // 🕳� Hole
+ {"\U0001F3B3", bowling, bowling_width, bowling_height} // 🎳 Bowling
#endif
};
const int numEmotes = sizeof(emotes) / sizeof(emotes[0]);
#ifndef EXCLUDE_EMOJI
-const unsigned char thumbup[] PROGMEM = {
- 0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
- 0xC0, 0x08, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00,
- 0x0C, 0xCE, 0x7F, 0x00, 0x04, 0x20, 0x80, 0x00, 0x02, 0x20, 0x80, 0x00, 0x02, 0x60, 0xC0, 0x00, 0x01, 0xF8, 0xFF, 0x01,
- 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0x18, 0x80, 0x00,
- 0x02, 0x30, 0xC0, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x38, 0x20, 0x10, 0x00, 0xE0, 0xCF, 0x1F, 0x00,
-};
+const unsigned char thumbup[] PROGMEM = {0x00, 0x03, 0x80, 0x04, 0x80, 0x04, 0x40, 0x04, 0x20, 0x02, 0x18,
+ 0x02, 0x06, 0x3F, 0x06, 0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x70,
+ 0x06, 0x40, 0x06, 0x30, 0x08, 0x20, 0xF0, 0x1F, 0x00, 0x00};
-const unsigned char thumbdown[] PROGMEM = {
- 0xE0, 0xCF, 0x1F, 0x00, 0x38, 0x20, 0x10, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x02, 0x30, 0xC0, 0x00,
- 0x01, 0x18, 0x80, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01,
- 0x01, 0xF8, 0xFF, 0x01, 0x02, 0x60, 0xC0, 0x00, 0x02, 0x20, 0x80, 0x00, 0x04, 0x20, 0x80, 0x00, 0x0C, 0xCE, 0x7F, 0x00,
- 0x18, 0x02, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00,
- 0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
-};
+const unsigned char thumbdown[] PROGMEM = {0xF0, 0x1F, 0x08, 0x20, 0x06, 0x30, 0x06, 0x40, 0x06, 0x70, 0x06,
+ 0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x3F, 0x18, 0x02, 0x20, 0x02,
+ 0x40, 0x04, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0x00, 0x00};
-const unsigned char Smiling_Eyes[] PROGMEM = {
- 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
- 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf,
- 0x7e, 0xf8, 0xc3, 0xdf, 0x3e, 0xf0, 0x81, 0xdf, 0xbf, 0xf7, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0x3f, 0xff,
- 0x6f, 0xff, 0xdf, 0xfe, 0x6f, 0xff, 0xdf, 0xfe, 0x9f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
- 0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
- 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
+const unsigned char Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
+ 0x4A, 0x02, 0x40, 0x02, 0x40, 0x22, 0x44, 0x22, 0x44, 0xC2, 0x43,
+ 0x04, 0x20, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char Grinning[] PROGMEM = {
- 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
- 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
- 0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
- 0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
- 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char Grinning[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
+ 0x42, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
+ 0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char Slightly_Smiling[] PROGMEM = {
- 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
- 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
- 0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
- 0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
- 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char Slightly_Smiling[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
+ 0x42, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
+ 0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char Winking_Face[] PROGMEM = {
- 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
- 0xf0, 0xf0, 0xff, 0xc3, 0x78, 0xef, 0xc3, 0xc7, 0xb8, 0xdf, 0xbd, 0xcf, 0xfc, 0xf9, 0x7f, 0xcf, 0xfc, 0xf0, 0xff, 0xcf,
- 0xfe, 0xf0, 0xc3, 0xdf, 0xfe, 0xf0, 0x81, 0xdf, 0xff, 0xf0, 0xbf, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
- 0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
- 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
+const unsigned char Winking_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x20, 0x42,
+ 0x46, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
+ 0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {
- 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
- 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf8, 0xe3, 0xcf, 0x7c, 0xf7, 0xdd, 0xcf,
- 0xbe, 0xef, 0xbe, 0xdf, 0xbe, 0xef, 0xbe, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x5e, 0x55, 0x55, 0xdf, 0x5e, 0x55, 0x55, 0xdf,
- 0x3c, 0x00, 0x80, 0xcf, 0x7c, 0x55, 0xd5, 0xcf, 0xf8, 0x54, 0xe5, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
- 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
+ 0x4A, 0x02, 0x40, 0xFA, 0x5F, 0x0A, 0x50, 0x0A, 0x50, 0x12, 0x48,
+ 0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char question[] PROGMEM = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00,
- 0xE0, 0xC3, 0x0F, 0x00, 0xF0, 0x81, 0x0F, 0x00, 0xF0, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x0F, 0x00,
- 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x7C, 0x00, 0x00,
- 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
+const unsigned char heart_smile[] PROGMEM = {0x00, 0x00, 0x6C, 0x07, 0x7C, 0x18, 0x7C, 0x20, 0x38, 0x24, 0x52,
+ 0x0A, 0x02, 0xD8, 0x02, 0xF8, 0x22, 0xFC, 0x20, 0x74, 0xDB, 0x23,
+ 0x1F, 0x00, 0x1F, 0x20, 0x0E, 0x18, 0xE4, 0x07, 0x00, 0x00};
-const unsigned char bang[] PROGMEM = {
- 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, 0xFF, 0x07, 0xF8, 0x3F,
- 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F,
- 0xFE, 0x03, 0xF0, 0x1F, 0xFE, 0x03, 0xF0, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F,
- 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0xFC, 0x03, 0xF0, 0x0F, 0xFE, 0x03, 0xF0, 0x1F,
- 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xF8, 0x01, 0xE0, 0x07,
-};
+const unsigned char Heart_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x54, 0x2A, 0xFA,
+ 0x5F, 0x72, 0x4E, 0x22, 0x44, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48,
+ 0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char haha[] PROGMEM = {
- 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xf9, 0xf3, 0xc0,
- 0xf0, 0xfe, 0xef, 0xc1, 0x38, 0xff, 0x9f, 0xc3, 0xd8, 0xff, 0x7f, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xcf,
- 0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xef, 0xff, 0xff, 0xde, 0xe7, 0xff, 0xff, 0xdc, 0xeb, 0xff, 0xff, 0xda,
- 0xed, 0xff, 0xff, 0xd6, 0xee, 0xff, 0xff, 0xce, 0x36, 0x00, 0x80, 0xcd, 0xb8, 0xff, 0xbf, 0xc3, 0x7e, 0x00, 0xc0, 0xdf,
- 0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
- 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char question[] PROGMEM = {0xE0, 0x07, 0x10, 0x08, 0x08, 0x10, 0x88, 0x11, 0x48, 0x12, 0x48,
+ 0x12, 0x48, 0x12, 0x30, 0x11, 0x80, 0x08, 0x40, 0x04, 0x40, 0x02,
+ 0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x40, 0x02, 0x80, 0x01};
-const unsigned char ROFL[] PROGMEM = {
- 0x00, 0x00, 0x00, 0xc0, 0x00, 0xfc, 0x07, 0xc0, 0x00, 0xff, 0x1f, 0xc0, 0x80, 0xff, 0x7f, 0xc0, 0xc0, 0xff, 0xff, 0xc0,
- 0xe0, 0x9f, 0xff, 0xc1, 0xf0, 0x9f, 0xff, 0xc0, 0xf8, 0x9f, 0x7f, 0xcb, 0xf8, 0x9f, 0xbf, 0xcb, 0xfc, 0x9f, 0xdf, 0xdb,
- 0xfc, 0x1f, 0x08, 0xdc, 0xfe, 0x1f, 0xf8, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0x1e, 0xf0, 0x7f, 0xfe, 0x1e, 0xf0, 0xbf, 0xfe,
- 0xfe, 0xf3, 0xdf, 0xfe, 0xfe, 0xf3, 0x6f, 0xfe, 0xfe, 0xf3, 0x37, 0xfe, 0xfe, 0xeb, 0x1b, 0xfe, 0xfc, 0xef, 0x0d, 0xde,
- 0xfc, 0xe7, 0x06, 0xcf, 0xf8, 0x6b, 0x83, 0xcf, 0xf8, 0x0d, 0xc0, 0xc7, 0xf0, 0xed, 0xff, 0xc7, 0xe0, 0xee, 0xff, 0xc3,
- 0xc0, 0xee, 0xff, 0xc1, 0x80, 0xee, 0xff, 0xc0, 0x00, 0xe6, 0x3f, 0xc0, 0x00, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char bang[] PROGMEM = {0x30, 0x0C, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48,
+ 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x30, 0x0C,
+ 0x00, 0x00, 0x30, 0x0C, 0x48, 0x12, 0x30, 0x0C, 0x00, 0x00};
-const unsigned char Smiling_Closed_Eyes[] PROGMEM = {
- 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
- 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0x7c, 0xfe, 0xcf, 0xcf, 0xfc, 0xfc, 0xe7, 0xcf,
- 0xfe, 0xf9, 0xf3, 0xdf, 0xfe, 0xf3, 0xf9, 0xdf, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xfc, 0xe7, 0xff, 0x7f, 0xfe, 0xcf, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
- 0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
- 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char haha[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
+ 0x4A, 0x0A, 0x50, 0x0E, 0x70, 0xF2, 0x4F, 0x12, 0x48, 0x32, 0x44,
+ 0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char Grinning_SmilingEyes2[] PROGMEM = {
- 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xff, 0xff, 0xc0,
- 0xf0, 0xff, 0xff, 0xc1, 0xf8, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xc7,
- 0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf,
- 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0x3f, 0x00, 0x80, 0xdf, 0xbe, 0xff, 0xbf, 0xcf, 0x7e, 0x00, 0xc0, 0xcf,
- 0x7c, 0x00, 0xc0, 0xc7, 0xfc, 0x00, 0xe0, 0xc7, 0xf8, 0x01, 0xf0, 0xc3, 0xf8, 0x03, 0xf8, 0xc3, 0xf0, 0xff, 0xff, 0xc1,
- 0xe0, 0xff, 0xff, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char ROFL[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x84, 0x21, 0x84, 0x20, 0x02,
+ 0x4C, 0x02, 0x4A, 0x1A, 0x49, 0x8A, 0x48, 0x42, 0x48, 0x22, 0x44,
+ 0xE4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char wave_icon[] PROGMEM = {
- 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0xc7,
- 0x00, 0x00, 0x1e, 0xcc, 0x00, 0x00, 0x30, 0xc8, 0x00, 0x00, 0x60, 0xd8, 0x00, 0x08, 0xc0, 0xd0, 0x00, 0x1a, 0x81, 0xd1,
- 0x00, 0x36, 0x03, 0xd3, 0x80, 0x6d, 0x06, 0xd2, 0x00, 0xdb, 0x0c, 0xc2, 0x80, 0xb6, 0x1d, 0xc0, 0x80, 0x6d, 0x1f, 0xc0,
- 0x00, 0xdb, 0x3f, 0xc0, 0x00, 0xf6, 0x7f, 0xc0, 0x00, 0xfc, 0x7f, 0xc0, 0x08, 0xf8, 0x7f, 0xc0, 0x48, 0xf0, 0x7f, 0xc0,
- 0x48, 0xe0, 0x7f, 0xc0, 0xc8, 0xc0, 0x3f, 0xc0, 0x98, 0x81, 0x1f, 0xc0, 0x10, 0x03, 0x00, 0xc0, 0x30, 0x0e, 0x00, 0xc0,
- 0x20, 0x38, 0x00, 0xc0, 0xe0, 0x00, 0x00, 0xc0, 0x80, 0x07, 0x00, 0xc0, 0x00, 0x1e, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char Smiling_Closed_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x42,
+ 0x42, 0x22, 0x44, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
+ 0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char cowboy[] PROGMEM = {
- 0x00, 0x0c, 0x0c, 0xc0, 0x00, 0x02, 0x10, 0xc0, 0x00, 0x01, 0x20, 0xc0, 0xbc, 0x00, 0x40, 0xcf, 0xc2, 0x01, 0xe0, 0xd0,
- 0x01, 0x01, 0x20, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0,
- 0xc1, 0x3f, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe1, 0xf2, 0xf3, 0xf3, 0xd3, 0xf4, 0xf1, 0xe3, 0xcb, 0xfc, 0xf1, 0xe3, 0xc7,
- 0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xfb, 0xf7, 0xc7, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xc7,
- 0x70, 0xf8, 0x8f, 0xc3, 0x70, 0x03, 0xb0, 0xc3, 0x70, 0xfe, 0xbf, 0xc3, 0x60, 0x00, 0x80, 0xc1, 0xc0, 0x00, 0xc0, 0xc0,
- 0x80, 0x01, 0x60, 0xc0, 0x00, 0x07, 0x38, 0xc0, 0x00, 0xfe, 0x1f, 0xc0, 0x00, 0xf0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char Grinning_SmilingEyes2[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
+ 0x4A, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
+ 0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char deadmau5[] PROGMEM = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00,
- 0x00, 0xFC, 0x03, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0x00,
- 0xE0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0x7F, 0x00, 0xF0, 0xFF, 0xFF, 0x03, 0xF8, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x07,
- 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFE, 0xFF, 0xFF, 0x00,
- 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0xFC,
- 0x0F, 0xFF, 0x7F, 0x00, 0xC0, 0xFF, 0x1F, 0xF8, 0x0F, 0xFC, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0xF8, 0x1F, 0xFC, 0x1F, 0x00,
- 0x00, 0xFF, 0x0F, 0xFC, 0x3F, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0xFF, 0xFF, 0xFE, 0x01, 0x00, 0x00, 0x00, 0xFC, 0xFF,
- 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
- 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x80, 0x07, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
+const unsigned char Loudly_Crying_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x2C, 0x4A,
+ 0x52, 0x12, 0x48, 0x12, 0x48, 0x92, 0x49, 0x52, 0x4A, 0x52, 0x4A,
+ 0x54, 0x2A, 0x94, 0x29, 0x18, 0x18, 0xF0, 0x0F, 0x00, 0x00};
-const unsigned char sun[] PROGMEM = {
- 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x30, 0xC0, 0x00, 0x03,
- 0x70, 0x00, 0x80, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0xF0, 0xF8, 0xC7, 0x03, 0xE0, 0xFC, 0xCF, 0x01, 0x00, 0xFE, 0x1F, 0x00,
- 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x8E, 0xFF, 0x7F, 0x1C, 0x9F, 0xFF, 0x7F, 0x3E,
- 0x9F, 0xFF, 0x7F, 0x3E, 0x8E, 0xFF, 0x7F, 0x1C, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00,
- 0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0xC0, 0xF9, 0xE7, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xE0, 0x03,
- 0xF0, 0xC0, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00,
-};
+const unsigned char wave_icon[] PROGMEM = {0x00, 0x00, 0xC0, 0x18, 0x30, 0x21, 0x48, 0x5A, 0x94, 0x64, 0x24,
+ 0x25, 0x4A, 0x24, 0x12, 0x44, 0x22, 0x44, 0x04, 0x40, 0x08, 0x40,
+ 0x12, 0x40, 0x22, 0x20, 0xC4, 0x10, 0x18, 0x0F, 0x00, 0x00};
-const unsigned char rain[] PROGMEM = {
- 0xC0, 0x0F, 0xC0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x03, 0x38, 0x00,
- 0x00, 0x0E, 0x0C, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x20,
- 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00,
- 0x00, 0x10, 0x06, 0x00, 0x00, 0x08, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x01, 0x80, 0x00, 0x01, 0x00,
- 0xC0, 0xC0, 0x81, 0x03, 0xA0, 0x60, 0xC1, 0x03, 0x90, 0x20, 0x41, 0x01, 0xF0, 0xE0, 0xC0, 0x01, 0x60, 0x4C,
- 0x98, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x0B, 0x12, 0x00, 0x00, 0x09, 0x1A, 0x00, 0x00, 0x06, 0x0E, 0x00,
-};
+const unsigned char cowboy[] PROGMEM = {0x70, 0x0E, 0x8F, 0xF1, 0x11, 0x88, 0x21, 0x84, 0xC2, 0x43, 0x1E,
+ 0x78, 0xE2, 0x47, 0x42, 0x42, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
+ 0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
-const unsigned char cloud[] PROGMEM = {
- 0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0x10, 0x60, 0x00, 0x80, 0x1F, 0x40, 0x00,
- 0xC0, 0x0F, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x01,
- 0x20, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10,
- 0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20,
- 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x10,
- 0x02, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03,
-};
+const unsigned char deadmau5[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0xE4, 0x27, 0x12, 0x48, 0x0A,
+ 0x50, 0x0E, 0x70, 0x11, 0x88, 0x19, 0x98, 0x19, 0x98, 0x19, 0x98,
+ 0x19, 0x98, 0x19, 0x98, 0x11, 0x88, 0x0E, 0x70, 0x00, 0x00};
-const unsigned char fog[] PROGMEM = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01,
- 0x00, 0x38, 0x00, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0xFF, 0x83, 0xFF, 0x01, 0x03, 0xFF, 0x81, 0x01, 0x00, 0x7C, 0x00, 0x00,
- 0xF8, 0x00, 0x3E, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, 0x00, 0x38, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
+const unsigned char sun[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0xEC, 0x37, 0xFC, 0x3F, 0xF8, 0x1F, 0xFC,
+ 0x3F, 0xFE, 0x7F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFE, 0x7F, 0xFC, 0x3F,
+ 0xF8, 0x1F, 0xFC, 0x3F, 0xEC, 0x37, 0x80, 0x01, 0x00, 0x00};
-const unsigned char devil[] PROGMEM = {
- 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x0f, 0xfc, 0x0f, 0xfc,
- 0x3f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfc, 0xff, 0xff, 0xcf,
- 0xfc, 0xff, 0xff, 0xcf, 0xf8, 0xff, 0xff, 0xc7, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xf1, 0xe3, 0xc3,
- 0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3,
- 0xf0, 0xff, 0xff, 0xc3, 0xe0, 0xfd, 0xef, 0xc1, 0xe0, 0xf3, 0xf3, 0xc1, 0xc0, 0x07, 0xf8, 0xc0, 0x80, 0x1f, 0x7e, 0xc0,
- 0x00, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char rain[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC, 0x3F, 0xFE,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x00, 0x48, 0x12,
+ 0x48, 0x12, 0x24, 0x09, 0x24, 0x09, 0x00, 0x00, 0x00, 0x00};
-const unsigned char heart[] PROGMEM = {
- 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18,
- 0xFE, 0xFF, 0x7F, 0x10, 0xFF, 0xFF, 0xFF, 0x31, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 0xFF, 0x37,
- 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F,
- 0xFC, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0x03,
- 0xE0, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0x1F, 0x00,
- 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
-};
+const unsigned char cloud[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC,
+ 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-const unsigned char poo[] PROGMEM = {
- 0x00, 0x1c, 0x00, 0xc0, 0x00, 0x7c, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0x7c, 0x03, 0xc0, 0x00, 0xbe, 0x03, 0xc0,
- 0x00, 0xdf, 0x0f, 0xc0, 0x80, 0xcf, 0x0f, 0xc0, 0xc0, 0xf1, 0x0f, 0xc0, 0x60, 0xfc, 0x0f, 0xc0, 0x30, 0xff, 0x07, 0xc0,
- 0x90, 0xff, 0x3b, 0xc0, 0xc0, 0xff, 0x7d, 0xc0, 0xf8, 0xff, 0xfc, 0xc0, 0xf8, 0x3f, 0xf0, 0xc0, 0x78, 0x88, 0xc0, 0xc0,
- 0x20, 0xe3, 0x18, 0xc0, 0x98, 0xe7, 0xbc, 0xc1, 0x9c, 0x64, 0xa4, 0xc3, 0x9e, 0x64, 0xa4, 0xc7, 0xbe, 0xe4, 0xa4, 0xc7,
- 0xbc, 0x27, 0xbc, 0xc7, 0x38, 0x03, 0xd9, 0xc3, 0x00, 0xf0, 0x63, 0xc0, 0xf8, 0xfc, 0x3f, 0xcf, 0xfc, 0xff, 0x87, 0xdf,
- 0xfe, 0xff, 0xe0, 0xdf, 0xfc, 0x1f, 0xfe, 0xdf, 0xf8, 0x07, 0xf8, 0xcf, 0xf0, 0x03, 0xe0, 0xc7, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char fog[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x54, 0x55, 0x22, 0x22, 0x00,
+ 0x00, 0x44, 0x44, 0xAA, 0x2A, 0x11, 0x11, 0x00, 0x00, 0x88, 0x88,
+ 0x54, 0x55, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-const unsigned char bell_icon[] PROGMEM = {
- 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11110000,
- 0b00000011, 0b00000000, 0b00000000, 0b11111100, 0b00001111, 0b00000000, 0b00000000, 0b00001111, 0b00111100, 0b00000000,
- 0b00000000, 0b00000011, 0b00110000, 0b00000000, 0b10000000, 0b00000001, 0b01100000, 0b00000000, 0b11000000, 0b00000000,
- 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
- 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
- 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
- 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b01000000, 0b00000000, 0b10000000, 0b00000000, 0b01100000, 0b00000000,
- 0b10000000, 0b00000001, 0b01110000, 0b00000000, 0b10000000, 0b00000011, 0b00110000, 0b00000000, 0b00000000, 0b00000011,
- 0b00011000, 0b00000000, 0b00000000, 0b00000110, 0b11110000, 0b11111111, 0b11111111, 0b00000011, 0b00000000, 0b00001100,
- 0b00001100, 0b00000000, 0b00000000, 0b00011000, 0b00000110, 0b00000000, 0b00000000, 0b11111000, 0b00000111, 0b00000000,
- 0b00000000, 0b11100000, 0b00000001, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
- 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000};
+const unsigned char devil[] PROGMEM = {0x06, 0x60, 0xCA, 0x53, 0x32, 0x4C, 0x22, 0x44, 0x44, 0x22, 0x3A,
+ 0x5C, 0x32, 0x4C, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x22, 0x44,
+ 0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
+
+const unsigned char heart[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C, 0x7E, 0x7E, 0xFE, 0x7F, 0xFE,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF0, 0x0F, 0xE0, 0x07, 0xC0, 0x03, 0x80, 0x01, 0x00, 0x00};
+
+const unsigned char poo[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x20, 0x04, 0x10, 0x04, 0xF0,
+ 0x08, 0x10, 0x10, 0x48, 0x12, 0x08, 0x18, 0xE8, 0x21, 0x1C, 0x40,
+ 0x42, 0x42, 0x82, 0x41, 0x02, 0x30, 0xFC, 0x0F, 0x00, 0x00};
+
+const unsigned char bell_icon[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0xE0, 0x07, 0xF0, 0x0F, 0xF0,
+ 0x0F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFC, 0x3F,
+ 0xFC, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x01, 0x00, 0x00};
+
+const unsigned char cookie[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x22, 0x32,
+ 0x40, 0x02, 0x58, 0x82, 0x5B, 0x92, 0x43, 0x82, 0x43, 0x02, 0x40,
+ 0x64, 0x28, 0x64, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
+
+const unsigned char Fire[] PROGMEM = {0x30, 0x00, 0xF0, 0x00, 0xF8, 0x03, 0xF8, 0x07, 0xFC, 0x1F, 0xFC,
+ 0x1F, 0xFE, 0x3E, 0x7E, 0x3E, 0x3E, 0x7C, 0x1E, 0x78, 0x1E, 0x70,
+ 0x1C, 0x70, 0x1C, 0x70, 0x38, 0x38, 0x30, 0x38, 0x60, 0x0C};
+
+const unsigned char peace_sign[] PROGMEM = {0xC0, 0x30, 0x40, 0x29, 0x40, 0x25, 0x40, 0x15, 0x40, 0x12, 0x38,
+ 0x0A, 0x54, 0x68, 0x54, 0x58, 0x54, 0x44, 0x3C, 0x22, 0x04, 0x22,
+ 0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
+
+const unsigned char Praying[] PROGMEM = {0x00, 0x00, 0x40, 0x02, 0xA0, 0x05, 0x90, 0x09, 0x90, 0x09, 0x90,
+ 0x09, 0x98, 0x19, 0x94, 0x29, 0xA4, 0x25, 0xA4, 0x25, 0x84, 0x21,
+ 0x84, 0x21, 0x86, 0x61, 0x4E, 0x72, 0x7F, 0x7E, 0x3F, 0xFC};
+
+const unsigned char Sparkles[] PROGMEM = {0x00, 0x00, 0x10, 0x00, 0x38, 0x04, 0x10, 0x04, 0x00, 0x0E, 0x00,
+ 0x1F, 0x80, 0x3F, 0xE0, 0xFF, 0x80, 0x3F, 0x10, 0x1F, 0x10, 0x0E,
+ 0x38, 0x04, 0xFE, 0x04, 0x38, 0x00, 0x10, 0x00, 0x10, 0x00};
+
+const unsigned char clown[] PROGMEM = {0x00, 0x00, 0xEE, 0x77, 0x1A, 0x58, 0x06, 0x60, 0x24, 0x24, 0x72,
+ 0x4E, 0x22, 0x44, 0x82, 0x41, 0x82, 0x41, 0x1A, 0x58, 0xF2, 0x4F,
+ 0x14, 0x28, 0xE4, 0x27, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
+
+const unsigned char robo[] PROGMEM = {0x80, 0x01, 0xC0, 0x03, 0x80, 0x01, 0xFC, 0x3F, 0x04, 0x20, 0x74,
+ 0x2E, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x02, 0x40, 0xA2, 0x4A,
+ 0x52, 0x45, 0x04, 0x20, 0x04, 0x20, 0xFC, 0x3F, 0x00, 0x00};
+
+const unsigned char hole[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x3C, 0x3C,
+ 0x06, 0x60, 0x0C, 0x30, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00};
+
+const unsigned char bowling[] PROGMEM = {0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00,
+ 0x38, 0x00, 0x28, 0x78, 0x44, 0x84, 0x82, 0x22, 0x83, 0x52, 0x83,
+ 0x02, 0x83, 0x02, 0x45, 0x84, 0x44, 0x78, 0x38, 0x00, 0x00};
+
+const unsigned char vulcan_salute[] PROGMEM = {0x08, 0x02, 0x16, 0x0D, 0x15, 0x15, 0x15, 0x15, 0xA9, 0x12, 0x4A,
+ 0x0A, 0x02, 0x38, 0x04, 0x48, 0x04, 0x44, 0x04, 0x22, 0x04, 0x22,
+ 0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
#endif
} // namespace graphics
+#endif
\ No newline at end of file
diff --git a/src/graphics/emotes.h b/src/graphics/emotes.h
index 30b164cbc..b1b2d16da 100644
--- a/src/graphics/emotes.h
+++ b/src/graphics/emotes.h
@@ -17,98 +17,150 @@ extern const int numEmotes;
#ifndef EXCLUDE_EMOJI
// === Emote Bitmaps ===
-#define thumbs_height 25
-#define thumbs_width 25
+#define thumbs_height 16
+#define thumbs_width 16
extern const unsigned char thumbup[] PROGMEM;
extern const unsigned char thumbdown[] PROGMEM;
-#define Smiling_Eyes_height 30
-#define Smiling_Eyes_width 30
+#define Smiling_Eyes_height 16
+#define Smiling_Eyes_width 16
extern const unsigned char Smiling_Eyes[] PROGMEM;
-#define Grinning_height 30
-#define Grinning_width 30
+#define Grinning_height 16
+#define Grinning_width 16
extern const unsigned char Grinning[] PROGMEM;
-#define Slightly_Smiling_height 30
-#define Slightly_Smiling_width 30
+#define Slightly_Smiling_height 16
+#define Slightly_Smiling_width 16
extern const unsigned char Slightly_Smiling[] PROGMEM;
-#define Winking_Face_height 30
-#define Winking_Face_width 30
+#define Winking_Face_height 16
+#define Winking_Face_width 16
extern const unsigned char Winking_Face[] PROGMEM;
-#define Grinning_Smiling_Eyes_height 30
-#define Grinning_Smiling_Eyes_width 30
+#define Grinning_Smiling_Eyes_height 16
+#define Grinning_Smiling_Eyes_width 16
extern const unsigned char Grinning_Smiling_Eyes[] PROGMEM;
-#define question_height 25
-#define question_width 25
+#define heart_smile_height 16
+#define heart_smile_width 16
+extern const unsigned char heart_smile[] PROGMEM;
+
+#define Heart_eyes_height 16
+#define Heart_eyes_width 16
+extern const unsigned char Heart_eyes[] PROGMEM;
+
+#define question_height 16
+#define question_width 16
extern const unsigned char question[] PROGMEM;
-#define bang_height 30
-#define bang_width 30
+#define bang_height 16
+#define bang_width 16
extern const unsigned char bang[] PROGMEM;
-#define haha_height 30
-#define haha_width 30
+#define haha_height 16
+#define haha_width 16
extern const unsigned char haha[] PROGMEM;
-#define ROFL_height 30
-#define ROFL_width 30
+#define ROFL_height 16
+#define ROFL_width 16
extern const unsigned char ROFL[] PROGMEM;
-#define Smiling_Closed_Eyes_height 30
-#define Smiling_Closed_Eyes_width 30
+#define Smiling_Closed_Eyes_height 16
+#define Smiling_Closed_Eyes_width 16
extern const unsigned char Smiling_Closed_Eyes[] PROGMEM;
-#define Grinning_SmilingEyes2_height 30
-#define Grinning_SmilingEyes2_width 30
+#define Grinning_SmilingEyes2_height 16
+#define Grinning_SmilingEyes2_width 16
extern const unsigned char Grinning_SmilingEyes2[] PROGMEM;
-#define wave_icon_height 30
-#define wave_icon_width 30
+#define Loudly_Crying_Face_height 16
+#define Loudly_Crying_Face_width 16
+extern const unsigned char Loudly_Crying_Face[] PROGMEM;
+
+#define wave_icon_height 16
+#define wave_icon_width 16
extern const unsigned char wave_icon[] PROGMEM;
-#define cowboy_height 30
-#define cowboy_width 30
+#define cowboy_height 16
+#define cowboy_width 16
extern const unsigned char cowboy[] PROGMEM;
-#define deadmau5_height 30
-#define deadmau5_width 60
+#define deadmau5_height 16
+#define deadmau5_width 16
extern const unsigned char deadmau5[] PROGMEM;
-#define sun_height 30
-#define sun_width 30
+#define sun_height 16
+#define sun_width 16
extern const unsigned char sun[] PROGMEM;
-#define rain_height 30
-#define rain_width 30
+#define rain_height 16
+#define rain_width 16
extern const unsigned char rain[] PROGMEM;
-#define cloud_height 30
-#define cloud_width 30
+#define cloud_height 16
+#define cloud_width 16
extern const unsigned char cloud[] PROGMEM;
-#define fog_height 25
-#define fog_width 25
+#define fog_height 16
+#define fog_width 16
extern const unsigned char fog[] PROGMEM;
-#define devil_height 30
-#define devil_width 30
+#define devil_height 16
+#define devil_width 16
extern const unsigned char devil[] PROGMEM;
-#define heart_height 30
-#define heart_width 30
+#define heart_height 16
+#define heart_width 16
extern const unsigned char heart[] PROGMEM;
-#define poo_height 30
-#define poo_width 30
+#define poo_height 16
+#define poo_width 16
extern const unsigned char poo[] PROGMEM;
-#define bell_icon_width 30
-#define bell_icon_height 30
+#define bell_icon_width 16
+#define bell_icon_height 16
extern const unsigned char bell_icon[] PROGMEM;
+
+#define cookie_width 16
+#define cookie_height 16
+extern const unsigned char cookie[] PROGMEM;
+
+#define Fire_width 16
+#define Fire_height 16
+extern const unsigned char Fire[] PROGMEM;
+
+#define peace_sign_width 16
+#define peace_sign_height 16
+extern const unsigned char peace_sign[] PROGMEM;
+
+#define Praying_width 16
+#define Praying_height 16
+extern const unsigned char Praying[] PROGMEM;
+
+#define Sparkles_width 16
+#define Sparkles_height 16
+extern const unsigned char Sparkles[] PROGMEM;
+
+#define clown_width 16
+#define clown_height 16
+extern const unsigned char clown[] PROGMEM;
+
+#define robo_width 16
+#define robo_height 16
+extern const unsigned char robo[] PROGMEM;
+
+#define hole_width 16
+#define hole_height 16
+extern const unsigned char hole[] PROGMEM;
+
+#define bowling_width 16
+#define bowling_height 16
+extern const unsigned char bowling[] PROGMEM;
+
+#define vulcan_salute_width 16
+#define vulcan_salute_height 16
+extern const unsigned char vulcan_salute[] PROGMEM;
#endif // EXCLUDE_EMOJI
-} // namespace graphics
+} // namespace graphics
\ No newline at end of file
diff --git a/src/graphics/images.h b/src/graphics/images.h
index b5010b116..8670d78d9 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -360,6 +360,10 @@ const uint8_t chirpy_hirez[] = {
#define chirpy_small_image_height 8
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
+#define connection_icon_width 7
+#define connection_icon_height 5
+const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
+
#ifdef M5STACK_UNITC6L
#include "img/icon_small.xbm"
#else
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index db0805f4e..d383a11e4 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -13,45 +13,147 @@ void InkHUD::MapApplet::onRender()
return;
}
+ // Helper: draw rounded rectangle centered at x,y
+ auto fillRoundedRect = [&](int16_t cx, int16_t cy, int16_t w, int16_t h, int16_t r, uint16_t color) {
+ int16_t x = cx - (w / 2);
+ int16_t y = cy - (h / 2);
+
+ // center rects
+ fillRect(x + r, y, w - 2 * r, h, color);
+ fillRect(x, y + r, r, h - 2 * r, color);
+ fillRect(x + w - r, y + r, r, h - 2 * r, color);
+
+ // corners
+ fillCircle(x + r, y + r, r, color);
+ fillCircle(x + w - r - 1, y + r, r, color);
+ fillCircle(x + r, y + h - r - 1, r, color);
+ fillCircle(x + w - r - 1, y + h - r - 1, r, color);
+ };
+
// Find center of map
- // - latitude and longitude
- // - will be placed at X(0.5), Y(0.5)
getMapCenter(&latCenter, &lngCenter);
-
- // Calculate North+East distance of each node to map center
- // - which nodes to use controlled by virtual shouldDrawNode method
calculateAllMarkers();
-
- // Set the region shown on the map
- // - default: fit all nodes, plus padding
- // - maybe overriden by derived applet
- // - getMapSize *sets* passed parameters (C-style)
getMapSize(&widthMeters, &heightMeters);
-
- // Set the metersToPx conversion value
calculateMapScale();
- // Special marker for own node
- meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
- if (ourNode && nodeDB->hasValidPosition(ourNode))
- drawLabeledMarker(ourNode);
-
- // Draw all markers
+ // Draw all markers first
for (Marker m : markers) {
int16_t x = X(0.5) + (m.eastMeters * metersToPx);
int16_t y = Y(0.5) - (m.northMeters * metersToPx);
- // Cross Size
- constexpr uint16_t csMin = 5;
- constexpr uint16_t csMax = 12;
+ // Add white halo outline first
+ constexpr int outlinePad = 1;
+ int boxSize = 11;
+ int radius = 2; // rounded corner radius
- // Too many hops away
- if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) // Too many mops
- printAt(x, y, "!", CENTER, MIDDLE);
- else if (!m.hasHopsAway) // Unknown hops
- drawCross(x, y, csMin);
- else // The fewer hops, the larger the cross
- drawCross(x, y, map(m.hopsAway, 0, config.lora.hop_limit, csMax, csMin));
+ // White halo background
+ fillRoundedRect(x, y, boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), radius + 1, WHITE);
+
+ // Draw inner box
+ fillRoundedRect(x, y, boxSize, boxSize, radius, BLACK);
+
+ // Text inside
+ setFont(fontSmall);
+ setTextColor(WHITE);
+
+ // Draw actual marker on top
+ if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) {
+ printAt(x + 1, y + 1, "X", CENTER, MIDDLE);
+ } else if (!m.hasHopsAway) {
+ printAt(x + 1, y + 1, "?", CENTER, MIDDLE);
+ } else {
+ char hopStr[4];
+ snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway);
+ printAt(x, y + 1, hopStr, CENTER, MIDDLE);
+ }
+
+ // Restore default font and color
+ setFont(fontSmall);
+ setTextColor(BLACK);
+ }
+
+ // Dual map scale bars
+ int16_t horizPx = width() * 0.25f;
+ int16_t vertPx = height() * 0.25f;
+ float horizMeters = horizPx / metersToPx;
+ float vertMeters = vertPx / metersToPx;
+
+ auto formatDistance = [&](float meters, char *out, size_t len) {
+ if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
+ float feet = meters * 3.28084f;
+ if (feet < 528)
+ snprintf(out, len, "%.0f ft", feet);
+ else {
+ float miles = feet / 5280.0f;
+ snprintf(out, len, miles < 10 ? "%.1f mi" : "%.0f mi", miles);
+ }
+ } else {
+ if (meters >= 1000)
+ snprintf(out, len, "%.1f km", meters / 1000.0f);
+ else
+ snprintf(out, len, "%.0f m", meters);
+ }
+ };
+
+ // Horizontal scale bar
+ int16_t horizBarY = height() - 2;
+ int16_t horizBarX = 1;
+ drawLine(horizBarX, horizBarY, horizBarX + horizPx, horizBarY, BLACK);
+ drawLine(horizBarX, horizBarY - 3, horizBarX, horizBarY + 3, BLACK);
+ drawLine(horizBarX + horizPx, horizBarY - 3, horizBarX + horizPx, horizBarY + 3, BLACK);
+
+ char horizLabel[32];
+ formatDistance(horizMeters, horizLabel, sizeof(horizLabel));
+ int16_t horizLabelW = getTextWidth(horizLabel);
+ int16_t horizLabelH = getFont().lineHeight();
+ int16_t horizLabelX = horizBarX + horizPx + 4;
+ int16_t horizLabelY = horizBarY - horizLabelH + 1;
+ fillRect(horizLabelX - 2, horizLabelY - 1, horizLabelW + 4, horizLabelH + 2, WHITE);
+ printAt(horizLabelX, horizBarY, horizLabel, LEFT, BOTTOM);
+
+ // Vertical scale bar
+ int16_t vertBarX = 1;
+ int16_t vertBarBottom = horizBarY;
+ int16_t vertBarTop = vertBarBottom - vertPx;
+ drawLine(vertBarX, vertBarBottom, vertBarX, vertBarTop, BLACK);
+ drawLine(vertBarX - 3, vertBarBottom, vertBarX + 3, vertBarBottom, BLACK);
+ drawLine(vertBarX - 3, vertBarTop, vertBarX + 3, vertBarTop, BLACK);
+
+ char vertTopLabel[32];
+ formatDistance(vertMeters, vertTopLabel, sizeof(vertTopLabel));
+ int16_t topLabelY = vertBarTop - getFont().lineHeight() - 2;
+ int16_t topLabelW = getTextWidth(vertTopLabel);
+ int16_t topLabelH = getFont().lineHeight();
+ fillRect(vertBarX - 2, topLabelY - 1, topLabelW + 6, topLabelH + 2, WHITE);
+ printAt(vertBarX + (topLabelW / 2) + 1, topLabelY + (topLabelH / 2), vertTopLabel, CENTER, MIDDLE);
+
+ char vertBottomLabel[32];
+ formatDistance(vertMeters, vertBottomLabel, sizeof(vertBottomLabel));
+ int16_t bottomLabelY = vertBarBottom + 4;
+ int16_t bottomLabelW = getTextWidth(vertBottomLabel);
+ int16_t bottomLabelH = getFont().lineHeight();
+ fillRect(vertBarX - 2, bottomLabelY - 1, bottomLabelW + 6, bottomLabelH + 2, WHITE);
+ printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
+
+ // Draw our node LAST with full white fill + outline
+ meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
+ if (ourNode && nodeDB->hasValidPosition(ourNode)) {
+ Marker self = calculateMarker(ourNode->position.latitude_i * 1e-7, ourNode->position.longitude_i * 1e-7, false, 0);
+
+ int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
+ int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
+
+ // White fill background + halo
+ fillCircle(centerX, centerY, 8, WHITE); // big white base
+ drawCircle(centerX, centerY, 8, WHITE); // crisp edge
+
+ // Black bullseye on top
+ drawCircle(centerX, centerY, 6, BLACK);
+ fillCircle(centerX, centerY, 2, BLACK);
+
+ // Crosshairs
+ drawLine(centerX - 8, centerY, centerX + 8, centerY, BLACK);
+ drawLine(centerX, centerY - 8, centerX, centerY + 8, BLACK);
}
}
@@ -63,116 +165,129 @@ void InkHUD::MapApplet::onRender()
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
{
- // Find mean lat long coords
- // ============================
- // - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
- // - averages the x, y and z coords
- // - uses tan to find angles for lat / long degrees
- // - longitude: triangle formed by x and y (on plane of the equator)
- // - latitude: triangle formed by z (north south),
- // and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
+ // If we have a valid position for our own node, use that as the anchor
+ meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
+ if (ourNode && nodeDB->hasValidPosition(ourNode)) {
+ *lat = ourNode->position.latitude_i * 1e-7;
+ *lng = ourNode->position.longitude_i * 1e-7;
+ } else {
+ // Find mean lat long coords
+ // ============================
+ // - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
+ // - averages the x, y and z coords
+ // - uses tan to find angles for lat / long degrees
+ // - longitude: triangle formed by x and y (on plane of the equator)
+ // - latitude: triangle formed by z (north south),
+ // and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's
+ // surface
- // Working totals, averaged after nodeDB processed
- uint32_t positionCount = 0;
- float xAvg = 0;
- float yAvg = 0;
- float zAvg = 0;
+ // Working totals, averaged after nodeDB processed
+ uint32_t positionCount = 0;
+ float xAvg = 0;
+ float yAvg = 0;
+ float zAvg = 0;
- // For each node in db
- for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
- meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
+ // For each node in db
+ for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
- // Skip if no position
- if (!nodeDB->hasValidPosition(node))
- continue;
+ // Skip if no position
+ if (!nodeDB->hasValidPosition(node))
+ continue;
- // Skip if derived applet doesn't want to show this node on the map
- if (!shouldDrawNode(node))
- continue;
+ // Skip if derived applet doesn't want to show this node on the map
+ if (!shouldDrawNode(node))
+ continue;
- // Latitude and Longitude of node, in radians
- float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
- float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
+ // Latitude and Longitude of node, in radians
+ float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
+ float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
- // Convert to cartesian points, with center of earth at 0, 0, 0
- // Exact distance from center is irrelevant, as we're only interested in the vector
- float x = cos(latRad) * cos(lngRad);
- float y = cos(latRad) * sin(lngRad);
- float z = sin(latRad);
+ // Convert to cartesian points, with center of earth at 0, 0, 0
+ // Exact distance from center is irrelevant, as we're only interested in the vector
+ float x = cos(latRad) * cos(lngRad);
+ float y = cos(latRad) * sin(lngRad);
+ float z = sin(latRad);
- // To find mean values shortly
- xAvg += x;
- yAvg += y;
- zAvg += z;
- positionCount++;
+ // To find mean values shortly
+ xAvg += x;
+ yAvg += y;
+ zAvg += z;
+ positionCount++;
+ }
+
+ // All NodeDB processed, find mean values
+ xAvg /= positionCount;
+ yAvg /= positionCount;
+ zAvg /= positionCount;
+
+ // Longitude from cartesian coords
+ // (Angle from 3D coords describing a point of globe's surface)
+ /*
+ UK
+ /-------\
+ (Top View) /- -\
+ /- (You) -\
+ /- . -\
+ /- . X -\
+ Asia - ... - USA
+ \- Y -/
+ \- -/
+ \- -/
+ \- -/
+ \- -----/
+ Pacific
+
+ */
+
+ *lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
+
+ // Latitude from cartesian coords
+ // (Angle from 3D coords describing a point on the globe's surface)
+ // As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
+ // Means we need to first find the hypotenuse which becomes base of our triangle in the second step
+ /*
+ UK North
+ /-------\ (Front View) /-------\
+ (Top View) /- -\ /- -\
+ /- (You) -\ /-(You) -\
+ /- /. -\ /- . -\
+ /- √X²+Y²/ . X -\ /- Z . -\
+ Asia - /... - USA - ..... -
+ \- Y -/ \- √X²+Y² -/
+ \- -/ \- -/
+ \- -/ \- -/
+ \- -/ \- -/
+ \- -----/ \- -----/
+ Pacific South
+ */
+
+ float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
+ *lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
}
- // All NodeDB processed, find mean values
- xAvg /= positionCount;
- yAvg /= positionCount;
- zAvg /= positionCount;
-
- // Longitude from cartesian coords
- // (Angle from 3D coords describing a point of globe's surface)
- /*
- UK
- /-------\
- (Top View) /- -\
- /- (You) -\
- /- . -\
- /- . X -\
- Asia - ... - USA
- \- Y -/
- \- -/
- \- -/
- \- -/
- \- -----/
- Pacific
-
- */
-
- *lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
-
- // Latitude from cartesian coords
- // (Angle from 3D coords describing a point on the globe's surface)
- // As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
- // Means we need to first find the hypotenuse which becomes base of our triangle in the second step
- /*
- UK North
- /-------\ (Front View) /-------\
- (Top View) /- -\ /- -\
- /- (You) -\ /-(You) -\
- /- /. -\ /- . -\
- /- √X²+Y²/ . X -\ /- Z . -\
- Asia - /... - USA - ..... -
- \- Y -/ \- √X²+Y² -/
- \- -/ \- -/
- \- -/ \- -/
- \- -/ \- -/
- \- -----/ \- -----/
- Pacific South
- */
-
- float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
- *lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
+ // Use either our node position, or the mean fallback as the center
+ latCenter = *lat;
+ lngCenter = *lng;
// ----------------------------------------------
- // This has given us the "mean position"
- // This will be a position *somewhere* near the center of our nodes.
- // What we actually want is to place our center so that our outermost nodes end up on the border of our map.
- // The only real use of our "mean position" is to give us a reference frame:
- // which direction is east, and which is west.
+ // This has given us either:
+ // - our actual position (preferred), or
+ // - a mean position (fallback if we had no fix)
+ //
+ // What we actually want is to place our center so that our outermost nodes
+ // end up on the border of our map. The only real use of our "center" is to give
+ // us a reference frame: which direction is east, and which is west.
//------------------------------------------------
- // Find furthest nodes from "mean lat long"
+ // Find furthest nodes from our center
// ========================================
-
float northernmost = latCenter;
float southernmost = latCenter;
float easternmost = lngCenter;
float westernmost = lngCenter;
- for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+ for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Skip if no position
@@ -184,14 +299,14 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
continue;
// Check for a new top or bottom latitude
- float lat = node->position.latitude_i * 1e-7;
- northernmost = max(northernmost, lat);
- southernmost = min(southernmost, lat);
+ float latNode = node->position.latitude_i * 1e-7;
+ northernmost = max(northernmost, latNode);
+ southernmost = min(southernmost, latNode);
// Longitude is trickier
- float lng = node->position.longitude_i * 1e-7;
- float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
- float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
+ float lngNode = node->position.longitude_i * 1e-7;
+ float degEastward = fmod(((lngNode - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
+ float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
if (degEastward < degWestward)
easternmost = max(easternmost, lngCenter + degEastward);
else
@@ -250,7 +365,6 @@ InkHUD::MapApplet::Marker InkHUD::MapApplet::calculateMarker(float lat, float ln
m.hopsAway = hopsAway;
return m;
}
-
// Draw a marker on the map for a node, with a shortname label, and backing box
void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
{
@@ -324,6 +438,18 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
textX = labelX + paddingW;
}
+ // Prevent overlap with scale bars and their labels
+ // Define a "safe zone" in the bottom-left where the scale bars and text are drawn
+ constexpr int16_t safeZoneHeight = 28; // adjust based on your label font height
+ constexpr int16_t safeZoneWidth = 60; // adjust based on horizontal label width zone
+ bool overlapsScale = (labelY + labelH > height() - safeZoneHeight) && (labelX < safeZoneWidth);
+
+ // If it overlaps, shift label upward slightly above the safe zone
+ if (overlapsScale) {
+ labelY = height() - safeZoneHeight - labelH - 2;
+ textY = labelY + (labelH / 2);
+ }
+
// Backing box
fillRect(labelX, labelY, labelW, labelH, WHITE);
drawRect(labelX, labelY, labelW, labelH, BLACK);
@@ -348,8 +474,8 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
// Need at least two, to draw a sensible map
bool InkHUD::MapApplet::enoughMarkers()
{
- uint8_t count = 0;
- for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+ size_t count = 0;
+ for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Count nodes
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
index 1b0bfa9d0..5c9906fba 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
@@ -127,6 +127,11 @@ void InkHUD::NodeListApplet::onRender()
// Y value (top) of the current card. Increases as we draw.
uint16_t cardTopY = headerDivY + padDivH;
+ // Clean up deleted nodes before drawing
+ cards.erase(
+ std::remove_if(cards.begin(), cards.end(), [](const CardInfo &c) { return nodeDB->getMeshNode(c.nodeNum) == nullptr; }),
+ cards.end());
+
// -- Each node in list --
for (auto card = cards.begin(); card != cards.end(); ++card) {
@@ -141,6 +146,11 @@ void InkHUD::NodeListApplet::onRender()
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
+ // Skip deleted nodes
+ if (!node) {
+ continue;
+ }
+
// -- Shortname --
// Parse special chars in the short name
// Use "?" if unknown
@@ -188,7 +198,7 @@ void InkHUD::NodeListApplet::onRender()
drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
}
// Otherwise, print "hops away" info, if available
- else if (hopsAway != CardInfo::HOPS_UNKNOWN) {
+ else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
std::string hopString = to_string(node->hops_away);
hopString += " Hop";
if (node->hops_away != 1)
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
index 7876276a8..09f76ed46 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
@@ -709,7 +709,7 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
// Voltage
float voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
char voltageStr[6]; // "XX.XV"
- sprintf(voltageStr, "%.1fV", voltage);
+ sprintf(voltageStr, "%.2fV", voltage);
printAt(colC[0], labelT, "Bat", CENTER, TOP);
printAt(colC[0], valT, voltageStr, CENTER, TOP);
diff --git a/src/graphics/niche/README.md b/src/graphics/niche/README.md
index e87464abc..e58578f6b 100644
--- a/src/graphics/niche/README.md
+++ b/src/graphics/niche/README.md
@@ -5,7 +5,6 @@ A pattern / collection of resources for creating custom UIs, to target small gro
For an example, see the `heltec-vision-master-e290-inkhud` platformio env.
- platformio.ini
-
- suppress default Meshtastic components (Screen, ButtonThread, etc)
- define `MESHTASTIC_INCLUDE_NICHE_GRAPHICS`
- (possibly) Edit `build_src_filter` to include our new nicheGraphics.h file
diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp
index c588a9a0f..0aa78e2b8 100644
--- a/src/input/InputBroker.cpp
+++ b/src/input/InputBroker.cpp
@@ -1,11 +1,13 @@
#include "InputBroker.h"
#include "PowerFSM.h" // needed for event trigger
+#include "configuration.h"
+#include "modules/ExternalNotificationModule.h"
InputBroker *inputBroker = nullptr;
InputBroker::InputBroker()
{
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
@@ -17,7 +19,7 @@ void InputBroker::registerSource(Observable *source)
this->inputEventObserver.observe(source);
}
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
void InputBroker::requestPollSoon(InputPollable *pollable)
{
if (xPortInIsrContext() == pdTRUE) {
@@ -48,11 +50,17 @@ void InputBroker::processInputEventQueue()
int InputBroker::handleInputEvent(const InputEvent *event)
{
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();
+ }
+
this->notifyObservers(event);
return 0;
}
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
void InputBroker::pollSoonWorker(void *p)
{
InputBroker *instance = (InputBroker *)p;
diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h
index 192bd7e8b..36328ca64 100644
--- a/src/input/InputBroker.h
+++ b/src/input/InputBroker.h
@@ -59,7 +59,7 @@ class InputBroker : public Observable
InputBroker();
void registerSource(Observable *source);
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
void requestPollSoon(InputPollable *pollable);
void queueInputEvent(const InputEvent *event);
void processInputEventQueue();
@@ -69,7 +69,7 @@ class InputBroker : public Observable
int handleInputEvent(const InputEvent *event);
private:
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
QueueHandle_t inputEventQueue;
QueueHandle_t pollSoonQueue;
TaskHandle_t pollSoonTask;
diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp
index 098e0804a..eeafe4949 100644
--- a/src/input/TDeckProKeyboard.cpp
+++ b/src/input/TDeckProKeyboard.cpp
@@ -57,7 +57,7 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x20, 0x00, 0x00},
- {0x00, 0x00, 0x00},
+ {0x00, 0x00, '0'},
{0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift
};
diff --git a/src/main.cpp b/src/main.cpp
index 4530b3ff0..61975063a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -23,6 +23,7 @@
#include "power.h"
#if !MESHTASTIC_EXCLUDE_I2C
+#include "detect/ScanI2CConsumer.h"
#include "detect/ScanI2CTwoWire.h"
#include
#endif
@@ -435,6 +436,12 @@ void setup()
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
+#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
+ DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
+ DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
+ DEBUG_PORT.printf("Debug mute is enabled, there will be no serial output.\r\n");
+#endif
+
initDeepSleep();
#if defined(MODEM_POWER_EN)
@@ -470,6 +477,10 @@ void setup()
#ifdef RESET_OLED
pinMode(RESET_OLED, OUTPUT);
digitalWrite(RESET_OLED, 1);
+ delay(2);
+ digitalWrite(RESET_OLED, 0);
+ delay(10);
+ digitalWrite(RESET_OLED, 1);
#endif
#ifdef SENSOR_POWER_CTRL_PIN
@@ -718,46 +729,21 @@ void setup()
LOG_DEBUG("acc_info = %i", acc_info.type);
#endif
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_3XX, meshtastic_TelemetrySensorType_BMP3XX);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHTC3, meshtastic_TelemetrySensorType_SHTC3);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LPS22HB, meshtastic_TelemetrySensorType_LPS22);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::CGRADSENS, meshtastic_TelemetrySensorType_RADSENS);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LTR390UV, meshtastic_TelemetrySensorType_LTR390UV);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DPS310, meshtastic_TelemetrySensorType_DPS310);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2561, meshtastic_TelemetrySensorType_TSL2561);
- i2cScanner.reset();
#endif
#ifdef HAS_SDCARD
@@ -865,7 +851,14 @@ void setup()
SPI.begin();
}
#elif !defined(ARCH_ESP32) // ARCH_RP2040
+#if defined(RAK3401) || defined(RAK13302)
+ pinMode(WB_IO2, OUTPUT);
+ digitalWrite(WB_IO2, HIGH);
+ SPI1.setPins(LORA_MISO, LORA_SCK, LORA_MOSI);
+ SPI1.begin();
+#else
SPI.begin();
+#endif
#else
// ESP32
#if defined(HW_SPI1_DEVICE)
@@ -964,6 +957,13 @@ void setup()
// Now that the mesh service is created, create any modules
setupModules();
+#if !MESHTASTIC_EXCLUDE_I2C
+ // Inform modules about I2C devices
+ ScanI2CCompleted(i2cScanner.get());
+ i2cScanner.reset();
+#endif
+
+#if !defined(MESHTASTIC_EXCLUDE_PKI)
// warn the user about a low entropy key
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
LOG_WARN(LOW_ENTROPY_WARNING);
@@ -974,6 +974,7 @@ void setup()
service->sendClientNotification(cn);
nodeDB->hasWarned = true;
}
+#endif
// buttons are now inputBroker, so have to come after setupModules
#if HAS_BUTTON
@@ -1406,7 +1407,7 @@ void setup()
#endif
// check if the radio chip matches the selected region
- if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) {
+ if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) {
LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
nodeDB->saveToDisk(SEGMENT_CONFIG);
@@ -1600,7 +1601,7 @@ void loop()
#endif
service->loop();
-#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
+#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
if (inputBroker)
inputBroker->processInputEventQueue();
#endif
diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp
index 82d0a9f57..9ca16878d 100644
--- a/src/mesh/CryptoEngine.cpp
+++ b/src/mesh/CryptoEngine.cpp
@@ -92,10 +92,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
LOG_DEBUG("Node %d or their public_key not found", toNode);
return false;
}
- if (!crypto->setDHPublicKey(remotePublic.bytes)) {
+ if (!setDHPublicKey(remotePublic.bytes)) {
return false;
}
- crypto->hash(shared_key, 32);
+ hash(shared_key, 32);
initNonce(fromNode, packetNum, extraNonceTmp);
// Calculate the shared secret with the destination node and encrypt
@@ -134,10 +134,10 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_publ
}
// Calculate the shared secret with the sending node and decrypt
- if (!crypto->setDHPublicKey(remotePublic.bytes)) {
+ if (!setDHPublicKey(remotePublic.bytes)) {
return false;
}
- crypto->hash(shared_key, 32);
+ hash(shared_key, 32);
initNonce(fromNode, packetNum, extraNonce);
printBytes("Attempt decrypt with nonce: ", nonce, 13);
diff --git a/src/mesh/Default.h b/src/mesh/Default.h
index 2f05da98d..218d8d0fb 100644
--- a/src/mesh/Default.h
+++ b/src/mesh/Default.h
@@ -27,8 +27,9 @@
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
#else
-#define default_ringtone_nag_secs 60
+#define default_ringtone_nag_secs 15
#endif
+#define default_network_ipv6_enabled false
#define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev"
@@ -46,12 +47,15 @@ class Default
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
+ // Note: numOnlineNodes uses uint32_t to match the public API and allow flexibility,
+ // even though internal node counts use uint16_t (max 65535 nodes)
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue);
private:
- static float congestionScalingCoefficient(int numOnlineNodes)
+ // Note: Kept as uint32_t to match the public API parameter type
+ static float congestionScalingCoefficient(uint32_t numOnlineNodes)
{
// Increase frequency of broadcasts for small networks regardless of preset
if (numOnlineNodes <= 10) {
@@ -84,4 +88,4 @@ class Default
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
}
}
-};
\ No newline at end of file
+};
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 1d8ac247f..032be241b 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -31,33 +31,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
// Handle hop_limit upgrade scenario for rebroadcasters
- // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
- if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
- // wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit
- // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
- // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
- uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
- if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
- LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
- p->hop_limit, dropThreshold);
-
- if (nodeDB)
- nodeDB->updateFrom(*p);
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
- if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
- p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
- traceRouteModule->processUpgradedPacket(*p);
-#endif
-
- perhapsRebroadcast(p);
-
- // We already enqueued the improved copy, so make sure the incoming packet stops here.
- return true;
- }
-
- // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
- // delivering the same packet to applications/phone twice with different hop limits.
- seenRecently = true;
+ if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
+ return true; // we handled it, so stop processing
}
if (seenRecently) {
@@ -70,8 +45,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
if (isRepeated) {
LOG_DEBUG("Repeated reliable tx");
// Check if it's still in the Tx queue, if not, we have to relay it again
- if (!findInTxQueue(p->from, p->id))
+ if (!findInTxQueue(p->from, p->id)) {
+ reprocessPacket(p);
perhapsRebroadcast(p);
+ }
} else {
perhapsCancelDupe(p);
}
@@ -82,6 +59,40 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return Router::shouldFilterReceived(p);
}
+bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p)
+{
+ // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
+ if (isRebroadcaster() && iface && p->hop_limit > 0) {
+ // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
+ // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
+ uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
+ if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
+ LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
+ p->hop_limit, dropThreshold);
+
+ reprocessPacket(p);
+ perhapsRebroadcast(p);
+
+ rxDupe++;
+ // We already enqueued the improved copy, so make sure the incoming packet stops here.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
+{
+ if (nodeDB)
+ nodeDB->updateFrom(*p);
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+ if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+ p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
+ traceRouteModule->processUpgradedPacket(*p);
+#endif
+}
+
bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
{
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
@@ -121,41 +132,6 @@ bool FloodingRouter::isRebroadcaster()
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
}
-void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
-{
- if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
- if (p->id != 0) {
- if (isRebroadcaster()) {
- meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
-
- // Use shared logic to determine if hop_limit should be decremented
- if (shouldDecrementHopLimit(p)) {
- tosend->hop_limit--; // bump down the hop count
- } else {
- LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
- }
-#if USERPREFS_EVENT_MODE
- if (tosend->hop_limit > 2) {
- // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
- tosend->hop_start -= (tosend->hop_limit - 2);
- tosend->hop_limit = 2;
- }
-#endif
-
- tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
-
- LOG_INFO("Rebroadcast received floodmsg");
- // Note: we are careful to resend using the original senders node id
- send(tosend);
- } else {
- LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
- }
- } else {
- LOG_DEBUG("Ignore 0 id broadcast");
- }
- }
-}
-
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index eaf71d294..e8a2e9685 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -27,10 +27,6 @@
*/
class FloodingRouter : public Router
{
- private:
- /* Check if we should rebroadcast this packet, and do so if needed */
- void perhapsRebroadcast(const meshtastic_MeshPacket *p);
-
public:
/**
* Constructor
@@ -59,6 +55,17 @@ class FloodingRouter : public Router
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
+ /* Check if we should rebroadcast this packet, and do so if needed */
+ virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0;
+
+ /* Check if we should handle an upgraded packet (with higher hop_limit)
+ * @return true if we handled it (so stop processing)
+ */
+ bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p);
+
+ /* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */
+ void reprocessPacket(const meshtastic_MeshPacket *p);
+
// Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
// the same packet
bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp
index 0e23405e5..af6dd92e9 100644
--- a/src/mesh/LR11x0Interface.cpp
+++ b/src/mesh/LR11x0Interface.cpp
@@ -218,6 +218,7 @@ template void LR11x0Interface::addReceiveMetadata(meshtastic_Mes
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
+ LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
}
/** We override to turn on transmitter power as needed.
@@ -243,6 +244,8 @@ template void LR11x0Interface::startReceive()
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
int err =
lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
+ if (err)
+ LOG_ERROR("StartReceive error: %d", err);
assert(err == RADIOLIB_ERR_NONE);
RadioLibInterface::startReceive();
@@ -303,4 +306,4 @@ template bool LR11x0Interface::sleep()
return true;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h
index 66d9d9679..71fb544a0 100644
--- a/src/mesh/MeshService.h
+++ b/src/mesh/MeshService.h
@@ -79,6 +79,18 @@ class MeshService
uint32_t oldFromNum = 0;
public:
+ enum APIState {
+ STATE_DISCONNECTED, // Initial state, no API is connected
+ STATE_BLE,
+ STATE_WIFI,
+ STATE_SERIAL,
+ STATE_PACKET,
+ STATE_HTTP,
+ STATE_ETH
+ };
+
+ APIState api_state = STATE_DISCONNECTED;
+
static bool isTextPayload(const meshtastic_MeshPacket *p)
{
if (moduleConfig.range_test.enabled && p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 0461d7eb6..afdb4d096 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -43,31 +43,8 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
&wasUpgraded); // Updates history; returns false when an upgrade is detected
// Handle hop_limit upgrade scenario for rebroadcasters
- // isRebroadcaster() is duplicated in perhapsRelay(), but this avoids confusing log messages
- if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
- // Upgrade detection bypasses the duplicate short-circuit so we replace the queued packet before exiting
- uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
- if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
- LOG_DEBUG("Processing upgraded packet 0x%08x for relay with hop limit %d (dropping queued < %d)", p->id, p->hop_limit,
- dropThreshold);
-
- if (nodeDB)
- nodeDB->updateFrom(*p);
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
- if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
- p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
- traceRouteModule->processUpgradedPacket(*p);
-#endif
-
- perhapsRelay(p);
-
- // We already enqueued the improved copy, so make sure the incoming packet stops here.
- return true;
- }
-
- // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
- // delivering the same packet to applications/phone twice with different hop limits.
- seenRecently = true;
+ if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
+ return true; // we handled it, so stop processing
}
if (seenRecently) {
@@ -82,14 +59,20 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
if (wasFallback) {
LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
// Check if it's still in the Tx queue, if not, we have to relay it again
- if (!findInTxQueue(p->from, p->id))
- perhapsRelay(p);
+ if (!findInTxQueue(p->from, p->id)) {
+ reprocessPacket(p);
+ perhapsRebroadcast(p);
+ }
} else {
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
if (isRepeated) {
- if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
- sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
+ if (!findInTxQueue(p->from, p->id)) {
+ reprocessPacket(p);
+ if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
+ sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
+ }
+ }
} else if (!weWereNextHop) {
perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
}
@@ -107,13 +90,14 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
if (isAckorReply) {
- // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
- // not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
+ // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from"
+ // is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the
+ // destination
if (p->from != 0) {
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
if (origTx) {
- // Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly
- // from the destination
+ // Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came
+ // directly from the destination
bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
bool weWereSoleRelayer = false;
bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
@@ -134,34 +118,49 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
}
}
- perhapsRelay(p);
+ perhapsRebroadcast(p);
// handle the packet as normal
Router::sniffReceived(p, c);
}
-/* Check if we should be relaying this packet if so, do so. */
-bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
+/* Check if we should be rebroadcasting this packet if so, do so. */
+bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
{
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
- if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
+ if (p->id != 0) {
if (isRebroadcaster()) {
- meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
- LOG_INFO("Relaying received message coming from %x", p->relay_node);
+ if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
+ meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
+ LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
- // Use shared logic to determine if hop_limit should be decremented
- if (shouldDecrementHopLimit(p)) {
- tosend->hop_limit--; // bump down the hop count
- } else {
- LOG_INFO("Router/CLIENT_BASE-to-favorite-router/CLIENT_BASE relay: preserving hop_limit");
+ // Use shared logic to determine if hop_limit should be decremented
+ if (shouldDecrementHopLimit(p)) {
+ tosend->hop_limit--; // bump down the hop count
+ } else {
+ LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
+ }
+#if USERPREFS_EVENT_MODE
+ if (tosend->hop_limit > 2) {
+ // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
+ tosend->hop_start -= (tosend->hop_limit - 2);
+ tosend->hop_limit = 2;
+ }
+#endif
+
+ if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
+ FloodingRouter::send(tosend);
+ } else {
+ NextHopRouter::send(tosend);
+ }
+
+ return true;
}
-
- NextHopRouter::send(tosend);
-
- return true;
} else {
- LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
+ LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
+ } else {
+ LOG_DEBUG("Ignore 0 id broadcast");
}
}
@@ -231,13 +230,13 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
}
}
- // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
- // get scheduled again. (This is the core of stopRetransmission.)
+ // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it
+ // doesn't get scheduled again. (This is the core of stopRetransmission.)
auto numErased = pending.erase(key);
assert(numErased == 1);
- // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
- // to startRetransmission.
+ // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the
+ // call to startRetransmission.
packetPool.release(p);
return true;
diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h
index 0022644e9..c1df3596b 100644
--- a/src/mesh/NextHopRouter.h
+++ b/src/mesh/NextHopRouter.h
@@ -148,7 +148,7 @@ class NextHopRouter : public FloodingRouter
*/
uint8_t getNextHop(NodeNum to, uint8_t relay_node);
- /** Check if we should be relaying this packet if so, do so.
- * @return true if we did relay */
- bool perhapsRelay(const meshtastic_MeshPacket *p);
+ /** Check if we should be rebroadcasting this packet if so, do so.
+ * @return true if we did rebroadcast */
+ bool perhapsRebroadcast(const meshtastic_MeshPacket *p) override;
};
\ No newline at end of file
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index e3240462d..ff76baaa1 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -653,7 +653,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
- defined(ELECROW_PANEL)) && \
+ defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) && \
HAS_TFT
// switch BT off by default; use TFT programming mode or hotkey to enable
config.bluetooth.enabled = false;
@@ -718,6 +718,12 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
strncpy(config.network.wifi_psk, USERPREFS_NETWORK_WIFI_PSK, sizeof(config.network.wifi_psk));
#endif
+#if defined(USERPREFS_NETWORK_IPV6_ENABLED)
+ config.network.ipv6_enabled = USERPREFS_NETWORK_IPV6_ENABLED;
+#else
+ config.network.ipv6_enabled = default_network_ipv6_enabled;
+#endif
+
#ifdef DISPLAY_FLIP_SCREEN
config.display.flip_screen = true;
#endif
@@ -728,6 +734,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.display.screen_on_secs = 30;
config.display.wake_on_tap_or_motion = true;
#endif
+#ifdef COMPASS_ORIENTATION
+ config.display.compass_orientation = COMPASS_ORIENTATION;
+#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
if (WiFiOTA::isUpdated()) {
WiFiOTA::recoverConfig(&config.network);
@@ -978,12 +987,25 @@ void NodeDB::installDefaultChannels()
channelFile.version = DEVICESTATE_CUR_VER;
}
-void NodeDB::resetNodes()
+void NodeDB::resetNodes(bool keepFavorites)
{
if (!config.position.fixed_position)
clearLocalPosition();
numMeshNodes = 1;
- std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
+ if (keepFavorites) {
+ LOG_INFO("Clearing node database - preserving favorites");
+ for (size_t i = 0; i < meshNodes->size(); i++) {
+ meshtastic_NodeInfoLite &node = meshNodes->at(i);
+ if (i > 0 && !node.is_favorite) {
+ node = meshtastic_NodeInfoLite();
+ } else {
+ numMeshNodes += 1;
+ }
+ };
+ } else {
+ LOG_INFO("Clearing node database - removing favorites");
+ std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
+ }
devicestate.has_rx_text_message = false;
devicestate.has_rx_waypoint = false;
saveNodeDatabaseToDisk();
@@ -1632,13 +1654,32 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
// If should_ignore is set,
// we need to clear the public key and other cruft, in addition to setting the node as ignored
info->is_ignored = true;
+ info->is_favorite = false;
info->has_device_metrics = false;
info->has_position = false;
info->user.public_key.size = 0;
info->user.public_key.bytes[0] = 0;
} else {
- info->last_heard = getValidTime(RTCQualityNTP);
- info->is_favorite = true;
+ /* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
+ * public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
+ */
+
+ /* "Boring old nodes" are the first to be evicted out of the node database when full. This includes a newly-zeroed
+ * nodeinfo because it has: !is_favorite && last_heard==0. To keep this from happening when we addFromContact, we set the
+ * new node as a favorite, and we leave last_heard alone (even if it's zero).
+ */
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+ // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
+ // without the user doing so deliberately. We don't normally expect users to use a CLIENT_BASE to send DMs or to add
+ // contacts, but we should make sure it doesn't auto-favorite in case they do. Instead, as a workaround, we'll set
+ // last_heard to now, so that the add_contact node doesn't immediately get evicted.
+ info->last_heard = getTime();
+ } else {
+ // Normal case: set is_favorite to prevent expiration.
+ // last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB).
+ info->is_favorite = true;
+ }
+
// As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
if (contact.manually_verified) {
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
@@ -1874,6 +1915,13 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
return info->channel;
}
+std::string NodeDB::getNodeId() const
+{
+ char nodeId[16];
+ snprintf(nodeId, sizeof(nodeId), "!%08x", myNodeInfo.my_node_num);
+ return std::string(nodeId);
+}
+
/// Find a node in our DB, return null for missing
/// NOTE: This function might be called from an ISR
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
@@ -1963,6 +2011,7 @@ UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
}
+#if !defined(MESHTASTIC_EXCLUDE_PKI)
bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest)
{
if (keyToTest.size == 32) {
@@ -1977,6 +2026,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
}
return false;
}
+#endif
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
{
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index f73f64f92..306acc0a5 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include "MeshTypes.h"
@@ -203,6 +204,9 @@ class NodeDB
/// @return our node number
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
+ /// @return our node ID as a string in the format "!xxxxxxxx"
+ std::string getNodeId() const;
+
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
@@ -225,7 +229,8 @@ class NodeDB
*/
size_t getNumOnlineMeshNodes(bool localOnly = false);
- void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
+ void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
+ removeNodeByNum(NodeNum nodeNum);
bool factoryReset(bool eraseBleBonds = false);
@@ -278,7 +283,9 @@ class NodeDB
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
+#if !defined(MESHTASTIC_EXCLUDE_PKI)
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
+#endif
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
@@ -368,4 +375,4 @@ extern uint32_t error_address;
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
-// Please do not remove this comment, it makes trunk and compiler happy at the same time.
\ No newline at end of file
+// Please do not remove this comment, it makes trunk and compiler happy at the same time.
diff --git a/src/mesh/PacketCache.cpp b/src/mesh/PacketCache.cpp
new file mode 100644
index 000000000..0edf81741
--- /dev/null
+++ b/src/mesh/PacketCache.cpp
@@ -0,0 +1,253 @@
+#include "PacketCache.h"
+#include "Router.h"
+
+PacketCache packetCache{};
+
+/**
+ * Allocate a new cache entry and copy the packet header and payload into it
+ */
+PacketCacheEntry *PacketCache::cache(const meshtastic_MeshPacket *p, bool preserveMetadata)
+{
+ size_t payload_size =
+ (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) ? p->encrypted.size : p->decoded.payload.size;
+ PacketCacheEntry *e = (PacketCacheEntry *)malloc(sizeof(PacketCacheEntry) + payload_size +
+ (preserveMetadata ? sizeof(PacketCacheMetadata) : 0));
+ if (!e) {
+ LOG_ERROR("Unable to allocate memory for packet cache entry");
+ return NULL;
+ }
+
+ *e = {};
+ e->header.from = p->from;
+ e->header.to = p->to;
+ e->header.id = p->id;
+ e->header.channel = p->channel;
+ e->header.next_hop = p->next_hop;
+ e->header.relay_node = p->relay_node;
+ e->header.flags = (p->hop_limit & PACKET_FLAGS_HOP_LIMIT_MASK) | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) |
+ (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0) |
+ ((p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK);
+
+ PacketCacheMetadata m{};
+ if (preserveMetadata) {
+ e->has_metadata = true;
+ m.rx_rssi = (uint8_t)(p->rx_rssi + 200);
+ m.rx_snr = (uint8_t)((p->rx_snr + 30.0f) / 0.25f);
+ m.rx_time = p->rx_time;
+ m.transport_mechanism = p->transport_mechanism;
+ m.priority = p->priority;
+ }
+ if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
+ e->encrypted = true;
+ e->payload_len = p->encrypted.size;
+ memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->encrypted.bytes, p->encrypted.size);
+ } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
+ e->encrypted = false;
+ if (preserveMetadata) {
+ m.portnum = p->decoded.portnum;
+ m.want_response = p->decoded.want_response;
+ m.emoji = p->decoded.emoji;
+ m.bitfield = p->decoded.bitfield;
+ if (p->decoded.reply_id)
+ m.reply_id = p->decoded.reply_id;
+ else if (p->decoded.request_id)
+ m.request_id = p->decoded.request_id;
+ }
+ e->payload_len = p->decoded.payload.size;
+ memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->decoded.payload.bytes, p->decoded.payload.size);
+ } else {
+ LOG_ERROR("Unable to cache packet with unknown payload type %d", p->which_payload_variant);
+ free(e);
+ return NULL;
+ }
+ if (preserveMetadata)
+ memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry) + e->payload_len, &m, sizeof(m));
+
+ size += sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
+ insert(e);
+ return e;
+};
+
+/**
+ * Dump a list of packets into the provided buffer
+ */
+void PacketCache::dump(void *dest, const PacketCacheEntry **entries, size_t num_entries)
+{
+ unsigned char *pos = (unsigned char *)dest;
+ for (size_t i = 0; i < num_entries; i++) {
+ size_t entry_len =
+ sizeof(PacketCacheEntry) + entries[i]->payload_len + (entries[i]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
+ memcpy(pos, entries[i], entry_len);
+ pos += entry_len;
+ }
+}
+
+/**
+ * Calculate the length of buffer needed to dump the specified entries
+ */
+size_t PacketCache::dumpSize(const PacketCacheEntry **entries, size_t num_entries)
+{
+ size_t total_size = 0;
+ for (size_t i = 0; i < num_entries; i++) {
+ total_size += sizeof(PacketCacheEntry) + entries[i]->payload_len;
+ if (entries[i]->has_metadata)
+ total_size += sizeof(PacketCacheMetadata);
+ }
+ return total_size;
+}
+
+/**
+ * Find a packet in the cache
+ */
+PacketCacheEntry *PacketCache::find(NodeNum from, PacketId id)
+{
+ uint16_t h = PACKET_HASH(from, id);
+ PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
+ while (e) {
+ if (e->header.from == from && e->header.id == id)
+ return e;
+ e = e->next;
+ }
+ return NULL;
+}
+
+/**
+ * Find a packet in the cache by its hash
+ */
+PacketCacheEntry *PacketCache::find(PacketHash h)
+{
+ PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
+ while (e) {
+ if (PACKET_HASH(e->header.from, e->header.id) == h)
+ return e;
+ e = e->next;
+ }
+ return NULL;
+}
+
+/**
+ * Load a list of packets from the provided buffer
+ */
+bool PacketCache::load(void *src, PacketCacheEntry **entries, size_t num_entries)
+{
+ memset(entries, 0, sizeof(PacketCacheEntry *) * num_entries);
+ unsigned char *pos = (unsigned char *)src;
+ for (size_t i = 0; i < num_entries; i++) {
+ PacketCacheEntry e{};
+ memcpy(&e, pos, sizeof(PacketCacheEntry));
+ size_t entry_len = sizeof(PacketCacheEntry) + e.payload_len + (e.has_metadata ? sizeof(PacketCacheMetadata) : 0);
+ entries[i] = (PacketCacheEntry *)malloc(entry_len);
+ size += entry_len;
+ if (!entries[i]) {
+ LOG_ERROR("Unable to allocate memory for packet cache entry");
+ for (size_t j = 0; j < i; j++) {
+ size -= sizeof(PacketCacheEntry) + entries[j]->payload_len +
+ (entries[j]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
+ free(entries[j]);
+ entries[j] = NULL;
+ }
+ return false;
+ }
+ memcpy(entries[i], pos, entry_len);
+ pos += entry_len;
+ }
+ for (size_t i = 0; i < num_entries; i++)
+ insert(entries[i]);
+ return true;
+}
+
+/**
+ * Copy the cached packet into the provided MeshPacket structure
+ */
+void PacketCache::rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p)
+{
+ if (!e || !p)
+ return;
+
+ *p = {};
+ p->from = e->header.from;
+ p->to = e->header.to;
+ p->id = e->header.id;
+ p->channel = e->header.channel;
+ p->next_hop = e->header.next_hop;
+ p->relay_node = e->header.relay_node;
+ p->hop_limit = e->header.flags & PACKET_FLAGS_HOP_LIMIT_MASK;
+ p->want_ack = !!(e->header.flags & PACKET_FLAGS_WANT_ACK_MASK);
+ p->via_mqtt = !!(e->header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
+ p->hop_start = (e->header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
+ p->which_payload_variant = e->encrypted ? meshtastic_MeshPacket_encrypted_tag : meshtastic_MeshPacket_decoded_tag;
+
+ unsigned char *payload = ((unsigned char *)e) + sizeof(PacketCacheEntry);
+ PacketCacheMetadata m{};
+ if (e->has_metadata) {
+ memcpy(&m, (payload + e->payload_len), sizeof(m));
+ p->rx_rssi = ((int)m.rx_rssi) - 200;
+ p->rx_snr = ((float)m.rx_snr * 0.25f) - 30.0f;
+ p->rx_time = m.rx_time;
+ p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)m.transport_mechanism;
+ p->priority = (meshtastic_MeshPacket_Priority)m.priority;
+ }
+ if (e->encrypted) {
+ memcpy(p->encrypted.bytes, payload, e->payload_len);
+ p->encrypted.size = e->payload_len;
+ } else {
+ memcpy(p->decoded.payload.bytes, payload, e->payload_len);
+ p->decoded.payload.size = e->payload_len;
+ if (e->has_metadata) {
+ // Decrypted-only metadata
+ p->decoded.portnum = (meshtastic_PortNum)m.portnum;
+ p->decoded.want_response = m.want_response;
+ p->decoded.emoji = m.emoji;
+ p->decoded.bitfield = m.bitfield;
+ if (m.reply_id)
+ p->decoded.reply_id = m.reply_id;
+ else if (m.request_id)
+ p->decoded.request_id = m.request_id;
+ }
+ }
+}
+
+/**
+ * Release a cache entry
+ */
+void PacketCache::release(PacketCacheEntry *e)
+{
+ if (!e)
+ return;
+ remove(e);
+ size -= sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
+ free(e);
+}
+
+/**
+ * Insert a new entry into the hash table
+ */
+void PacketCache::insert(PacketCacheEntry *e)
+{
+ assert(e);
+ PacketHash h = PACKET_HASH(e->header.from, e->header.id);
+ PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
+ e->next = *target;
+ *target = e;
+ num_entries++;
+}
+
+/**
+ * Remove an entry from the hash table
+ */
+void PacketCache::remove(PacketCacheEntry *e)
+{
+ assert(e);
+ PacketHash h = PACKET_HASH(e->header.from, e->header.id);
+ PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
+ while (*target) {
+ if (*target == e) {
+ *target = e->next;
+ e->next = NULL;
+ num_entries--;
+ break;
+ } else {
+ target = &(*target)->next;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/mesh/PacketCache.h b/src/mesh/PacketCache.h
new file mode 100644
index 000000000..85660922b
--- /dev/null
+++ b/src/mesh/PacketCache.h
@@ -0,0 +1,75 @@
+#pragma once
+#include "RadioInterface.h"
+
+#define PACKET_HASH(a, b) ((((a ^ b) >> 16) ^ (a ^ b)) & 0xFFFF) // 16 bit fold of packet (from, id) tuple
+typedef uint16_t PacketHash;
+
+#define PACKET_CACHE_BUCKETS 64 // Number of hash table buckets
+#define PACKET_CACHE_BUCKET(h) (((h >> 12) ^ (h >> 6) ^ h) & 0x3F) // Fold hash down to 6-bit bucket index
+
+typedef struct PacketCacheEntry {
+ PacketCacheEntry *next;
+ PacketHeader header;
+ uint16_t payload_len = 0;
+ union {
+ uint16_t bitfield;
+ struct {
+ uint8_t encrypted : 1; // Payload is encrypted
+ uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata
+ uint8_t : 6; // Reserved for future use
+ uint8_t : 8; // Reserved for future use
+ };
+ };
+} PacketCacheEntry;
+
+typedef struct PacketCacheMetadata {
+ PacketCacheMetadata() : _bitfield(0), reply_id(0), _bitfield2(0) {}
+ union {
+ uint32_t _bitfield;
+ struct {
+ uint16_t portnum : 9; // meshtastic_MeshPacket::decoded::portnum
+ uint16_t want_response : 1; // meshtastic_MeshPacket::decoded::want_response
+ uint16_t emoji : 1; // meshtastic_MeshPacket::decoded::emoji
+ uint16_t bitfield : 5; // meshtastic_MeshPacket::decoded::bitfield (truncated)
+ uint8_t rx_rssi : 8; // meshtastic_MeshPacket::rx_rssi (map via actual RSSI + 200)
+ uint8_t rx_snr : 8; // meshtastic_MeshPacket::rx_snr (map via (p->rx_snr + 30.0f) / 0.25f)
+ };
+ };
+ union {
+ uint32_t reply_id; // meshtastic_MeshPacket::decoded.reply_id
+ uint32_t request_id; // meshtastic_MeshPacket::decoded.request_id
+ };
+ uint32_t rx_time = 0; // meshtastic_MeshPacket::rx_time
+ uint8_t transport_mechanism = 0; // meshtastic_MeshPacket::transport_mechanism
+ struct {
+ uint8_t _bitfield2;
+ union {
+ uint8_t priority : 7; // meshtastic_MeshPacket::priority
+ uint8_t reserved : 1; // Reserved for future use
+ };
+ };
+} PacketCacheMetadata;
+
+class PacketCache
+{
+ public:
+ PacketCacheEntry *cache(const meshtastic_MeshPacket *p, bool preserveMetadata);
+ static void dump(void *dest, const PacketCacheEntry **entries, size_t num_entries);
+ size_t dumpSize(const PacketCacheEntry **entries, size_t num_entries);
+ PacketCacheEntry *find(NodeNum from, PacketId id);
+ PacketCacheEntry *find(PacketHash h);
+ bool load(void *src, PacketCacheEntry **entries, size_t num_entries);
+ size_t getNumEntries() { return num_entries; }
+ size_t getSize() { return size; }
+ void rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p);
+ void release(PacketCacheEntry *e);
+
+ private:
+ PacketCacheEntry *buckets[PACKET_CACHE_BUCKETS]{};
+ size_t num_entries = 0;
+ size_t size = 0;
+ void insert(PacketCacheEntry *e);
+ void remove(PacketCacheEntry *e);
+};
+
+extern PacketCache packetCache;
\ No newline at end of file
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 49d581d9a..b4af707ae 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -94,7 +94,6 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit,
p->hop_limit);
*wasUpgraded = true;
- seenRecently = false; // Allow router processing but prevent duplicate app delivery
} else if (wasUpgraded) {
*wasUpgraded = false; // Initialize to false if not an upgrade
}
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 07f314415..9050ee89d 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -15,6 +15,7 @@
#include "Router.h"
#include "SPILock.h"
#include "TypeConversions.h"
+#include "concurrency/LockGuard.h"
#include "main.h"
#include "xmodem.h"
@@ -56,6 +57,9 @@ void PhoneAPI::handleStartConfig()
#endif
}
+ // Allow subclasses to prepare for high-throughput config traffic
+ onConfigStart();
+
// even if we were already connected - restart our state machine
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
// If client only wants node info, jump directly to sending nodes
@@ -70,14 +74,31 @@ void PhoneAPI::handleStartConfig()
spiLock->unlock();
LOG_DEBUG("Got %d files in manifest", filesManifest.size());
- LOG_INFO("Start API client config");
- nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos
+ LOG_INFO("Start API client config millis=%u", millis());
+ // Protect against concurrent BLE callbacks: they run in NimBLE's FreeRTOS task and also touch nodeInfoQueue.
+ {
+ concurrency::LockGuard guard(&nodeInfoMutex);
+ nodeInfoForPhone = {};
+ nodeInfoQueue.clear();
+ }
resetReadIndex();
}
void PhoneAPI::close()
{
LOG_DEBUG("PhoneAPI::close()");
+ if (service->api_state == service->STATE_BLE && api_type == TYPE_BLE)
+ service->api_state = service->STATE_DISCONNECTED;
+ else if (service->api_state == service->STATE_WIFI && api_type == TYPE_WIFI)
+ service->api_state = service->STATE_DISCONNECTED;
+ else if (service->api_state == service->STATE_SERIAL && api_type == TYPE_SERIAL)
+ service->api_state = service->STATE_DISCONNECTED;
+ else if (service->api_state == service->STATE_PACKET && api_type == TYPE_PACKET)
+ service->api_state = service->STATE_DISCONNECTED;
+ else if (service->api_state == service->STATE_HTTP && api_type == TYPE_HTTP)
+ service->api_state = service->STATE_DISCONNECTED;
+ else if (service->api_state == service->STATE_ETH && api_type == TYPE_ETH)
+ service->api_state = service->STATE_DISCONNECTED;
if (state != STATE_SEND_NOTHING) {
state = STATE_SEND_NOTHING;
@@ -93,7 +114,12 @@ void PhoneAPI::close()
onConnectionChanged(false);
fromRadioScratch = {};
toRadioScratch = {};
- nodeInfoForPhone = {};
+ // Clear cached node info under lock because NimBLE callbacks can still be draining it.
+ {
+ concurrency::LockGuard guard(&nodeInfoMutex);
+ nodeInfoForPhone = {};
+ nodeInfoQueue.clear();
+ }
packetForPhone = NULL;
filesManifest.clear();
fromRadioNum = 0;
@@ -148,6 +174,10 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
#if !MESHTASTIC_EXCLUDE_MQTT
case meshtastic_ToRadio_mqttClientProxyMessage_tag:
LOG_DEBUG("Got MqttClientProxy message");
+ if (state != STATE_SEND_PACKETS) {
+ LOG_WARN("Ignore MqttClientProxy message while completing config handshake");
+ break;
+ }
if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled &&
(channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) {
mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage);
@@ -239,13 +269,20 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
LOG_DEBUG("Send My NodeInfo");
auto us = nodeDB->readNextMeshNode(readIndex);
if (us) {
- nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us);
- nodeInfoForPhone.has_hops_away = false;
- nodeInfoForPhone.is_favorite = true;
+ auto info = TypeConversions::ConvertToNodeInfo(us);
+ info.has_hops_away = false;
+ info.is_favorite = true;
+ {
+ concurrency::LockGuard guard(&nodeInfoMutex);
+ nodeInfoForPhone = info;
+ }
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
- fromRadioScratch.node_info = nodeInfoForPhone;
+ fromRadioScratch.node_info = info;
// Should allow us to resume sending NodeInfo in STATE_SEND_OTHER_NODEINFOS
- nodeInfoForPhone.num = 0;
+ {
+ concurrency::LockGuard guard(&nodeInfoMutex);
+ nodeInfoForPhone.num = 0;
+ }
}
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
// If client only wants node info, jump directly to sending nodes
@@ -431,16 +468,44 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
break;
case STATE_SEND_OTHER_NODEINFOS: {
- LOG_DEBUG("Send known nodes");
- if (nodeInfoForPhone.num != 0) {
- LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
- nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
+ if (readIndex == 2) { // readIndex==2 will be true for the first non-us node
+ LOG_INFO("Start sending nodeinfos millis=%u", millis());
+ }
+
+ meshtastic_NodeInfo infoToSend = {};
+ {
+ concurrency::LockGuard guard(&nodeInfoMutex);
+ if (nodeInfoForPhone.num == 0 && !nodeInfoQueue.empty()) {
+ // Serve the next cached node without re-reading from the DB iterator.
+ nodeInfoForPhone = nodeInfoQueue.front();
+ nodeInfoQueue.pop_front();
+ }
+ infoToSend = nodeInfoForPhone;
+ if (infoToSend.num != 0)
+ nodeInfoForPhone = {};
+ }
+
+ if (infoToSend.num != 0) {
+ // Just in case we stored a different user.id in the past, but should never happen going forward
+ sprintf(infoToSend.user.id, "!%08x", infoToSend.num);
+
+ // Logging this really slows down sending nodes on initial connection because the serial console is so slow, so only
+ // uncomment if you really need to:
+ // LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
+ // nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
+
+ // Occasional progress logging. (readIndex==2 will be true for the first non-us node)
+ if (readIndex == 2 || readIndex % 20 == 0) {
+ LOG_DEBUG("nodeinfo: %d/%d", readIndex, nodeDB->getNumMeshNodes());
+ }
+
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
- fromRadioScratch.node_info = nodeInfoForPhone;
- // Stay in current state until done sending nodeinfos
- nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time
+ fromRadioScratch.node_info = infoToSend;
+ prefetchNodeInfos();
} else {
- LOG_DEBUG("Done sending nodeinfo");
+ LOG_DEBUG("Done sending %d of %d nodeinfos millis=%u", readIndex, nodeDB->getNumMeshNodes(), millis());
+ concurrency::LockGuard guard(&nodeInfoMutex);
+ nodeInfoQueue.clear();
state = STATE_SEND_FILEMANIFEST;
// Go ahead and send that ID right now
return getFromRadio(buf);
@@ -520,11 +585,28 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
void PhoneAPI::sendConfigComplete()
{
- LOG_INFO("Config Send Complete");
+ LOG_INFO("Config Send Complete millis=%u", millis());
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag;
fromRadioScratch.config_complete_id = config_nonce;
config_nonce = 0;
state = STATE_SEND_PACKETS;
+ if (api_type == TYPE_BLE) {
+ service->api_state = service->STATE_BLE;
+ } else if (api_type == TYPE_WIFI) {
+ service->api_state = service->STATE_WIFI;
+ } else if (api_type == TYPE_SERIAL) {
+ service->api_state = service->STATE_SERIAL;
+ } else if (api_type == TYPE_PACKET) {
+ service->api_state = service->STATE_PACKET;
+ } else if (api_type == TYPE_HTTP) {
+ service->api_state = service->STATE_HTTP;
+ } else if (api_type == TYPE_ETH) {
+ service->api_state = service->STATE_ETH;
+ }
+
+ // Allow subclasses to know we've entered steady-state so they can lower power consumption
+ onConfigComplete();
+
pauseBluetoothLogging = false;
}
@@ -544,6 +626,33 @@ void PhoneAPI::releaseQueueStatusPhonePacket()
}
}
+void PhoneAPI::prefetchNodeInfos()
+{
+ bool added = false;
+ // Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment.
+ {
+ concurrency::LockGuard guard(&nodeInfoMutex);
+ while (nodeInfoQueue.size() < kNodePrefetchDepth) {
+ auto nextNode = nodeDB->readNextMeshNode(readIndex);
+ if (!nextNode)
+ break;
+
+ auto info = TypeConversions::ConvertToNodeInfo(nextNode);
+ bool isUs = info.num == nodeDB->getNodeNum();
+ info.hops_away = isUs ? 0 : info.hops_away;
+ info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
+ info.snr = isUs ? 0 : info.snr;
+ info.via_mqtt = isUs ? false : info.via_mqtt;
+ info.is_favorite = info.is_favorite || isUs;
+ nodeInfoQueue.push_back(info);
+ added = true;
+ }
+ }
+
+ if (added)
+ onNowHasData(0);
+}
+
void PhoneAPI::releaseMqttClientProxyPhonePacket()
{
if (mqttClientProxyMessageForPhone) {
@@ -579,21 +688,17 @@ bool PhoneAPI::available()
case STATE_SEND_COMPLETE_ID:
return true;
- case STATE_SEND_OTHER_NODEINFOS:
- if (nodeInfoForPhone.num == 0) {
- auto nextNode = nodeDB->readNextMeshNode(readIndex);
- if (nextNode) {
- nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode);
- bool isUs = nodeInfoForPhone.num == nodeDB->getNodeNum();
- nodeInfoForPhone.hops_away = isUs ? 0 : nodeInfoForPhone.hops_away;
- nodeInfoForPhone.last_heard = isUs ? getValidTime(RTCQualityFromNet) : nodeInfoForPhone.last_heard;
- nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
- nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
- nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
- onNowHasData(0);
- }
+ case STATE_SEND_OTHER_NODEINFOS: {
+ concurrency::LockGuard guard(&nodeInfoMutex);
+ if (nodeInfoQueue.empty()) {
+ // Drop the lock before prefetching; prefetchNodeInfos() will re-acquire it.
+ goto PREFETCH_NODEINFO;
}
+ }
return true; // Always say we have something, because we might need to advance our state machine
+ PREFETCH_NODEINFO:
+ prefetchNodeInfos();
+ return true;
case STATE_SEND_PACKETS: {
if (!queueStatusPacketForPhone)
queueStatusPacketForPhone = service->getQueueStatusForPhone();
@@ -732,7 +837,7 @@ int PhoneAPI::onNotify(uint32_t newValue)
LOG_INFO("Tell client we have new packets %u", newValue);
onNowHasData(newValue);
} else {
- LOG_DEBUG("(Client not yet interested in packets)");
+ LOG_DEBUG("Client not yet interested in packets (state=%d)", state);
}
return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index 0d7772d17..7f79b5792 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -1,8 +1,10 @@
#pragma once
#include "Observer.h"
+#include "concurrency/Lock.h"
#include "mesh-pb-constants.h"
#include "meshtastic/portnums.pb.h"
+#include
#include
#include
#include
@@ -79,6 +81,12 @@ class PhoneAPI
/// We temporarily keep the nodeInfo here between the call to available and getFromRadio
meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default;
+ // Prefetched node info entries ready for immediate transmission to the phone.
+ std::deque nodeInfoQueue;
+ // Tunable size of the node info cache so we can keep BLE reads non-blocking.
+ static constexpr size_t kNodePrefetchDepth = 4;
+ // Protect nodeInfoForPhone + nodeInfoQueue because NimBLE callbacks run in a separate FreeRTOS task.
+ concurrency::Lock nodeInfoMutex;
meshtastic_ToRadio toRadioScratch = {
0}; // this is a static scratch object, any data must be copied elsewhere before returning
@@ -128,6 +136,7 @@ class PhoneAPI
bool available();
bool isConnected() { return state != STATE_SEND_NOTHING; }
+ bool isSendingPackets() { return state == STATE_SEND_PACKETS; }
protected:
/// Our fromradio packet while it is being assembled
@@ -150,14 +159,33 @@ class PhoneAPI
*/
virtual void onNowHasData(uint32_t fromRadioNum) {}
+ /// Subclasses can use these lifecycle hooks for transport-specific behavior around config/steady-state
+ /// (i.e. BLE connection params)
+ virtual void onConfigStart() {}
+ virtual void onConfigComplete() {}
+
/// begin a new connection
void handleStartConfig();
+ enum APIType {
+ TYPE_NONE, // Initial state, don't send anything until the client starts asking for config
+ TYPE_BLE,
+ TYPE_WIFI,
+ TYPE_SERIAL,
+ TYPE_PACKET,
+ TYPE_HTTP,
+ TYPE_ETH
+ };
+
+ APIType api_type = TYPE_NONE;
+
private:
void releasePhonePacket();
void releaseQueueStatusPhonePacket();
+ void prefetchNodeInfos();
+
void releaseMqttClientProxyPhonePacket();
void releaseClientNotification();
diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h
index e038e9bb8..725477eae 100644
--- a/src/mesh/ProtobufModule.h
+++ b/src/mesh/ProtobufModule.h
@@ -13,7 +13,7 @@ template class ProtobufModule : protected SinglePortModule
const pb_msgdesc_t *fields;
public:
- uint8_t numOnlineNodes = 0;
+ uint16_t numOnlineNodes = 0;
/** Constructor
* name is for debugging output
*/
diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp
index 0f32f3427..da0039d38 100644
--- a/src/mesh/RF95Interface.cpp
+++ b/src/mesh/RF95Interface.cpp
@@ -260,6 +260,7 @@ void RF95Interface::addReceiveMetadata(meshtastic_MeshPacket *mp)
{
mp->rx_snr = lora->getSNR();
mp->rx_rssi = lround(lora->getRSSI());
+ LOG_DEBUG("Corrected frequency offset: %f", lora->getFrequencyError());
}
void RF95Interface::setStandby()
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 88218e406..3c0da4494 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -272,10 +272,10 @@ uint32_t RadioInterface::getTxDelayMsec()
uint8_t RadioInterface::getCWsize(float snr)
{
// The minimum value for a LoRa SNR
- const uint32_t SNR_MIN = -20;
+ const int32_t SNR_MIN = -20;
// The maximum value for a LoRa SNR
- const uint32_t SNR_MAX = 10;
+ const int32_t SNR_MAX = 10;
return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
}
@@ -647,23 +647,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
}
#ifndef NUM_PA_POINTS
- if (TX_GAIN_LORA > 0) {
+ if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) {
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
power -= TX_GAIN_LORA;
}
#else
- // we have an array of PA gain values. Find the highest power setting that works.
- const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
- for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
- if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
- ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
- // 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", power, tx_gain[radio_dbm]);
- power -= tx_gain[radio_dbm];
- break;
+ if (!devicestate.owner.is_licensed) {
+ // we have an array of PA gain values. Find the highest power setting that works.
+ const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
+ for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
+ if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
+ ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
+ // 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", power, tx_gain[radio_dbm]);
+ power -= tx_gain[radio_dbm];
+ break;
+ }
}
}
-
#endif
if (power > loraMaxPower) // Clamp power to maximum defined level
power = loraMaxPower;
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index 2567d9e7f..80e51b8bc 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -289,12 +289,7 @@ void RadioLibInterface::onNotify(uint32_t notification)
// actual transmission as short as possible
txp = txQueue.dequeue();
assert(txp);
- bool sent = startSend(txp);
- if (sent) {
- // Packet has been sent, count it toward our TX airtime utilization.
- uint32_t xmitMsec = getPacketTime(txp);
- airTime->logAirtime(TX_LOG, xmitMsec);
- }
+ startSend(txp);
LOG_DEBUG("%d packets remain in the TX queue", txQueue.getMaxLen() - txQueue.getFree());
}
}
@@ -413,6 +408,10 @@ void RadioLibInterface::completeSending()
sendingPacket = NULL;
if (p) {
+ // Packet has been sent, count it toward our TX airtime utilization.
+ uint32_t xmitMsec = getPacketTime(p);
+ airTime->logAirtime(TX_LOG, xmitMsec);
+
txGood++;
if (!isFromUs(p))
txRelay++;
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 60637cbd1..05f47d7f4 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -35,6 +35,15 @@
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
+static MemoryDynamic dynamicPool;
+Allocator &packetPool = dynamicPool;
+#elif defined(ARCH_STM32WL)
+// On STM32 there isn't enough heap left over for the rest of the firmware if we allocate this statically.
+// For now, make it dynamic again.
+#define MAX_PACKETS \
+ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
+ 2) // max number of packets which can be in flight (either queued from reception or queued for sending)
+
static MemoryDynamic dynamicPool;
Allocator &packetPool = dynamicPool;
#else
@@ -470,6 +479,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
LOG_ERROR("Invalid portnum (bad psk?)!");
+#if !(MESHTASTIC_EXCLUDE_PKI)
+ } else if (!owner.is_licensed && isToUs(p) && decodedtmp.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
+ LOG_WARN("Rejecting legacy DM");
+ return DecodeState::DECODE_FAILURE;
+#endif
} else {
p->decoded = decodedtmp;
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index c60ef3a80..e1f07a32b 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -266,6 +266,7 @@ template void SX126xInterface::addReceiveMetadata(meshtastic_Mes
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
+ LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
}
/** We override to turn on transmitter power as needed.
diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp
index cbc98eeb1..80872af07 100644
--- a/src/mesh/SX128xInterface.cpp
+++ b/src/mesh/SX128xInterface.cpp
@@ -204,6 +204,7 @@ template void SX128xInterface::addReceiveMetadata(meshtastic_Mes
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
+ LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
}
/** We override to turn on transmitter power as needed.
diff --git a/src/mesh/api/PacketAPI.cpp b/src/mesh/api/PacketAPI.cpp
index ab380d696..f4d5de540 100644
--- a/src/mesh/api/PacketAPI.cpp
+++ b/src/mesh/api/PacketAPI.cpp
@@ -19,6 +19,7 @@ PacketAPI *PacketAPI::create(PacketServer *_server)
PacketAPI::PacketAPI(PacketServer *_server)
: concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server)
{
+ api_type = TYPE_PACKET;
}
int32_t PacketAPI::runOnce()
diff --git a/src/mesh/api/WiFiServerAPI.cpp b/src/mesh/api/WiFiServerAPI.cpp
index b19194f78..4d729f5c7 100644
--- a/src/mesh/api/WiFiServerAPI.cpp
+++ b/src/mesh/api/WiFiServerAPI.cpp
@@ -25,6 +25,7 @@ void deInitApiServer()
WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client)
{
+ api_type = TYPE_WIFI;
LOG_INFO("Incoming wifi connection");
}
diff --git a/src/mesh/api/ethServerAPI.cpp b/src/mesh/api/ethServerAPI.cpp
index ce722c033..892718e04 100644
--- a/src/mesh/api/ethServerAPI.cpp
+++ b/src/mesh/api/ethServerAPI.cpp
@@ -20,6 +20,7 @@ void initApiServer(int port)
ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
{
LOG_INFO("Incoming ethernet connection");
+ api_type = TYPE_ETH;
}
ethServerPort::ethServerPort(int port) : APIServerPort(port) {}
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 7cc896292..a542cf29c 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -272,8 +272,9 @@ typedef struct _meshtastic_AdminMessage {
int32_t shutdown_seconds;
/* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */
int32_t factory_reset_config;
- /* Tell the node to reset the nodedb. */
- int32_t nodedb_reset;
+ /* Tell the node to reset the nodedb.
+ When true, favorites are preserved through reset. */
+ bool nodedb_reset;
};
/* The node generates this key and sends it with any get_x_response packets.
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
@@ -459,7 +460,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulato
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \
X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \
-X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) \
+X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101)
#define meshtastic_AdminMessage_CALLBACK NULL
#define meshtastic_AdminMessage_DEFAULT NULL
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index db9dedaaf..f4c33bd79 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size 695
+#define meshtastic_ChannelSet_size 679
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index d5573a1e2..9dc757ab4 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -34,9 +34,9 @@ typedef enum _meshtastic_Channel_Role {
typedef struct _meshtastic_ModuleSettings {
/* Bits of precision for the location sent in position packets. */
uint32_t position_precision;
- /* Controls whether or not the phone / clients should mute the current channel
+ /* Controls whether or not the client / device should mute the current channel
Useful for noisy public channels you don't necessarily want to disable */
- bool is_client_muted;
+ bool is_muted;
} meshtastic_ModuleSettings;
typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t;
@@ -97,8 +97,6 @@ typedef struct _meshtastic_ChannelSettings {
/* Per-channel module settings. */
bool has_module_settings;
meshtastic_ModuleSettings module_settings;
- /* Whether or not we should receive notifactions / alerts through this channel */
- bool mute;
} meshtastic_ChannelSettings;
/* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -130,16 +128,16 @@ extern "C" {
/* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
+#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
#define meshtastic_ModuleSettings_init_default {0, 0}
#define meshtastic_Channel_init_default {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
+#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
#define meshtastic_ModuleSettings_init_zero {0, 0}
#define meshtastic_Channel_init_zero {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_ModuleSettings_position_precision_tag 1
-#define meshtastic_ModuleSettings_is_client_muted_tag 2
+#define meshtastic_ModuleSettings_is_muted_tag 2
#define meshtastic_ChannelSettings_channel_num_tag 1
#define meshtastic_ChannelSettings_psk_tag 2
#define meshtastic_ChannelSettings_name_tag 3
@@ -147,7 +145,6 @@ extern "C" {
#define meshtastic_ChannelSettings_uplink_enabled_tag 5
#define meshtastic_ChannelSettings_downlink_enabled_tag 6
#define meshtastic_ChannelSettings_module_settings_tag 7
-#define meshtastic_ChannelSettings_mute_tag 8
#define meshtastic_Channel_index_tag 1
#define meshtastic_Channel_settings_tag 2
#define meshtastic_Channel_role_tag 3
@@ -160,15 +157,14 @@ X(a, STATIC, SINGULAR, STRING, name, 3) \
X(a, STATIC, SINGULAR, FIXED32, id, 4) \
X(a, STATIC, SINGULAR, BOOL, uplink_enabled, 5) \
X(a, STATIC, SINGULAR, BOOL, downlink_enabled, 6) \
-X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7) \
-X(a, STATIC, SINGULAR, BOOL, mute, 8)
+X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7)
#define meshtastic_ChannelSettings_CALLBACK NULL
#define meshtastic_ChannelSettings_DEFAULT NULL
#define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
#define meshtastic_ModuleSettings_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, position_precision, 1) \
-X(a, STATIC, SINGULAR, BOOL, is_client_muted, 2)
+X(a, STATIC, SINGULAR, BOOL, is_muted, 2)
#define meshtastic_ModuleSettings_CALLBACK NULL
#define meshtastic_ModuleSettings_DEFAULT NULL
@@ -191,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size 74
-#define meshtastic_Channel_size 89
+#define meshtastic_ChannelSettings_size 72
+#define meshtastic_Channel_size 87
#define meshtastic_ModuleSettings_size 8
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index b5b116137..7fab82ff7 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -360,8 +360,8 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
-#define meshtastic_BackupPreferences_size 2293
-#define meshtastic_ChannelFile_size 734
+#define meshtastic_BackupPreferences_size 2277
+#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1737
#define meshtastic_NodeInfoLite_size 196
#define meshtastic_PositionLite_size 28
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index d8d2f2e8a..46de1dee0 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -282,6 +282,18 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113,
/* LilyGo T-Watch Ultra */
meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
+ /* Elecrow ThinkNode M3 */
+ meshtastic_HardwareModel_THINKNODE_M3 = 115,
+ /* RAK WISMESH_TAP_V2 with ESP32-S3 CPU */
+ meshtastic_HardwareModel_WISMESH_TAP_V2 = 116,
+ /* RAK3401 */
+ meshtastic_HardwareModel_RAK3401 = 117,
+ /* RAK6421 Hat+ */
+ meshtastic_HardwareModel_RAK6421 = 118,
+ /* Elecrow ThinkNode M4 */
+ meshtastic_HardwareModel_THINKNODE_M4 = 119,
+ /* Elecrow ThinkNode M6 */
+ meshtastic_HardwareModel_THINKNODE_M6 = 120,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -819,7 +831,11 @@ typedef struct _meshtastic_MeshPacket {
Note: Our crypto implementation uses this field as well.
See [crypto](/docs/overview/encryption) for details. */
uint32_t from;
- /* The (immediate) destination for this packet */
+ /* The (immediate) destination for this packet
+ If the value is 4,294,967,295 (maximum value of an unsigned 32bit integer), this indicates that the packet was
+ not destined for a specific node, but for a channel as indicated by the value of `channel` below.
+ If the value is another, this indicates that the packet was destined for a specific
+ node (i.e. a kind of "Direct Message" to this node) and not broadcast on a channel. */
uint32_t to;
/* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on.
If unset, packet was on the primary channel.
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index a8ca96e95..dec89ba15 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -101,7 +101,9 @@ typedef enum _meshtastic_TelemetrySensorType {
/* SEN5X PM SENSORS */
meshtastic_TelemetrySensorType_SEN5X = 43,
/* TSL2561 light sensor */
- meshtastic_TelemetrySensorType_TSL2561 = 44
+ meshtastic_TelemetrySensorType_TSL2561 = 44,
+ /* BH1750 light sensor */
+ meshtastic_TelemetrySensorType_BH1750 = 45
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@@ -438,8 +440,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
-#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL2561
-#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL2561+1))
+#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BH1750
+#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BH1750+1))
diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index f87c6e3b0..7b7ebb595 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -148,6 +148,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
{
+ if (webServerThread)
+ webServerThread->markActivity();
LOG_DEBUG("webAPI handleAPIv1FromRadio");
@@ -391,6 +393,9 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
void handleStatic(HTTPRequest *req, HTTPResponse *res)
{
+ if (webServerThread)
+ webServerThread->markActivity();
+
// Get access to the parameters
ResourceParameters *params = req->getParams();
diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h
index 2066a6d57..91cad3359 100644
--- a/src/mesh/http/ContentHandler.h
+++ b/src/mesh/http/ContentHandler.h
@@ -26,7 +26,7 @@ class HttpAPI : public PhoneAPI
{
public:
- // Nothing here yet
+ HttpAPI() { api_type = TYPE_HTTP; }
private:
// Nothing here yet
diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp
index bf170de59..3a264fa5a 100644
--- a/src/mesh/http/WebServer.cpp
+++ b/src/mesh/http/WebServer.cpp
@@ -49,6 +49,12 @@ Preferences prefs;
using namespace httpsserver;
#include "mesh/http/ContentHandler.h"
+static const uint32_t ACTIVE_THRESHOLD_MS = 5000;
+static const uint32_t MEDIUM_THRESHOLD_MS = 30000;
+static const int32_t ACTIVE_INTERVAL_MS = 50;
+static const int32_t MEDIUM_INTERVAL_MS = 200;
+static const int32_t IDLE_INTERVAL_MS = 1000;
+
static SSLCert *cert;
static HTTPSServer *secureServer;
static HTTPServer *insecureServer;
@@ -175,6 +181,32 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer")
if (!config.network.wifi_enabled && !config.network.eth_enabled) {
disable();
}
+ lastActivityTime = millis();
+}
+
+void WebServerThread::markActivity()
+{
+ lastActivityTime = millis();
+}
+
+int32_t WebServerThread::getAdaptiveInterval()
+{
+ uint32_t currentTime = millis();
+ uint32_t timeSinceActivity;
+
+ if (currentTime >= lastActivityTime) {
+ timeSinceActivity = currentTime - lastActivityTime;
+ } else {
+ timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1;
+ }
+
+ if (timeSinceActivity < ACTIVE_THRESHOLD_MS) {
+ return ACTIVE_INTERVAL_MS;
+ } else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) {
+ return MEDIUM_INTERVAL_MS;
+ } else {
+ return IDLE_INTERVAL_MS;
+ }
}
int32_t WebServerThread::runOnce()
@@ -189,8 +221,7 @@ int32_t WebServerThread::runOnce()
ESP.restart();
}
- // Loop every 5ms.
- return (5);
+ return getAdaptiveInterval();
}
void initWebServer()
diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h
index 815d87432..e7a29a5a7 100644
--- a/src/mesh/http/WebServer.h
+++ b/src/mesh/http/WebServer.h
@@ -10,13 +10,17 @@ void createSSLCert();
class WebServerThread : private concurrency::OSThread
{
+ private:
+ uint32_t lastActivityTime = 0;
public:
WebServerThread();
uint32_t requestRestart = 0;
+ void markActivity();
protected:
virtual int32_t runOnce() override;
+ int32_t getAdaptiveInterval();
};
extern WebServerThread *webServerThread;
diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h
index b45348cf3..5a4adedaa 100644
--- a/src/mesh/raspihttp/PiWebServer.h
+++ b/src/mesh/raspihttp/PiWebServer.h
@@ -27,7 +27,7 @@ class HttpAPI : public PhoneAPI
{
public:
- // Nothing here yet
+ HttpAPI() { api_type = TYPE_HTTP; }
private:
// Nothing here yet
diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp
index f8aa7331b..a497e2b69 100644
--- a/src/mesh/wifi/WiFiAPClient.cpp
+++ b/src/mesh/wifi/WiFiAPClient.cpp
@@ -94,11 +94,13 @@ static void onNetworkConnected()
// ESPmDNS (ESP32) and SimpleMDNS (RP2040) have slightly different APIs for adding TXT records
#ifdef ARCH_ESP32
MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name));
- MDNS.addServiceTxt("meshtastic", "tcp", "id", String(owner.id));
+ MDNS.addServiceTxt("meshtastic", "tcp", "id", String(nodeDB->getNodeId().c_str()));
+ MDNS.addServiceTxt("meshtastic", "tcp", "pio_env", optstr(APP_ENV));
// ESP32 prints obtained IP address in WiFiEvent
#elif defined(ARCH_RP2040)
MDNS.addServiceTxt("meshtastic", "shortname", owner.short_name);
- MDNS.addServiceTxt("meshtastic", "id", owner.id);
+ MDNS.addServiceTxt("meshtastic", "id", nodeDB->getNodeId().c_str());
+ MDNS.addServiceTxt("meshtastic", "pio_env", optstr(APP_ENV));
LOG_INFO("Obtained IP address: %s", WiFi.localIP().toString().c_str());
#endif
}
@@ -332,6 +334,23 @@ bool initWifi()
}
#ifdef ARCH_ESP32
+#if ESP_ARDUINO_VERSION <= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+// Most of the next 12 lines of code are adapted from espressif/arduino-esp32
+// Licensed under the GNU Lesser General Public License v2.1
+// https://github.com/espressif/arduino-esp32/blob/1f038677eb2eaf5e9ca6b6074486803c15468bed/libraries/WiFi/src/WiFiSTA.cpp#L755
+esp_netif_t *get_esp_interface_netif(esp_interface_t interface);
+IPv6Address GlobalIPv6()
+{
+ esp_ip6_addr_t addr;
+ if (WiFiGenericClass::getMode() == WIFI_MODE_NULL) {
+ return IPv6Address();
+ }
+ if (esp_netif_get_ip6_global(get_esp_interface_netif(ESP_IF_WIFI_STA), &addr)) {
+ return IPv6Address();
+ }
+ return IPv6Address(addr.addr);
+}
+#endif
// Called by the Espressif SDK to
static void WiFiEvent(WiFiEvent_t event)
{
@@ -353,6 +372,17 @@ static void WiFiEvent(WiFiEvent_t event)
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
LOG_INFO("Connected to access point");
+ if (config.network.ipv6_enabled) {
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ if (!WiFi.enableIPv6()) {
+ LOG_WARN("Failed to enable IPv6");
+ }
+#else
+ if (!WiFi.enableIpV6()) {
+ LOG_WARN("Failed to enable IPv6");
+ }
+#endif
+ }
#ifdef WIFI_LED
digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON);
#endif
@@ -381,7 +411,8 @@ static void WiFiEvent(WiFiEvent_t event)
LOG_INFO("Obtained Local IP6 address: %s", WiFi.linkLocalIPv6().toString().c_str());
LOG_INFO("Obtained GlobalIP6 address: %s", WiFi.globalIPv6().toString().c_str());
#else
- LOG_INFO("Obtained IP6 address: %s", WiFi.localIPv6().toString().c_str());
+ LOG_INFO("Obtained Local IP6 address: %s", WiFi.localIPv6().toString().c_str());
+ LOG_INFO("Obtained GlobalIP6 address: %s", GlobalIPv6().toString().c_str());
#endif
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
@@ -512,4 +543,4 @@ uint8_t getWifiDisconnectReason()
{
return wifiDisconnectReason;
}
-#endif // HAS_WIFI
\ No newline at end of file
+#endif // HAS_WIFI
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index b852f28c8..7784a473f 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -104,9 +104,18 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
(config.security.admin_key[2].size == 32 &&
memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) {
LOG_INFO("PKC admin payload with authorized sender key");
+
+ // Automatically favorite the node that is using the admin key
auto remoteNode = nodeDB->getMeshNode(mp.from);
if (remoteNode && !remoteNode->is_favorite) {
- remoteNode->is_favorite = true;
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+ // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
+ // without the user doing so deliberately.
+ LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from);
+ } else {
+ LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from);
+ remoteNode->is_favorite = true;
+ }
}
} else {
myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp);
@@ -280,7 +289,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
case meshtastic_AdminMessage_nodedb_reset_tag: {
disableBluetooth();
LOG_INFO("Initiate node-db reset");
- nodeDB->resetNodes();
+ // CLIENT_BASE, ROUTER and ROUTER_LATE are able to preserve the remaining hop count when relaying a packet via a
+ // favorited node, so ensure that their favorites are kept on reset
+ bool rolePreference =
+ isOneOf(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE,
+ meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE);
+ nodeDB->resetNodes(rolePreference ? rolePreference : r->nodedb_reset);
reboot(DEFAULT_REBOOT_SECONDS);
break;
}
@@ -759,6 +773,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
config.lora = validatedLora;
// If we're setting region for the first time, init the region and regenerate the keys
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
+#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) {
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
@@ -777,6 +792,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
}
+#endif
config.lora.tx_enabled = true;
initRegion();
if (myRegion->dutyCycle < 100) {
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index e9f52bb7d..f435f6060 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -255,7 +255,7 @@ void CannedMessageModule::updateDestinationSelectionList()
for (size_t i = 0; i < numMeshNodes; ++i) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
- if (!node || node->num == myNodeNum)
+ if (!node || node->num == myNodeNum || !node->has_user || node->user.public_key.size != 32)
continue;
const String &nodeName = node->user.long_name;
@@ -973,9 +973,17 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
LOG_INFO("Send message id=%u, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
if (p->to != 0xffffffff) {
- LOG_INFO("Proactively adding %x as favorite node", p->to);
- nodeDB->set_favorite(true, p->to);
+ // Only add as favorite if our role is NOT CLIENT_BASE
+ if (config.device.role != 12) {
+ LOG_INFO("Proactively adding %x as favorite node", p->to);
+ nodeDB->set_favorite(true, p->to);
+ } else {
+ LOG_DEBUG("Not favoriting node %x as we are CLIENT_BASE role", p->to);
+ }
+
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
+ p->pki_encrypted = true;
+ p->channel = 0;
}
// Send to mesh and phone (even if no phone connected, to track ACKs)
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 2346cd299..91e96b8d4 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -95,22 +95,9 @@ int32_t ExternalNotificationModule::runOnce()
isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
#endif
if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
- // let the song finish if we reach timeout
+ // Turn off external notification immediately when timeout is reached, regardless of song state
nagCycleCutoff = UINT32_MAX;
- LOG_INFO("Turning off external notification: ");
- for (int i = 0; i < 3; i++) {
- setExternalState(i, false);
- externalTurnedOn[i] = 0;
- LOG_INFO("%d ", i);
- }
- LOG_INFO("");
-#ifdef HAS_I2S
- // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
- // T-Deck uses GPIO0 as trackball button, so restore the mode
-#if defined(T_DECK) || (defined(BUTTON_PIN) && BUTTON_PIN == 0)
- pinMode(0, INPUT);
-#endif
-#endif
+ ExternalNotificationModule::stopNow();
isNagging = false;
return INT32_MAX; // save cycles till we're needed again
}
@@ -318,14 +305,16 @@ bool ExternalNotificationModule::nagging()
void ExternalNotificationModule::stopNow()
{
+ LOG_INFO("Turning off external notification: ");
+ LOG_INFO("Stop RTTTL playback");
rtttl::stop();
#ifdef HAS_I2S
+ LOG_INFO("Stop audioThread playback");
if (audioThread->isPlaying())
audioThread->stop();
#endif
- nagCycleCutoff = 1; // small value
- isNagging = false;
// Turn off all outputs
+ LOG_INFO("Turning off setExternalStates");
for (int i = 0; i < 3; i++) {
setExternalState(i, false);
externalTurnedOn[i] = 0;
@@ -333,6 +322,18 @@ void ExternalNotificationModule::stopNow()
setIntervalFromNow(0);
#ifdef T_WATCH_S3
drv.stop();
+#endif
+
+ // Prevent the state machine from immediately re-triggering outputs after a manual stop.
+ isNagging = false;
+ nagCycleCutoff = UINT32_MAX;
+
+#ifdef HAS_I2S
+ // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
+ // T-Deck uses GPIO0 as trackball button, so restore the mode
+#if defined(T_DECK) || (defined(BUTTON_PIN) && BUTTON_PIN == 0)
+ pinMode(0, INPUT);
+#endif
#endif
}
@@ -510,7 +511,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
- if (moduleConfig.external_notification.alert_message && !ch.settings.mute) {
+ if (moduleConfig.external_notification.alert_message &&
+ (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
LOG_INFO("externalNotificationModule - Notification Module");
isNagging = true;
setExternalState(0, true);
@@ -521,7 +523,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
- if (moduleConfig.external_notification.alert_message_vibra && !ch.settings.mute) {
+ if (moduleConfig.external_notification.alert_message_vibra &&
+ (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
isNagging = true;
setExternalState(1, true);
@@ -532,7 +535,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
- if (moduleConfig.external_notification.alert_message_buzzer && !ch.settings.mute) {
+ if (moduleConfig.external_notification.alert_message_buzzer &&
+ (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(!isBroadcast(mp.to) && isToUs(&mp))) {
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index e477574dd..827524fc3 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -13,6 +13,8 @@
#include "input/TrackballInterruptImpl1.h"
#endif
+#include "modules/StatusLEDModule.h"
+
#if !MESHTASTIC_EXCLUDE_I2C
#include "input/cardKbI2cImpl.h"
#endif
@@ -119,6 +121,10 @@ void setupModules()
buzzerFeedbackThread = new BuzzerFeedbackThread();
}
#endif
+#if defined(LED_CHARGE) || defined(LED_PAIRING)
+ statusLEDModule = new StatusLEDModule();
+#endif
+
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
#endif
@@ -175,12 +181,13 @@ void setupModules()
// new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+#ifndef T_LORA_PAGER
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
-#ifdef T_LORA_PAGER
+#elif defined(T_LORA_PAGER)
// use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) {
diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp
index 97dc17001..936a7b44a 100644
--- a/src/modules/NeighborInfoModule.cpp
+++ b/src/modules/NeighborInfoModule.cpp
@@ -34,7 +34,8 @@ void NeighborInfoModule::printNodeDBNeighbors()
}
}
-/* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */
+/* Send our initial owner announcement 35 seconds after we start (to give
+ * network time to setup) */
NeighborInfoModule::NeighborInfoModule()
: ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg),
concurrency::OSThread("NeighborInfo")
@@ -53,8 +54,8 @@ NeighborInfoModule::NeighborInfoModule()
}
/*
-Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time
-Assumes that the neighborInfo packet has been allocated
+Collect neighbor info from the nodeDB's history, capping at a maximum number of
+entries and max time Assumes that the neighborInfo packet has been allocated
@returns the number of entries collected
*/
uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo)
@@ -71,8 +72,8 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb
if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) {
neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id;
neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr;
- // Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over
- // the mesh
+ // Note: we don't set the last_rx_time and node_broadcast_intervals_secs
+ // here, because we don't want to send this over the mesh
neighborInfo->neighbors_count++;
}
}
@@ -88,8 +89,9 @@ void NeighborInfoModule::cleanUpNeighbors()
uint32_t now = getTime();
NodeNum my_node_id = nodeDB->getNodeNum();
for (auto it = neighbors.rbegin(); it != neighbors.rend();) {
- // We will remove a neighbor if we haven't heard from them in twice the broadcast interval
- // cannot use isWithinTimespanMs() as it->last_rx_time is seconds since 1970
+ // We will remove a neighbor if we haven't heard from them in twice the
+ // broadcast interval cannot use isWithinTimespanMs() as it->last_rx_time is
+ // seconds since 1970
if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) {
LOG_DEBUG("Remove neighbor with node ID 0x%x", it->node_id);
it = std::vector::reverse_iterator(
@@ -132,25 +134,55 @@ int32_t NeighborInfoModule::runOnce()
return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs);
}
+meshtastic_MeshPacket *NeighborInfoModule::allocReply()
+{
+ LOG_INFO("NeighborInfoRequested.");
+ if (lastSentReply && Throttle::isWithinTimespanMs(lastSentReply, 3 * 60 * 1000)) {
+ LOG_DEBUG("Skip Neighbors reply since we sent a reply <3min ago");
+ ignoreRequest = true; // Mark it as ignored for MeshModule
+ return nullptr;
+ }
+
+ meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
+ collectNeighborInfo(&neighborInfo);
+
+ meshtastic_MeshPacket *reply = allocDataProtobuf(neighborInfo);
+
+ if (reply) {
+ lastSentReply = millis(); // Track when we sent this reply
+ }
+ return reply;
+}
+
/*
Collect a received neighbor info packet from another node
Pass it to an upper client; do not persist this data on the mesh
*/
bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np)
{
+ LOG_DEBUG("NeighborInfo: handleReceivedProtobuf");
if (np) {
printNeighborInfo("RECEIVED", np);
- updateNeighbors(mp, np);
+ // Ignore dummy/interceptable packets: single neighbor with nodeId 0 and snr 0
+ if (np->neighbors_count != 1 || np->neighbors[0].node_id != 0 || np->neighbors[0].snr != 0.0f) {
+ LOG_DEBUG(" Updating neighbours");
+ updateNeighbors(mp, np);
+ } else {
+ LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)");
+ }
} else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
+ LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr);
// If the hopLimit is the same as hopStart, then it is a neighbor
- getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
+ getOrCreateNeighbor(mp.from, mp.from, 0,
+ mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
}
// Allow others to handle this packet
return false;
}
/*
-Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum
+Copy the content of a current NeighborInfo packet into a new one and update the
+last_sent_by_id to our NodeNum
*/
void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n)
{
@@ -168,8 +200,10 @@ void NeighborInfoModule::resetNeighbors()
void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np)
{
- // The last sent ID will be 0 if the packet is from the phone, which we don't count as
- // an edge. So we assume that if it's zero, then this packet is from our node.
+ LOG_DEBUG("updateNeighbors");
+ // The last sent ID will be 0 if the packet is from the phone, which we don't
+ // count as an edge. So we assume that if it's zero, then this packet is from
+ // our node.
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
getOrCreateNeighbor(mp.from, np->last_sent_by_id, np->node_broadcast_interval_secs, mp.rx_snr);
}
@@ -188,7 +222,8 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
// if found, update it
neighbors[i].snr = snr;
neighbors[i].last_rx_time = getTime();
- // Only if this is the original sender, the broadcast interval corresponds to it
+ // Only if this is the original sender, the broadcast interval corresponds
+ // to it
if (originalSender == n && node_broadcast_interval_secs != 0)
neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs;
return &neighbors[i];
@@ -200,10 +235,12 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
new_nbr.node_id = n;
new_nbr.snr = snr;
new_nbr.last_rx_time = getTime();
- // Only if this is the original sender, the broadcast interval corresponds to it
+ // Only if this is the original sender, the broadcast interval corresponds to
+ // it
if (originalSender == n && node_broadcast_interval_secs != 0)
new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs;
- else // Assume the same broadcast interval as us for the neighbor if we don't know it
+ else // Assume the same broadcast interval as us for the neighbor if we don't
+ // know it
new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval;
if (neighbors.size() < MAX_NUM_NEIGHBORS) {
diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h
index aa76a2187..abb530329 100644
--- a/src/modules/NeighborInfoModule.h
+++ b/src/modules/NeighborInfoModule.h
@@ -28,6 +28,10 @@ class NeighborInfoModule : public ProtobufModule, priva
*/
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *nb) override;
+ /* Messages can be received that have the want_response bit set. If set, this callback will be invoked
+ * so that subclasses can (optionally) send a response back to the original sender. */
+ virtual meshtastic_MeshPacket *allocReply() override;
+
/*
* Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time
* @return the number of entries collected
@@ -66,5 +70,8 @@ class NeighborInfoModule : public ProtobufModule, priva
/* These are for debugging only */
void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np);
void printNodeDBNeighbors();
+
+ private:
+ uint32_t lastSentReply = 0; // Last time we sent a position reply (used for reply throttling only)
};
extern NeighborInfoModule *neighborInfoModule;
\ No newline at end of file
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 9b94b3933..aaab019d6 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -113,8 +113,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
u.public_key.size = 0;
}
- // Clear the user.id field since it should be derived from node number on the receiving end
- u.id[0] = '\0';
+ // FIXME: Clear the user.id field since it should be derived from node number on the receiving end
+ // u.id[0] = '\0';
+
+ // Ensure our user.id is derived correctly
+ strcpy(u.id, nodeDB->getNodeId().c_str());
+
LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
lastSentToMesh = millis();
return allocDataProtobuf(u);
diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp
index 3d78d0dc9..026b3028d 100644
--- a/src/modules/RangeTestModule.cpp
+++ b/src/modules/RangeTestModule.cpp
@@ -159,6 +159,7 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
LOG_DEBUG("---- Received Packet:");
LOG_DEBUG("mp.from %d", mp.from);
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
+ LOG_DEBUG("mp.rx_rssi %f", mp.rx_rssi);
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
@@ -234,8 +235,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
}
// Print the CSV header
- if (fileToWrite.println(
- "time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload")) {
+ if (fileToWrite.println("time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx "
+ "snr,distance,hop limit,payload,rx rssi")) {
LOG_INFO("File was written");
} else {
LOG_ERROR("File write failed");
@@ -297,6 +298,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
// TODO: If quotes are found in the payload, it has to be escaped.
fileToAppend.printf("\"%s\"\n", p.payload.bytes);
+ fileToAppend.printf("%i,", mp.rx_rssi); // RX RSSI
+
fileToAppend.flush();
fileToAppend.close();
diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp
index 7485f1c2d..719e342b1 100644
--- a/src/modules/SerialModule.cpp
+++ b/src/modules/SerialModule.cpp
@@ -64,14 +64,24 @@ SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio;
#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
- defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE)
-SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
+ defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || \
+ defined(MUZI_BASE)
+SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
+{
+ api_type = TYPE_SERIAL;
+}
static Print *serialPrint = &Serial;
-#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172)
-SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {}
+#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL)
+SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial")
+{
+ api_type = TYPE_SERIAL;
+}
static Print *serialPrint = &Serial1;
#else
-SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") {}
+SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial")
+{
+ api_type = TYPE_SERIAL;
+}
static Print *serialPrint = &Serial2;
#endif
@@ -195,7 +205,7 @@ int32_t SerialModule::runOnce()
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
}
#elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
- !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
+ !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
#ifdef ARCH_RP2040
Serial2.setFIFOSize(RX_BUFFER);
@@ -252,7 +262,7 @@ int32_t SerialModule::runOnce()
}
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
- !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
+ !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial();
@@ -527,7 +537,8 @@ ParsedLine parseLine(const char *line)
void SerialModule::processWXSerial()
{
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
- !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL)
+ !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \
+ !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0;
diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp
new file mode 100644
index 000000000..fc9ed310e
--- /dev/null
+++ b/src/modules/StatusLEDModule.cpp
@@ -0,0 +1,94 @@
+#include "StatusLEDModule.h"
+#include "MeshService.h"
+#include "configuration.h"
+#include
+
+/*
+StatusLEDModule manages the device's status LEDs, updating their states based on power and Bluetooth status.
+It reflects charging, charged, discharging, and Bluetooth connection states using the appropriate LEDs.
+*/
+StatusLEDModule *statusLEDModule;
+
+StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
+{
+ bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
+ powerStatusObserver.observe(&powerStatus->onNewStatus);
+}
+
+int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
+{
+ switch (arg->getStatusType()) {
+ case STATUS_TYPE_POWER: {
+ meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg;
+ if (powerStatus->getHasUSB()) {
+ power_state = charging;
+ if (powerStatus->getBatteryChargePercent() >= 100) {
+ power_state = charged;
+ }
+ } else {
+ power_state = discharging;
+ }
+ break;
+ }
+ case STATUS_TYPE_BLUETOOTH: {
+ meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)arg;
+ switch (bluetoothStatus->getConnectionState()) {
+ case meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED: {
+ ble_state = unpaired;
+ PAIRING_LED_starttime = millis();
+ break;
+ }
+ case meshtastic::BluetoothStatus::ConnectionState::PAIRING: {
+ ble_state = pairing;
+ PAIRING_LED_starttime = millis();
+ break;
+ }
+ case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: {
+ ble_state = connected;
+ PAIRING_LED_starttime = millis();
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+ return 0;
+};
+
+int32_t StatusLEDModule::runOnce()
+{
+
+ if (power_state == charging) {
+ CHARGE_LED_state = !CHARGE_LED_state;
+ } else if (power_state == charged) {
+ CHARGE_LED_state = LED_STATE_ON;
+ } else {
+ CHARGE_LED_state = LED_STATE_OFF;
+ }
+
+ if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis()) {
+ PAIRING_LED_state = LED_STATE_OFF;
+ } else if (ble_state == unpaired) {
+ if (slowTrack) {
+ PAIRING_LED_state = !PAIRING_LED_state;
+ slowTrack = false;
+ } else {
+ slowTrack = true;
+ }
+ } else if (ble_state == pairing) {
+ PAIRING_LED_state = !PAIRING_LED_state;
+ } else {
+ PAIRING_LED_state = LED_STATE_ON;
+ }
+
+#ifdef LED_CHARGE
+ digitalWrite(LED_CHARGE, CHARGE_LED_state);
+#endif
+ // digitalWrite(green_LED_PIN, LED_STATE_OFF);
+#ifdef LED_PAIRING
+ digitalWrite(LED_PAIRING, PAIRING_LED_state);
+#endif
+
+ return (my_interval);
+}
diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h
new file mode 100644
index 000000000..d9e3a4f33
--- /dev/null
+++ b/src/modules/StatusLEDModule.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "BluetoothStatus.h"
+#include "MeshModule.h"
+#include "PowerStatus.h"
+#include "concurrency/OSThread.h"
+#include "configuration.h"
+#include
+#include
+
+class StatusLEDModule : private concurrency::OSThread
+{
+ bool slowTrack = false;
+
+ public:
+ StatusLEDModule();
+
+ int handleStatusUpdate(const meshtastic::Status *);
+
+ protected:
+ unsigned int my_interval = 1000; // interval in millisconds
+ virtual int32_t runOnce() override;
+
+ CallbackObserver bluetoothStatusObserver =
+ CallbackObserver(this, &StatusLEDModule::handleStatusUpdate);
+ CallbackObserver powerStatusObserver =
+ CallbackObserver(this, &StatusLEDModule::handleStatusUpdate);
+
+ private:
+ bool CHARGE_LED_state = LED_STATE_OFF;
+ bool PAIRING_LED_state = LED_STATE_OFF;
+
+ uint32_t PAIRING_LED_starttime = 0;
+
+ enum PowerState { discharging, charging, charged };
+
+ PowerState power_state = discharging;
+
+ enum BLEState { unpaired, pairing, connected };
+
+ BLEState ble_state = unpaired;
+};
+
+extern StatusLEDModule *statusLEDModule;
diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp
index 72ac99118..b8a710bf5 100644
--- a/src/modules/StoreForwardModule.cpp
+++ b/src/modules/StoreForwardModule.cpp
@@ -204,6 +204,10 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
this->packetHistory[this->packetHistoryTotalCount].rx_rssi = mp.rx_rssi;
this->packetHistory[this->packetHistoryTotalCount].rx_snr = mp.rx_snr;
+ this->packetHistory[this->packetHistoryTotalCount].hop_start = mp.hop_start;
+ this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
+ this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt;
+ this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism;
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
this->packetHistoryTotalCount++;
@@ -256,6 +260,10 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t
p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
p->rx_rssi = this->packetHistory[i].rx_rssi;
p->rx_snr = this->packetHistory[i].rx_snr;
+ p->hop_start = this->packetHistory[i].hop_start;
+ p->hop_limit = this->packetHistory[i].hop_limit;
+ p->via_mqtt = this->packetHistory[i].via_mqtt;
+ p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)this->packetHistory[i].transport_mechanism;
// Let's assume that if the server received the S&F request that the client is in range.
// TODO: Make this configurable.
diff --git a/src/modules/StoreForwardModule.h b/src/modules/StoreForwardModule.h
index 25836eded..148568e1b 100644
--- a/src/modules/StoreForwardModule.h
+++ b/src/modules/StoreForwardModule.h
@@ -21,6 +21,10 @@ struct PacketHistoryStruct {
pb_size_t payload_size;
int32_t rx_rssi;
float rx_snr;
+ uint8_t hop_start;
+ uint8_t hop_limit;
+ bool via_mqtt;
+ uint8_t transport_mechanism;
};
class StoreForwardModule : private concurrency::OSThread, public ProtobufModule
diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp
index 74b9678f4..dc5d8b41f 100644
--- a/src/modules/SystemCommandsModule.cpp
+++ b/src/modules/SystemCommandsModule.cpp
@@ -85,10 +85,8 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
switch (event->inputEvent) {
// GPS
case INPUT_BROKER_GPS_TOGGLE:
- LOG_WARN("GPS Toggle");
#if !MESHTASTIC_EXCLUDE_GPS
if (gps) {
- LOG_WARN("GPS Toggle2");
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED &&
config.position.fixed_position == false) {
nodeDB->clearLocalPosition();
diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index 7e3018564..066b9361d 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -26,7 +26,8 @@ int32_t DeviceTelemetryModule::runOnce()
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) {
+ config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN &&
+ moduleConfig.telemetry.device_telemetry_enabled) {
sendTelemetry();
lastSentToMesh = uptimeLastMs;
} else if (service->isToPhoneQueueEmpty()) {
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 8ac160f8b..29e815092 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -35,175 +35,107 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
}
#if __has_include()
#include "Sensor/AHT10.h"
-AHT10Sensor aht10Sensor;
-#else
-NullSensor aht10Sensor;
#endif
+
#if __has_include()
#include "Sensor/BME280Sensor.h"
-BME280Sensor bme280Sensor;
-#else
-NullSensor bmp280Sensor;
#endif
#if __has_include()
#include "Sensor/BMP085Sensor.h"
-BMP085Sensor bmp085Sensor;
-#else
-NullSensor bmp085Sensor;
#endif
#if __has_include()
#include "Sensor/BMP280Sensor.h"
-BMP280Sensor bmp280Sensor;
-#else
-NullSensor bme280Sensor;
#endif
#if __has_include()
#include "Sensor/LTR390UVSensor.h"
-LTR390UVSensor ltr390uvSensor;
-#else
-NullSensor ltr390uvSensor;
#endif
#if __has_include()
#include "Sensor/BME680Sensor.h"
-BME680Sensor bme680Sensor;
-#else
-NullSensor bme680Sensor;
#endif
#if __has_include()
#include "Sensor/DPS310Sensor.h"
-DPS310Sensor dps310Sensor;
-#else
-NullSensor dps310Sensor;
#endif
#if __has_include()
#include "Sensor/MCP9808Sensor.h"
-MCP9808Sensor mcp9808Sensor;
-#else
-NullSensor mcp9808Sensor;
#endif
#if __has_include()
#include "Sensor/SHT31Sensor.h"
-SHT31Sensor sht31Sensor;
-#else
-NullSensor sht31Sensor;
#endif
#if __has_include()
#include "Sensor/LPS22HBSensor.h"
-LPS22HBSensor lps22hbSensor;
-#else
-NullSensor lps22hbSensor;
#endif
#if __has_include()
#include "Sensor/SHTC3Sensor.h"
-SHTC3Sensor shtc3Sensor;
-#else
-NullSensor shtc3Sensor;
#endif
#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1
#include "Sensor/RAK12035Sensor.h"
-RAK12035Sensor rak12035Sensor;
-#else
-NullSensor rak12035Sensor;
#endif
#if __has_include()
#include "Sensor/VEML7700Sensor.h"
-VEML7700Sensor veml7700Sensor;
-#else
-NullSensor veml7700Sensor;
#endif
#if __has_include()
#include "Sensor/TSL2591Sensor.h"
-TSL2591Sensor tsl2591Sensor;
-#else
-NullSensor tsl2591Sensor;
#endif
#if __has_include()
#include "Sensor/OPT3001Sensor.h"
-OPT3001Sensor opt3001Sensor;
-#else
-NullSensor opt3001Sensor;
#endif
#if __has_include()
#include "Sensor/SHT4XSensor.h"
-SHT4XSensor sht4xSensor;
-#else
-NullSensor sht4xSensor;
#endif
#if __has_include()
#include "Sensor/MLX90632Sensor.h"
-MLX90632Sensor mlx90632Sensor;
-#else
-NullSensor mlx90632Sensor;
#endif
#if __has_include()
#include "Sensor/DFRobotLarkSensor.h"
-DFRobotLarkSensor dfRobotLarkSensor;
-#else
-NullSensor dfRobotLarkSensor;
#endif
#if __has_include()
#include "Sensor/DFRobotGravitySensor.h"
-DFRobotGravitySensor dfRobotGravitySensor;
-#else
-NullSensor dfRobotGravitySensor;
#endif
#if __has_include()
#include "Sensor/NAU7802Sensor.h"
-NAU7802Sensor nau7802Sensor;
-#else
-NullSensor nau7802Sensor;
#endif
#if __has_include()
#include "Sensor/BMP3XXSensor.h"
-BMP3XXSensor bmp3xxSensor;
-#else
-NullSensor bmp3xxSensor;
#endif
#if __has_include()
#include "Sensor/PCT2075Sensor.h"
-PCT2075Sensor pct2075Sensor;
-#else
-NullSensor pct2075Sensor;
#endif
-RCWL9620Sensor rcwl9620Sensor;
-CGRadSensSensor cgRadSens;
-
#endif
#ifdef T1000X_SENSOR_EN
#include "Sensor/T1000xSensor.h"
-T1000xSensor t1000xSensor;
#endif
+
#ifdef SENSECAP_INDICATOR
#include "Sensor/IndicatorSensor.h"
-IndicatorSensor indicatorSensor;
#endif
#if __has_include()
#include "Sensor/TSL2561Sensor.h"
-TSL2561Sensor tsl2561Sensor;
-#else
-NullSensor tsl2561Sensor;
+#endif
+
+#if __has_include()
+#include "Sensor/BH1750Sensor.h"
#endif
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
@@ -212,6 +144,135 @@ NullSensor tsl2561Sensor;
#include "graphics/ScreenFonts.h"
#include
+#include
+
+static std::forward_list sensors;
+
+template void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type)
+{
+ ScanI2C::FoundDevice dev = i2cScanner->find(type);
+ if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) {
+ TelemetrySensor *sensor = new T();
+#if WIRE_INTERFACES_COUNT > 1
+ TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address);
+ if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) {
+ // This sensor only works on Wire (Wire1 is not supported)
+ delete sensor;
+ return;
+ }
+#else
+ TwoWire *bus = &Wire;
+#endif
+ if (sensor->initDevice(bus, &dev)) {
+ sensors.push_front(sensor);
+ return;
+ }
+ // destroy sensor
+ delete sensor;
+ }
+}
+
+void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
+{
+ if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
+ return;
+ }
+ LOG_INFO("Environment Telemetry adding I2C devices...");
+
+ // order by priority of metrics/values (low top, high bottom)
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#ifdef T1000X_SENSOR_EN
+ // Not a real I2C device
+ addSensor(i2cScanner, ScanI2C::DeviceType::NONE);
+#else
+#ifdef SENSECAP_INDICATOR
+ // Not a real I2C device, uses UART
+ addSensor(i2cScanner, ScanI2C::DeviceType::NONE);
+#endif
+ addSensor(i2cScanner, ScanI2C::DeviceType::RCWL9620);
+ addSensor(i2cScanner, ScanI2C::DeviceType::CGRADSENS);
+#endif
+#endif
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::AHT10);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::BMP_085);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::BME_280);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::LTR390UV);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::BME_680);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::BMP_280);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::DPS310);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::MCP9808);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::SHT31);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::LPS22HB);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::SHTC3);
+#endif
+#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1
+ addSensor(i2cScanner, ScanI2C::DeviceType::RAK12035);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::VEML7700);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::TSL2591);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::OPT3001);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::SHT4X);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::MLX90632);
+#endif
+
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::BMP_3XX);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::PCT2075);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::TSL2561);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::NAU7802);
+#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::BH1750);
+#endif
+
+#endif
+}
+
int32_t EnvironmentTelemetryModule::runOnce()
{
if (sleepOnNextExecution == true) {
@@ -244,81 +305,27 @@ int32_t EnvironmentTelemetryModule::runOnce()
if (moduleConfig.telemetry.environment_measurement_enabled || ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
LOG_INFO("Environment Telemetry: init");
-#ifdef SENSECAP_INDICATOR
- result = indicatorSensor.runOnce();
-#endif
+
+ // check if we have at least one sensor
+ if (!sensors.empty()) {
+ result = DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+
#ifdef T1000X_SENSOR_EN
- result = t1000xSensor.runOnce();
#elif !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
- if (dfRobotLarkSensor.hasSensor())
- result = dfRobotLarkSensor.runOnce();
- if (dfRobotGravitySensor.hasSensor())
- result = dfRobotGravitySensor.runOnce();
- if (bmp085Sensor.hasSensor())
- result = bmp085Sensor.runOnce();
-#if __has_include()
- if (bmp280Sensor.hasSensor())
- result = bmp280Sensor.runOnce();
-#endif
- if (bme280Sensor.hasSensor())
- result = bme280Sensor.runOnce();
- if (ltr390uvSensor.hasSensor())
- result = ltr390uvSensor.runOnce();
- if (bmp3xxSensor.hasSensor())
- result = bmp3xxSensor.runOnce();
- if (bme680Sensor.hasSensor())
- result = bme680Sensor.runOnce();
- if (dps310Sensor.hasSensor())
- result = dps310Sensor.runOnce();
- if (mcp9808Sensor.hasSensor())
- result = mcp9808Sensor.runOnce();
- if (shtc3Sensor.hasSensor())
- result = shtc3Sensor.runOnce();
- if (lps22hbSensor.hasSensor())
- result = lps22hbSensor.runOnce();
- if (sht31Sensor.hasSensor())
- result = sht31Sensor.runOnce();
- if (sht4xSensor.hasSensor())
- result = sht4xSensor.runOnce();
if (ina219Sensor.hasSensor())
result = ina219Sensor.runOnce();
if (ina260Sensor.hasSensor())
result = ina260Sensor.runOnce();
if (ina3221Sensor.hasSensor())
result = ina3221Sensor.runOnce();
- if (veml7700Sensor.hasSensor())
- result = veml7700Sensor.runOnce();
- if (tsl2591Sensor.hasSensor())
- result = tsl2591Sensor.runOnce();
- if (opt3001Sensor.hasSensor())
- result = opt3001Sensor.runOnce();
- if (rcwl9620Sensor.hasSensor())
- result = rcwl9620Sensor.runOnce();
- if (aht10Sensor.hasSensor())
- result = aht10Sensor.runOnce();
- if (mlx90632Sensor.hasSensor())
- result = mlx90632Sensor.runOnce();
- if (nau7802Sensor.hasSensor())
- result = nau7802Sensor.runOnce();
if (max17048Sensor.hasSensor())
result = max17048Sensor.runOnce();
- if (cgRadSens.hasSensor())
- result = cgRadSens.runOnce();
- if (tsl2561Sensor.hasSensor())
- result = tsl2561Sensor.runOnce();
- if (pct2075Sensor.hasSensor())
- result = pct2075Sensor.runOnce();
// this only works on the wismesh hub with the solar option. This is not an I2C sensor, so we don't need the
// sensormap here.
#ifdef HAS_RAKPROT
-
result = rak9154Sensor.runOnce();
#endif
-#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1
- if (rak12035Sensor.hasSensor()) {
- result = rak12035Sensor.runOnce();
- }
-#endif
#endif
}
// it's possible to have this module enabled, only for displaying values on the screen.
@@ -328,11 +335,13 @@ int32_t EnvironmentTelemetryModule::runOnce()
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
return disable();
- } else {
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
- if (bme680Sensor.hasSensor())
- result = bme680Sensor.runTrigger();
-#endif
+ }
+
+ for (TelemetrySensor *sensor : sensors) {
+ uint32_t delay = sensor->runOnce();
+ if (delay < result) {
+ result = delay;
+ }
}
if (((lastSentToMesh == 0) ||
@@ -359,6 +368,7 @@ bool EnvironmentTelemetryModule::wantUIFrame()
return moduleConfig.telemetry.environment_screen_enabled;
}
+#if HAS_SCREEN
void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// === Setup display ===
@@ -507,7 +517,9 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
currentY += rowHeight;
}
+ graphics::drawCommonFooter(display, x, y);
}
+#endif
bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
@@ -550,72 +562,12 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
m->which_variant = meshtastic_Telemetry_environment_metrics_tag;
m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero;
-#ifdef SENSECAP_INDICATOR
- valid = valid && indicatorSensor.getMetrics(m);
- hasSensor = true;
-#endif
-#ifdef T1000X_SENSOR_EN // add by WayenWeng
- valid = valid && t1000xSensor.getMetrics(m);
- hasSensor = true;
-#else
- if (dfRobotLarkSensor.hasSensor()) {
- valid = valid && dfRobotLarkSensor.getMetrics(m);
- hasSensor = true;
- }
- if (dfRobotGravitySensor.hasSensor()) {
- valid = valid && dfRobotGravitySensor.getMetrics(m);
- hasSensor = true;
- }
- if (sht31Sensor.hasSensor()) {
- valid = valid && sht31Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (sht4xSensor.hasSensor()) {
- valid = valid && sht4xSensor.getMetrics(m);
- hasSensor = true;
- }
- if (lps22hbSensor.hasSensor()) {
- valid = valid && lps22hbSensor.getMetrics(m);
- hasSensor = true;
- }
- if (shtc3Sensor.hasSensor()) {
- valid = valid && shtc3Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (bmp085Sensor.hasSensor()) {
- valid = valid && bmp085Sensor.getMetrics(m);
- hasSensor = true;
- }
-#if __has_include()
- if (bmp280Sensor.hasSensor()) {
- valid = valid && bmp280Sensor.getMetrics(m);
- hasSensor = true;
- }
-#endif
- if (bme280Sensor.hasSensor()) {
- valid = valid && bme280Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (ltr390uvSensor.hasSensor()) {
- valid = valid && ltr390uvSensor.getMetrics(m);
- hasSensor = true;
- }
- if (bmp3xxSensor.hasSensor()) {
- valid = valid && bmp3xxSensor.getMetrics(m);
- hasSensor = true;
- }
- if (bme680Sensor.hasSensor()) {
- valid = valid && bme680Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (dps310Sensor.hasSensor()) {
- valid = valid && dps310Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (mcp9808Sensor.hasSensor()) {
- valid = valid && mcp9808Sensor.getMetrics(m);
+ for (TelemetrySensor *sensor : sensors) {
+ valid = valid && sensor->getMetrics(m);
hasSensor = true;
}
+
+#ifndef T1000X_SENSOR_EN
if (ina219Sensor.hasSensor()) {
valid = valid && ina219Sensor.getMetrics(m);
hasSensor = true;
@@ -628,78 +580,14 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
valid = valid && ina3221Sensor.getMetrics(m);
hasSensor = true;
}
- if (veml7700Sensor.hasSensor()) {
- valid = valid && veml7700Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (tsl2591Sensor.hasSensor()) {
- valid = valid && tsl2591Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (opt3001Sensor.hasSensor()) {
- valid = valid && opt3001Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (mlx90632Sensor.hasSensor()) {
- valid = valid && mlx90632Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (rcwl9620Sensor.hasSensor()) {
- valid = valid && rcwl9620Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (nau7802Sensor.hasSensor()) {
- valid = valid && nau7802Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (tsl2561Sensor.hasSensor()) {
- valid = valid && tsl2561Sensor.getMetrics(m);
- hasSensor = true;
- }
- if (aht10Sensor.hasSensor()) {
- if (!bmp280Sensor.hasSensor() && !bmp3xxSensor.hasSensor()) {
- valid = valid && aht10Sensor.getMetrics(m);
- hasSensor = true;
- } else if (bmp280Sensor.hasSensor()) {
- // prefer bmp280 temp if both sensors are present, fetch only humidity
- meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero;
- LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0");
- aht10Sensor.getMetrics(&m_ahtx);
- m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
- m->variant.environment_metrics.has_relative_humidity = m_ahtx.variant.environment_metrics.has_relative_humidity;
- } else {
- // prefer bmp3xx temp if both sensors are present, fetch only humidity
- meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero;
- LOG_INFO("AHTX0+BMP3XX module detected: using temp from BMP3XX and humy from AHTX0");
- aht10Sensor.getMetrics(&m_ahtx);
- m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
- m->variant.environment_metrics.has_relative_humidity = m_ahtx.variant.environment_metrics.has_relative_humidity;
- }
- }
if (max17048Sensor.hasSensor()) {
valid = valid && max17048Sensor.getMetrics(m);
hasSensor = true;
}
- if (cgRadSens.hasSensor()) {
- valid = valid && cgRadSens.getMetrics(m);
- hasSensor = true;
- }
- if (pct2075Sensor.hasSensor()) {
- valid = valid && pct2075Sensor.getMetrics(m);
- hasSensor = true;
- }
+#endif
#ifdef HAS_RAKPROT
valid = valid && rak9154Sensor.getMetrics(m);
hasSensor = true;
-#endif
-#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && \
- RAK_4631 == \
- 1 // Not really needed, but may as well just skip at a lower level it if no library or not a RAK_4631
- if (rak12035Sensor.hasSensor()) {
- valid = valid && rak12035Sensor.getMetrics(m);
- hasSensor = true;
- }
-#endif
#endif
return valid && hasSensor;
}
@@ -737,11 +625,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
m.which_variant = meshtastic_Telemetry_environment_metrics_tag;
m.time = getTime();
-#ifdef T1000X_SENSOR_EN
- if (t1000xSensor.getMetrics(&m)) {
-#else
+
if (getEnvironmentTelemetry(&m)) {
-#endif
LOG_INFO("Send: barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f",
m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current,
m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity,
@@ -803,71 +688,13 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
{
AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED;
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
- if (dfRobotLarkSensor.hasSensor()) {
- result = dfRobotLarkSensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (dfRobotGravitySensor.hasSensor()) {
- result = dfRobotGravitySensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (sht31Sensor.hasSensor()) {
- result = sht31Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (lps22hbSensor.hasSensor()) {
- result = lps22hbSensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (shtc3Sensor.hasSensor()) {
- result = shtc3Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (bmp085Sensor.hasSensor()) {
- result = bmp085Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (bmp280Sensor.hasSensor()) {
- result = bmp280Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (bme280Sensor.hasSensor()) {
- result = bme280Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (ltr390uvSensor.hasSensor()) {
- result = ltr390uvSensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (bmp3xxSensor.hasSensor()) {
- result = bmp3xxSensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (bme680Sensor.hasSensor()) {
- result = bme680Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (dps310Sensor.hasSensor()) {
- result = dps310Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (mcp9808Sensor.hasSensor()) {
- result = mcp9808Sensor.handleAdminMessage(mp, request, response);
+
+ for (TelemetrySensor *sensor : sensors) {
+ result = sensor->handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
+
if (ina219Sensor.hasSensor()) {
result = ina219Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
@@ -883,60 +710,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
- if (veml7700Sensor.hasSensor()) {
- result = veml7700Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (tsl2591Sensor.hasSensor()) {
- result = tsl2591Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (opt3001Sensor.hasSensor()) {
- result = opt3001Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (mlx90632Sensor.hasSensor()) {
- result = mlx90632Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (rcwl9620Sensor.hasSensor()) {
- result = rcwl9620Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (nau7802Sensor.hasSensor()) {
- result = nau7802Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
- if (aht10Sensor.hasSensor()) {
- result = aht10Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
if (max17048Sensor.hasSensor()) {
result = max17048Sensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
- if (cgRadSens.hasSensor()) {
- result = cgRadSens.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
-#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && \
- RAK_4631 == \
- 1 // Not really needed, but may as well just skip it at a lower level if no library or not a RAK_4631
- if (rak12035Sensor.hasSensor()) {
- result = rak12035Sensor.handleAdminMessage(mp, request, response);
- if (result != AdminMessageHandleResult::NOT_HANDLED)
- return result;
- }
-#endif
#endif
return result;
}
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h
index d70c063fc..6e4ce82e7 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.h
+++ b/src/modules/Telemetry/EnvironmentTelemetry.h
@@ -11,10 +11,13 @@
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "NodeDB.h"
#include "ProtobufModule.h"
+#include "detect/ScanI2CConsumer.h"
#include
#include
-class EnvironmentTelemetryModule : private concurrency::OSThread, public ProtobufModule
+class EnvironmentTelemetryModule : private concurrency::OSThread,
+ public ScanI2CConsumer,
+ public ProtobufModule
{
CallbackObserver nodeStatusObserver =
CallbackObserver(this,
@@ -22,7 +25,7 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu
public:
EnvironmentTelemetryModule()
- : concurrency::OSThread("EnvironmentTelemetry"),
+ : concurrency::OSThread("EnvironmentTelemetry"), ScanI2CConsumer(),
ProtobufModule("EnvironmentTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
{
lastMeasurementPacket = nullptr;
@@ -56,6 +59,8 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response) override;
+ void i2cScanFinished(ScanI2C *i2cScanner);
+
private:
bool firstTime = 1;
meshtastic_MeshPacket *lastMeasurementPacket;
diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp
index 479861a2e..29dd1def8 100644
--- a/src/modules/Telemetry/PowerTelemetry.cpp
+++ b/src/modules/Telemetry/PowerTelemetry.cpp
@@ -108,6 +108,7 @@ bool PowerTelemetryModule::wantUIFrame()
return moduleConfig.telemetry.power_screen_enabled;
}
+#if HAS_SCREEN
void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->clear();
@@ -164,7 +165,9 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
if (m.has_ch3_voltage || m.has_ch3_current) {
drawLine("Ch3", m.ch3_voltage, m.ch3_current);
}
+ graphics::drawCommonFooter(display, x, y);
}
+#endif
bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp
index 35934533b..c38fd2a92 100644
--- a/src/modules/Telemetry/Sensor/AHT10.cpp
+++ b/src/modules/Telemetry/Sensor/AHT10.cpp
@@ -15,20 +15,16 @@
AHT10Sensor::AHT10Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_AHT10, "AHT10") {}
-int32_t AHT10Sensor::runOnce()
+bool AHT10Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
aht10 = Adafruit_AHTX0();
- status = aht10.begin(nodeTelemetrySensorsMap[sensorType].second, 0, nodeTelemetrySensorsMap[sensorType].first);
+ status = aht10.begin(bus, 0, dev->address.address);
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void AHT10Sensor::setup() {}
-
bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
LOG_DEBUG("AHT10 getMetrics");
@@ -36,11 +32,16 @@ bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement)
sensors_event_t humidity, temp;
aht10.getEvent(&humidity, &temp);
- measurement->variant.environment_metrics.has_temperature = true;
- measurement->variant.environment_metrics.has_relative_humidity = true;
+ // prefer other sensors like bmp280, bmp3xx
+ if (!measurement->variant.environment_metrics.has_temperature) {
+ measurement->variant.environment_metrics.has_temperature = true;
+ measurement->variant.environment_metrics.temperature = temp.temperature + AHT10_TEMP_OFFSET;
+ }
- measurement->variant.environment_metrics.temperature = temp.temperature;
- measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity;
+ if (!measurement->variant.environment_metrics.has_relative_humidity) {
+ measurement->variant.environment_metrics.has_relative_humidity = true;
+ measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity;
+ }
return true;
}
diff --git a/src/modules/Telemetry/Sensor/AHT10.h b/src/modules/Telemetry/Sensor/AHT10.h
index a6fa19952..f85f04aa0 100644
--- a/src/modules/Telemetry/Sensor/AHT10.h
+++ b/src/modules/Telemetry/Sensor/AHT10.h
@@ -6,6 +6,10 @@
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+#ifndef AHT10_TEMP_OFFSET
+#define AHT10_TEMP_OFFSET 0
+#endif
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
@@ -15,13 +19,10 @@ class AHT10Sensor : public TelemetrySensor
private:
Adafruit_AHTX0 aht10;
- protected:
- virtual void setup() override;
-
public:
AHT10Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
diff --git a/src/modules/Telemetry/Sensor/BH1750Sensor.cpp b/src/modules/Telemetry/Sensor/BH1750Sensor.cpp
new file mode 100644
index 000000000..b8790dcd5
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/BH1750Sensor.cpp
@@ -0,0 +1,54 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "BH1750Sensor.h"
+#include "TelemetrySensor.h"
+#include
+
+#ifndef BH1750_SENSOR_MODE
+#define BH1750_SENSOR_MODE BH1750Mode::CHM
+#endif
+
+BH1750Sensor::BH1750Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BH1750, "BH1750") {}
+
+bool BH1750Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
+{
+ LOG_INFO("Init sensor: %s with mode %d", sensorName, BH1750_SENSOR_MODE);
+
+ bh1750 = BH1750_WE(bus, dev->address.address);
+ status = bh1750.init();
+ if (!status) {
+ return status;
+ }
+
+ bh1750.setMode(BH1750_SENSOR_MODE);
+
+ initI2CSensor();
+ return status;
+}
+
+bool BH1750Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+
+ /* An OTH and OTH_2 measurement takes ~120 ms. I suggest to wait
+ 140 ms to be on the safe side.
+ An OTL measurement takes about 16 ms. I suggest to wait 20 ms
+ to be on the safe side. */
+ if (BH1750_SENSOR_MODE == BH1750Mode::OTH || BH1750_SENSOR_MODE == BH1750Mode::OTH_2) {
+ bh1750.setMode(BH1750_SENSOR_MODE);
+ delay(140); // wait for measurement to be completed
+ } else if (BH1750_SENSOR_MODE == BH1750Mode::OTL) {
+ bh1750.setMode(BH1750_SENSOR_MODE);
+ delay(20);
+ }
+
+ measurement->variant.environment_metrics.has_lux = true;
+ float lightIntensity = bh1750.getLux();
+
+ measurement->variant.environment_metrics.lux = lightIntensity;
+ return true;
+}
+
+#endif
diff --git a/src/modules/Telemetry/Sensor/BH1750Sensor.h b/src/modules/Telemetry/Sensor/BH1750Sensor.h
new file mode 100644
index 000000000..d9a4ded95
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/BH1750Sensor.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class BH1750Sensor : public TelemetrySensor
+{
+ private:
+ BH1750_WE bh1750;
+
+ public:
+ BH1750Sensor();
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+};
+
+#endif
diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.cpp b/src/modules/Telemetry/Sensor/BME280Sensor.cpp
index d7b0a8a38..779b2e603 100644
--- a/src/modules/Telemetry/Sensor/BME280Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BME280Sensor.cpp
@@ -10,13 +10,13 @@
BME280Sensor::BME280Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME280, "BME280") {}
-int32_t BME280Sensor::runOnce()
+bool BME280Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ status = bme280.begin(dev->address.address, bus);
+ if (!status) {
+ return status;
}
- status = bme280.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
bme280.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // Temp. oversampling
@@ -24,11 +24,10 @@ int32_t BME280Sensor::runOnce()
Adafruit_BME280::SAMPLING_X1, // Humidity oversampling
Adafruit_BME280::FILTER_OFF, Adafruit_BME280::STANDBY_MS_1000);
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void BME280Sensor::setup() {}
-
bool BME280Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.h b/src/modules/Telemetry/Sensor/BME280Sensor.h
index d1e21c8d5..fadae46cd 100644
--- a/src/modules/Telemetry/Sensor/BME280Sensor.h
+++ b/src/modules/Telemetry/Sensor/BME280Sensor.h
@@ -11,13 +11,10 @@ class BME280Sensor : public TelemetrySensor
private:
Adafruit_BME280 bme280;
- protected:
- virtual void setup() override;
-
public:
BME280Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
index fce029deb..95f3dc5f0 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
@@ -10,7 +10,7 @@
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
-int32_t BME680Sensor::runTrigger()
+int32_t BME680Sensor::runOnce()
{
if (!bme680.run()) {
checkStatus("runTrigger");
@@ -18,13 +18,10 @@ int32_t BME680Sensor::runTrigger()
return 35;
}
-int32_t BME680Sensor::runOnce()
+bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
-
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
- if (!bme680.begin(nodeTelemetrySensorsMap[sensorType].first, *nodeTelemetrySensorsMap[sensorType].second))
+ status = 0;
+ if (!bme680.begin(dev->address.address, *bus))
checkStatus("begin");
if (bme680.status == BSEC_OK) {
@@ -40,17 +37,15 @@ int32_t BME680Sensor::runOnce()
}
LOG_INFO("Init sensor: %s with the BSEC Library version %d.%d.%d.%d ", sensorName, bme680.version.major,
bme680.version.minor, bme680.version.major_bugfix, bme680.version.minor_bugfix);
- } else {
- status = 0;
}
+
if (status == 0)
LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status);
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void BME680Sensor::setup() {}
-
bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h
index ce1fa4f3b..f4ead95f7 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.h
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.h
@@ -18,7 +18,6 @@ class BME680Sensor : public TelemetrySensor
Bsec2 bme680;
protected:
- virtual void setup() override;
const char *bsecConfigFileName = "/prefs/bsec.dat";
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint8_t accuracy = 0;
@@ -38,9 +37,9 @@ class BME680Sensor : public TelemetrySensor
public:
BME680Sensor();
- int32_t runTrigger();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
index 8087eb4b9..1fb2ecc28 100644
--- a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
@@ -10,20 +10,17 @@
BMP085Sensor::BMP085Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP085, "BMP085") {}
-int32_t BMP085Sensor::runOnce()
+bool BMP085Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
+
bmp085 = Adafruit_BMP085();
- status = bmp085.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
+ status = bmp085.begin(dev->address.address, bus);
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void BMP085Sensor::setup() {}
-
bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h
index 8dadceab4..12ccf35a1 100644
--- a/src/modules/Telemetry/Sensor/BMP085Sensor.h
+++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h
@@ -11,13 +11,10 @@ class BMP085Sensor : public TelemetrySensor
private:
Adafruit_BMP085 bmp085;
- protected:
- virtual void setup() override;
-
public:
BMP085Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
index 47069b8e0..2b7407c43 100644
--- a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
@@ -10,25 +10,25 @@
BMP280Sensor::BMP280Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP280, "BMP280") {}
-int32_t BMP280Sensor::runOnce()
+bool BMP280Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+
+ bmp280 = Adafruit_BMP280(bus);
+ status = bmp280.begin(dev->address.address);
+ if (!status) {
+ return status;
}
- bmp280 = Adafruit_BMP280(nodeTelemetrySensorsMap[sensorType].second);
- status = bmp280.begin(nodeTelemetrySensorsMap[sensorType].first);
bmp280.setSampling(Adafruit_BMP280::MODE_FORCED,
Adafruit_BMP280::SAMPLING_X1, // Temp. oversampling
Adafruit_BMP280::SAMPLING_X1, // Pressure oversampling
Adafruit_BMP280::FILTER_OFF, Adafruit_BMP280::STANDBY_MS_1000);
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void BMP280Sensor::setup() {}
-
bool BMP280Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.h b/src/modules/Telemetry/Sensor/BMP280Sensor.h
index d615411b2..2199fc0cd 100644
--- a/src/modules/Telemetry/Sensor/BMP280Sensor.h
+++ b/src/modules/Telemetry/Sensor/BMP280Sensor.h
@@ -11,13 +11,10 @@ class BMP280Sensor : public TelemetrySensor
private:
Adafruit_BMP280 bmp280;
- protected:
- virtual void setup() override;
-
public:
BMP280Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp
index 28a71b48f..ac80732bf 100644
--- a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp
+++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp
@@ -6,20 +6,18 @@
BMP3XXSensor::BMP3XXSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP3XX, "BMP3XX") {}
-void BMP3XXSensor::setup() {}
-
-int32_t BMP3XXSensor::runOnce()
+bool BMP3XXSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
// Get a singleton instance and initialise the bmp3xx
if (bmp3xx == nullptr) {
bmp3xx = BMP3XXSingleton::GetInstance();
}
- status = bmp3xx->begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
+ status = bmp3xx->begin_I2C(dev->address.address, bus);
+ if (!status) {
+ return status;
+ }
// set up oversampling and filter initialization
bmp3xx->setTemperatureOversampling(BMP3_OVERSAMPLING_4X);
@@ -31,7 +29,8 @@ int32_t BMP3XXSensor::runOnce()
for (int i = 0; i < 3; i++) {
bmp3xx->performReading();
}
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.h b/src/modules/Telemetry/Sensor/BMP3XXSensor.h
index 6ab0f533d..7ce14d9db 100644
--- a/src/modules/Telemetry/Sensor/BMP3XXSensor.h
+++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.h
@@ -43,12 +43,11 @@ class BMP3XXSensor : public TelemetrySensor
{
protected:
BMP3XXSingleton *bmp3xx = nullptr;
- virtual void setup() override;
public:
BMP3XXSensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
diff --git a/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp b/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp
index ac5df1b81..e7b191398 100644
--- a/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp
+++ b/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp
@@ -14,22 +14,16 @@
CGRadSensSensor::CGRadSensSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RADSENS, "RadSens") {}
-int32_t CGRadSensSensor::runOnce()
+bool CGRadSensSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
// Initialize the sensor following the same pattern as RCWL9620Sensor
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
-
status = true;
- begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first);
-
- return initI2CSensor();
+ begin(bus, dev->address.address);
+ initI2CSensor();
+ return status;
}
-void CGRadSensSensor::setup() {}
-
void CGRadSensSensor::begin(TwoWire *wire, uint8_t addr)
{
// Store the Wire and address to the sensor following the same pattern as RCWL9620Sensor
diff --git a/src/modules/Telemetry/Sensor/CGRadSensSensor.h b/src/modules/Telemetry/Sensor/CGRadSensSensor.h
index 3b15a19a2..c677e8899 100644
--- a/src/modules/Telemetry/Sensor/CGRadSensSensor.h
+++ b/src/modules/Telemetry/Sensor/CGRadSensSensor.h
@@ -17,14 +17,13 @@ class CGRadSensSensor : public TelemetrySensor
TwoWire *_wire = &Wire;
protected:
- virtual void setup() override;
void begin(TwoWire *wire = &Wire, uint8_t addr = 0x66);
float getStaticRadiation();
public:
CGRadSensSensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp
index 9581057b0..101b01f8f 100644
--- a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp
+++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp
@@ -10,31 +10,42 @@
DFRobotGravitySensor::DFRobotGravitySensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_RAIN, "DFROBOT_RAIN") {}
-int32_t DFRobotGravitySensor::runOnce()
+DFRobotGravitySensor::~DFRobotGravitySensor()
{
- LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ if (gravity) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
+ delete gravity;
+#pragma GCC diagnostic pop
+ gravity = nullptr;
}
-
- gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second);
- status = gravity.begin();
-
- return initI2CSensor();
}
-void DFRobotGravitySensor::setup()
+bool DFRobotGravitySensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
- LOG_DEBUG("%s VID: %x, PID: %x, Version: %s", sensorName, gravity.vid, gravity.pid, gravity.getFirmwareVersion().c_str());
+ LOG_INFO("Init sensor: %s", sensorName);
+
+ gravity = new DFRobot_RainfallSensor_I2C(bus);
+ status = gravity->begin();
+
+ LOG_DEBUG("%s VID: %x, PID: %x, Version: %s", sensorName, gravity->vid, gravity->pid, gravity->getFirmwareVersion().c_str());
+
+ initI2CSensor();
+ return status;
}
bool DFRobotGravitySensor::getMetrics(meshtastic_Telemetry *measurement)
{
+ if (!gravity) {
+ LOG_ERROR("DFRobotGravitySensor not initialized");
+ return false;
+ }
+
measurement->variant.environment_metrics.has_rainfall_1h = true;
measurement->variant.environment_metrics.has_rainfall_24h = true;
- measurement->variant.environment_metrics.rainfall_1h = gravity.getRainfall(1);
- measurement->variant.environment_metrics.rainfall_24h = gravity.getRainfall(24);
+ measurement->variant.environment_metrics.rainfall_1h = gravity->getRainfall(1);
+ measurement->variant.environment_metrics.rainfall_24h = gravity->getRainfall(24);
LOG_INFO("Rain 1h: %f mm", measurement->variant.environment_metrics.rainfall_1h);
LOG_INFO("Rain 24h: %f mm", measurement->variant.environment_metrics.rainfall_24h);
diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h
index dfd81a913..2b4890e30 100644
--- a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h
+++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h
@@ -14,15 +14,13 @@
class DFRobotGravitySensor : public TelemetrySensor
{
private:
- DFRobot_RainfallSensor_I2C gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second);
-
- protected:
- virtual void setup() override;
+ DFRobot_RainfallSensor_I2C *gravity = nullptr;
public:
DFRobotGravitySensor();
- virtual int32_t runOnce() override;
+ ~DFRobotGravitySensor();
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp
index d962f1634..2c2aeed6d 100644
--- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp
+++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp
@@ -11,14 +11,10 @@
DFRobotLarkSensor::DFRobotLarkSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_LARK, "DFROBOT_LARK") {}
-int32_t DFRobotLarkSensor::runOnce()
+bool DFRobotLarkSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
-
- lark = DFRobot_LarkWeatherStation_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
+ lark = DFRobot_LarkWeatherStation_I2C(dev->address.address, bus);
if (lark.begin() == 0) // DFRobotLarkSensor init
{
@@ -28,11 +24,10 @@ int32_t DFRobotLarkSensor::runOnce()
LOG_ERROR("DFRobotLarkSensor Init Failed");
status = false;
}
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void DFRobotLarkSensor::setup() {}
-
bool DFRobotLarkSensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
index 7b67bc5b6..f3e4661a1 100644
--- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
+++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
@@ -16,13 +16,10 @@ class DFRobotLarkSensor : public TelemetrySensor
private:
DFRobot_LarkWeatherStation_I2C lark = DFRobot_LarkWeatherStation_I2C();
- protected:
- virtual void setup() override;
-
public:
DFRobotLarkSensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
diff --git a/src/modules/Telemetry/Sensor/DPS310Sensor.cpp b/src/modules/Telemetry/Sensor/DPS310Sensor.cpp
index cc9b83af8..19e54aa4b 100644
--- a/src/modules/Telemetry/Sensor/DPS310Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/DPS310Sensor.cpp
@@ -9,23 +9,22 @@
DPS310Sensor::DPS310Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DPS310, "DPS310") {}
-int32_t DPS310Sensor::runOnce()
+bool DPS310Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ status = dps310.begin_I2C(dev->address.address, bus);
+ if (!status) {
+ return status;
}
- status = dps310.begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
dps310.configurePressure(DPS310_1HZ, DPS310_4SAMPLES);
dps310.configureTemperature(DPS310_1HZ, DPS310_4SAMPLES);
dps310.setMode(DPS310_CONT_PRESTEMP);
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void DPS310Sensor::setup() {}
-
bool DPS310Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
sensors_event_t temp, press;
diff --git a/src/modules/Telemetry/Sensor/DPS310Sensor.h b/src/modules/Telemetry/Sensor/DPS310Sensor.h
index e9b4ece89..4de8b2d1a 100644
--- a/src/modules/Telemetry/Sensor/DPS310Sensor.h
+++ b/src/modules/Telemetry/Sensor/DPS310Sensor.h
@@ -11,13 +11,10 @@ class DPS310Sensor : public TelemetrySensor
private:
Adafruit_DPS310 dps310;
- protected:
- virtual void setup() override;
-
public:
DPS310Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/IndicatorSensor.cpp b/src/modules/Telemetry/Sensor/IndicatorSensor.cpp
index 317357137..26a4bc5fc 100644
--- a/src/modules/Telemetry/Sensor/IndicatorSensor.cpp
+++ b/src/modules/Telemetry/Sensor/IndicatorSensor.cpp
@@ -61,11 +61,11 @@ static int cmd_send(uint8_t cmd, const char *p_data, uint8_t len)
return -1;
}
-int32_t IndicatorSensor::runOnce()
+bool IndicatorSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("%s: init", sensorName);
setup();
- return 2 * DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; // give it some time to start up
+ return true;
}
void IndicatorSensor::setup()
diff --git a/src/modules/Telemetry/Sensor/IndicatorSensor.h b/src/modules/Telemetry/Sensor/IndicatorSensor.h
index 48ecef8de..22a0d9c83 100644
--- a/src/modules/Telemetry/Sensor/IndicatorSensor.h
+++ b/src/modules/Telemetry/Sensor/IndicatorSensor.h
@@ -7,13 +7,13 @@
class IndicatorSensor : public TelemetrySensor
{
- protected:
- virtual void setup() override;
-
public:
IndicatorSensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+
+ private:
+ void setup();
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
index cf0fbe4a9..4ed78dcb0 100644
--- a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
+++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
@@ -10,19 +10,17 @@
LPS22HBSensor::LPS22HBSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_LPS22, "LPS22HB") {}
-int32_t LPS22HBSensor::runOnce()
+bool LPS22HBSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ status = lps22hb.begin_I2C(dev->address.address, bus);
+ if (!status) {
+ return status;
}
- status = lps22hb.begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
- return initI2CSensor();
-}
-
-void LPS22HBSensor::setup()
-{
lps22hb.setDataRate(LPS22_RATE_10_HZ);
+
+ initI2CSensor();
+ return status;
}
bool LPS22HBSensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.h b/src/modules/Telemetry/Sensor/LPS22HBSensor.h
index 24d75e903..90b006fa2 100644
--- a/src/modules/Telemetry/Sensor/LPS22HBSensor.h
+++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.h
@@ -12,13 +12,10 @@ class LPS22HBSensor : public TelemetrySensor
private:
Adafruit_LPS22 lps22hb;
- protected:
- virtual void setup() override;
-
public:
LPS22HBSensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp b/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp
index fb84700c4..cb7290fee 100644
--- a/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp
+++ b/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp
@@ -9,23 +9,23 @@
LTR390UVSensor::LTR390UVSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_LTR390UV, "LTR390UV") {}
-int32_t LTR390UVSensor::runOnce()
+bool LTR390UVSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+
+ status = ltr390uv.begin(bus);
+ if (!status) {
+ return status;
}
- status = ltr390uv.begin(nodeTelemetrySensorsMap[sensorType].second);
ltr390uv.setMode(LTR390_MODE_UVS);
ltr390uv.setGain(LTR390_GAIN_18); // Datasheet default
ltr390uv.setResolution(LTR390_RESOLUTION_20BIT); // Datasheet default
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void LTR390UVSensor::setup() {}
-
bool LTR390UVSensor::getMetrics(meshtastic_Telemetry *measurement)
{
LOG_DEBUG("LTR390UV getMetrics");
diff --git a/src/modules/Telemetry/Sensor/LTR390UVSensor.h b/src/modules/Telemetry/Sensor/LTR390UVSensor.h
index 40206bce8..e12d17274 100644
--- a/src/modules/Telemetry/Sensor/LTR390UVSensor.h
+++ b/src/modules/Telemetry/Sensor/LTR390UVSensor.h
@@ -13,13 +13,10 @@ class LTR390UVSensor : public TelemetrySensor
float lastLuxReading = 0;
float lastUVReading = 0;
- protected:
- virtual void setup() override;
-
public:
LTR390UVSensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
index 906634c40..c93d6a927 100644
--- a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
@@ -9,19 +9,17 @@
MCP9808Sensor::MCP9808Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MCP9808, "MCP9808") {}
-int32_t MCP9808Sensor::runOnce()
+bool MCP9808Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ status = mcp9808.begin(dev->address.address, bus);
+ if (!status) {
+ return status;
}
- status = mcp9808.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
- return initI2CSensor();
-}
-
-void MCP9808Sensor::setup()
-{
mcp9808.setResolution(2);
+
+ initI2CSensor();
+ return status;
}
bool MCP9808Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.h b/src/modules/Telemetry/Sensor/MCP9808Sensor.h
index 705a71700..cef7a48c2 100644
--- a/src/modules/Telemetry/Sensor/MCP9808Sensor.h
+++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.h
@@ -11,13 +11,10 @@ class MCP9808Sensor : public TelemetrySensor
private:
Adafruit_MCP9808 mcp9808;
- protected:
- virtual void setup() override;
-
public:
MCP9808Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp
index dfc049023..eb84edffc 100644
--- a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp
@@ -8,16 +8,12 @@
MLX90632Sensor::MLX90632Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MLX90632, "MLX90632") {}
-int32_t MLX90632Sensor::runOnce()
+bool MLX90632Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
MLX90632::status returnError;
- if (mlx.begin(nodeTelemetrySensorsMap[sensorType].first, *nodeTelemetrySensorsMap[sensorType].second, returnError) ==
- true) // MLX90632 init
+ if (mlx.begin(dev->address.address, *bus, returnError) == true) // MLX90632 init
{
LOG_DEBUG("MLX90632 Init Succeed");
status = true;
@@ -25,11 +21,10 @@ int32_t MLX90632Sensor::runOnce()
LOG_ERROR("MLX90632 Init Failed");
status = false;
}
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void MLX90632Sensor::setup() {}
-
bool MLX90632Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.h b/src/modules/Telemetry/Sensor/MLX90632Sensor.h
index ef7be180a..566db8319 100644
--- a/src/modules/Telemetry/Sensor/MLX90632Sensor.h
+++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.h
@@ -11,13 +11,10 @@ class MLX90632Sensor : public TelemetrySensor
private:
MLX90632 mlx = MLX90632();
- protected:
- virtual void setup() override;
-
public:
MLX90632Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
index b6b5d89f7..e67b78145 100644
--- a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
@@ -16,24 +16,23 @@ meshtastic_Nau7802Config nau7802config = meshtastic_Nau7802Config_init_zero;
NAU7802Sensor::NAU7802Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_NAU7802, "NAU7802") {}
-int32_t NAU7802Sensor::runOnce()
+bool NAU7802Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ status = nau7802.begin(*bus);
+ if (!status) {
+ return status;
}
- status = nau7802.begin(*nodeTelemetrySensorsMap[sensorType].second);
nau7802.setSampleRate(NAU7802_SPS_320);
if (!loadCalibrationData()) {
LOG_ERROR("Failed to load calibration data");
}
nau7802.calibrateAFE();
LOG_INFO("Offset: %d, Calibration factor: %.2f", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void NAU7802Sensor::setup() {}
-
bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
LOG_DEBUG("NAU7802 getMetrics");
diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.h b/src/modules/Telemetry/Sensor/NAU7802Sensor.h
index cb9e64829..a45e9a78a 100644
--- a/src/modules/Telemetry/Sensor/NAU7802Sensor.h
+++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.h
@@ -13,15 +13,14 @@ class NAU7802Sensor : public TelemetrySensor
NAU7802 nau7802;
protected:
- virtual void setup() override;
const char *nau7802ConfigFileName = "/prefs/nau7802.dat";
bool saveCalibrationData();
bool loadCalibrationData();
public:
NAU7802Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
void tare();
void calibrate(float weight);
AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp
index 1f0407374..3407f2f0f 100644
--- a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp
@@ -9,20 +9,15 @@
OPT3001Sensor::OPT3001Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_OPT3001, "OPT3001") {}
-int32_t OPT3001Sensor::runOnce()
+bool OPT3001Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
- auto errorCode = opt3001.begin(nodeTelemetrySensorsMap[sensorType].first);
+ auto errorCode = opt3001.begin(dev->address.address);
status = errorCode == NO_ERROR;
+ if (!status) {
+ return status;
+ }
- return initI2CSensor();
-}
-
-void OPT3001Sensor::setup()
-{
OPT3001_Config newConfig;
newConfig.RangeNumber = 0b1100;
@@ -34,6 +29,10 @@ void OPT3001Sensor::setup()
if (errorConfig != NO_ERROR) {
LOG_ERROR("OPT3001 configuration error #%d", errorConfig);
}
+ status = errorConfig == NO_ERROR;
+
+ initI2CSensor();
+ return status;
}
bool OPT3001Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.h b/src/modules/Telemetry/Sensor/OPT3001Sensor.h
index a9da2d705..c8a140b51 100644
--- a/src/modules/Telemetry/Sensor/OPT3001Sensor.h
+++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.h
@@ -12,13 +12,13 @@ class OPT3001Sensor : public TelemetrySensor
private:
ClosedCube_OPT3001 opt3001;
- protected:
- virtual void setup() override;
-
public:
OPT3001Sensor();
- virtual int32_t runOnce() override;
+#if WIRE_INTERFACES_COUNT > 1
+ virtual bool onlyWire1() { return true; }
+#endif
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp b/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp
index d2b50d983..189317bf2 100644
--- a/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp
@@ -9,24 +9,18 @@
PCT2075Sensor::PCT2075Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PCT2075, "PCT2075") {}
-int32_t PCT2075Sensor::runOnce()
+bool PCT2075Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
+ status = pct2075.begin(dev->address.address, bus);
- status = pct2075.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
-
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void PCT2075Sensor::setup() {}
-
bool PCT2075Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_temperature = true;
-
measurement->variant.environment_metrics.temperature = pct2075.getTemperature();
return true;
diff --git a/src/modules/Telemetry/Sensor/PCT2075Sensor.h b/src/modules/Telemetry/Sensor/PCT2075Sensor.h
index 842c973d0..55f9423d4 100644
--- a/src/modules/Telemetry/Sensor/PCT2075Sensor.h
+++ b/src/modules/Telemetry/Sensor/PCT2075Sensor.h
@@ -12,13 +12,10 @@ class PCT2075Sensor : public TelemetrySensor
private:
Adafruit_PCT2075 pct2075;
- protected:
- virtual void setup() override;
-
public:
PCT2075Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp
index 7a1bb01ce..ff0628cc3 100644
--- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp
@@ -6,16 +6,12 @@
RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {}
-int32_t RAK12035Sensor::runOnce()
+bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
-
// TODO:: check for up to 2 additional sensors and start them if present.
sensor.set_sensor_addr(RAK120351_ADDR);
delay(100);
- sensor.begin(nodeTelemetrySensorsMap[sensorType].first);
+ sensor.begin(dev->address.address);
// Get sensor firmware version
uint8_t data = 0;
@@ -31,8 +27,13 @@ int32_t RAK12035Sensor::runOnce()
LOG_ERROR("RAK12035Sensor Init Failed");
status = false;
}
+ if (!status) {
+ return status;
+ }
+ setup();
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
void RAK12035Sensor::setup()
diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.h b/src/modules/Telemetry/Sensor/RAK12035Sensor.h
index 2c32a840d..6a38d2eb3 100644
--- a/src/modules/Telemetry/Sensor/RAK12035Sensor.h
+++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.h
@@ -16,13 +16,14 @@ class RAK12035Sensor : public TelemetrySensor
{
private:
RAK12035 sensor;
-
- protected:
- virtual void setup() override;
+ void setup();
public:
RAK12035Sensor();
- virtual int32_t runOnce() override;
+#if WIRE_INTERFACES_COUNT > 1
+ virtual bool onlyWire1() { return true; }
+#endif
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp
index 9f7a55cc5..3dbd06e8d 100644
--- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp
@@ -8,19 +8,15 @@
RCWL9620Sensor::RCWL9620Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RCWL9620, "RCWL9620") {}
-int32_t RCWL9620Sensor::runOnce()
+bool RCWL9620Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
status = 1;
- begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first);
- return initI2CSensor();
+ begin(bus, dev->address.address);
+ initI2CSensor();
+ return status;
}
-void RCWL9620Sensor::setup() {}
-
bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_distance = true;
diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h
index 7f9486d25..408db3633 100644
--- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h
+++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h
@@ -16,14 +16,13 @@ class RCWL9620Sensor : public TelemetrySensor
uint32_t _speed = 200000UL;
protected:
- virtual void setup() override;
void begin(TwoWire *wire = &Wire, uint8_t addr = 0x57, uint8_t sda = -1, uint8_t scl = -1, uint32_t speed = 200000UL);
float getDistance();
public:
RCWL9620Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
index 8619a7905..67a36933d 100644
--- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
@@ -9,20 +9,13 @@
SHT31Sensor::SHT31Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT31, "SHT31") {}
-int32_t SHT31Sensor::runOnce()
+bool SHT31Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
- sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second);
- status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first);
- return initI2CSensor();
-}
-
-void SHT31Sensor::setup()
-{
- // Set up oversampling and filter initialization
+ sht31 = Adafruit_SHT31(bus);
+ status = sht31.begin(dev->address.address);
+ initI2CSensor();
+ return status;
}
bool SHT31Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h
index c3d81af95..ecb7d63a6 100644
--- a/src/modules/Telemetry/Sensor/SHT31Sensor.h
+++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h
@@ -11,13 +11,10 @@ class SHT31Sensor : public TelemetrySensor
private:
Adafruit_SHT31 sht31;
- protected:
- virtual void setup() override;
-
public:
SHT31Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp
index 83fdaf6c6..b11795d97 100644
--- a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp
@@ -9,16 +9,16 @@
SHT4XSensor::SHT4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT4X, "SHT4X") {}
-int32_t SHT4XSensor::runOnce()
+bool SHT4XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
uint32_t serialNumber = 0;
- sht4x.begin(nodeTelemetrySensorsMap[sensorType].second);
+ status = sht4x.begin(bus);
+ if (!status) {
+ return status;
+ }
serialNumber = sht4x.readSerial();
if (serialNumber != 0) {
@@ -29,12 +29,8 @@ int32_t SHT4XSensor::runOnce()
status = 0;
}
- return initI2CSensor();
-}
-
-void SHT4XSensor::setup()
-{
- // Set up oversampling and filter initialization
+ initI2CSensor();
+ return status;
}
bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.h b/src/modules/Telemetry/Sensor/SHT4XSensor.h
index da608cb82..7311d2366 100644
--- a/src/modules/Telemetry/Sensor/SHT4XSensor.h
+++ b/src/modules/Telemetry/Sensor/SHT4XSensor.h
@@ -11,13 +11,10 @@ class SHT4XSensor : public TelemetrySensor
private:
Adafruit_SHT4x sht4x = Adafruit_SHT4x();
- protected:
- virtual void setup() override;
-
public:
SHT4XSensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
index e9c4d2a0b..fdab0b266 100644
--- a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
@@ -9,19 +9,13 @@
SHTC3Sensor::SHTC3Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHTC3, "SHTC3") {}
-int32_t SHTC3Sensor::runOnce()
+bool SHTC3Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
- status = shtc3.begin(nodeTelemetrySensorsMap[sensorType].second);
- return initI2CSensor();
-}
+ status = shtc3.begin(bus);
-void SHTC3Sensor::setup()
-{
- // Set up oversampling and filter initialization
+ initI2CSensor();
+ return status;
}
bool SHTC3Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.h b/src/modules/Telemetry/Sensor/SHTC3Sensor.h
index 458af6465..51cee18f7 100644
--- a/src/modules/Telemetry/Sensor/SHTC3Sensor.h
+++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.h
@@ -11,13 +11,10 @@ class SHTC3Sensor : public TelemetrySensor
private:
Adafruit_SHTC3 shtc3 = Adafruit_SHTC3();
- protected:
- virtual void setup() override;
-
public:
SHTC3Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.cpp b/src/modules/Telemetry/Sensor/T1000xSensor.cpp
index 068969e8e..b123450ec 100644
--- a/src/modules/Telemetry/Sensor/T1000xSensor.cpp
+++ b/src/modules/Telemetry/Sensor/T1000xSensor.cpp
@@ -38,18 +38,10 @@ int8_t ntc_temp2[136] = {
T1000xSensor::T1000xSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR_UNSET, "T1000x") {}
-int32_t T1000xSensor::runOnce()
+bool T1000xSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
- }
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-}
-
-void T1000xSensor::setup()
-{
- // Set up oversampling and filter initialization
+ return true;
}
float T1000xSensor::getLux()
diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.h b/src/modules/Telemetry/Sensor/T1000xSensor.h
index a1c771cfa..b840a2d88 100644
--- a/src/modules/Telemetry/Sensor/T1000xSensor.h
+++ b/src/modules/Telemetry/Sensor/T1000xSensor.h
@@ -7,13 +7,10 @@
class T1000xSensor : public TelemetrySensor
{
- protected:
- virtual void setup() override;
-
public:
T1000xSensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
virtual float getLux();
virtual float getTemp();
};
diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp
index 9f3b7e460..4e02af642 100644
--- a/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp
@@ -9,22 +9,19 @@
TSL2561Sensor::TSL2561Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL2561, "TSL2561") {}
-int32_t TSL2561Sensor::runOnce()
+bool TSL2561Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+
+ status = tsl.begin(bus);
+ if (!status) {
+ return status;
}
-
- status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second);
-
- return initI2CSensor();
-}
-
-void TSL2561Sensor::setup()
-{
tsl.setGain(TSL2561_GAIN_1X);
tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS);
+
+ initI2CSensor();
+ return status;
}
bool TSL2561Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.h b/src/modules/Telemetry/Sensor/TSL2561Sensor.h
index 0329becd8..abf5a8f73 100644
--- a/src/modules/Telemetry/Sensor/TSL2561Sensor.h
+++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.h
@@ -12,12 +12,9 @@ class TSL2561Sensor : public TelemetrySensor
// The magic number is a sensor id, the actual value doesn't matter
Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_LOW, 12345);
- protected:
- virtual void setup() override;
-
public:
TSL2561Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp
index 04443ebec..0899d4470 100644
--- a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp
@@ -10,21 +10,18 @@
TSL2591Sensor::TSL2591Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL25911FN, "TSL2591") {}
-int32_t TSL2591Sensor::runOnce()
+bool TSL2591Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ status = tsl.begin(bus);
+ if (!status) {
+ return status;
}
- status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second);
-
- return initI2CSensor();
-}
-
-void TSL2591Sensor::setup()
-{
tsl.setGain(TSL2591_GAIN_LOW); // 1x gain
tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);
+
+ initI2CSensor();
+ return status;
}
bool TSL2591Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.h b/src/modules/Telemetry/Sensor/TSL2591Sensor.h
index edf7698b1..1ac430a03 100644
--- a/src/modules/Telemetry/Sensor/TSL2591Sensor.h
+++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.h
@@ -11,12 +11,9 @@ class TSL2591Sensor : public TelemetrySensor
private:
Adafruit_TSL2591 tsl;
- protected:
- virtual void setup() override;
-
public:
TSL2591Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h
index 83d7b38b0..3c3e61808 100644
--- a/src/modules/Telemetry/Sensor/TelemetrySensor.h
+++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h
@@ -6,6 +6,7 @@
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "MeshModule.h"
#include "NodeDB.h"
+#include "detect/ScanI2C.h"
#include
#if !ARCH_PORTDUINO
@@ -42,22 +43,32 @@ class TelemetrySensor
initialized = true;
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
- virtual void setup() = 0;
+
+ // TODO: check is setup used at all?
+ virtual void setup() {}
public:
+ virtual ~TelemetrySensor() {}
+
virtual AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response)
{
return AdminMessageHandleResult::NOT_HANDLED;
}
+ // TODO: delete after migration
bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; }
- virtual int32_t runOnce() = 0;
+#if WIRE_INTERFACES_COUNT > 1
+ // Set to true if Implementation only works first I2C port (Wire)
+ virtual bool onlyWire1() { return false; }
+#endif
+ virtual int32_t runOnce() { return INT32_MAX; }
virtual bool isInitialized() { return initialized; }
virtual bool isRunning() { return status > 0; }
virtual bool getMetrics(meshtastic_Telemetry *measurement) = 0;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { return false; };
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp
index b075bf405..c89463be5 100644
--- a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp
@@ -11,23 +11,22 @@
VEML7700Sensor::VEML7700Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_VEML7700, "VEML7700") {}
-int32_t VEML7700Sensor::runOnce()
+bool VEML7700Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
- if (!hasSensor()) {
- return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ status = veml7700.begin(bus);
+ if (!status) {
+ return status;
}
- status = veml7700.begin(nodeTelemetrySensorsMap[sensorType].second);
veml7700.setLowThreshold(10000);
veml7700.setHighThreshold(20000);
veml7700.interruptEnable(true);
- return initI2CSensor();
+ initI2CSensor();
+ return status;
}
-void VEML7700Sensor::setup() {}
-
/*!
* @brief Copmute lux from ALS reading.
* @param rawALS raw ALS register value
diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.h b/src/modules/Telemetry/Sensor/VEML7700Sensor.h
index f40384ad3..92883df08 100644
--- a/src/modules/Telemetry/Sensor/VEML7700Sensor.h
+++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.h
@@ -16,12 +16,9 @@ class VEML7700Sensor : public TelemetrySensor
float computeLux(uint16_t rawALS, bool corrected);
float getResolution(void);
- protected:
- virtual void setup() override;
-
public:
VEML7700Sensor();
- virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/nullSensor.cpp b/src/modules/Telemetry/Sensor/nullSensor.cpp
index c84b9d27f..1d545186a 100644
--- a/src/modules/Telemetry/Sensor/nullSensor.cpp
+++ b/src/modules/Telemetry/Sensor/nullSensor.cpp
@@ -11,7 +11,7 @@ NullSensor::NullSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR
int32_t NullSensor::runOnce()
{
- return 0;
+ return INT32_MAX;
}
void NullSensor::setup() {}
diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index fc2cc232b..87a2f1bd2 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -11,6 +11,113 @@ extern graphics::Screen *screen;
TraceRouteModule *traceRouteModule;
+void TraceRouteModule::setResultText(const String &text)
+{
+ resultText = text;
+ resultLines.clear();
+ resultLinesDirty = true;
+}
+
+void TraceRouteModule::clearResultLines()
+{
+ resultLines.clear();
+ resultLinesDirty = false;
+}
+#if HAS_SCREEN
+void TraceRouteModule::rebuildResultLines(OLEDDisplay *display)
+{
+ if (!display) {
+ resultLinesDirty = false;
+ return;
+ }
+
+ resultLines.clear();
+
+ if (resultText.length() == 0) {
+ resultLinesDirty = false;
+ return;
+ }
+
+ int maxWidth = display->getWidth() - 4;
+ if (maxWidth <= 0) {
+ resultLinesDirty = false;
+ return;
+ }
+
+ int start = 0;
+ int textLength = resultText.length();
+
+ while (start <= textLength) {
+ int newlinePos = resultText.indexOf('\n', start);
+ String segment;
+
+ if (newlinePos != -1) {
+ segment = resultText.substring(start, newlinePos);
+ start = newlinePos + 1;
+ } else {
+ segment = resultText.substring(start);
+ start = textLength + 1;
+ }
+
+ if (segment.length() == 0) {
+ resultLines.push_back("");
+ continue;
+ }
+
+ if (display->getStringWidth(segment) <= maxWidth) {
+ resultLines.push_back(segment);
+ continue;
+ }
+
+ String remaining = segment;
+
+ while (remaining.length() > 0) {
+ String tempLine = "";
+ int lastGoodBreak = -1;
+ bool lineComplete = false;
+
+ for (int i = 0; i < static_cast(remaining.length()); i++) {
+ char ch = remaining.charAt(i);
+ String testLine = tempLine + ch;
+
+ if (display->getStringWidth(testLine) > maxWidth) {
+ if (lastGoodBreak >= 0) {
+ resultLines.push_back(remaining.substring(0, lastGoodBreak + 1));
+ remaining = remaining.substring(lastGoodBreak + 1);
+ lineComplete = true;
+ break;
+ } else if (tempLine.length() > 0) {
+ resultLines.push_back(tempLine);
+ remaining = remaining.substring(i);
+ lineComplete = true;
+ break;
+ } else {
+ resultLines.push_back(String(ch));
+ remaining = remaining.substring(i + 1);
+ lineComplete = true;
+ break;
+ }
+ } else {
+ tempLine = testLine;
+ if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')' || ch == ',') {
+ lastGoodBreak = i;
+ }
+ }
+ }
+
+ if (!lineComplete) {
+ if (tempLine.length() > 0) {
+ resultLines.push_back(tempLine);
+ }
+ break;
+ }
+ }
+ }
+
+ resultLinesDirty = false;
+}
+#endif
+
bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r)
{
// We only alter the packet in alterReceivedProtobuf()
@@ -21,6 +128,11 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
{
const meshtastic_Data &incoming = p.decoded;
+ // Update next-hops using returned route
+ if (incoming.request_id) {
+ updateNextHops(p, r);
+ }
+
// Insert unknown hops if necessary
insertUnknownHops(p, r, !incoming.request_id);
@@ -153,6 +265,65 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
}
}
+void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r)
+{
+ // E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D
+ // Similarly, if we are C, we can set D as next-hop for D
+ // If we are A, we can set B as next-hop for B, C and D
+
+ // First check if we were the original sender or in the original route
+ int8_t nextHopIndex = -1;
+ if (isToUs(&p)) {
+ nextHopIndex = 0; // We are the original sender, next hop is first in route
+ } else {
+ // Check if we are in the original route
+ for (uint8_t i = 0; i < r->route_count; i++) {
+ if (r->route[i] == nodeDB->getNodeNum()) {
+ nextHopIndex = i + 1; // Next hop is the one after us
+ break;
+ }
+ }
+ }
+
+ // If we are in the original route, update the next hops
+ if (nextHopIndex != -1) {
+ // For every node after us, we can set the next-hop to the first node after us
+ NodeNum nextHop;
+ if (nextHopIndex == r->route_count) {
+ nextHop = p.from; // We are the last in the route, next hop is destination
+ } else {
+ nextHop = r->route[nextHopIndex];
+ }
+
+ if (nextHop == NODENUM_BROADCAST) {
+ return;
+ }
+ uint8_t nextHopByte = nodeDB->getLastByteOfNodeNum(nextHop);
+
+ // For the rest of the nodes in the route, set their next-hop
+ // Note: if we are the last in the route, this loop will not run
+ for (int8_t i = nextHopIndex; i < r->route_count; i++) {
+ NodeNum targetNode = r->route[i];
+ maybeSetNextHop(targetNode, nextHopByte);
+ }
+
+ // Also set next-hop for the destination node
+ maybeSetNextHop(p.from, nextHopByte);
+ }
+}
+
+void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte)
+{
+ if (target == NODENUM_BROADCAST)
+ return;
+
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target);
+ if (node && node->next_hop != nextHopByte) {
+ LOG_INFO("Updating next-hop for 0x%08x to 0x%02x based on traceroute", target, nextHopByte);
+ node->next_hop = nextHopByte;
+ }
+}
+
void TraceRouteModule::processUpgradedPacket(const meshtastic_MeshPacket &mp)
{
if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag || mp.decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP)
@@ -342,7 +513,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
if (node == 0 || node == NODENUM_BROADCAST) {
LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT;
- resultText = "Invalid node";
+ setResultText("Invalid node");
resultShowTime = millis();
tracingNode = 0;
@@ -356,7 +527,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
if (node == nodeDB->getNodeNum()) {
LOG_ERROR("Cannot trace route to self: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT;
- resultText = "Cannot trace self";
+ setResultText("Cannot trace self");
resultShowTime = millis();
tracingNode = 0;
@@ -383,6 +554,8 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
bannerText = String("Wait for ") + String(wait) + String("s");
runState = TRACEROUTE_STATE_COOLDOWN;
+ resultText = "";
+ clearResultLines();
requestFocus();
UIFrameEvent e;
@@ -395,6 +568,8 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
tracingNode = node;
lastTraceRouteTime = now;
runState = TRACEROUTE_STATE_TRACKING;
+ resultText = "";
+ clearResultLines();
bannerText = String("Tracing ") + getNodeName(node);
LOG_INFO("TraceRoute UI: Starting trace route to node 0x%08x, requesting focus", node);
@@ -437,7 +612,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
} else {
LOG_ERROR("MeshService is NULL!");
runState = TRACEROUTE_STATE_RESULT;
- resultText = "Service unavailable";
+ setResultText("Service unavailable");
resultShowTime = millis();
tracingNode = 0;
@@ -450,7 +625,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
} else {
LOG_ERROR("Failed to allocate TraceRoute packet from router");
runState = TRACEROUTE_STATE_RESULT;
- resultText = "Failed to send";
+ setResultText("Failed to send");
resultShowTime = millis();
tracingNode = 0;
@@ -468,7 +643,7 @@ void TraceRouteModule::launch(NodeNum node)
if (node == 0 || node == NODENUM_BROADCAST) {
LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT;
- resultText = "Invalid node";
+ setResultText("Invalid node");
resultShowTime = millis();
tracingNode = 0;
@@ -482,7 +657,7 @@ void TraceRouteModule::launch(NodeNum node)
if (node == nodeDB->getNodeNum()) {
LOG_ERROR("Cannot trace route to self: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT;
- resultText = "Cannot trace self";
+ setResultText("Cannot trace self");
resultShowTime = millis();
tracingNode = 0;
@@ -504,6 +679,8 @@ void TraceRouteModule::launch(NodeNum node)
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
bannerText = String("Wait for ") + String(wait) + String("s");
runState = TRACEROUTE_STATE_COOLDOWN;
+ resultText = "";
+ clearResultLines();
requestFocus();
UIFrameEvent e;
@@ -516,6 +693,8 @@ void TraceRouteModule::launch(NodeNum node)
runState = TRACEROUTE_STATE_TRACKING;
tracingNode = node;
lastTraceRouteTime = now;
+ resultText = "";
+ clearResultLines();
bannerText = String("Tracing ") + getNodeName(node);
requestFocus();
@@ -550,14 +729,14 @@ void TraceRouteModule::launch(NodeNum node)
} else {
LOG_ERROR("MeshService is NULL!");
runState = TRACEROUTE_STATE_RESULT;
- resultText = "Service unavailable";
+ setResultText("Service unavailable");
resultShowTime = millis();
tracingNode = 0;
}
} else {
LOG_ERROR("Failed to allocate TraceRoute packet from router");
runState = TRACEROUTE_STATE_RESULT;
- resultText = "Failed to send";
+ setResultText("Failed to send");
resultShowTime = millis();
tracingNode = 0;
}
@@ -565,7 +744,7 @@ void TraceRouteModule::launch(NodeNum node)
void TraceRouteModule::handleTraceRouteResult(const String &result)
{
- resultText = result;
+ setResultText(result);
runState = TRACEROUTE_STATE_RESULT;
resultShowTime = millis();
tracingNode = 0;
@@ -615,83 +794,15 @@ void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
display->setFont(FONT_SMALL);
if (resultText.length() > 0) {
- std::vector lines;
- String currentLine = "";
- int maxWidth = display->getWidth() - 4;
-
- int start = 0;
- int newlinePos = resultText.indexOf('\n', start);
-
- while (newlinePos != -1 || start < static_cast(resultText.length())) {
- String segment;
- if (newlinePos != -1) {
- segment = resultText.substring(start, newlinePos);
- start = newlinePos + 1;
- newlinePos = resultText.indexOf('\n', start);
- } else {
- segment = resultText.substring(start);
- start = resultText.length();
- }
-
- if (display->getStringWidth(segment) <= maxWidth) {
- lines.push_back(segment);
- } else {
- // Try to break at better positions (space, >, <, -)
- String remaining = segment;
-
- while (remaining.length() > 0) {
- String tempLine = "";
- int lastGoodBreak = -1;
- bool lineComplete = false;
-
- for (int i = 0; i < static_cast(remaining.length()); i++) {
- char ch = remaining.charAt(i);
- String testLine = tempLine + ch;
-
- if (display->getStringWidth(testLine) > maxWidth) {
- if (lastGoodBreak >= 0) {
- // Break at the last good position
- lines.push_back(remaining.substring(0, lastGoodBreak + 1));
- remaining = remaining.substring(lastGoodBreak + 1);
- lineComplete = true;
- break;
- } else if (tempLine.length() > 0) {
- lines.push_back(tempLine);
- remaining = remaining.substring(i);
- lineComplete = true;
- break;
- } else {
- // Single character exceeds width
- lines.push_back(String(ch));
- remaining = remaining.substring(i + 1);
- lineComplete = true;
- break;
- }
- } else {
- tempLine = testLine;
- // Mark good break positions
- if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')') {
- lastGoodBreak = i;
- }
- }
- }
-
- if (!lineComplete) {
- // Reached end of remaining text
- if (tempLine.length() > 0) {
- lines.push_back(tempLine);
- }
- break;
- }
- }
- }
+ if (resultLinesDirty) {
+ rebuildResultLines(display);
}
int lineHeight = FONT_HEIGHT_SMALL + 1; // Use proper font height with 1px spacing
- for (size_t i = 0; i < lines.size(); i++) {
+ for (size_t i = 0; i < resultLines.size(); i++) {
int lineY = contentStartY + (i * lineHeight);
if (lineY + FONT_HEIGHT_SMALL <= display->getHeight()) {
- display->drawString(x + 2, lineY, lines[i]);
+ display->drawString(x + 2, lineY, resultLines[i]);
}
}
}
@@ -715,7 +826,7 @@ int32_t TraceRouteModule::runOnce()
if (runState == TRACEROUTE_STATE_TRACKING && now - lastTraceRouteTime > trackingTimeoutMs) {
LOG_INFO("TraceRoute timeout, no response received");
runState = TRACEROUTE_STATE_RESULT;
- resultText = "No response received";
+ setResultText("No response received");
resultShowTime = now;
tracingNode = 0;
@@ -751,6 +862,8 @@ int32_t TraceRouteModule::runOnce()
// Cooldown finished
LOG_INFO("TraceRoute cooldown finished, returning to IDLE");
runState = TRACEROUTE_STATE_IDLE;
+ resultText = "";
+ clearResultLines();
bannerText = "";
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
@@ -764,6 +877,7 @@ int32_t TraceRouteModule::runOnce()
LOG_INFO("TraceRoute result display timeout, returning to IDLE");
runState = TRACEROUTE_STATE_IDLE;
resultText = "";
+ clearResultLines();
bannerText = "";
tracingNode = 0;
UIFrameEvent e;
diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h
index a069f7157..a40ed7733 100644
--- a/src/modules/TraceRouteModule.h
+++ b/src/modules/TraceRouteModule.h
@@ -7,6 +7,7 @@
#if HAS_SCREEN
#include "OLEDDisplayUi.h"
#endif
+#include
#define ROUTE_SIZE sizeof(((meshtastic_RouteDiscovery *)0)->route) / sizeof(((meshtastic_RouteDiscovery *)0)->route[0])
@@ -49,12 +50,23 @@ class TraceRouteModule : public ProtobufModule,
virtual int32_t runOnce() override;
private:
+ void setResultText(const String &text);
+ void clearResultLines();
+#if HAS_SCREEN
+ void rebuildResultLines(OLEDDisplay *display);
+#endif
// Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit
void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination);
// Call to add your ID to the route array of a RouteDiscovery message
void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly);
+ // Update next-hops in the routing table based on the returned route
+ void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r);
+
+ // Helper to update next-hop for a single node
+ void maybeSetNextHop(NodeNum target, uint8_t nextHopByte);
+
/* Call to print the route array of a RouteDiscovery message.
Set origin to where the request came from.
Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */
@@ -68,6 +80,8 @@ class TraceRouteModule : public ProtobufModule,
unsigned long trackingTimeoutMs = 10000;
String bannerText;
String resultText;
+ std::vector resultLines;
+ bool resultLinesDirty = false;
NodeNum tracingNode = 0;
bool initialized = false;
};
diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp
index 8b1fc5302..9c25177bc 100644
--- a/src/modules/esp32/PaxcounterModule.cpp
+++ b/src/modules/esp32/PaxcounterModule.cpp
@@ -141,6 +141,7 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
display->drawStringf(display->getWidth() / 2 + x, graphics::getTextPositions(display)[line++], buffer,
"WiFi: %d\nBLE: %d\nUptime: %ds", count_from_libpax.wifi_count, count_from_libpax.ble_count,
millis() / 1000);
+ graphics::drawCommonFooter(display, x, y);
}
#endif // HAS_SCREEN
diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp
index 003ee850c..5888c20be 100755
--- a/src/motion/BMX160Sensor.cpp
+++ b/src/motion/BMX160Sensor.cpp
@@ -115,7 +115,13 @@ int32_t BMX160Sensor::runOnce()
void BMX160Sensor::calibrate(uint16_t forSeconds)
{
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
+ sBmx160SensorData_t magAccel;
+ sBmx160SensorData_t gAccel;
LOG_DEBUG("BMX160 calibration started for %is", forSeconds);
+ sensor.getAllData(&magAccel, NULL, &gAccel);
+ highestX = magAccel.x, lowestX = magAccel.x;
+ highestY = magAccel.y, lowestY = magAccel.y;
+ highestZ = magAccel.z, lowestZ = magAccel.z;
doCalibration = true;
uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided
@@ -127,4 +133,4 @@ void BMX160Sensor::calibrate(uint16_t forSeconds)
#endif
-#endif
\ No newline at end of file
+#endif
diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp
index 76ba8e8cf..9455eafe0 100755
--- a/src/motion/ICM20948Sensor.cpp
+++ b/src/motion/ICM20948Sensor.cpp
@@ -47,6 +47,21 @@ int32_t ICM20948Sensor::runOnce()
int32_t ICM20948Sensor::runOnce()
{
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
+#if defined(MUZI_BASE) // temporarily gated to single device due to feature freeze
+ if (screen && !screen->isScreenOn() && !config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) {
+ if (!isAsleep) {
+ LOG_DEBUG("sleeping IMU");
+ sensor->sleep(true);
+ isAsleep = true;
+ }
+ return MOTION_SENSOR_CHECK_INTERVAL_MS;
+ }
+ if (isAsleep) {
+ sensor->sleep(false);
+ isAsleep = false;
+ }
+#endif
+
float magX = 0, magY = 0, magZ = 0;
if (sensor->dataReady()) {
sensor->getAGMT();
@@ -156,7 +171,20 @@ int32_t ICM20948Sensor::runOnce()
void ICM20948Sensor::calibrate(uint16_t forSeconds)
{
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
+ LOG_DEBUG("Old calibration data: highestX = %f, lowestX = %f, highestY = %f, lowestY = %f, highestZ = %f, lowestZ = %f",
+ highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
LOG_DEBUG("BMX160 calibration started for %is", forSeconds);
+ if (sensor->dataReady()) {
+ sensor->getAGMT();
+ highestX = sensor->agmt.mag.axes.x;
+ lowestX = sensor->agmt.mag.axes.x;
+ highestY = sensor->agmt.mag.axes.y;
+ lowestY = sensor->agmt.mag.axes.y;
+ highestZ = sensor->agmt.mag.axes.z;
+ lowestZ = sensor->agmt.mag.axes.z;
+ } else {
+ highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
+ }
doCalibration = true;
uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided
@@ -295,4 +323,4 @@ bool ICM20948Singleton::setWakeOnMotion()
return true;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/motion/ICM20948Sensor.h b/src/motion/ICM20948Sensor.h
index 27ce4f451..a9b7b69d0 100755
--- a/src/motion/ICM20948Sensor.h
+++ b/src/motion/ICM20948Sensor.h
@@ -82,7 +82,13 @@ class ICM20948Sensor : public MotionSensor
private:
ICM20948Singleton *sensor = nullptr;
bool showingScreen = false;
+#ifdef MUZI_BASE
+ bool isAsleep = false;
+ float highestX = 449.000000, lowestX = -140.000000, highestY = 422.000000, lowestY = -232.000000, highestZ = 749.000000,
+ lowestZ = 98.000000;
+#else
float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
+#endif
public:
explicit ICM20948Sensor(ScanI2C::FoundDevice foundDevice);
diff --git a/src/motion/MotionSensor.cpp b/src/motion/MotionSensor.cpp
index b00460aff..d0bfe4e2c 100755
--- a/src/motion/MotionSensor.cpp
+++ b/src/motion/MotionSensor.cpp
@@ -69,7 +69,8 @@ void MotionSensor::wakeScreen()
{
if (powerFSM.getState() == &stateDARK) {
LOG_DEBUG("Motion wakeScreen detected");
- powerFSM.trigger(EVENT_INPUT);
+ if (config.display.wake_on_tap_or_motion)
+ powerFSM.trigger(EVENT_INPUT);
}
}
@@ -87,4 +88,4 @@ void MotionSensor::buttonPress() {}
#endif
-#endif
\ No newline at end of file
+#endif
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 7f7a9d511..ad35e152a 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -51,6 +51,7 @@ constexpr int reconnectMax = 5;
static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid
static bool isMqttServerAddressPrivate = false;
+static bool isConnected = false;
inline void onReceiveProto(char *topic, byte *payload, size_t length)
{
@@ -59,8 +60,30 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
return;
}
+
const meshtastic_Channel &ch = channels.getByName(e.channel_id);
- if (strcmp(e.gateway_id, owner.id) == 0) {
+ // Find channel by channel_id and check downlink_enabled
+ if (!(strcmp(e.channel_id, "PKI") == 0 ||
+ (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
+ return;
+ }
+
+ bool anyChannelHasDownlink = false;
+ size_t numChan = channels.getNumChannels();
+ for (size_t i = 0; i < numChan; ++i) {
+ const auto &c = channels.getByIndex(i);
+ if (c.settings.downlink_enabled) {
+ anyChannelHasDownlink = true;
+ break;
+ }
+ }
+
+ if (strcmp(e.channel_id, "PKI") == 0 && !anyChannelHasDownlink) {
+ return;
+ }
+ // Generate node ID from nodenum for comparison
+ std::string nodeId = nodeDB->getNodeId();
+ if (strcmp(e.gateway_id, nodeId.c_str()) == 0) {
// Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message.
// We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node
// receives it when we get our own packet back. Then we'll stop our retransmissions.
@@ -75,11 +98,6 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
return;
}
- // Find channel by channel_id and check downlink_enabled
- if (!(strcmp(e.channel_id, "PKI") == 0 ||
- (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
- return;
- }
LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
if (e.packet->hop_limit > HOP_MAX || e.packet->hop_start > HOP_MAX) {
LOG_INFO("Invalid hop_limit(%u) or hop_start(%u)", e.packet->hop_limit, e.packet->hop_start);
@@ -128,8 +146,10 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
// returns true if this is a valid JSON envelope which we accept on downlink
inline bool isValidJsonEnvelope(JSONObject &json)
{
+ // Generate node ID from nodenum for comparison
+ std::string nodeId = nodeDB->getNodeId();
// if "sender" is provided, avoid processing packets we uplinked
- return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) &&
+ return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(nodeId) != 0) : true) &&
(json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number
(json.find("from") != json.end()) && json["from"]->IsNumber() &&
(json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us
@@ -297,10 +317,14 @@ bool connectPubSub(const PubSubConfig &config, PubSubClient &pubSub, Client &cli
LOG_INFO("Connecting directly to MQTT server %s, port: %d, username: %s, password: %s", config.serverAddr.c_str(),
config.serverPort, config.mqttUsername, config.mqttPassword);
- const bool connected = pubSub.connect(owner.id, config.mqttUsername, config.mqttPassword);
+ // Generate node ID from nodenum for client identification
+ std::string nodeId = nodeDB->getNodeId();
+ const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword);
if (connected) {
+ isConnected = true;
LOG_INFO("MQTT connected");
} else {
+ isConnected = false;
LOG_WARN("Failed to connect to MQTT server");
}
return connected;
@@ -467,7 +491,9 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo
if (moduleConfig.mqtt.proxy_to_client_enabled) {
meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed();
msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag;
- strcpy(msg->topic, topic);
+ strlcpy(msg->topic, topic, sizeof(msg->topic));
+ if (length > sizeof(msg->payload_variant.data.bytes))
+ length = sizeof(msg->payload_variant.data.bytes);
msg->payload_variant.data.size = length;
memcpy(msg->payload_variant.data.bytes, payload, length);
msg->retained = retained;
@@ -484,6 +510,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo
void MQTT::reconnect()
{
+ isConnected = false;
if (wantsLink()) {
if (moduleConfig.mqtt.proxy_to_client_enabled) {
LOG_INFO("MQTT connect via client proxy instead");
@@ -511,7 +538,7 @@ void MQTT::reconnect()
runASAP = true;
reconnectCount = 0;
isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP());
-
+ isConnected = true;
publishNodeInfo();
sendSubscriptions();
} else {
@@ -668,6 +695,9 @@ void MQTT::publishQueuedMessages()
if (mqttQueue.isEmpty())
return;
+ if (!moduleConfig.mqtt.proxy_to_client_enabled && !isConnected)
+ return;
+
LOG_DEBUG("Publish enqueued MQTT message");
const std::unique_ptr entry(mqttQueue.dequeuePtr(0));
LOG_INFO("publish %s, %u bytes from queue", entry->topic.c_str(), entry->envBytes.size());
@@ -687,11 +717,14 @@ void MQTT::publishQueuedMessages()
if (jsonString.length() == 0)
return;
+ // Generate node ID from nodenum for topic
+ std::string nodeId = nodeDB->getNodeId();
+
std::string topicJson;
if (env.packet->pki_encrypted) {
- topicJson = jsonTopic + "PKI/" + owner.id;
+ topicJson = jsonTopic + "PKI/" + nodeId;
} else {
- topicJson = jsonTopic + env.channel_id + "/" + owner.id;
+ topicJson = jsonTopic + env.channel_id + "/" + nodeId;
}
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
publish(topicJson.c_str(), jsonString.c_str(), false);
@@ -749,10 +782,14 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
return; // Don't upload a still-encrypted PKI packet if not encryption_enabled
}
- const meshtastic_ServiceEnvelope env = {
- .packet = const_cast(p), .channel_id = const_cast(channelId), .gateway_id = owner.id};
+ // Generate node ID from nodenum for service envelope
+ std::string nodeId = nodeDB->getNodeId();
+
+ const meshtastic_ServiceEnvelope env = {.packet = const_cast(p),
+ .channel_id = const_cast(channelId),
+ .gateway_id = const_cast(nodeId.c_str())};
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env);
- std::string topic = cryptTopic + channelId + "/" + owner.id;
+ std::string topic = cryptTopic + channelId + "/" + nodeId;
if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) {
LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
@@ -766,7 +803,9 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
auto jsonString = MeshPacketSerializer::JsonSerialize(&mp_decoded);
if (jsonString.length() == 0)
return;
- std::string topicJson = jsonTopic + channelId + "/" + owner.id;
+ // Generate node ID from nodenum for JSON topic
+ std::string nodeIdForJson = nodeDB->getNodeId();
+ std::string topicJson = jsonTopic + channelId + "/" + nodeIdForJson;
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
publish(topicJson.c_str(), jsonString.c_str(), false);
#endif // ARCH_NRF52 NRF52_USE_JSON
@@ -845,11 +884,14 @@ void MQTT::perhapsReportToMap()
mp->decoded.payload.size =
pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_MapReport_msg, &mapReport);
+ // Generate node ID from nodenum for service envelope
+ std::string nodeId = nodeDB->getNodeId();
+
// Encode the MeshPacket into a binary ServiceEnvelope and publish
const meshtastic_ServiceEnvelope se = {
.packet = mp,
.channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()), // Use primary channel as the channel_id
- .gateway_id = owner.id};
+ .gateway_id = const_cast(nodeId.c_str())};
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &se);
LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str());
@@ -860,4 +902,4 @@ void MQTT::perhapsReportToMap()
// Update the last report time
last_report_to_map = millis();
-}
\ No newline at end of file
+}
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 5524765f3..69da25884 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -3,12 +3,15 @@
#include "BluetoothCommon.h"
#include "NimbleBluetooth.h"
#include "PowerFSM.h"
+#include "StaticPointerQueue.h"
+#include "concurrency/OSThread.h"
#include "main.h"
#include "mesh/PhoneAPI.h"
#include "mesh/mesh-pb-constants.h"
#include "sleep.h"
#include
+#include
#include
#ifdef NIMBLE_TWO
@@ -17,45 +20,292 @@
#include "PowerStatus.h"
#endif
+#if defined(CONFIG_NIMBLE_CPP_IDF)
+#include "host/ble_gap.h"
+#else
+#include "nimble/nimble/host/include/host/ble_gap.h"
+#endif
+
+#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
+
+namespace
+{
+constexpr uint16_t kPreferredBleMtu = 517;
+constexpr uint16_t kPreferredBleTxOctets = 251;
+constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8;
+} // namespace
+#endif
+
+// Debugging options: careful, they slow things down quite a bit!
+// #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration
+// #define DEBUG_NIMBLE_ON_WRITE_TIMING // uncomment to time onWrite duration
+// #define DEBUG_NIMBLE_NOTIFY // uncomment to enable notify logging
+
+#define NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE 3
+#define NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE 3
+
NimBLECharacteristic *fromNumCharacteristic;
NimBLECharacteristic *BatteryCharacteristic;
NimBLECharacteristic *logRadioCharacteristic;
NimBLEServer *bleServer;
static bool passkeyShowing;
+static std::atomic nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection"
class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
{
+ /*
+ CAUTION: There's a lot going on here and lots of room to break things.
+
+ This NimbleBluetooth.cpp file does some tricky synchronization between the NimBLE FreeRTOS task (which runs the onRead and
+ onWrite callbacks) and the main task (which runs runOnce and the rest of PhoneAPI).
+
+ The main idea is to add a little bit of synchronization here to make it so that the rest of the codebase doesn't have to
+ know about concurrency and mutexes, and can just run happily ever after as a cooperative multitasking OSThread system, where
+ locking isn't something that anyone has to worry about too much! :)
+
+ We achieve this by having some queues and mutexes in this file only, and ensuring that all calls to getFromRadio and
+ handleToRadio are only made from the main FreeRTOS task. This way, the rest of the codebase doesn't have to worry about
+ being run concurrently, which would make everything else much much much more complicated.
+
+ PHONE -> RADIO:
+ - [NimBLE FreeRTOS task:] onWrite callback holds fromPhoneMutex and pushes received packets into fromPhoneQueue.
+ - [Main task:] runOnceHandleFromPhoneQueue in main task holds fromPhoneMutex, pulls packets from fromPhoneQueue, and calls
+ handleToRadio **in main task**.
+
+ RADIO -> PHONE:
+ - [NimBLE FreeRTOS task:] onRead callback sets onReadCallbackIsWaitingForData flag and polls in a busy loop. (unless
+ there's already a packet waiting in toPhoneQueue)
+ - [Main task:] runOnceHandleToPhoneQueue sees onReadCallbackIsWaitingForData flag, calls getFromRadio **in main task** to
+ get packets from radio, holds toPhoneMutex, pushes the packet into toPhoneQueue, and clears the
+ onReadCallbackIsWaitingForData flag.
+ - [NimBLE FreeRTOS task:] onRead callback sees that the onReadCallbackIsWaitingForData flag cleared, holds toPhoneMutex,
+ pops the packet from toPhoneQueue, and returns it to NimBLE.
+
+ MUTEXES:
+ - fromPhoneMutex protects fromPhoneQueue and fromPhoneQueueSize
+ - toPhoneMutex protects toPhoneQueue, toPhoneQueueByteSizes, and toPhoneQueueSize
+
+ ATOMICS:
+ - fromPhoneQueueSize is only increased by onWrite, and only decreased by runOnceHandleFromPhoneQueue (or onDisconnect).
+ - toPhoneQueueSize is only increased by runOnceHandleToPhoneQueue, and only decreased by onRead (or onDisconnect).
+ - onReadCallbackIsWaitingForData is a flag. It's only set by onRead, and only cleared by runOnceHandleToPhoneQueue (or
+ onDisconnect).
+
+ PRELOADING: see comments in runOnceToPhoneCanPreloadNextPacket about when it's safe to preload packets from getFromRadio.
+
+ BLE CONNECTION PARAMS:
+ - During config, we request a high-throughput, low-latency BLE connection for speed.
+ - After config, we switch to a lower-power BLE connection for steady-state use to extend battery life.
+
+ MEMORY MANAGEMENT:
+ - We keep packets on the stack and do not allocate heap.
+ - We use std::array for fromPhoneQueue and toPhoneQueue to avoid mallocs and frees across FreeRTOS tasks.
+ - Yes, we have to do some copy operations on pop because of this, but it's worth it to avoid cross-task memory management.
+
+ NOTIFY IS BROKEN:
+ - Adding NIMBLE_PROPERTY::NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards compatible.
+
+ ZERO-SIZE READS:
+ - Returning a zero-size read from onRead breaks some clients during the config phase. So we have to block onRead until we
+ have data.
+ - During the STATE_SEND_PACKETS phase, it's totally OK to return zero-size reads, as clients are expected to do reads
+ until they get a 0-byte response.
+
+ CROSS-TASK WAKEUP:
+ - If you call: bluetoothPhoneAPI->setIntervalFromNow(0); to schedule immediate processing of new data,
+ - Then you should also call: concurrency::mainDelay.interrupt(); to wake up the main loop if it's sleeping.
+ - Otherwise, you're going to wait ~100ms or so until the main loop wakes up from some other cause.
+ */
+
public:
- BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") { nimble_queue.resize(3); }
- std::vector nimble_queue;
- std::mutex nimble_mutex;
- uint8_t queue_size = 0;
- bool has_fromRadio = false;
- uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
- size_t numBytes = 0;
- bool hasChecked = false;
- bool phoneWants = false;
+ BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") { api_type = TYPE_BLE; }
+
+ /* Packets from phone (BLE onWrite callback) */
+ std::mutex fromPhoneMutex;
+ std::atomic fromPhoneQueueSize{0};
+ // We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks.
+ std::array fromPhoneQueue{};
+
+ /* Packets to phone (BLE onRead callback) */
+ std::mutex toPhoneMutex;
+ std::atomic toPhoneQueueSize{0};
+ // We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks.
+ std::array, NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE> toPhoneQueue{};
+ std::array toPhoneQueueByteSizes{};
+ // The onReadCallbackIsWaitingForData flag provides synchronization between the NimBLE task's onRead callback and our main
+ // task's runOnce. It's only set by onRead, and only cleared by runOnce.
+ std::atomic onReadCallbackIsWaitingForData{false};
+
+ /* Statistics/logging helpers */
+ std::atomic readCount{0};
+ std::atomic notifyCount{0};
+ std::atomic writeCount{0};
protected:
virtual int32_t runOnce() override
{
- std::lock_guard guard(nimble_mutex);
- if (queue_size > 0) {
- for (uint8_t i = 0; i < queue_size; i++) {
- handleToRadio(nimble_queue.at(i).data(), nimble_queue.at(i).length());
- }
- LOG_DEBUG("Queue_size %u", queue_size);
- queue_size = 0;
- }
- if (hasChecked == false && phoneWants == true) {
- numBytes = getFromRadio(fromRadioBytes);
- hasChecked = true;
+ while (runOnceHasWorkToDo()) {
+ /*
+ PROCESS fromPhoneQueue BEFORE toPhoneQueue:
+
+ In normal STATE_SEND_PACKETS operation, it's unlikely that we'll have both writes and reads to process at the same
+ time, because either onWrite or onRead will trigger this runOnce. And in STATE_SEND_PACKETS, it's generally ok to
+ service either the reads or writes first.
+
+ However, during the initial setup wantConfig packet, the clients send a write and immediately send a read, and they
+ expect the read will respond to the write. (This also happens when a client goes from STATE_SEND_PACKETS back to
+ another wantConfig, like the iOS client does when requesting the nodedb after requesting the main config only.)
+
+ So it's safest to always service writes (fromPhoneQueue) before reads (toPhoneQueue), so that any "synchronous"
+ write-then-read sequences from the client work as expected, even if this means we block onRead for a while: this is
+ what the client wants!
+ */
+
+ // PHONE -> RADIO:
+ runOnceHandleFromPhoneQueue(); // pull data from onWrite to handleToRadio
+
+ // RADIO -> PHONE:
+ runOnceHandleToPhoneQueue(); // push data from getFromRadio to onRead
}
// the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
return INT32_MAX;
}
+
+ virtual void onConfigStart() override
+ {
+ LOG_INFO("BLE onConfigStart");
+
+ // Prefer high throughput during config/setup, at the cost of high power consumption (for a few seconds)
+ if (bleServer && isConnected()) {
+ uint16_t conn_handle = nimbleBluetoothConnHandle.load();
+ if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
+ requestHighThroughputConnection(conn_handle);
+ }
+ }
+ }
+
+ virtual void onConfigComplete() override
+ {
+ LOG_INFO("BLE onConfigComplete");
+
+ // Switch to lower power consumption BLE connection params for steady-state use after config/setup is complete
+ if (bleServer && isConnected()) {
+ uint16_t conn_handle = nimbleBluetoothConnHandle.load();
+ if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
+ requestLowerPowerConnection(conn_handle);
+ }
+ }
+ }
+
+ bool runOnceHasWorkToDo() { return runOnceHasWorkToPhone() || runOnceHasWorkFromPhone(); }
+
+ bool runOnceHasWorkToPhone() { return onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket(); }
+
+ bool runOnceToPhoneCanPreloadNextPacket()
+ {
+ /*
+ * PRELOADING getFromRadio RESPONSES:
+ *
+ * It's not safe to preload packets if we're in STATE_SEND_PACKETS, because there may be a while between the time we call
+ * getFromRadio and when the client actually reads it. If the connection drops in that time, we might lose that packet
+ * forever. In STATE_SEND_PACKETS, if we wait for onRead before we call getFromRadio, we minimize the time window where
+ * the client might disconnect before completing the read.
+ *
+ * However, if we're in the setup states (sending config, nodeinfo, etc), it's safe and beneficial to preload packets into
+ * toPhoneQueue because the client will just reconnect after a disconnect, losing nothing.
+ */
+
+ if (!isConnected()) {
+ return false;
+ } else if (isSendingPackets()) {
+ // If we're in STATE_SEND_PACKETS, we must wait for onRead before calling getFromRadio.
+ return false;
+ } else {
+ // In other states, we can preload as long as there's space in the toPhoneQueue.
+ return toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE;
+ }
+ }
+
+ void runOnceHandleToPhoneQueue()
+ {
+ // Stack buffer for getFromRadio packet
+ uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
+ size_t numBytes = 0;
+
+ if (onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket()) {
+ numBytes = getFromRadio(fromRadioBytes);
+
+ if (numBytes == 0) {
+ /*
+ Client expected a read, but we have nothing to send.
+
+ In STATE_SEND_PACKETS, it is 100% OK to return a 0-byte response, as we expect clients to do read beyond
+ notifies regularly, to make sure they have nothing else to read.
+
+ In other states, this is fine **so long as we've already processed pending onWrites first**, because the client
+ may requesting wantConfig and immediately doing a read.
+ */
+ } else {
+ // Push to toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible.
+ if (toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE) {
+ // Note: the comparison above is safe without a mutex because we are the only method that *increases*
+ // toPhoneQueueSize. (It's okay if toPhoneQueueSize *decreases* in the NimBLE task meanwhile.)
+
+ { // scope for toPhoneMutex mutex
+ std::lock_guard guard(toPhoneMutex);
+ size_t storeAtIndex = toPhoneQueueSize.load();
+ memcpy(toPhoneQueue[storeAtIndex].data(), fromRadioBytes, numBytes);
+ toPhoneQueueByteSizes[storeAtIndex] = numBytes;
+ toPhoneQueueSize++;
+ }
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+ LOG_DEBUG("BLE getFromRadio returned numBytes=%u, pushed toPhoneQueueSize=%u", numBytes,
+ toPhoneQueueSize.load());
+#endif
+ } else {
+ // Shouldn't happen because the onRead callback shouldn't be waiting if the queue is full!
+ LOG_ERROR("Shouldn't happen! Drop FromRadio packet, toPhoneQueue full (%u bytes)", numBytes);
+ }
+ }
+
+ // Clear the onReadCallbackIsWaitingForData flag so onRead knows it can proceed.
+ onReadCallbackIsWaitingForData = false; // only clear this flag AFTER the push
+ }
+ }
+
+ bool runOnceHasWorkFromPhone() { return fromPhoneQueueSize > 0; }
+
+ void runOnceHandleFromPhoneQueue()
+ {
+ // Handle packets we received from onWrite from the phone.
+ if (fromPhoneQueueSize > 0) {
+ // Note: the comparison above is safe without a mutex because we are the only method that *decreases*
+ // fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *increases* in the NimBLE task meanwhile.)
+
+ LOG_DEBUG("NimbleBluetooth: handling ToRadio packet, fromPhoneQueueSize=%u", fromPhoneQueueSize.load());
+
+ // Pop the front of fromPhoneQueue, holding the mutex only briefly while we pop.
+ NimBLEAttValue val;
+ { // scope for fromPhoneMutex mutex
+ std::lock_guard guard(fromPhoneMutex);
+ val = fromPhoneQueue[0];
+
+ // Shift the rest of the queue down
+ for (uint8_t i = 1; i < fromPhoneQueueSize; i++) {
+ fromPhoneQueue[i - 1] = fromPhoneQueue[i];
+ }
+
+ // Safe decrement due to onDisconnect
+ if (fromPhoneQueueSize > 0)
+ fromPhoneQueueSize--;
+ }
+
+ handleToRadio(val.data(), val.length());
+ }
+ }
+
/**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
*/
@@ -63,14 +313,22 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
{
PhoneAPI::onNowHasData(fromRadioNum);
+ int currentNotifyCount = notifyCount.fetch_add(1);
+
uint8_t cc = bleServer->getConnectedCount();
- LOG_DEBUG("BLE notify fromNum: %d connections: %d", fromRadioNum, cc);
+
+#ifdef DEBUG_NIMBLE_NOTIFY
+ // This logging slows things down when there are lots of packets going to the phone, like initial connection:
+ LOG_DEBUG("BLE notify(%d) fromNum: %d connections: %d", currentNotifyCount, fromRadioNum, cc);
+#endif
uint8_t val[4];
put_le32(val, fromRadioNum);
fromNumCharacteristic->setValue(val, sizeof(val));
#ifdef NIMBLE_TWO
+ // NOTE: I don't have any NIMBLE_TWO devices, but this line makes me suspicious, and I suspect it needs to just be
+ // notify().
fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE);
#else
fromNumCharacteristic->notify();
@@ -79,6 +337,54 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
/// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() { return bleServer && bleServer->getConnectedCount() > 0; }
+
+ void requestHighThroughputConnection(uint16_t conn_handle)
+ {
+ /* Request a lower-latency, higher-throughput BLE connection.
+
+ This comes at the cost of higher power consumption, so we may want to only use this for initial setup, and then switch to
+ a slower mode.
+
+ See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS
+ constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple
+ recommendations.)
+
+ Selected settings:
+ minInterval (units of 1.25ms): 7.5ms = 6 (lower than the Apple recommended minimum, but allows faster when the client
+ supports it.)
+ maxInterval (units of 1.25ms): 15ms = 12
+ latency: 0 (don't allow peripheral to skip any connection events)
+ timeout (units of 10ms): 6 seconds = 600 (supervision timeout)
+
+ These are intentionally aggressive to prioritize speed over power consumption, but are only used for a few seconds at
+ setup. Not worth adjusting much.
+ */
+ LOG_INFO("BLE requestHighThroughputConnection");
+ bleServer->updateConnParams(conn_handle, 6, 12, 0, 600);
+ }
+
+ void requestLowerPowerConnection(uint16_t conn_handle)
+ {
+ /* Request a lower power consumption (but higher latency, lower throughput) BLE connection.
+
+ This is suitable for steady-state operation after initial setup is complete.
+
+ See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS
+ constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple
+ recommendations.)
+
+ Selected settings:
+ minInterval (units of 1.25ms): 30ms = 24
+ maxInterval (units of 1.25ms): 50ms = 40
+ latency: 2 (allow peripheral to skip up to 2 consecutive connection events to save power)
+ timeout (units of 10ms): 6 seconds = 600 (supervision timeout)
+
+ There's an opportunity for tuning here if anyone wants to do some power measurements, but these should allow 10-20 packets
+ per second.
+ */
+ LOG_INFO("BLE requestLowerPowerConnection");
+ bleServer->updateConnParams(conn_handle, 24, 40, 2, 600);
+ }
};
static BluetoothPhoneAPI *bluetoothPhoneAPI;
@@ -98,16 +404,45 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
#endif
{
+ // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
+ // Assumption: onWrite is serialized by NimBLE, so we don't need to lock here against multiple concurrent onWrite calls.
+
+ int currentWriteCount = bluetoothPhoneAPI->writeCount.fetch_add(1);
+
+#ifdef DEBUG_NIMBLE_ON_WRITE_TIMING
+ int startMillis = millis();
+ LOG_DEBUG("BLE onWrite(%d): start millis=%d", currentWriteCount, startMillis);
+#endif
+
auto val = pCharacteristic->getValue();
if (memcmp(lastToRadio, val.data(), val.length()) != 0) {
- if (bluetoothPhoneAPI->queue_size < 3) {
+ if (bluetoothPhoneAPI->fromPhoneQueueSize < NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE) {
+ // Note: the comparison above is safe without a mutex because we are the only method that *increases*
+ // fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *decreases* in the main task meanwhile.)
memcpy(lastToRadio, val.data(), val.length());
- std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
- bluetoothPhoneAPI->nimble_queue.at(bluetoothPhoneAPI->queue_size) = val;
- bluetoothPhoneAPI->queue_size++;
+
+ { // scope for fromPhoneMutex mutex
+ // Append to fromPhoneQueue, protected by fromPhoneMutex. Hold the mutex as briefly as possible.
+ std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex);
+ bluetoothPhoneAPI->fromPhoneQueue.at(bluetoothPhoneAPI->fromPhoneQueueSize) = val;
+ bluetoothPhoneAPI->fromPhoneQueueSize++;
+ }
+
+ // After releasing the mutex, schedule immediate processing of the new packet.
bluetoothPhoneAPI->setIntervalFromNow(0);
+ concurrency::mainDelay.interrupt(); // wake up main loop if sleeping
+
+#ifdef DEBUG_NIMBLE_ON_WRITE_TIMING
+ int finishMillis = millis();
+ LOG_DEBUG("BLE onWrite(%d): append to fromPhoneQueue took %u ms. numBytes=%d", currentWriteCount,
+ finishMillis - startMillis, val.length());
+#endif
+ } else {
+ LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount, val.length());
}
+ } else {
+ LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.length());
}
}
};
@@ -120,21 +455,107 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
virtual void onRead(NimBLECharacteristic *pCharacteristic)
#endif
{
- int tries = 0;
- bluetoothPhoneAPI->phoneWants = true;
- while (!bluetoothPhoneAPI->hasChecked && tries < 100) {
- bluetoothPhoneAPI->setIntervalFromNow(0);
- delay(20);
- tries++;
- }
- std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
- pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
+ // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
- if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
+ int currentReadCount = bluetoothPhoneAPI->readCount.fetch_add(1);
+ int tries = 0;
+ int startMillis = millis();
+
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+ LOG_DEBUG("BLE onRead(%d): start millis=%d", currentReadCount, startMillis);
+#endif
+
+ // Is there a packet ready to go, or do we have to ask the main task to get one for us?
+ if (bluetoothPhoneAPI->toPhoneQueueSize > 0) {
+ // Note: the comparison above is safe without a mutex because we are the only method that *decreases*
+ // toPhoneQueueSize. (It's okay if toPhoneQueueSize *increases* in the main task meanwhile.)
+
+ // There's already a packet queued. Great! We don't need to wait for onReadCallbackIsWaitingForData.
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+ LOG_DEBUG("BLE onRead(%d): packet already waiting, no need to set onReadCallbackIsWaitingForData", currentReadCount);
+#endif
+ } else {
+ // Tell the main task that we'd like a packet.
+ bluetoothPhoneAPI->onReadCallbackIsWaitingForData = true;
+
+ // Wait for the main task to produce a packet for us, up to about 20 seconds.
+ // It normally takes just a few milliseconds, but at initial startup, etc, the main task can get blocked for longer
+ // doing various setup tasks.
+ while (bluetoothPhoneAPI->onReadCallbackIsWaitingForData && tries < 4000) {
+ // Schedule the main task runOnce to run ASAP.
+ bluetoothPhoneAPI->setIntervalFromNow(0);
+ concurrency::mainDelay.interrupt(); // wake up main loop if sleeping
+
+ if (!bluetoothPhoneAPI->onReadCallbackIsWaitingForData) {
+ // we may be able to break even before a delay, if the call to interrupt woke up the main loop and it ran
+ // already
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+ LOG_DEBUG("BLE onRead(%d): broke before delay after %u ms, %d tries", currentReadCount,
+ millis() - startMillis, tries);
+#endif
+ break;
+ }
+
+ // This delay happens in the NimBLE FreeRTOS task, which really can't do anything until we get a value back.
+ // No harm in polling pretty frequently.
+ delay(tries < 20 ? 1 : 5);
+ tries++;
+
+ if (tries == 4000) {
+ LOG_WARN(
+ "BLE onRead(%d): timeout waiting for data after %u ms, %d tries, giving up and returning 0-size response",
+ currentReadCount, millis() - startMillis, tries);
+ }
+ }
+ }
+
+ // Pop from toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible.
+ uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; // Stack buffer for getFromRadio packet
+ size_t numBytes = 0;
+ { // scope for toPhoneMutex mutex
+ std::lock_guard guard(bluetoothPhoneAPI->toPhoneMutex);
+ size_t toPhoneQueueSize = bluetoothPhoneAPI->toPhoneQueueSize.load();
+ if (toPhoneQueueSize > 0) {
+ // Copy from the front of the toPhoneQueue
+ memcpy(fromRadioBytes, bluetoothPhoneAPI->toPhoneQueue[0].data(), bluetoothPhoneAPI->toPhoneQueueByteSizes[0]);
+ numBytes = bluetoothPhoneAPI->toPhoneQueueByteSizes[0];
+
+ // Shift the rest of the queue down
+ for (uint8_t i = 1; i < toPhoneQueueSize; i++) {
+ memcpy(bluetoothPhoneAPI->toPhoneQueue[i - 1].data(), bluetoothPhoneAPI->toPhoneQueue[i].data(),
+ bluetoothPhoneAPI->toPhoneQueueByteSizes[i]);
+ // The above line is similar to:
+ // bluetoothPhoneAPI->toPhoneQueue[i - 1] = bluetoothPhoneAPI->toPhoneQueue[i]
+ // but is usually faster because it doesn't have to copy all the trailing bytes beyond
+ // toPhoneQueueByteSizes[i].
+ //
+ // We deliberately use an array here (and pay the CPU cost of some memcpy) to avoid synchronizing dynamic
+ // memory allocations and frees across FreeRTOS tasks.
+
+ bluetoothPhoneAPI->toPhoneQueueByteSizes[i - 1] = bluetoothPhoneAPI->toPhoneQueueByteSizes[i];
+ }
+
+ // Safe decrement due to onDisconnect
+ if (bluetoothPhoneAPI->toPhoneQueueSize > 0)
+ bluetoothPhoneAPI->toPhoneQueueSize--;
+ } else {
+ // nothing in the toPhoneQueue; that's fine, and we'll just have numBytes=0.
+ }
+ }
+
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+ int finishMillis = millis();
+ LOG_DEBUG("BLE onRead(%d): onReadCallbackIsWaitingForData took %u ms, %d tries. numBytes=%d", currentReadCount,
+ finishMillis - startMillis, tries, numBytes);
+#endif
+
+ pCharacteristic->setValue(fromRadioBytes, numBytes);
+
+ // If we sent something, wake up the main loop if it's sleeping in case there are more packets ready to enqueue.
+ if (numBytes != 0) {
bluetoothPhoneAPI->setIntervalFromNow(0);
- bluetoothPhoneAPI->numBytes = 0;
- bluetoothPhoneAPI->hasChecked = false;
- bluetoothPhoneAPI->phoneWants = false;
+ concurrency::mainDelay.interrupt(); // wake up main loop if sleeping
+ }
}
};
@@ -216,12 +637,40 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
if (screen)
screen->endAlert();
}
+
+ // Store the connection handle for future use
+#ifdef NIMBLE_TWO
+ nimbleBluetoothConnHandle = connInfo.getConnHandle();
+#else
+ nimbleBluetoothConnHandle = desc->conn_handle;
+#endif
}
#ifdef NIMBLE_TWO
virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
{
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
+
+#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
+ const uint16_t connHandle = connInfo.getConnHandle();
+ int phyResult =
+ ble_gap_set_prefered_le_phy(connHandle, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_CODED_ANY);
+ if (phyResult == 0) {
+ LOG_INFO("BLE conn %u requested 2M PHY", connHandle);
+ } else {
+ LOG_WARN("Failed to prefer 2M PHY for conn %u, rc=%d", connHandle, phyResult);
+ }
+
+ int dataLenResult = ble_gap_set_data_len(connHandle, kPreferredBleTxOctets, kPreferredBleTxTimeUs);
+ if (dataLenResult == 0) {
+ LOG_INFO("BLE conn %u requested data length %u bytes", connHandle, kPreferredBleTxOctets);
+ } else {
+ LOG_WARN("Failed to raise data length for conn %u, rc=%d", connHandle, dataLenResult);
+ }
+
+ LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu);
+ pServer->updateConnParams(connHandle, 6, 12, 0, 200);
+#endif
}
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
@@ -241,19 +690,41 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
bluetoothStatus->updateStatus(&newStatus);
if (bluetoothPhoneAPI) {
- std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
bluetoothPhoneAPI->close();
- bluetoothPhoneAPI->hasChecked = false;
- bluetoothPhoneAPI->phoneWants = false;
- bluetoothPhoneAPI->numBytes = 0;
- bluetoothPhoneAPI->queue_size = 0;
+
+ { // scope for fromPhoneMutex mutex
+ std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex);
+ bluetoothPhoneAPI->fromPhoneQueueSize = 0;
+ }
+
+ bluetoothPhoneAPI->onReadCallbackIsWaitingForData = false;
+ { // scope for toPhoneMutex mutex
+ std::lock_guard guard(bluetoothPhoneAPI->toPhoneMutex);
+ bluetoothPhoneAPI->toPhoneQueueSize = 0;
+ }
+
+ bluetoothPhoneAPI->readCount = 0;
+ bluetoothPhoneAPI->notifyCount = 0;
+ bluetoothPhoneAPI->writeCount = 0;
}
// Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
memset(lastToRadio, 0, sizeof(lastToRadio));
+
+ nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection"
+
#ifdef NIMBLE_TWO
// Restart Advertising
ble->startAdvertising();
+#else
+ NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
+ if (!pAdvertising->start(0)) {
+ if (pAdvertising->isAdvertising()) {
+ LOG_DEBUG("BLE advertising already running");
+ } else {
+ LOG_ERROR("BLE failed to restart advertising");
+ }
+ }
#endif
}
};
@@ -306,16 +777,35 @@ bool NimbleBluetooth::isConnected()
int NimbleBluetooth::getRssi()
{
- if (bleServer && isConnected()) {
- auto service = bleServer->getServiceByUUID(MESH_SERVICE_UUID);
- uint16_t handle = service->getHandle();
-#ifdef NIMBLE_TWO
- return NimBLEDevice::getClientByHandle(handle)->getRssi();
-#else
- return NimBLEDevice::getClientByID(handle)->getRssi();
-#endif
+#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
+ if (!bleServer || !isConnected()) {
+ return 0; // No active BLE connection
}
- return 0; // FIXME figure out where to source this
+
+ uint16_t connHandle = nimbleBluetoothConnHandle.load();
+
+ if (connHandle == BLE_HS_CONN_HANDLE_NONE) {
+ const auto peers = bleServer->getPeerDevices();
+ if (!peers.empty()) {
+ connHandle = peers.front();
+ nimbleBluetoothConnHandle = connHandle;
+ }
+ }
+
+ if (connHandle == BLE_HS_CONN_HANDLE_NONE) {
+ return 0; // Connection handle not available yet
+ }
+
+ int8_t rssi = 0;
+ const int rc = ble_gap_conn_rssi(connHandle, &rssi);
+
+ if (rc == 0) {
+ return rssi;
+ }
+ LOG_DEBUG("BLE RSSI read failed, rc=%d", rc);
+#endif
+
+ return 0;
}
void NimbleBluetooth::setup()
@@ -328,6 +818,30 @@ void NimbleBluetooth::setup()
NimBLEDevice::init(getDeviceName());
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
+#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
+ int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu);
+ if (mtuResult == 0) {
+ LOG_INFO("BLE MTU request set to %u", kPreferredBleMtu);
+ } else {
+ LOG_WARN("Unable to request MTU %u, rc=%d", kPreferredBleMtu, mtuResult);
+ }
+
+ int phyResult = ble_gap_set_prefered_default_le_phy(BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK);
+ if (phyResult == 0) {
+ LOG_INFO("BLE default PHY preference set to 2M");
+ } else {
+ LOG_WARN("Failed to prefer 2M PHY by default, rc=%d", phyResult);
+ }
+
+ int dataLenResult = ble_gap_write_sugg_def_data_len(kPreferredBleTxOctets, kPreferredBleTxTimeUs);
+ if (dataLenResult == 0) {
+ LOG_INFO("BLE suggested data length set to %u bytes", kPreferredBleTxOctets);
+ } else {
+ LOG_WARN("Failed to raise suggested data length (%u/%u), rc=%d", kPreferredBleTxOctets, kPreferredBleTxTimeUs,
+ dataLenResult);
+ }
+#endif
+
if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC);
NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
@@ -353,6 +867,7 @@ void NimbleBluetooth::setupService()
// Define the characteristics that the app is looking for
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE);
+ // Allow notifications so phones can stream FromRadio without polling.
FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ);
fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ);
logRadioCharacteristic =
@@ -471,4 +986,4 @@ void clearNVS()
ESP.restart();
#endif
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index 53b23124d..9b5abfba0 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -191,6 +191,8 @@
#define HW_VENDOR meshtastic_HardwareModel_CROWPANEL
#elif defined(RAK3312)
#define HW_VENDOR meshtastic_HardwareModel_RAK3312
+#elif defined(RAK_WISMESH_TAP_V2)
+#define HW_VENDOR meshtastic_HardwareModel_WISMESH_TAP_V2
#elif defined(LINK_32)
#define HW_VENDOR meshtastic_HardwareModel_LINK_32
#elif defined(T_DECK_PRO)
diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index 79eef8f76..4f7fb4776 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -48,6 +48,9 @@ class BluetoothPhoneAPI : public PhoneAPI
/// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() override { return Bluefruit.connected(connectionHandle); }
+
+ public:
+ BluetoothPhoneAPI() { api_type = TYPE_BLE; }
};
static BluetoothPhoneAPI *bluetoothPhoneAPI;
diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index cee0e5a10..1568e1790 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -57,6 +57,8 @@
#define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO
#elif defined(R1_NEO)
#define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO
+#elif defined(RAK3401)
+#define HW_VENDOR meshtastic_HardwareModel_RAK3401
// MAke sure all custom RAK4630 boards are defined before the generic RAK4630
#elif defined(RAK4630)
#define HW_VENDOR meshtastic_HardwareModel_RAK4631
@@ -66,6 +68,10 @@
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO_LITE
#elif defined(ELECROW_ThinkNode_M1)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1
+#elif defined(ELECROW_ThinkNode_M3)
+#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M3
+#elif defined(ELECROW_ThinkNode_M6)
+#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M6
#elif defined(NANO_G2_ULTRA)
#define HW_VENDOR meshtastic_HardwareModel_NANO_G2_ULTRA
#elif defined(CANARYONE)
@@ -102,6 +108,8 @@
#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1
#elif defined(HELTEC_MESH_SOLAR)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR
+#elif defined(MUZI_BASE)
+#define HW_VENDOR meshtastic_HardwareModel_RESERVED_FRIED_CHICKEN
#else
#define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN
#endif
@@ -126,7 +134,9 @@
#endif
+#ifdef PIN_LED1
#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK
+#endif
#ifdef PIN_BUTTON1
#define BUTTON_PIN PIN_BUTTON1
diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp
index 8ce74d5f7..c03cc4454 100644
--- a/src/platform/nrf52/main-nrf52.cpp
+++ b/src/platform/nrf52/main-nrf52.cpp
@@ -4,6 +4,14 @@
#include
#include
#include
+
+#define APP_WATCHDOG_SECS 90
+#define NRFX_WDT_ENABLED 1
+#define NRFX_WDT0_ENABLED 1
+#define NRFX_WDT_CONFIG_NO_IRQ 1
+#include
+#include
+
#include
#include
#include
@@ -14,11 +22,22 @@
#include "error.h"
#include "main.h"
#include "meshUtils.h"
+#include "power.h"
+
+#include
#ifdef BQ25703A_ADDR
#include "BQ25713.h"
#endif
+// Weak empty variant initialization function.
+// May be redefined by variant files.
+void variant_shutdown() __attribute__((weak));
+void variant_shutdown() {}
+
+static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0);
+static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main;
+
static inline void debugger_break(void)
{
__asm volatile("bkpt #0x01\n\t"
@@ -202,6 +221,15 @@ void checkSDEvents()
void nrf52Loop()
{
+ {
+ static bool watchdog_running = false;
+ if (!watchdog_running) {
+ nrfx_wdt_enable(&nrfx_wdt);
+ watchdog_running = true;
+ }
+ }
+ nrfx_wdt_channel_feed(&nrfx_wdt, nrfx_wdt_channel_id_nrf52_main);
+
checkSDEvents();
reportLittleFSCorruptionOnce();
}
@@ -269,6 +297,22 @@ void nrf52Setup()
LOG_DEBUG("Set random seed %u", seed.seed32);
randomSeed(seed.seed32);
nRFCrypto.end();
+
+ // Set up nrfx watchdog. Do not enable the watchdog yet (we do that
+ // the first time through the main loop), so that other threads can
+ // allocate their own wdt channel to protect themselves from hangs.
+ nrfx_wdt_config_t wdt0_config = {
+ .behaviour = NRF_WDT_BEHAVIOUR_PAUSE_SLEEP_HALT, .reload_value = APP_WATCHDOG_SECS * 1000,
+ // Note: Not using wdt interrupts.
+ // .interrupt_priority = NRFX_WDT_DEFAULT_CONFIG_IRQ_PRIORITY
+ };
+ nrfx_err_t r = nrfx_wdt_init(&nrfx_wdt, &wdt0_config,
+ nullptr // Watchdog event handler, not used, we just reset.
+ );
+ assert(r == NRFX_SUCCESS);
+
+ r = nrfx_wdt_channel_alloc(&nrfx_wdt, &nrfx_wdt_channel_id_nrf52_main);
+ assert(r == NRFX_SUCCESS);
}
void cpuDeepSleep(uint32_t msecToWake)
@@ -352,6 +396,7 @@ void cpuDeepSleep(uint32_t msecToWake)
NRF_GPIO->DIRCLR = (1 << pin);
}
#endif
+ variant_shutdown();
// Sleepy trackers or sensors can low power "sleep"
// Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event
@@ -389,6 +434,23 @@ void cpuDeepSleep(uint32_t msecToWake)
nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep
#endif
+#ifdef BATTERY_LPCOMP_INPUT
+ // Wake up if power rises again
+ nrf_lpcomp_config_t c;
+ c.reference = BATTERY_LPCOMP_THRESHOLD;
+ c.detection = NRF_LPCOMP_DETECT_UP;
+ c.hyst = NRF_LPCOMP_HYST_NOHYST;
+ nrf_lpcomp_configure(NRF_LPCOMP, &c);
+ nrf_lpcomp_input_select(NRF_LPCOMP, BATTERY_LPCOMP_INPUT);
+ nrf_lpcomp_enable(NRF_LPCOMP);
+
+ battery_adcEnable();
+
+ nrf_lpcomp_task_trigger(NRF_LPCOMP, NRF_LPCOMP_TASK_START);
+ while (!nrf_lpcomp_event_check(NRF_LPCOMP, NRF_LPCOMP_EVENT_READY))
+ ;
+#endif
+
auto ok = sd_power_system_off();
if (ok != NRF_SUCCESS) {
LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!");
@@ -420,4 +482,4 @@ void enterDfuMode()
#else
enterUf2Dfu();
#endif
-}
\ No newline at end of file
+}
diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index dbc90f9a4..10b3a7fe4 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -37,6 +37,8 @@ bool yamlOnly = false;
const char *argp_program_version = optstr(APP_VERSION);
+char stdoutBuffer[512];
+
// FIXME - move setBluetoothEnable into a HALPlatform class
void setBluetoothEnable(bool enable)
{
@@ -144,6 +146,20 @@ void getMacAddr(uint8_t *dmac)
}
}
+std::string cleanupNameForAutoconf(std::string name)
+{
+ // Convert spaces -> dashes, lowercase
+
+ std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) {
+ if (c == ' ') {
+ return '-';
+ }
+ return (char)std::tolower(c);
+ });
+
+ return name;
+}
+
/** apps run under portduino can optionally define a portduinoSetup() to
* use portduino specific init code (such as gpioBind) to setup portduino on their host machine,
* before running 'arduino' code.
@@ -154,6 +170,9 @@ void portduinoSetup()
std::string gpioChipName = "gpiochip";
portduino_config.displayPanel = no_screen;
+ // Force stdout to be line buffered
+ setvbuf(stdout, stdoutBuffer, _IOLBF, sizeof(stdoutBuffer));
+
if (portduino_config.force_simradio == true) {
portduino_config.lora_module = use_simradio;
} else if (configPath != nullptr) {
@@ -213,6 +232,11 @@ void portduinoSetup()
// If LoRa `Module: auto` (default in config.yaml),
// attempt to auto config based on Product Strings
if (portduino_config.lora_module == use_autoconf) {
+ bool found_hat = false;
+ bool found_rak_eeprom = false;
+ bool found_ch341 = false;
+
+ char hat_vendor[96] = {0};
char autoconf_product[96] = {0};
// Try CH341
try {
@@ -222,21 +246,32 @@ void portduinoSetup()
ch341Hal->getProductString(autoconf_product, 95);
delete ch341Hal;
std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl;
+
+ found_ch341 = true;
} catch (...) {
std::cout << "autoconf: Could not locate CH341 device" << std::endl;
}
// Try Pi HAT+
if (strlen(autoconf_product) < 6) {
std::cout << "autoconf: Looking for Pi HAT+..." << std::endl;
+ if (access("/proc/device-tree/hat/vendor", R_OK) == 0) {
+ std::ifstream hatVendorFile("/proc/device-tree/hat/vendor");
+ if (hatVendorFile.is_open()) {
+ hatVendorFile.read(hat_vendor, 95);
+ hatVendorFile.close();
+ }
+ }
if (access("/proc/device-tree/hat/product", R_OK) == 0) {
std::ifstream hatProductFile("/proc/device-tree/hat/product");
if (hatProductFile.is_open()) {
hatProductFile.read(autoconf_product, 95);
hatProductFile.close();
}
- std::cout << "autoconf: Found Pi HAT+ " << autoconf_product << " at /proc/device-tree/hat/product" << std::endl;
+ std::cout << "autoconf: Found Pi HAT+ " << hat_vendor << " " << autoconf_product << " at /proc/device-tree/hat"
+ << std::endl;
+ found_hat = true;
} else {
- std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat/product" << std::endl;
+ std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat" << std::endl;
}
}
// attempt to load autoconf data from an EEPROM on 0x50
@@ -292,6 +327,7 @@ void portduinoSetup()
autoconf_product[0] = 0x0;
} else {
std::cout << "autoconf: Found eeprom data " << autoconf_raw << std::endl;
+ found_rak_eeprom = true;
if (mac_start != nullptr) {
std::cout << "autoconf: Found mac data " << mac_start << std::endl;
if (strlen(mac_start) == 12)
@@ -320,12 +356,29 @@ void portduinoSetup()
if (strlen(autoconf_product) > 0) {
// From configProducts map in PortduinoGlue.h
std::string product_config = "";
- try {
+
+ if (configProducts.find(autoconf_product) != configProducts.end()) {
product_config = configProducts.at(autoconf_product);
- } catch (std::out_of_range &e) {
- std::cerr << "autoconf: Unable to find config for " << autoconf_product << std::endl;
- exit(EXIT_FAILURE);
+ } else {
+ if (found_hat) {
+ product_config =
+ cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml");
+ } else if (found_ch341) {
+ product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml");
+ }
+
+ // Don't try to automatically find config for a device with RAK eeprom.
+ if (found_rak_eeprom) {
+ std::cerr << "autoconf: Found unknown RAK product " << autoconf_product << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (access((portduino_config.available_directory + product_config).c_str(), R_OK) != 0) {
+ std::cerr << "autoconf: Unable to find config for " << autoconf_product << "(tried " << product_config << ")"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
}
+
if (loadConfig((portduino_config.available_directory + product_config).c_str())) {
std::cout << "autoconf: Using " << product_config << " as config file for " << autoconf_product << std::endl;
} else {
@@ -393,11 +446,17 @@ void portduinoSetup()
// Need to bind all the configured GPIO pins so they're not simulated
// TODO: If one of these fails, we should log and terminate
for (auto i : portduino_config.all_pins) {
- if (i->enabled)
+ // In the case of a ch341 Lora device, we don't want to touch the system GPIO lines for Lora
+ // Those GPIO are handled in our usermode driver instead.
+ if (i->config_section == "Lora" && portduino_config.lora_spi_dev == "ch341") {
+ continue;
+ }
+ if (i->enabled) {
if (initGPIOPin(i->pin, gpioChipName + std::to_string(i->gpiochip), i->line) != ERRNO_OK) {
printf("Error setting pin number %d. It may not exist, or may already be in use.\n", i->line);
exit(EXIT_FAILURE);
}
+ }
}
// Only initialize the radio pins when dealing with real, kernel controlled SPI hardware
@@ -423,8 +482,7 @@ int initGPIOPin(int pinNum, const std::string gpioChipName, int line)
{
#ifdef PORTDUINO_LINUX_HARDWARE
std::string gpio_name = "GPIO" + std::to_string(pinNum);
- std::cout << gpio_name;
- printf("\n");
+ std::cout << "Initializing " << gpio_name << " on chip " << gpioChipName << std::endl;
try {
GPIOPin *csPin;
csPin = new LinuxGPIOPin(pinNum, gpioChipName.c_str(), line, gpio_name.c_str());
diff --git a/src/platform/stm32wl/main-stm32wl.cpp b/src/platform/stm32wl/main-stm32wl.cpp
index 3eddbb3cf..e841f8f29 100644
--- a/src/platform/stm32wl/main-stm32wl.cpp
+++ b/src/platform/stm32wl/main-stm32wl.cpp
@@ -26,3 +26,31 @@ void getMacAddr(uint8_t *dmac)
}
void cpuDeepSleep(uint32_t msecToWake) {}
+
+// Hacks to force more code and data out.
+
+// By default __assert_func uses fiprintf which pulls in stdio.
+extern "C" void __wrap___assert_func(const char *, int, const char *, const char *)
+{
+ while (true)
+ ;
+ return;
+}
+
+// By default strerror has a lot of strings we probably don't use. Make it return an empty string instead.
+char empty = 0;
+extern "C" char *__wrap_strerror(int)
+{
+ return ∅
+}
+
+#ifdef MESHTASTIC_EXCLUDE_TZ
+struct _reent;
+
+// Even if you don't use timezones, mktime will try to set the timezone anyway with _tzset_unlocked(), which pulls in scanf and
+// friends. The timezone is initialized to UTC by default.
+extern "C" void __wrap__tzset_unlocked_r(struct _reent *reent_ptr)
+{
+ return;
+}
+#endif
\ No newline at end of file
diff --git a/src/power.h b/src/power.h
index 23eb95064..3f28dedb2 100644
--- a/src/power.h
+++ b/src/power.h
@@ -34,8 +34,8 @@
#define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300
#elif defined(SEEED_SOLAR_NODE)
#define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786
-#elif defined(R1_NEO)
-#define OCV_ARRAY 4330, 4292, 4254, 4216, 4178, 4140, 4102, 4064, 4026, 3988, 3950
+#elif defined(WISMESH_TAG)
+#define OCV_ARRAY 4240, 4112, 4029, 3970, 3906, 3846, 3824, 3802, 3776, 3650, 3072
#else // LiIon
#define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100
#endif
@@ -138,9 +138,12 @@ class Power : private concurrency::OSThread
void reboot();
// open circuit voltage lookup table
uint8_t low_voltage_counter;
+ uint32_t lastLogTime = 0;
#ifdef DEBUG_HEAP
uint32_t lastheap;
#endif
};
+void battery_adcEnable();
+
extern Power *power;
diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp
index 5a1f8ed7e..b31d2dc2e 100644
--- a/src/serialization/MeshPacketSerializer.cpp
+++ b/src/serialization/MeshPacketSerializer.cpp
@@ -413,7 +413,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
jsonObj["from"] = new JSONValue((unsigned int)mp->from);
jsonObj["channel"] = new JSONValue((unsigned int)mp->channel);
jsonObj["type"] = new JSONValue(msgType.c_str());
- jsonObj["sender"] = new JSONValue(owner.id);
+ jsonObj["sender"] = new JSONValue(nodeDB->getNodeId().c_str());
if (mp->rx_rssi != 0)
jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
if (mp->rx_snr != 0)
diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp
index e0daa1a88..353c710a1 100644
--- a/src/serialization/MeshPacketSerializer_nRF52.cpp
+++ b/src/serialization/MeshPacketSerializer_nRF52.cpp
@@ -353,7 +353,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
jsonObj["from"] = (unsigned int)mp->from;
jsonObj["channel"] = (unsigned int)mp->channel;
jsonObj["type"] = msgType.c_str();
- jsonObj["sender"] = owner.id;
+ jsonObj["sender"] = nodeDB->getNodeId().c_str();
if (mp->rx_rssi != 0)
jsonObj["rssi"] = (int)mp->rx_rssi;
if (mp->rx_snr != 0)
diff --git a/src/sleep.cpp b/src/sleep.cpp
index c6efb0efd..756582c74 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -244,6 +244,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
// pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
#endif
+#ifdef RAK_WISMESH_TAP_V2
+ digitalWrite(SDCARD_CS, LOW);
+#endif
+
#ifdef TRACKER_T1000_E
#ifdef GNSS_AIROHA
digitalWrite(GPS_VRTC_EN, LOW);
diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp
index ede3d22b7..1c2f0642a 100644
--- a/test/test_mqtt/MQTT.cpp
+++ b/test/test_mqtt/MQTT.cpp
@@ -332,7 +332,7 @@ void setUp(void)
};
channelFile.channels_count = 1;
owner = meshtastic_User{.id = "!12345678"};
- myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 10};
+ myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 0x12345678}; // Match the expected gateway ID in topic
localPosition =
meshtastic_Position{.has_latitude_i = true, .latitude_i = 7 * 1e7, .has_longitude_i = true, .longitude_i = 3 * 1e7};
@@ -591,7 +591,7 @@ void test_receiveEncryptedPKITopicToUs(void)
// Should ignore messages published to MQTT by this gateway.
void test_receiveIgnoresOwnPublishedMessages(void)
{
- unitTest->publish(&decoded, owner.id);
+ unitTest->publish(&decoded, nodeDB->getNodeId().c_str());
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty());
@@ -603,7 +603,7 @@ void test_receiveAcksOwnSentMessages(void)
meshtastic_MeshPacket p = decoded;
p.from = myNodeInfo.my_node_num;
- unitTest->publish(&p, owner.id);
+ unitTest->publish(&p, nodeDB->getNodeId().c_str());
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
TEST_ASSERT_EQUAL(1, mockRoutingModule->ackNacks_.size());
diff --git a/userPrefs.jsonc b/userPrefs.jsonc
index 464eb968c..0c92eabcf 100644
--- a/userPrefs.jsonc
+++ b/userPrefs.jsonc
@@ -56,5 +56,6 @@
// "USERPREFS_MQTT_ROOT_TOPIC": "event/REPLACEME",
// "USERPREFS_RINGTONE_NAG_SECS": "60",
"USERPREFS_RINGTONE_RTTTL": "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p",
+ // "USERPREFS_NETWORK_IPV6_ENABLED": "1",
"USERPREFS_TZ_STRING": "tzplaceholder "
}
diff --git a/variants/esp32/diy/hydra/variant.h b/variants/esp32/diy/hydra/variant.h
index 0d64c1b5e..e5c10e26b 100644
--- a/variants/esp32/diy/hydra/variant.h
+++ b/variants/esp32/diy/hydra/variant.h
@@ -6,7 +6,6 @@
#define GPS_TX_PIN 15
#define GPS_RX_PIN 12
#define PIN_GPS_EN 4
-#define GPS_POWER_TOGGLE // Moved definition from platformio.ini to here
#define BUTTON_PIN 39 // The middle button GPIO on the T-Beam
// Note: On the ESP32 base version, gpio34-39 are input-only, and do not have internal pull-ups.
diff --git a/variants/esp32/heltec_v2.1/platformio.ini b/variants/esp32/heltec_v2.1/platformio.ini
index 763f9764c..4dcd9e583 100644
--- a/variants/esp32/heltec_v2.1/platformio.ini
+++ b/variants/esp32/heltec_v2.1/platformio.ini
@@ -7,4 +7,3 @@ build_flags =
${esp32_base.build_flags}
-D HELTEC_V2_1
-I variants/esp32/heltec_v2.1
- -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32/heltec_wsl_v2.1/platformio.ini b/variants/esp32/heltec_wsl_v2.1/platformio.ini
index eb44c88d2..6a77cf11b 100644
--- a/variants/esp32/heltec_wsl_v2.1/platformio.ini
+++ b/variants/esp32/heltec_wsl_v2.1/platformio.ini
@@ -6,4 +6,3 @@ build_flags =
${esp32_base.build_flags}
-D PRIVATE_HW
-I variants/esp32/heltec_wsl_v2.1
- -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini
index ea17751c6..e53f22d30 100644
--- a/variants/esp32/tbeam/platformio.ini
+++ b/variants/esp32/tbeam/platformio.ini
@@ -10,7 +10,6 @@ build_flags =
${esp32_base.build_flags}
-D TBEAM_V10
-I variants/esp32/tbeam
- -DGPS_POWER_TOGGLE ; comment this line to disable double press function on the user button to turn off gps entirely.
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
upload_speed = 921600
diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini
index bd85aa847..8d5bdab9e 100644
--- a/variants/esp32/tlora_v2_1_16/platformio.ini
+++ b/variants/esp32/tlora_v2_1_16/platformio.ini
@@ -4,5 +4,13 @@ board = ttgo-lora32-v21
board_check = true
build_flags =
${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16
- -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
upload_speed = 115200
+
+[env:sugarcube]
+extends = env:tlora-v2-1-1_6
+board_level = extra
+build_flags =
+ ${env:tlora-v2-1-1_6.build_flags}
+ -DBUTTON_PIN=0
+ -DPIN_BUZZER=25
+ -DLED_PIN=-1
\ No newline at end of file
diff --git a/variants/esp32/tlora_v2_1_16/variant.h b/variants/esp32/tlora_v2_1_16/variant.h
index 48c069ab7..9584dd68b 100644
--- a/variants/esp32/tlora_v2_1_16/variant.h
+++ b/variants/esp32/tlora_v2_1_16/variant.h
@@ -8,7 +8,11 @@
#define I2C_SDA 21 // I2C pins for this board
#define I2C_SCL 22
+#if defined(LED_PIN) && LED_PIN == -1
+#undef LED_PIN
+#else
#define LED_PIN 25 // If defined we will blink this LED
+#endif
#define USE_RF95
#define LORA_DIO0 26 // a No connect on the SX1262 module
diff --git a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini
index 9404faa02..a6b9d2254 100644
--- a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini
+++ b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini
@@ -6,6 +6,5 @@ build_flags =
${esp32_base.build_flags}
-D TLORA_V2_1_16
-I variants/esp32/tlora_v2_1_16
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-D LORA_TCXO_GPIO=33
upload_speed = 115200
\ No newline at end of file
diff --git a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini
index f1110386e..1258fd8b7 100644
--- a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini
+++ b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini
@@ -5,6 +5,5 @@ build_flags =
${esp32_base.build_flags}
-D TLORA_V2_1_16
-I variants/esp32/tlora_v2_1_16
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-D LORA_TCXO_GPIO=12
-D BUTTON_PIN=0
\ No newline at end of file
diff --git a/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini b/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini
index dbd420f04..3fcfbf281 100644
--- a/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini
+++ b/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini
@@ -5,4 +5,3 @@ build_flags =
${esp32s3_base.build_flags}
-D CDEBYTE_EORA_S3
-I variants/esp32s3/CDEBYTE_EoRa-S3
- -D GPS_POWER_TOGGLE
diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h
index a55808170..129b398e9 100644
--- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h
+++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h
@@ -14,6 +14,8 @@
#define BATTERY_PIN 8
#define ADC_CHANNEL ADC1_GPIO8_CHANNEL
+#define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage.
+
#define PIN_BUZZER 9
// Buttons
diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
index 49e84bf4f..eed21a412 100644
--- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
+++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
@@ -16,7 +16,6 @@ build_flags =
-I variants/esp32s3/crowpanel-esp32s3-5-epaper
-D PRIVATE_HW
-DBOARD_HAS_PSRAM
- -DGPS_POWER_TOGGLE
-DEINK_DISPLAY_MODEL=GxEPD2_579_GDEY0579T93 ;https://www.good-display.com/product/439.html
-DEINK_WIDTH=792
-DEINK_HEIGHT=272
@@ -46,7 +45,6 @@ build_flags =
-I variants/esp32s3/crowpanel-esp32s3-5-epaper
-D PRIVATE_HW
-DBOARD_HAS_PSRAM
- -DGPS_POWER_TOGGLE
-DEINK_DISPLAY_MODEL=GxEPD2_420_GYE042A87 ; similar Panel: GDEY042T81 : https://www.good-display.com/product/386.html
-DEINK_WIDTH=400
-DEINK_HEIGHT=300
@@ -76,7 +74,6 @@ build_flags =
-I variants/esp32s3/crowpanel-esp32s3-5-epaper
-D PRIVATE_HW
-DBOARD_HAS_PSRAM
- -DGPS_POWER_TOGGLE
-DEINK_DISPLAY_MODEL=GxEPD2_290_GDEY029T94 ;https://www.good-display.com/product/389.html
-DEINK_WIDTH=296
-DEINK_HEIGHT=128
diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini
index d43ffd0df..0bb21581a 100644
--- a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini
+++ b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini
@@ -6,5 +6,4 @@ board_build.partitions = default_8MB.csv
build_flags =
${esp32s3_base.build_flags} -I variants/esp32s3/heltec_capsule_sensor_v3
-D HELTEC_CAPSULE_SENSOR_V3
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
diff --git a/variants/esp32s3/heltec_v3/platformio.ini b/variants/esp32s3/heltec_v3/platformio.ini
index b521e11ca..af0854e49 100644
--- a/variants/esp32s3/heltec_v3/platformio.ini
+++ b/variants/esp32s3/heltec_v3/platformio.ini
@@ -8,4 +8,3 @@ build_flags =
${esp32s3_base.build_flags}
-D HELTEC_V3
-I variants/esp32s3/heltec_v3
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h
index 45561b4b5..d4485016d 100644
--- a/variants/esp32s3/heltec_v4/pins_arduino.h
+++ b/variants/esp32s3/heltec_v4/pins_arduino.h
@@ -13,8 +13,8 @@ static const uint8_t LED_BUILTIN = 35;
static const uint8_t TX = 43;
static const uint8_t RX = 44;
-static const uint8_t SDA = 3;
-static const uint8_t SCL = 4;
+static const uint8_t SDA = 4;
+static const uint8_t SCL = 3;
static const uint8_t SS = 8;
static const uint8_t MOSI = 10;
diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
index d0a250ad3..4ff7ff253 100644
--- a/variants/esp32s3/heltec_v4/platformio.ini
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -1,4 +1,4 @@
-[env:heltec-v4]
+[heltec_v4_base]
extends = esp32s3_base
board = heltec_v4
board_check = true
@@ -7,4 +7,106 @@ build_flags =
${esp32s3_base.build_flags}
-D HELTEC_V4
-I variants/esp32s3/heltec_v4
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
+lib_deps =
+ ${esp32s3_base.lib_deps}
+
+
+[env:heltec-v4]
+extends = heltec_v4_base
+build_flags =
+ ${heltec_v4_base.build_flags}
+ -D HELTEC_V4_OLED
+ -D USE_SSD1306 ; Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
+ -D LED_PIN=35
+ -D RESET_OLED=21
+ -D I2C_SDA=17
+ -D I2C_SCL=18
+ -D I2C_SDA1=4
+ -D I2C_SCL1=3
+lib_deps =
+ ${heltec_v4_base.lib_deps}
+
+[env:heltec-v4-tft]
+extends = heltec_v4_base
+build_flags =
+ ${heltec_v4_base.build_flags} ;-Os
+ -D HELTEC_V4_TFT
+ -D I2C_SDA=4
+ -D I2C_SCL=3
+ -D I2C_SDA1=47
+ -D I2C_SCL1=48
+ -D PIN_BUTTON2=35
+ -D PIN_BUZZER=6
+ -D USE_PIN_BUZZER=PIN_BUZZER
+ -D CONFIG_ARDUHAL_LOG_COLORS
+ -D RADIOLIB_DEBUG_SPI=0
+ -D RADIOLIB_DEBUG_PROTOCOL=0
+ -D RADIOLIB_DEBUG_BASIC=0
+ -D RADIOLIB_VERBOSE_ASSERT=0
+ -D RADIOLIB_SPI_PARANOID=0
+ -D CONFIG_DISABLE_HAL_LOCKS=1
+ -D INPUTDRIVER_BUTTON_TYPE=0
+ -D HAS_SCREEN=1
+ -D HAS_TFT=1
+ -D RAM_SIZE=1560
+ -D LV_LVGL_H_INCLUDE_SIMPLE
+ -D LV_CONF_INCLUDE_SIMPLE
+ -D LV_COMP_CONF_INCLUDE_SIMPLE
+ -D LV_USE_SYSMON=0
+ -D LV_USE_PROFILER=0
+ -D LV_USE_PERF_MONITOR=0
+ -D LV_USE_MEM_MONITOR=0
+ -D LV_USE_LOG=0
+ -D LV_BUILD_TEST=0
+ -D USE_LOG_DEBUG
+ -D LOG_DEBUG_INC=\"DebugConfiguration.h\"
+ -D USE_PACKET_API
+ -D LGFX_DRIVER=LGFX_HELTEC_V4_TFT
+ -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_HELTEC_V4_TFT.h\"
+ -D VIEW_320x240
+ -D MAP_FULL_REDRAW
+ -D DISPLAY_SIZE=320x240 ; landscape mode
+ -D LGFX_PIN_SCK=17
+ -D LGFX_PIN_MOSI=33
+ -D LGFX_PIN_DC=16
+ -D LGFX_PIN_CS=15
+ -D LGFX_PIN_BL=21
+ -D LGFX_PIN_RST=18
+ -D CUSTOM_TOUCH_DRIVER
+ -D TOUCH_SDA_PIN=I2C_SDA1
+ -D TOUCH_SCL_PIN=I2C_SCL1
+ -D TOUCH_INT_PIN=-1 ;45
+ -D TOUCH_RST_PIN=44
+;base UI
+ -D TFT_CS=LGFX_PIN_CS
+ -D ST7789_CS=TFT_CS
+ -D ST7789_RS=LGFX_PIN_DC
+ -D ST7789_SDA=LGFX_PIN_MOSI
+ -D ST7789_SCK=LGFX_PIN_SCK
+ -D ST7789_RESET=LGFX_PIN_RST
+ -D ST7789_MISO=-1
+ -D ST7789_BUSY=-1
+ -D ST7789_BL=LGFX_PIN_BL
+ -D ST7789_SPI_HOST=SPI3_HOST
+ -D TFT_BL=ST7789_BL
+ -D SPI_FREQUENCY=40000000
+ -D SPI_READ_FREQUENCY=4000000
+ -D TFT_HEIGHT=320
+ -D TFT_WIDTH=240
+ -D TFT_OFFSET_X=0
+ -D TFT_OFFSET_Y=0
+ -D TFT_OFFSET_ROTATION=0
+ -D SCREEN_ROTATE
+ -D SCREEN_TRANSITION_FRAMERATE=5
+ -D BRIGHTNESS_DEFAULT=130 ; Medium Low Brightness
+ -D HAS_TOUCHSCREEN=1
+ -D TOUCH_I2C_PORT=0
+ -D TOUCH_SLAVE_ADDRESS=0x2E
+ -D SCREEN_TOUCH_INT=TOUCH_INT_PIN
+ -D SCREEN_TOUCH_RST=TOUCH_RST_PIN
+
+lib_deps = ${heltec_v4_base.lib_deps}
+ ; ${device-ui_base.lib_deps}
+ lovyan03/LovyanGFX@1.2.0
+ https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
+ https://github.com/Quency-D/device-ui/archive/7c9870b8016641190b059bdd90fe16c1012a39eb.zip
diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h
index 1c9516e39..72bbf14fc 100644
--- a/variants/esp32s3/heltec_v4/variant.h
+++ b/variants/esp32s3/heltec_v4/variant.h
@@ -1,11 +1,3 @@
-#define LED_PIN 35
-
-#define USE_SSD1306 // Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
-
-#define RESET_OLED 21
-#define I2C_SDA 17 // I2C pins for this board
-#define I2C_SCL 18
-
#define VEXT_ENABLE 36 // active low, powers the oled display and the lora antenna boost
#define BUTTON_PIN 0
diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini
index 2faba45a8..3a373bf4f 100644
--- a/variants/esp32s3/heltec_wireless_tracker/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
${esp32s3_base.build_flags}
-I variants/esp32s3/heltec_wireless_tracker
-D HELTEC_TRACKER_V1_1
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
lib_deps =
diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini
index 89fe4b385..cd961533d 100644
--- a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
${esp32s3_base.build_flags}
-I variants/esp32s3/heltec_wireless_tracker_V1_0
-D HELTEC_TRACKER_V1_0
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
lib_deps =
${esp32s3_base.lib_deps}
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
index 4872561db..0f9265f91 100644
--- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
${esp32s3_base.build_flags}
-I variants/esp32s3/heltec_wireless_tracker_v2
-D HELTEC_WIRELESS_TRACKER_V2
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
lib_deps =
${esp32s3_base.lib_deps}
lovyan03/LovyanGFX@^1.2.0
diff --git a/variants/esp32s3/heltec_wsl_v3/platformio.ini b/variants/esp32s3/heltec_wsl_v3/platformio.ini
index 06cde2304..c038a463e 100644
--- a/variants/esp32s3/heltec_wsl_v3/platformio.ini
+++ b/variants/esp32s3/heltec_wsl_v3/platformio.ini
@@ -7,4 +7,3 @@ build_flags =
${esp32s3_base.build_flags}
-D HELTEC_WSL_V3
-I variants/esp32s3/heltec_wsl_v3
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32s3/link32_s3_v1/platformio.ini b/variants/esp32s3/link32_s3_v1/platformio.ini
index c1b71b3b5..8d88075c4 100644
--- a/variants/esp32s3/link32_s3_v1/platformio.ini
+++ b/variants/esp32s3/link32_s3_v1/platformio.ini
@@ -5,7 +5,6 @@ build_flags =
${esp32_base.build_flags}
-D LINK_32
-I variants/esp32s3/link32_s3_v1
- -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-DARDUINO_USB_CDC_ON_BOOT
-DARDUINO_USB_MODE=1
-DRADIOLIB_EXCLUDE_SX128X=1
diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini
index 45c3ae4ea..0b3c7843a 100644
--- a/variants/esp32s3/t-deck-pro/platformio.ini
+++ b/variants/esp32s3/t-deck-pro/platformio.ini
@@ -7,7 +7,6 @@ upload_protocol = esptool
build_flags =
${esp32_base.build_flags} -I variants/esp32s3/t-deck-pro
-D T_DECK_PRO
- -D GPS_POWER_TOGGLE
-D USE_EINK
-D EINK_DISPLAY_MODEL=GxEPD2_310_GDEQ031T10
-D EINK_WIDTH=240
diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini
index 7c8070c3e..9ab0756d1 100644
--- a/variants/esp32s3/t-deck/platformio.ini
+++ b/variants/esp32s3/t-deck/platformio.ini
@@ -9,7 +9,6 @@ upload_protocol = esptool
build_flags = ${esp32s3_base.build_flags}
-D T_DECK
-D BOARD_HAS_PSRAM
- -D GPS_POWER_TOGGLE
-I variants/esp32s3/t-deck
lib_deps = ${esp32s3_base.lib_deps}
diff --git a/variants/esp32s3/t-eth-elite/platformio.ini b/variants/esp32s3/t-eth-elite/platformio.ini
index 6107185ce..1a5823bc3 100644
--- a/variants/esp32s3/t-eth-elite/platformio.ini
+++ b/variants/esp32s3/t-eth-elite/platformio.ini
@@ -9,7 +9,6 @@ build_flags =
-D T_ETH_ELITE
-D HAS_UDP_MULTICAST=1
-I variants/esp32s3/t-eth-elite
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
lib_ignore =
Ethernet
diff --git a/variants/esp32s3/tbeam-s3-core/rfswitch.h b/variants/esp32s3/tbeam-s3-core/rfswitch.h
new file mode 100644
index 000000000..19080cec6
--- /dev/null
+++ b/variants/esp32s3/tbeam-s3-core/rfswitch.h
@@ -0,0 +1,11 @@
+#include "RadioLib.h"
+
+static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
+
+static const Module::RfSwitchMode_t rfswitch_table[] = {
+ // mode DIO5 DIO6
+ {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
+ {LR11x0::MODE_TX, {LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
+ {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
+ {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
+};
\ No newline at end of file
diff --git a/variants/esp32s3/tbeam-s3-core/variant.h b/variants/esp32s3/tbeam-s3-core/variant.h
index 40ba0307a..1d99fbf14 100644
--- a/variants/esp32s3/tbeam-s3-core/variant.h
+++ b/variants/esp32s3/tbeam-s3-core/variant.h
@@ -15,6 +15,7 @@
// not found then probe for SX1262
#define USE_SX1262
#define USE_SX1268
+#define USE_LR1121
#define LORA_DIO0 -1 // a No connect on the SX1262 module
#define LORA_RESET 5
@@ -34,6 +35,19 @@
// code)
#endif
+// LR1121
+#ifdef USE_LR1121
+#define LR1121_IRQ_PIN 1
+#define LR1121_NRESET_PIN LORA_RESET
+#define LR1121_BUSY_PIN 4
+#define LR1121_SPI_NSS_PIN 10
+#define LR1121_SPI_SCK_PIN 12
+#define LR1121_SPI_MOSI_PIN 11
+#define LR1121_SPI_MISO_PIN 13
+#define LR11X0_DIO3_TCXO_VOLTAGE 3.0
+#define LR11X0_DIO_AS_RF_SWITCH
+#endif
+
// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
// and waking from light sleep
// #define PMU_IRQ 40
@@ -64,4 +78,4 @@
// has 32768 Hz crystal
#define HAS_32768HZ 1
-#define USE_SH1106
+#define USE_SH1106
\ No newline at end of file
diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini
index 9800161bb..d63537904 100644
--- a/variants/esp32s3/tlora-pager/platformio.ini
+++ b/variants/esp32s3/tlora-pager/platformio.ini
@@ -10,7 +10,6 @@ build_flags = ${esp32s3_base.build_flags}
-I variants/esp32s3/tlora-pager
-D T_LORA_PAGER
-D BOARD_HAS_PSRAM
- -D GPS_POWER_TOGGLE
-D HAS_SDCARD
-D SDCARD_USE_SPI1
-D ENABLE_ROTARY_PULLUP
diff --git a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
index 71644ee77..eca052f57 100644
--- a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
+++ b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
${esp32_base.build_flags}
-D TLORA_T3S3_EPAPER
-I variants/esp32s3/tlora_t3s3_epaper
- -DGPS_POWER_TOGGLE
-DUSE_EINK
-DEINK_DISPLAY_MODEL=GxEPD2_213_BN
-DEINK_WIDTH=250
diff --git a/variants/esp32s3/tlora_t3s3_v1/platformio.ini b/variants/esp32s3/tlora_t3s3_v1/platformio.ini
index d9624f043..56ece0d62 100644
--- a/variants/esp32s3/tlora_t3s3_v1/platformio.ini
+++ b/variants/esp32s3/tlora_t3s3_v1/platformio.ini
@@ -6,4 +6,3 @@ upload_protocol = esptool
build_flags =
${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/esp32s3/tlora_t3s3_v1
- -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32s3/tracksenger/platformio.ini b/variants/esp32s3/tracksenger/platformio.ini
index 0e9f08541..213a917b1 100644
--- a/variants/esp32s3/tracksenger/platformio.ini
+++ b/variants/esp32s3/tracksenger/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
${esp32s3_base.build_flags}
-I variants/esp32s3/tracksenger/internal
-D HELTEC_TRACKER_V1_1
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
lib_deps =
@@ -25,7 +24,6 @@ build_flags =
${esp32s3_base.build_flags}
-I variants/esp32s3/tracksenger/lcd
-D HELTEC_TRACKER_V1_1
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
lib_deps =
@@ -42,5 +40,4 @@ build_flags =
${esp32s3_base.build_flags}
-I variants/esp32s3/tracksenger/oled
-D HELTEC_TRACKER_V1_1
- -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
diff --git a/variants/native/portduino-buildroot/platformio.ini b/variants/native/portduino-buildroot/platformio.ini
index d1bd39e10..a3d0f4639 100644
--- a/variants/native/portduino-buildroot/platformio.ini
+++ b/variants/native/portduino-buildroot/platformio.ini
@@ -4,5 +4,6 @@ extends = portduino_base
; environment variable in the buildroot environment.
build_flags = ${portduino_base.build_flags} -O0 -I variants/native/portduino-buildroot
board = buildroot
+board_level = extra
lib_deps = ${portduino_base.lib_deps}
build_src_filter = ${portduino_base.build_src_filter}
\ No newline at end of file
diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 61eadb459..474d45492 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -3,6 +3,7 @@ extends = portduino_base
build_flags = ${portduino_base.build_flags} -I variants/native/portduino
-I /usr/include
board = cross_platform
+board_level = extra
lib_deps =
${portduino_base.lib_deps}
melopero/Melopero RV3028@^1.1.0
@@ -16,6 +17,8 @@ extends = native_base
build_flags = ${native_base.build_flags}
!pkg-config --libs libulfius --silence-errors || :
!pkg-config --libs openssl --silence-errors || :
+ !pkg-config --cflags --libs sdl2 --silence-errors || :
+ !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
[env:native-tft]
extends = native_base
@@ -41,6 +44,7 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio
!pkg-config --libs libulfius --silence-errors || :
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs sdl2 --silence-errors || :
+ !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
build_src_filter =
${native_base.build_src_filter}
@@ -50,7 +54,6 @@ build_type = release
lib_deps =
${native_base.lib_deps}
${device-ui_base.lib_deps}
-board_level = extra
build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections -Wl,--gc-sections
-D RAM_SIZE=8192
-D USE_FRAMEBUFFER=1
@@ -70,6 +73,7 @@ build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections
-D MAP_FULL_REDRAW
!pkg-config --libs libulfius --silence-errors || :
!pkg-config --libs openssl --silence-errors || :
+ !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
build_src_filter =
${native_base.build_src_filter}
@@ -79,7 +83,6 @@ build_type = debug
lib_deps =
${native_base.lib_deps}
${device-ui_base.lib_deps}
-board_level = extra
build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -lxkbcommon
-D DEBUG_HEAP
-D RAM_SIZE=16384
@@ -103,8 +106,13 @@ build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -l
-D VIEW_320x240
!pkg-config --libs libulfius --silence-errors || :
!pkg-config --libs openssl --silence-errors || :
+ !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
build_src_filter = ${env:native-tft.build_src_filter}
[env:coverage]
extends = env:native
build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags}
+; https://docs.platformio.org/en/latest/projectconf/sections/env/options/test/test_testing_command.html
+test_testing_command =
+ ${platformio.build_dir}/${this.__env__}/program
+ -s
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
index 0578bcfe8..f89b05d1f 100644
--- a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
+++ b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
@@ -9,7 +9,6 @@ debug_tool = jlink
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/ELECROW-ThinkNode-M1
-DELECROW_ThinkNode_M1
- -DGPS_POWER_TOGGLE
-DUSE_EINK
-DEINK_DISPLAY_MODEL=GxEPD2_154_D67
-DEINK_WIDTH=200
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini
new file mode 100644
index 000000000..958e48e48
--- /dev/null
+++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini
@@ -0,0 +1,17 @@
+[env:thinknode_m3]
+extends = nrf52840_base
+board = ThinkNode-M3
+board_check = true
+debug_tool = jlink
+build_flags =
+ ${nrf52840_base.build_flags}
+ -Ivariants/nrf52840/ELECROW-ThinkNode-M3
+ -DELECROW_ThinkNode_M3
+ -DGPS_POWER_TOGGLE
+ -D CONFIG_NFCT_PINS_AS_GPIOS=1
+ -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M3>
+lib_deps =
+ ${nrf52840_base.lib_deps}
+ khoih-prog/nRF52_PWM@^1.0.1
+ lewisxhe/PCF8563_Library@^1.0.1
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h b/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h
new file mode 100644
index 000000000..77ae9ef73
--- /dev/null
+++ b/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h
@@ -0,0 +1,15 @@
+#include "RadioLib.h"
+#include "nrf.h"
+
+// set RF switch configuration for ELECROW ThinkNode M3
+// ELECROW ThinkNode M3 uses DIO5 and DIO6 for RF switching
+
+static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
+
+static const Module::RfSwitchMode_t rfswitch_table[] = {
+ // mode DIO5 DIO6
+ {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
+ {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
+ {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
+ {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
+};
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp
new file mode 100644
index 000000000..b7a7b7342
--- /dev/null
+++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp
@@ -0,0 +1,93 @@
+/*
+ Copyright (c) 2014-2015 Arduino LLC. All right reserved.
+ Copyright (c) 2016 Sandeep Mistry All right reserved.
+ Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "variant.h"
+#include "meshUtils.h"
+#include "nrf.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+
+const uint32_t g_ADigitalPinMap[] = {
+ // P0
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+
+ // P1
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
+
+void initVariant()
+{
+ pinMode(KEY_POWER, OUTPUT);
+ digitalWrite(KEY_POWER, HIGH);
+ pinMode(RGB_POWER, OUTPUT);
+ digitalWrite(RGB_POWER, HIGH);
+ pinMode(green_LED_PIN, OUTPUT);
+ digitalWrite(green_LED_PIN, LED_STATE_OFF);
+ pinMode(LED_BLUE, OUTPUT);
+ pinMode(PIN_POWER_USB, INPUT);
+ pinMode(PIN_POWER_DONE, INPUT);
+ pinMode(PIN_POWER_CHRG, INPUT);
+ pinMode(BUTTON_PIN, INPUT_PULLUP);
+ pinMode(EEPROM_POWER, OUTPUT);
+ digitalWrite(EEPROM_POWER, HIGH);
+ pinMode(PIN_EN1, OUTPUT);
+ digitalWrite(PIN_EN1, HIGH);
+ pinMode(PIN_EN2, OUTPUT);
+ digitalWrite(PIN_EN2, HIGH);
+ pinMode(ACC_POWER, OUTPUT);
+ digitalWrite(ACC_POWER, LOW);
+ pinMode(DHT_POWER, OUTPUT);
+ digitalWrite(DHT_POWER, HIGH);
+ pinMode(Battery_POWER, OUTPUT);
+ digitalWrite(Battery_POWER, HIGH);
+ pinMode(GPS_POWER, OUTPUT);
+ digitalWrite(GPS_POWER, HIGH);
+}
+
+// called from main-nrf52.cpp during the cpuDeepSleep() function
+void variant_shutdown()
+{
+ digitalWrite(EEPROM_POWER, LOW);
+ digitalWrite(KEY_POWER, LOW);
+
+ for (int pin = 0; pin < 48; pin++) {
+ if (pin == PIN_POWER_USB || pin == BUTTON_PIN || pin == PIN_EN1 || pin == PIN_EN2 || pin == DHT_POWER ||
+ pin == ACC_POWER || pin == Battery_POWER || pin == GPS_POWER || pin == LR1110_SPI_MISO_PIN ||
+ pin == LR1110_SPI_MOSI_PIN || pin == LR1110_SPI_SCK_PIN || pin == LR1110_SPI_NSS_PIN || pin == LR1110_BUSY_PIN ||
+ pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == green_LED_PIN ||
+ pin == red_LED_PIN || pin == LED_BLUE) {
+ continue;
+ }
+ pinMode(pin, OUTPUT);
+ digitalWrite(pin, LOW);
+ if (pin >= 32) {
+ NRF_P1->DIRCLR = (1 << (pin - 32));
+ } else {
+ NRF_GPIO->DIRCLR = (1 << pin);
+ }
+ }
+
+ nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
+ nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
+ nrf_gpio_cfg_sense_set(BUTTON_PIN, sense1);
+
+ nrf_gpio_cfg_input(PIN_POWER_USB, NRF_GPIO_PIN_PULLDOWN); // Configure the pin to be woken up as an input
+ nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_HIGH;
+ nrf_gpio_cfg_sense_set(PIN_POWER_USB, sense2);
+}
\ No newline at end of file
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h
new file mode 100644
index 000000000..cf940172b
--- /dev/null
+++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h
@@ -0,0 +1,122 @@
+/*
+ Copyright (c) 2014-2015 Arduino LLC. All right reserved.
+ Copyright (c) 2016 Sandeep Mistry All right reserved.
+ Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU Lesser General Public License for more details.
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef _VARIANT_ELECROW_EINK_V1_0_
+#define _VARIANT_ELECROW_EINK_V1_0_
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+#include "WVariant.h"
+
+#define VARIANT_MCK (64000000ul)
+#define USE_LFXO // Board uses 32khz crystal for LF
+
+#define ELECROW_ThinkNode_M3 1
+// Number of pins defined in PinDescription array
+#define PINS_COUNT (48)
+#define NUM_DIGITAL_PINS (48)
+#define NUM_ANALOG_INPUTS (1)
+#define NUM_ANALOG_OUTPUTS (0)
+
+// Power Pin
+#define NRF_APM
+#define GPS_POWER 14
+#define PIN_POWER_USB 31
+#define EXT_PWR_DETECT PIN_POWER_USB
+#define PIN_POWER_DONE 24
+#define PIN_POWER_CHRG 32
+#define KEY_POWER 16
+#define ACC_POWER 2
+#define DHT_POWER 3
+#define Battery_POWER 17
+#define RGB_POWER 29
+#define EEPROM_POWER 7
+
+// LED
+#define red_LED_PIN 33
+#define LED_POWER red_LED_PIN
+#define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED
+#define green_LED_PIN 35
+#define LED_BLUE 37
+#define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED
+
+#define LED_BUILTIN -1
+#define LED_STATE_ON LOW
+#define LED_STATE_OFF HIGH
+
+// BUZZER
+#define PIN_BUZZER 23
+#define PIN_EN1 36
+#define PIN_EN2 34
+/*Wire Interfaces*/
+#define WIRE_INTERFACES_COUNT 1
+#define PIN_WIRE_SDA 26
+#define PIN_WIRE_SCL 27
+
+// Temperature correction for sensor
+#define AHT10_TEMP_OFFSET -5.0
+
+/*GPS pins*/
+#define HAS_GPS 1
+#define GPS_BAUDRATE 9600
+#define PIN_GPS_RESET 25
+#define PIN_GPS_STANDBY 21
+#define GPS_TX_PIN 20
+#define GPS_RX_PIN 22
+#define GPS_THREAD_INTERVAL 50
+#define PIN_SERIAL1_RX GPS_TX_PIN
+#define PIN_SERIAL1_TX GPS_RX_PIN
+// Button
+#define BUTTON_PIN 12
+#define BUTTON_PIN_ALT (0 + 12)
+// Battery
+#define BATTERY_PIN 5
+#define BATTERY_SENSE_RESOLUTION_BITS 12
+#define BATTERY_SENSE_RESOLUTION 4096.0
+#undef AREF_VOLTAGE
+#define AREF_VOLTAGE 2.4
+#define VBAT_AR_INTERNAL AR_INTERNAL_2_4
+#define ADC_MULTIPLIER (1.75)
+/*SPI Interfaces*/
+#define SPI_INTERFACES_COUNT 1
+#define PIN_SPI_MISO (32 + 15) // P1.15 47
+#define PIN_SPI_MOSI (32 + 14) // P1.14 46
+#define PIN_SPI_SCK (32 + 13) // P1.13 45
+#define PIN_SPI_NSS (32 + 12) // P1.12 44
+/*LORA Interfaces*/
+#define USE_LR1110
+#define LR1110_IRQ_PIN 40
+#define LR1110_NRESET_PIN 42
+#define LR1110_BUSY_PIN 43
+#define LR1110_SPI_NSS_PIN 44
+#define LR1110_SPI_SCK_PIN 45
+#define LR1110_SPI_MOSI_PIN 46
+#define LR1110_SPI_MISO_PIN 47
+#define LR11X0_DIO3_TCXO_VOLTAGE 3.3
+#define LR11X0_DIO_AS_RF_SWITCH
+
+// PCF8563 RTC Module
+#define PCF8563_RTC 0x51
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini
new file mode 100644
index 000000000..2bf227791
--- /dev/null
+++ b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini
@@ -0,0 +1,15 @@
+; ThinkNode M6 - Outdoor Solar Power nrf52840/sx1262 device
+[env:thinknode_m6]
+extends = nrf52840_base
+board = ThinkNode-M6
+board_check = true
+debug_tool = jlink
+
+build_flags = ${nrf52840_base.build_flags}
+ -Ivariants/nrf52840/ELECROW-ThinkNode-M6
+ -DELECROW_ThinkNode_M6
+
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M6>
+lib_deps =
+ ${nrf52840_base.lib_deps}
+ lewisxhe/PCF8563_Library@^1.0.1
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp
new file mode 100644
index 000000000..09872d409
--- /dev/null
+++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp
@@ -0,0 +1,43 @@
+/*
+ Copyright (c) 2014-2015 Arduino LLC. All right reserved.
+ Copyright (c) 2016 Sandeep Mistry All right reserved.
+ Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "variant.h"
+#include "nrf.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+
+const uint32_t g_ADigitalPinMap[] = {
+ // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled
+ 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+
+ // P1
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
+
+void initVariant()
+{
+ pinMode(LED_CHARGE, OUTPUT);
+ ledOff(LED_CHARGE);
+
+ pinMode(LED_PAIRING, OUTPUT);
+ ledOff(LED_PAIRING);
+
+ pinMode(VDD_FLASH_EN, OUTPUT);
+ digitalWrite(VDD_FLASH_EN, HIGH);
+}
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h
new file mode 100644
index 000000000..5e543b21f
--- /dev/null
+++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h
@@ -0,0 +1,144 @@
+/*
+ Copyright (c) 2014-2015 Arduino LLC. All right reserved.
+ Copyright (c) 2016 Sandeep Mistry All right reserved.
+ Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU Lesser General Public License for more details.
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef _VARIANT_ELECROW_THINKNODE_M6_
+#define _VARIANT_ELECROW_THINKNODE_M6_
+
+/** Master clock frequency */
+#define VARIANT_MCK (64000000ul)
+
+#define USE_LFXO
+
+/*----------------------------------------------------------------------------
+ * Headers
+ *----------------------------------------------------------------------------*/
+
+#include "WVariant.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+#define PINS_COUNT (48)
+#define NUM_DIGITAL_PINS (48)
+#define NUM_ANALOG_INPUTS (1)
+#define NUM_ANALOG_OUTPUTS (0)
+
+// LEDs
+#define LED_BUILTIN -1
+#define LED_BLUE -1
+#define LED_CHARGE (12)
+#define LED_PAIRING (7)
+
+#define LED_STATE_ON 1
+
+// USB power detection
+#define EXT_PWR_DETECT (13)
+
+// Button
+#define PIN_BUTTON1 (17)
+
+// Battery ADC
+#define PIN_A0 (28)
+#define BATTERY_PIN PIN_A0
+#define ADC_CTRL (11)
+#define ADC_CTRL_ENABLED 1
+
+static const uint8_t A0 = PIN_A0;
+
+#define ADC_RESOLUTION 14
+#define BATTERY_SENSE_SAMPLES 30
+
+#define PIN_NFC1 (9)
+#define PIN_NFC2 (10)
+
+// I2C
+#define WIRE_INTERFACES_COUNT 1
+#define PIN_WIRE_SDA (32 + 9)
+#define PIN_WIRE_SCL (8)
+
+// Peripheral power enable
+#define PIN_POWER_EN (27)
+
+// Solar charger status
+#define EXT_CHRG_DETECT (15)
+#define EXT_CHRG_DETECT_VALUE LOW
+
+// QSPI Flash
+#define PIN_QSPI_SCK (32 + 3)
+#define PIN_QSPI_CS (23)
+#define PIN_QSPI_IO0 (32 + 1)
+#define PIN_QSPI_IO1 (32 + 2)
+#define PIN_QSPI_IO2 (32 + 4)
+#define PIN_QSPI_IO3 (32 + 5)
+
+#define EXTERNAL_FLASH_DEVICES MX25R1635F
+#define EXTERNAL_FLASH_USE_QSPI
+#define VDD_FLASH_EN (21)
+
+// LoRa SX1262
+#define USE_SX1262
+#define SX126X_CS (32 + 12)
+#define SX126X_DIO1 (32 + 6)
+#define SX126X_BUSY (32 + 11)
+#define SX126X_RESET (32 + 10)
+#define SX126X_DIO2_AS_RF_SWITCH
+#define SX126X_DIO3_TCXO_VOLTAGE 3.3
+
+// GPS L76K
+#define GPS_L76K
+#define GPS_BAUDRATE 9600
+#define PIN_GPS_EN (6)
+#define PIN_GPS_REINIT (29)
+#define PIN_GPS_STANDBY (30)
+#define PIN_GPS_PPS (31)
+#define GPS_TX_PIN (3)
+#define GPS_RX_PIN (2)
+#define GPS_THREAD_INTERVAL 50
+
+#define PIN_SERIAL1_RX GPS_TX_PIN
+#define PIN_SERIAL1_TX GPS_RX_PIN
+
+// Secondary UART
+#define PIN_SERIAL2_RX (22)
+#define PIN_SERIAL2_TX (24)
+
+// PCF8563 RTC Module
+#define PCF8563_RTC 0x51
+
+// SPI
+#define SPI_INTERFACES_COUNT 1
+#define PIN_SPI_MISO (32 + 15)
+#define PIN_SPI_MOSI (32 + 14)
+#define PIN_SPI_SCK (32 + 13)
+
+// Battery
+#define BATTERY_SENSE_RESOLUTION_BITS 12
+#define BATTERY_SENSE_RESOLUTION 4096.0
+#undef AREF_VOLTAGE
+#define AREF_VOLTAGE 3.0
+#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
+#define ADC_MULTIPLIER (1.75F)
+
+#define HAS_SOLAR
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini
index 89a45694c..1279f12c6 100644
--- a/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini
+++ b/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini
@@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags}
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
-DME25LS01_4Y10TD
- -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS01-4Y10TD>
lib_deps =
diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
index ad5867bd5..f8d6da008 100644
--- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
+++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
@@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags}
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
-DME25LS01_4Y10TD
- -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-DEINK_DISPLAY_MODEL=GxEPD2_420_GDEY042T81
-DEINK_WIDTH=400
-DEINK_HEIGHT=300
diff --git a/variants/nrf52840/MS24SF1/platformio.ini b/variants/nrf52840/MS24SF1/platformio.ini
index f162cbd60..df15b5605 100644
--- a/variants/nrf52840/MS24SF1/platformio.ini
+++ b/variants/nrf52840/MS24SF1/platformio.ini
@@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/MS24SF1
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
- -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/MS24SF1>
lib_deps =
diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf
deleted file mode 100644
index de87af141..000000000
--- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf
+++ /dev/null
@@ -1,9836 +0,0 @@
-%PDF-1.4
-%ºß¬à
-3 0 obj
-<>
-endobj
-4 0 obj
-<<
-/Length 102720
->>
-stream
-0.20 w
-0 G
-2 J
-0 j
-100 M
-1.00 g
-[] 0 d
-0.00 826.80 1169.00 -826.80 re
-f
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-216.000 806.000 m
-216.000 816.000 l
-216.000 20.000 m
-216.000 10.000 l
-412.000 806.000 m
-412.000 816.000 l
-412.000 20.000 m
-412.000 10.000 l
-608.000 806.000 m
-608.000 816.000 l
-608.000 20.000 m
-608.000 10.000 l
-804.000 806.000 m
-804.000 816.000 l
-804.000 20.000 m
-804.000 10.000 l
-1000.000 806.000 m
-1000.000 816.000 l
-1000.000 20.000 m
-1000.000 10.000 l
-20.000 610.000 m
-10.000 610.000 l
-1149.000 610.000 m
-1159.000 610.000 l
-20.000 414.000 m
-10.000 414.000 l
-1149.000 414.000 m
-1159.000 414.000 l
-20.000 218.000 m
-10.000 218.000 l
-1149.000 218.000 m
-1159.000 218.000 l
-20.000 22.000 m
-10.000 22.000 l
-1149.000 22.000 m
-1159.000 22.000 l
-S
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-11.50 708.00 Td
-(A) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-1150.50 708.00 Td
-(A) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-11.50 512.00 Td
-(B) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-1150.50 512.00 Td
-(B) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-11.50 316.00 Td
-(C) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-1150.50 316.00 Td
-(C) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-11.50 120.00 Td
-(D) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-1150.50 120.00 Td
-(D) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-118.00 807.50 Td
-(1) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-118.00 11.50 Td
-(1) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-314.00 807.50 Td
-(2) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-314.00 11.50 Td
-(2) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-510.00 807.50 Td
-(3) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-510.00 11.50 Td
-(3) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-706.00 807.50 Td
-(4) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-706.00 11.50 Td
-(4) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-902.00 807.50 Td
-(5) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-902.00 11.50 Td
-(5) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-20.00 806.00 1129.00 -786.00 re
-S
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-10.00 816.00 1149.00 -806.00 re
-S
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-705.00 100.00 444.00 -80.00 re
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-705.100 60.750 m
-1148.630 60.750 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-809.630 40.750 m
-1148.630 40.750 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-1069.610 99.930 m
-1069.630 60.750 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-1069.630 60.750 m
-1069.630 40.750 l
-S
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-710.00 87.00 Td
-(TITLE:) Tj
-ET
-10.00 w
-BT
-/F1 13 Tf
-13.00 TL
-0.000 0.000 1.000 rg
-767.62 74.41 Td
-(Pro-Micro Pinouts) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-1074.62 73.75 Td
-(REV:) Tj
-ET
-10.00 w
-BT
-/F1 12 Tf
-12.00 TL
-0.000 0.000 1.000 rg
-1112.62 73.75 Td
-(1.0) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-814.62 25.00 Td
-(Date:) Tj
-ET
-10.00 w
-BT
-/F1 12 Tf
-12.00 TL
-0.000 0.000 1.000 rg
-861.62 24.52 Td
-(2024-12-17) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-1073.62 45.00 Td
-(Sheet:) Tj
-ET
-10.00 w
-BT
-/F1 12 Tf
-12.00 TL
-0.000 0.000 1.000 rg
-1118.62 44.52 Td
-(1/1) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-953.62 24.75 Td
-(Drawn By:) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-814.62 46.75 Td
-(Company:) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-809.630 60.750 m
-809.630 20.750 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-541.000 684.000 m
-549.000 676.000 l
-549.000 684.000 m
-541.000 676.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-665.000 680.000 m
-635.000 680.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-986.000 649.000 m
-994.000 641.000 l
-994.000 649.000 m
-986.000 641.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-541.000 614.000 m
-549.000 606.000 l
-549.000 614.000 m
-541.000 606.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-541.000 624.000 m
-549.000 616.000 l
-549.000 624.000 m
-541.000 616.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-541.000 644.000 m
-549.000 636.000 l
-549.000 644.000 m
-541.000 636.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-631.000 634.000 m
-639.000 626.000 l
-639.000 634.000 m
-631.000 626.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 804.82 611.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-830.000 615.000 m
-840.000 615.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-830.000 610.000 m
-830.000 620.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 797.50 621.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-830.000 625.000 m
-840.000 625.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-830.000 634.000 m
-830.000 616.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-828.000 631.000 m
-828.000 619.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-826.000 628.000 m
-826.000 622.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-824.000 626.000 m
-824.000 624.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1006.50 651.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1000.000 655.000 m
-990.000 655.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1000.000 646.000 m
-1000.000 664.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1002.000 649.000 m
-1002.000 661.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1004.000 652.000 m
-1004.000 658.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1006.000 654.000 m
-1006.000 656.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 1011.94 671.15 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-990.000 675.000 m
-995.000 680.000 l
-1010.000 680.000 l
-1010.000 670.000 l
-995.000 670.000 l
-990.000 675.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 797.25 651.75 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 655.000 m
-835.000 650.000 l
-820.000 650.000 l
-820.000 660.000 l
-835.000 660.000 l
-840.000 655.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 804.69 631.45 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 635.000 m
-835.000 630.000 l
-820.000 630.000 l
-820.000 640.000 l
-835.000 640.000 l
-840.000 635.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 791.48 661.75 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 665.000 m
-835.000 660.000 l
-820.000 660.000 l
-820.000 670.000 l
-835.000 670.000 l
-840.000 665.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 791.41 671.75 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 675.000 m
-835.000 670.000 l
-820.000 670.000 l
-820.000 680.000 l
-835.000 680.000 l
-840.000 675.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 790.06 641.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 645.000 m
-835.000 640.000 l
-820.000 640.000 l
-820.000 650.000 l
-835.000 650.000 l
-840.000 645.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 1011.92 661.15 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-990.000 665.000 m
-995.000 670.000 l
-1010.000 670.000 l
-1010.000 660.000 l
-995.000 660.000 l
-990.000 665.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-908.96 708.00 Td
-(Seeed-wio-SX1262) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-908.96 717.00 Td
-(SEEED_WIO-SX1262) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 682.00 Td
-(RF_SW) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 686.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 685.000 m
-860.000 685.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 672.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 676.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 675.000 m
-860.000 675.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 662.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 666.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 665.000 m
-860.000 665.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 652.00 Td
-(CLK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 656.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 655.000 m
-860.000 655.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 642.00 Td
-(RST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 646.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 645.000 m
-860.000 645.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 632.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 636.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 635.000 m
-860.000 635.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 622.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 626.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 625.000 m
-860.000 625.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 612.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 616.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 615.000 m
-860.000 615.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-949.58 642.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-975.00 646.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-990.000 645.000 m
-970.000 645.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-947.36 652.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-975.00 656.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-990.000 655.000 m
-970.000 655.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-943.57 662.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-975.00 666.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-990.000 665.000 m
-970.000 665.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-944.49 672.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-975.00 676.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-990.000 675.000 m
-970.000 675.000 l
-S
-2 J
-0 j
-100 M
-1.00 w
-0.00 G
-[] 0 d
-860.00 705.00 110.00 -110.00 re
-S
-1.00 w
-0.00 G
-[] 0 d
-965.00 615.00 m 965.00 623.28 958.28 630.00 950.00 630.00 c
-941.72 630.00 935.00 623.28 935.00 615.00 c
-935.00 606.72 941.72 600.00 950.00 600.00 c
-958.28 600.00 965.00 606.72 965.00 615.00 c
-S
-2 J
-0 j
-100 M
-1.00 w
-0.00 G
-[] 0 d
-930.00 635.00 40.00 -40.00 re
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-535.000 660.000 m
-545.000 660.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 499.82 655.93 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-525.000 660.000 m
-535.000 660.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-525.000 655.000 m
-525.000 665.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-520.000 670.000 m
-545.000 670.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 652.00 699.09 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-665.000 690.000 m
-665.000 680.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-674.000 690.000 m
-656.000 690.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-671.000 692.000 m
-659.000 692.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-668.000 694.000 m
-662.000 694.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-666.000 696.000 m
-664.000 696.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 507.00 689.09 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-520.000 680.000 m
-520.000 670.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-529.000 680.000 m
-511.000 680.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-526.000 682.000 m
-514.000 682.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-523.000 684.000 m
-517.000 684.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-521.000 686.000 m
-519.000 686.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 622.00 582.76 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-635.000 600.000 m
-635.000 610.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-626.000 600.000 m
-644.000 600.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-629.000 598.000 m
-641.000 598.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-632.000 596.000 m
-638.000 596.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-634.000 594.000 m
-636.000 594.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 656.92 616.15 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 620.000 m
-640.000 625.000 l
-655.000 625.000 l
-655.000 615.000 l
-640.000 615.000 l
-635.000 620.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 656.55 636.15 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 640.000 m
-640.000 645.000 l
-655.000 645.000 l
-655.000 635.000 l
-640.000 635.000 l
-635.000 640.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 656.92 646.15 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 650.000 m
-640.000 655.000 l
-655.000 655.000 l
-655.000 645.000 l
-640.000 645.000 l
-635.000 650.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 656.85 656.15 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 660.000 m
-640.000 665.000 l
-655.000 665.000 l
-655.000 655.000 l
-640.000 655.000 l
-635.000 660.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 657.00 666.45 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 670.000 m
-640.000 675.000 l
-655.000 675.000 l
-655.000 665.000 l
-640.000 665.000 l
-635.000 670.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 504.19 626.40 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-545.000 630.000 m
-540.000 625.000 l
-525.000 625.000 l
-525.000 635.000 l
-540.000 635.000 l
-545.000 630.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 495.06 646.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-545.000 650.000 m
-540.000 645.000 l
-525.000 645.000 l
-525.000 655.000 l
-540.000 655.000 l
-545.000 650.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-584.95 693.33 Td
-(RA-01SH) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-584.95 702.33 Td
-(HT-RA62) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-557.000 690.000 m
-623.000 690.000 l
-624.105 690.000 625.000 689.105 625.000 688.000 c
-625.000 602.000 l
-625.000 600.895 623.895 600.000 623.000 600.000 c
-557.000 600.000 l
-555.895 600.000 555.000 601.105 555.000 602.000 c
-555.000 688.000 l
-555.000 689.105 556.105 690.000 557.000 690.000 c
-S
-1.00 w
-0.53 0.00 0.00 RG
-0.53 0.00 0.00 rg
-[] 0 d
-561.50 685.00 m 561.50 685.83 560.83 686.50 560.00 686.50 c
-559.17 686.50 558.50 685.83 558.50 685.00 c
-558.50 684.17 559.17 683.50 560.00 683.50 c
-560.83 683.50 561.50 684.17 561.50 685.00 c
-B
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 676.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 681.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 680.000 m
-555.000 680.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-558.70 666.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-548.78 671.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-545.000 670.000 m
-555.000 670.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 656.00 Td
-(3.3V) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 661.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 660.000 m
-555.000 660.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 646.00 Td
-(RESET) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 651.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 650.000 m
-555.000 650.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 636.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 641.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 640.000 m
-555.000 640.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 626.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 631.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 630.000 m
-555.000 630.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 616.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 621.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 620.000 m
-555.000 620.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 606.00 Td
-(DIO3) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 611.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 610.000 m
-555.000 610.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-600.66 606.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-625.50 611.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-635.000 610.000 m
-625.000 610.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-596.87 616.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 621.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 620.000 m
-625.000 620.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-596.46 626.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 631.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 630.000 m
-625.000 630.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-602.64 636.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 641.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 640.000 m
-625.000 640.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-596.71 646.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 651.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 650.000 m
-625.000 650.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-596.71 656.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 661.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 660.000 m
-625.000 660.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-602.27 666.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 671.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 670.000 m
-625.000 670.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-600.66 676.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-625.50 681.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-635.000 680.000 m
-625.000 680.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-153.95 479.05 Td
-(AMC-U_FL) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-153.95 488.16 Td
-(U6) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-165.00 465.00 20.00 -20.00 re
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-0.00 1.00 -1.00 0.00 174.00 434.29 Tm
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-175.000 425.000 m
-175.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-159.28 456.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-155.000 455.000 m
-165.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-185.00 456.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-195.000 455.000 m
-185.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-0.00 1.00 -1.00 0.00 174.00 465.00 Tm
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-175.000 475.000 m
-175.000 465.000 l
-S
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-177.00 455.00 m 177.00 456.10 176.10 457.00 175.00 457.00 c
-173.90 457.00 173.00 456.10 173.00 455.00 c
-173.00 453.90 173.90 453.00 175.00 453.00 c
-176.10 453.00 177.00 453.90 177.00 455.00 c
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-175.000 453.000 m
-175.000 445.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-506.000 394.000 m
-514.000 386.000 l
-514.000 394.000 m
-506.000 386.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-646.000 394.000 m
-654.000 386.000 l
-654.000 394.000 m
-646.000 386.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 666.50 415.93 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-660.000 420.000 m
-650.000 420.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-660.000 411.000 m
-660.000 429.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-662.000 414.000 m
-662.000 426.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-664.000 417.000 m
-664.000 423.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-666.000 419.000 m
-666.000 421.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-650.000 430.000 m
-650.000 400.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-796.000 389.000 m
-804.000 381.000 l
-804.000 389.000 m
-796.000 381.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-573.96 503.33 Td
-(E22-900M22S) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-573.96 512.33 Td
-(E22-900M22S) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 377.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 381.00 Td
-(22) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 380.000 m
-530.000 380.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 387.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 391.00 Td
-(21) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 390.000 m
-530.000 390.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 397.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 401.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 400.000 m
-530.000 400.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 417.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 421.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 420.000 m
-530.000 420.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 427.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 431.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 430.000 m
-530.000 430.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 437.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 441.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 440.000 m
-530.000 440.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 447.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 451.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 450.000 m
-530.000 450.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 457.00 Td
-(NRST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 461.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 460.000 m
-530.000 460.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 467.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 471.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 470.000 m
-530.000 470.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 477.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 481.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 480.000 m
-530.000 480.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 487.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 491.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 490.000 m
-530.000 490.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 487.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 491.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 490.000 m
-630.000 490.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 477.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 481.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 480.000 m
-630.000 480.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-609.29 467.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 471.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 470.000 m
-630.000 470.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-604.49 457.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 461.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 460.000 m
-630.000 460.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-603.87 447.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 451.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 450.000 m
-630.000 450.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-603.16 437.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 441.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 440.000 m
-630.000 440.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 427.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 431.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 430.000 m
-630.000 430.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 417.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 421.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 420.000 m
-630.000 420.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 397.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 401.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 400.000 m
-630.000 400.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 387.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 391.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 390.000 m
-630.000 390.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 377.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 381.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 380.000 m
-630.000 380.000 l
-S
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-532.000 500.000 m
-628.000 500.000 l
-629.105 500.000 630.000 499.105 630.000 498.000 c
-630.000 362.000 l
-630.000 360.895 628.895 360.000 628.000 360.000 c
-532.000 360.000 l
-530.895 360.000 530.000 361.105 530.000 362.000 c
-530.000 498.000 l
-530.000 499.105 531.105 500.000 532.000 500.000 c
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-863.96 498.33 Td
-(E22-900M30S) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-863.96 507.33 Td
-(E22-900M30S) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-822.000 495.000 m
-918.000 495.000 l
-919.105 495.000 920.000 494.105 920.000 493.000 c
-920.000 357.000 l
-920.000 355.895 918.895 355.000 918.000 355.000 c
-822.000 355.000 l
-820.895 355.000 820.000 356.105 820.000 357.000 c
-820.000 493.000 l
-820.000 494.105 821.105 495.000 822.000 495.000 c
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 372.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 376.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 375.000 m
-920.000 375.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 382.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 386.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 385.000 m
-920.000 385.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 392.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 396.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 395.000 m
-920.000 395.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 412.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 416.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 415.000 m
-920.000 415.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 422.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 426.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 425.000 m
-920.000 425.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-893.16 432.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 436.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 435.000 m
-920.000 435.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-893.87 442.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 446.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 445.000 m
-920.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-894.49 452.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 456.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 455.000 m
-920.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-899.29 462.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 466.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 465.000 m
-920.000 465.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-899.29 472.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 476.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 475.000 m
-920.000 475.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 482.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 486.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 485.000 m
-920.000 485.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 482.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 486.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 485.000 m
-820.000 485.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 472.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 476.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 475.000 m
-820.000 475.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 462.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 466.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 465.000 m
-820.000 465.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 452.00 Td
-(NRST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 456.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 455.000 m
-820.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 442.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 446.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 445.000 m
-820.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 432.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 436.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 435.000 m
-820.000 435.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 422.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 426.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 425.000 m
-820.000 425.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 412.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 416.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 415.000 m
-820.000 415.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 392.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 396.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 395.000 m
-820.000 395.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 382.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 386.00 Td
-(21) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 385.000 m
-820.000 385.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 372.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 376.00 Td
-(22) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 375.000 m
-820.000 375.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-293.99 488.33 Td
-(E22-400MM22S) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-293.99 497.33 Td
-(E22-900MM22S) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-267.000 485.000 m
-333.000 485.000 l
-334.105 485.000 335.000 484.105 335.000 483.000 c
-335.000 377.000 l
-335.000 375.895 333.895 375.000 333.000 375.000 c
-267.000 375.000 l
-265.895 375.000 265.000 376.105 265.000 377.000 c
-265.000 483.000 l
-265.000 484.105 266.105 485.000 267.000 485.000 c
-S
-1.00 w
-0.53 0.00 0.00 RG
-0.53 0.00 0.00 rg
-[] 0 d
-271.50 480.00 m 271.50 480.83 270.83 481.50 270.00 481.50 c
-269.17 481.50 268.50 480.83 268.50 480.00 c
-268.50 479.17 269.17 478.50 270.00 478.50 c
-270.83 478.50 271.50 479.17 271.50 480.00 c
-B
-BT
-/F1 9 Tf
-9.00 TL
-1.000 0.000 0.000 rg
-268.70 471.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-1.000 0.000 0.000 rg
-258.79 476.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-1.00 0.00 0.00 RG
-[] 0 d
-255.000 475.000 m
-265.000 475.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-268.70 461.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-258.79 466.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-255.000 465.000 m
-265.000 465.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 451.00 Td
-(NRST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 456.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 455.000 m
-265.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 441.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 446.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 445.000 m
-265.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 431.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 436.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 435.000 m
-265.000 435.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 421.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 426.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 425.000 m
-265.000 425.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-268.70 411.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-258.79 416.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-255.000 415.000 m
-265.000 415.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 401.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 406.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 405.000 m
-265.000 405.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 391.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 396.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 395.000 m
-265.000 395.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 381.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-253.07 386.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 385.000 m
-265.000 385.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-306.87 381.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 386.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 385.000 m
-335.000 385.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-306.71 391.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 396.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 395.000 m
-335.000 395.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-306.71 401.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 406.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 405.000 m
-335.000 405.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-312.27 411.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 416.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 415.000 m
-335.000 415.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-312.64 421.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 426.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 425.000 m
-335.000 425.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-310.66 431.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-335.50 436.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-345.000 435.000 m
-335.000 435.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-318.29 441.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 446.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 445.000 m
-335.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-307.79 451.00 Td
-(DIO3) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 456.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 455.000 m
-335.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-307.79 461.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 466.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 465.000 m
-335.000 465.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-307.79 471.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 476.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 475.000 m
-335.000 475.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 366.70 381.60 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 385.000 m
-350.000 390.000 l
-365.000 390.000 l
-365.000 380.000 l
-350.000 380.000 l
-345.000 385.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 203.19 381.40 Tm
-(RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-255.000 385.000 m
-250.000 380.000 l
-235.000 380.000 l
-235.000 390.000 l
-250.000 390.000 l
-255.000 385.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 366.71 421.60 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 425.000 m
-350.000 430.000 l
-365.000 430.000 l
-365.000 420.000 l
-350.000 420.000 l
-345.000 425.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.00 391.60 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 395.000 m
-350.000 400.000 l
-365.000 400.000 l
-365.000 390.000 l
-350.000 390.000 l
-345.000 395.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.00 401.60 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 405.000 m
-350.000 410.000 l
-365.000 410.000 l
-365.000 400.000 l
-350.000 400.000 l
-345.000 405.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.00 411.60 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 415.000 m
-350.000 420.000 l
-365.000 420.000 l
-365.000 410.000 l
-350.000 410.000 l
-345.000 415.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.01 461.15 Tm
-(DIO2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 465.000 m
-350.000 470.000 l
-365.000 470.000 l
-365.000 460.000 l
-350.000 460.000 l
-345.000 465.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.00 471.60 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 475.000 m
-350.000 480.000 l
-365.000 480.000 l
-365.000 470.000 l
-350.000 470.000 l
-345.000 475.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 203.74 391.40 Tm
-(TXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-255.000 395.000 m
-250.000 390.000 l
-235.000 390.000 l
-235.000 400.000 l
-250.000 400.000 l
-255.000 395.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 205.06 451.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-255.000 455.000 m
-250.000 450.000 l
-235.000 450.000 l
-235.000 460.000 l
-250.000 460.000 l
-255.000 455.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.01 451.15 Tm
-(DIO3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 455.000 m
-350.000 460.000 l
-365.000 460.000 l
-365.000 450.000 l
-350.000 450.000 l
-345.000 455.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 460.06 456.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 460.000 m
-505.000 455.000 l
-490.000 455.000 l
-490.000 465.000 l
-505.000 465.000 l
-510.000 460.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 671.71 446.60 Tm
-(TXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-650.000 450.000 m
-655.000 455.000 l
-670.000 455.000 l
-670.000 445.000 l
-655.000 445.000 l
-650.000 450.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 469.19 476.40 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 480.000 m
-505.000 475.000 l
-490.000 475.000 l
-490.000 485.000 l
-505.000 485.000 l
-510.000 480.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 672.01 456.15 Tm
-(DIO2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-650.000 460.000 m
-655.000 465.000 l
-670.000 465.000 l
-670.000 455.000 l
-655.000 455.000 l
-650.000 460.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 474.69 416.40 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 420.000 m
-505.000 415.000 l
-490.000 415.000 l
-490.000 425.000 l
-505.000 425.000 l
-510.000 420.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 460.61 436.40 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 440.000 m
-505.000 435.000 l
-490.000 435.000 l
-490.000 445.000 l
-505.000 445.000 l
-510.000 440.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 460.61 446.40 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 450.000 m
-505.000 445.000 l
-490.000 445.000 l
-490.000 455.000 l
-505.000 455.000 l
-510.000 450.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 466.77 426.40 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 430.000 m
-505.000 425.000 l
-490.000 425.000 l
-490.000 435.000 l
-505.000 435.000 l
-510.000 430.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 671.71 436.60 Tm
-(RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-650.000 440.000 m
-655.000 445.000 l
-670.000 445.000 l
-670.000 435.000 l
-655.000 435.000 l
-650.000 440.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 458.85 466.40 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 470.000 m
-505.000 465.000 l
-490.000 465.000 l
-490.000 475.000 l
-505.000 475.000 l
-510.000 470.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 467.50 395.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 400.000 m
-510.000 400.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 409.000 m
-500.000 391.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-498.000 406.000 m
-498.000 394.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-496.000 403.000 m
-496.000 397.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-494.000 401.000 m
-494.000 399.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 467.50 375.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 380.000 m
-510.000 380.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 389.000 m
-500.000 371.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-498.000 386.000 m
-498.000 374.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-496.000 383.000 m
-496.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-494.000 381.000 m
-494.000 379.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 637.00 352.76 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-650.000 370.000 m
-650.000 380.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-641.000 370.000 m
-659.000 370.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-644.000 368.000 m
-656.000 368.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-647.000 366.000 m
-653.000 366.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-649.000 364.000 m
-651.000 364.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 637.00 509.13 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-650.000 500.000 m
-650.000 490.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-659.000 500.000 m
-641.000 500.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-656.000 502.000 m
-644.000 502.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-653.000 504.000 m
-647.000 504.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-651.000 506.000 m
-649.000 506.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 467.50 485.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 490.000 m
-510.000 490.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 499.000 m
-500.000 481.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-498.000 496.000 m
-498.000 484.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-496.000 493.000 m
-496.000 487.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-494.000 491.000 m
-494.000 489.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 361.50 430.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 435.000 m
-345.000 435.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 426.000 m
-355.000 444.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-357.000 429.000 m
-357.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-359.000 432.000 m
-359.000 438.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-361.000 434.000 m
-361.000 436.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 212.50 410.93 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-245.000 415.000 m
-255.000 415.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-245.000 424.000 m
-245.000 406.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-243.000 421.000 m
-243.000 409.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-241.000 418.000 m
-241.000 412.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-239.000 416.000 m
-239.000 414.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 212.50 460.93 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-245.000 465.000 m
-255.000 465.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-245.000 474.000 m
-245.000 456.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-243.000 471.000 m
-243.000 459.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-241.000 468.000 m
-241.000 462.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-239.000 466.000 m
-239.000 464.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-940.000 370.000 m
-940.000 425.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 757.50 480.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 485.000 m
-800.000 485.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 494.000 m
-790.000 476.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-788.000 491.000 m
-788.000 479.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-786.000 488.000 m
-786.000 482.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-784.000 486.000 m
-784.000 484.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 927.00 504.09 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-940.000 495.000 m
-940.000 485.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-949.000 495.000 m
-931.000 495.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-946.000 497.000 m
-934.000 497.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-943.000 499.000 m
-937.000 499.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-941.000 501.000 m
-939.000 501.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 757.50 370.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 375.000 m
-800.000 375.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 384.000 m
-790.000 366.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-788.000 381.000 m
-788.000 369.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-786.000 378.000 m
-786.000 372.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-784.000 376.000 m
-784.000 374.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 757.50 390.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 395.000 m
-800.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 404.000 m
-790.000 386.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-788.000 401.000 m
-788.000 389.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-786.000 398.000 m
-786.000 392.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-784.000 396.000 m
-784.000 394.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 748.85 461.40 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 465.000 m
-795.000 460.000 l
-780.000 460.000 l
-780.000 470.000 l
-795.000 470.000 l
-800.000 465.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 961.71 431.60 Tm
-(RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-940.000 435.000 m
-945.000 440.000 l
-960.000 440.000 l
-960.000 430.000 l
-945.000 430.000 l
-940.000 435.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 756.77 421.40 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 425.000 m
-795.000 420.000 l
-780.000 420.000 l
-780.000 430.000 l
-795.000 430.000 l
-800.000 425.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 750.61 441.40 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 445.000 m
-795.000 440.000 l
-780.000 440.000 l
-780.000 450.000 l
-795.000 450.000 l
-800.000 445.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 750.61 431.40 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 435.000 m
-795.000 430.000 l
-780.000 430.000 l
-780.000 440.000 l
-795.000 440.000 l
-800.000 435.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 764.69 411.40 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 415.000 m
-795.000 410.000 l
-780.000 410.000 l
-780.000 420.000 l
-795.000 420.000 l
-800.000 415.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 962.01 451.15 Tm
-(DIO2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-940.000 455.000 m
-945.000 460.000 l
-960.000 460.000 l
-960.000 450.000 l
-945.000 450.000 l
-940.000 455.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 759.19 471.40 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 475.000 m
-795.000 470.000 l
-780.000 470.000 l
-780.000 480.000 l
-795.000 480.000 l
-800.000 475.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 961.71 441.60 Tm
-(TXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-940.000 445.000 m
-945.000 450.000 l
-960.000 450.000 l
-960.000 440.000 l
-945.000 440.000 l
-940.000 445.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 750.06 451.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 455.000 m
-795.000 450.000 l
-780.000 450.000 l
-780.000 460.000 l
-795.000 460.000 l
-800.000 455.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 927.00 342.76 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-940.000 360.000 m
-940.000 370.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-931.000 360.000 m
-949.000 360.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-934.000 358.000 m
-946.000 358.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-937.000 356.000 m
-943.000 356.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-939.000 354.000 m
-941.000 354.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 243.00 487.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-255.000 485.000 m
-255.000 475.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-250.000 485.000 m
-260.000 485.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 660.50 465.93 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-660.000 470.000 m
-650.000 470.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-660.000 475.000 m
-660.000 465.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-175.000 425.000 m
-210.000 425.000 l
-255.000 425.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 142.00 429.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-155.000 445.000 m
-155.000 455.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-146.000 445.000 m
-164.000 445.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-149.000 443.000 m
-161.000 443.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-152.000 441.000 m
-158.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-154.000 439.000 m
-156.000 439.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 182.00 428.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-195.000 445.000 m
-195.000 455.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-186.000 445.000 m
-204.000 445.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-189.000 443.000 m
-201.000 443.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-192.000 441.000 m
-198.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-194.000 439.000 m
-196.000 439.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 950.50 460.92 Tm
-(+5V) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-950.000 465.000 m
-940.000 465.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-950.000 470.000 m
-950.000 460.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-650.000 490.000 m
-650.000 480.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-940.000 465.000 m
-940.000 475.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-231.000 294.000 m
-239.000 286.000 l
-239.000 294.000 m
-231.000 286.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-371.000 244.000 m
-379.000 236.000 l
-379.000 244.000 m
-371.000 236.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-371.000 284.000 m
-379.000 276.000 l
-379.000 284.000 m
-371.000 276.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-371.000 274.000 m
-379.000 266.000 l
-379.000 274.000 m
-371.000 266.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-231.000 204.000 m
-239.000 196.000 l
-239.000 204.000 m
-231.000 196.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-371.000 204.000 m
-379.000 196.000 l
-379.000 204.000 m
-371.000 196.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 397.71 246.28 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-375.000 250.000 m
-380.000 255.000 l
-395.000 255.000 l
-395.000 245.000 l
-380.000 245.000 l
-375.000 250.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 397.00 256.60 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-375.000 260.000 m
-380.000 265.000 l
-395.000 265.000 l
-395.000 255.000 l
-380.000 255.000 l
-375.000 260.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 183.85 236.40 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 240.000 m
-230.000 235.000 l
-215.000 235.000 l
-215.000 245.000 l
-230.000 245.000 l
-235.000 240.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 199.69 246.40 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 250.000 m
-230.000 245.000 l
-215.000 245.000 l
-215.000 255.000 l
-230.000 255.000 l
-235.000 250.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 185.61 266.40 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 270.000 m
-230.000 265.000 l
-215.000 265.000 l
-215.000 275.000 l
-230.000 275.000 l
-235.000 270.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 191.77 256.40 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 260.000 m
-230.000 255.000 l
-215.000 255.000 l
-215.000 265.000 l
-230.000 265.000 l
-235.000 260.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 185.61 276.40 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 280.000 m
-230.000 275.000 l
-215.000 275.000 l
-215.000 285.000 l
-230.000 285.000 l
-235.000 280.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-298.96 313.33 Td
-(E80-900M2213S) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-298.96 322.33 Td
-(E80-900M2213S) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 187.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 191.00 Td
-(22) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 190.000 m
-255.000 190.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 197.00 Td
-(ANT_2.4) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 201.00 Td
-(21) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 200.000 m
-255.000 200.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 207.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 211.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 210.000 m
-255.000 210.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 227.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 231.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 230.000 m
-255.000 230.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 237.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 241.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 240.000 m
-255.000 240.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 247.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 251.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 250.000 m
-255.000 250.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 257.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 261.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 260.000 m
-255.000 260.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 267.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 271.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 270.000 m
-255.000 270.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 277.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 281.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 280.000 m
-255.000 280.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 287.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 291.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 290.000 m
-255.000 290.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 297.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 301.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 300.000 m
-255.000 300.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-332.36 297.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 301.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 300.000 m
-355.000 300.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-334.29 287.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 291.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 290.000 m
-355.000 290.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-329.49 277.00 Td
-(DIO7) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 281.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 280.000 m
-355.000 280.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-329.49 267.00 Td
-(DIO8) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 271.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 270.000 m
-355.000 270.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-329.49 257.00 Td
-(DIO9) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 261.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 260.000 m
-355.000 260.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-328.32 247.00 Td
-(NRST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 251.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 250.000 m
-355.000 250.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-339.99 237.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 241.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 240.000 m
-355.000 240.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-332.36 227.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 231.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 230.000 m
-355.000 230.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-332.36 207.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 211.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 210.000 m
-355.000 210.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-311.72 197.00 Td
-(ANT_900) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 201.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 200.000 m
-355.000 200.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-332.36 187.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 191.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 190.000 m
-355.000 190.000 l
-S
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-257.000 310.000 m
-353.000 310.000 l
-354.105 310.000 355.000 309.105 355.000 308.000 c
-355.000 172.000 l
-355.000 170.895 353.895 170.000 353.000 170.000 c
-257.000 170.000 l
-255.895 170.000 255.000 171.105 255.000 172.000 c
-255.000 308.000 l
-255.000 309.105 256.105 310.000 257.000 310.000 c
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 362.00 163.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-375.000 180.000 m
-375.000 190.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-366.000 180.000 m
-384.000 180.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-369.000 178.000 m
-381.000 178.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-372.000 176.000 m
-378.000 176.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-374.000 174.000 m
-376.000 174.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 391.50 206.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 210.000 m
-375.000 210.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 201.000 m
-385.000 219.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-387.000 204.000 m
-387.000 216.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-389.000 207.000 m
-389.000 213.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-391.000 209.000 m
-391.000 211.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 391.50 226.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 230.000 m
-375.000 230.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 221.000 m
-385.000 239.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-387.000 224.000 m
-387.000 236.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-389.000 227.000 m
-389.000 233.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-391.000 229.000 m
-391.000 231.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 391.50 296.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 300.000 m
-375.000 300.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 291.000 m
-385.000 309.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-387.000 294.000 m
-387.000 306.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-389.000 297.000 m
-389.000 303.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-391.000 299.000 m
-391.000 301.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 192.50 296.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 300.000 m
-235.000 300.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 309.000 m
-225.000 291.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-223.000 306.000 m
-223.000 294.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-221.000 303.000 m
-221.000 297.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-219.000 301.000 m
-219.000 299.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 192.50 226.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 230.000 m
-235.000 230.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 239.000 m
-225.000 221.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-223.000 236.000 m
-223.000 224.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-221.000 233.000 m
-221.000 227.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-219.000 231.000 m
-219.000 229.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 192.50 206.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 210.000 m
-235.000 210.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 219.000 m
-225.000 201.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-223.000 216.000 m
-223.000 204.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-221.000 213.000 m
-221.000 207.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-219.000 211.000 m
-219.000 209.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 192.50 186.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 190.000 m
-235.000 190.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 199.000 m
-225.000 181.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-223.000 196.000 m
-223.000 184.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-221.000 193.000 m
-221.000 187.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-219.000 191.000 m
-219.000 189.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 386.00 286.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 290.000 m
-375.000 290.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 295.000 m
-385.000 285.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-160.000 675.000 m
-160.000 680.000 l
-160.000 685.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 655.000 m
-160.000 655.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 645.000 m
-160.000 645.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 635.000 m
-160.000 635.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 625.000 m
-160.000 625.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 615.000 m
-160.000 615.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 605.000 m
-160.000 605.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 112.70 591.40 Tm
-(P1.06) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-160.000 595.000 m
-155.000 590.000 l
-140.000 590.000 l
-140.000 600.000 l
-155.000 600.000 l
-160.000 595.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 112.70 701.40 Tm
-(P0.06) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-160.000 705.000 m
-155.000 700.000 l
-140.000 700.000 l
-140.000 710.000 l
-155.000 710.000 l
-160.000 705.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 112.70 691.40 Tm
-(P0.08) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-160.000 695.000 m
-155.000 690.000 l
-140.000 690.000 l
-140.000 700.000 l
-155.000 700.000 l
-160.000 695.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-417.00 632.75 Td
-(1.5M) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-417.00 641.75 Td
-(R2) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.63 0.00 0.00 RG
-[] 0 d
-405.00 655.00 10.00 -20.00 re
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-410.000 655.000 m
-410.000 665.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-410.000 635.000 m
-410.000 625.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-0.00 1.00 -1.00 0.00 413.70 727.50 Tm
-(Batt) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-410.000 705.000 m
-405.000 710.000 l
-405.000 725.000 l
-415.000 725.000 l
-415.000 710.000 l
-410.000 705.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-186.000 549.000 m
-194.000 541.000 l
-194.000 549.000 m
-186.000 541.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-196.000 549.000 m
-204.000 541.000 l
-204.000 549.000 m
-196.000 541.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-206.000 549.000 m
-214.000 541.000 l
-214.000 549.000 m
-206.000 541.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-276.000 719.000 m
-284.000 711.000 l
-284.000 719.000 m
-276.000 711.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-156.000 719.000 m
-164.000 711.000 l
-164.000 719.000 m
-156.000 711.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 397.00 599.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-410.000 615.000 m
-410.000 625.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-401.000 615.000 m
-419.000 615.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-404.000 613.000 m
-416.000 613.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-407.000 611.000 m
-413.000 611.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-409.000 609.000 m
-411.000 609.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-410.000 665.000 m
-280.000 665.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-417.00 672.75 Td
-(1M) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-417.00 681.75 Td
-(R1) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.63 0.00 0.00 RG
-[] 0 d
-405.00 695.00 10.00 -20.00 re
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-410.000 695.000 m
-410.000 705.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-410.000 675.000 m
-410.000 665.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 685.000 m
-280.000 685.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.01 681.07 Tm
-(RBtn) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 685.000 m
-295.000 690.000 l
-310.000 690.000 l
-310.000 680.000 l
-295.000 680.000 l
-290.000 685.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 99.24 621.60 Tm
-(UBtn) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 625.000 m
-140.000 620.000 l
-125.000 620.000 l
-125.000 630.000 l
-140.000 630.000 l
-145.000 625.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 117.50 676.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-150.000 680.000 m
-160.000 680.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-150.000 689.000 m
-150.000 671.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-148.000 686.000 m
-148.000 674.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-146.000 683.000 m
-146.000 677.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-144.000 681.000 m
-144.000 679.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 595.000 m
-280.000 595.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 655.000 m
-280.000 655.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 605.000 m
-280.000 605.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 625.000 m
-280.000 625.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 92.64 631.40 Tm
-(GPSen) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 635.000 m
-140.000 630.000 l
-125.000 630.000 l
-125.000 640.000 l
-140.000 640.000 l
-145.000 635.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 93.85 651.40 Tm
-(GPSrx) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 655.000 m
-140.000 650.000 l
-125.000 650.000 l
-125.000 660.000 l
-140.000 660.000 l
-145.000 655.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 94.40 641.40 Tm
-(GPStx) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 645.000 m
-140.000 640.000 l
-125.000 640.000 l
-125.000 650.000 l
-140.000 650.000 l
-145.000 645.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 615.000 m
-280.000 615.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 635.000 m
-280.000 635.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 645.000 m
-280.000 645.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-345.000 695.000 m
-280.000 695.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-345.000 675.000 m
-280.000 675.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.00 601.60 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 605.000 m
-295.000 610.000 l
-310.000 610.000 l
-310.000 600.000 l
-295.000 600.000 l
-290.000 605.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.50 591.60 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 595.000 m
-295.000 600.000 l
-310.000 600.000 l
-310.000 590.000 l
-295.000 590.000 l
-290.000 595.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 356.00 671.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 675.000 m
-345.000 675.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 680.000 m
-355.000 670.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.00 621.60 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 625.000 m
-295.000 630.000 l
-310.000 630.000 l
-310.000 620.000 l
-295.000 620.000 l
-290.000 625.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.00 631.60 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 635.000 m
-295.000 640.000 l
-310.000 640.000 l
-310.000 630.000 l
-295.000 630.000 l
-290.000 635.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.00 641.60 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 645.000 m
-295.000 650.000 l
-310.000 650.000 l
-310.000 640.000 l
-295.000 640.000 l
-290.000 645.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 311.84 611.60 Tm
-(SCk) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 615.000 m
-295.000 620.000 l
-310.000 620.000 l
-310.000 610.000 l
-295.000 610.000 l
-290.000 615.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 311.70 651.60 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 655.000 m
-295.000 660.000 l
-310.000 660.000 l
-310.000 650.000 l
-295.000 650.000 l
-290.000 655.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 361.50 691.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 695.000 m
-345.000 695.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 686.000 m
-355.000 704.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-357.000 689.000 m
-357.000 701.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-359.000 692.000 m
-359.000 698.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-361.000 694.000 m
-361.000 696.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 302.50 701.30 Tm
-(Batt) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-280.000 705.000 m
-285.000 710.000 l
-300.000 710.000 l
-300.000 700.000 l
-285.000 700.000 l
-280.000 705.000 l
-S
-10.00 w
-BT
-/F1 13 Tf
-13.00 TL
-0.000 0.000 1.000 rg
-1015.00 25.00 Td
-(Nom De Tom) Tj
-ET
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 647.00 251.60 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-625.000 255.000 m
-630.000 260.000 l
-645.000 260.000 l
-645.000 250.000 l
-630.000 250.000 l
-625.000 255.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 513.00 297.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-525.000 295.000 m
-525.000 285.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-520.000 295.000 m
-530.000 295.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-625.000 175.000 m
-625.000 215.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 612.00 149.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-625.000 165.000 m
-625.000 175.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-616.000 165.000 m
-634.000 165.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-619.000 163.000 m
-631.000 163.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-622.000 161.000 m
-628.000 161.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-624.000 159.000 m
-626.000 159.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 474.33 201.85 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 205.000 m
-520.000 200.000 l
-505.000 200.000 l
-505.000 210.000 l
-520.000 210.000 l
-525.000 205.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 473.74 191.85 Tm
-(RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 195.000 m
-520.000 190.000 l
-505.000 190.000 l
-505.000 200.000 l
-520.000 200.000 l
-525.000 195.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 482.41 231.85 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 235.000 m
-520.000 230.000 l
-505.000 230.000 l
-505.000 240.000 l
-520.000 240.000 l
-525.000 235.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 476.41 251.85 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 255.000 m
-520.000 250.000 l
-505.000 250.000 l
-505.000 260.000 l
-520.000 260.000 l
-525.000 255.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 476.48 241.85 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 245.000 m
-520.000 240.000 l
-505.000 240.000 l
-505.000 250.000 l
-520.000 250.000 l
-525.000 245.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 489.69 221.45 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 225.000 m
-520.000 220.000 l
-505.000 220.000 l
-505.000 230.000 l
-520.000 230.000 l
-525.000 225.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 647.01 241.15 Tm
-(DIO2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-625.000 245.000 m
-630.000 250.000 l
-645.000 250.000 l
-645.000 240.000 l
-630.000 240.000 l
-625.000 245.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 474.33 181.85 Tm
-(TXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 185.000 m
-520.000 180.000 l
-505.000 180.000 l
-505.000 190.000 l
-520.000 190.000 l
-525.000 185.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 475.06 261.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 265.000 m
-520.000 260.000 l
-505.000 260.000 l
-505.000 270.000 l
-520.000 270.000 l
-525.000 265.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-568.96 298.33 Td
-(SX1262_MOD) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-584.58 282.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 286.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 285.000 m
-605.000 285.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-568.96 307.33 Td
-(CORE_SX1262) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-582.36 202.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 206.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 205.000 m
-605.000 205.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 192.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-534.28 196.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 195.000 m
-545.000 195.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 182.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-534.28 186.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 185.000 m
-545.000 185.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-579.49 242.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 246.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 245.000 m
-605.000 245.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-579.49 252.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 256.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 255.000 m
-605.000 255.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-582.36 192.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 196.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 195.000 m
-605.000 195.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 282.00 Td
-(3V3) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-534.28 286.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 285.000 m
-545.000 285.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 202.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-534.28 206.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 205.000 m
-545.000 205.000 l
-S
-1.00 w
-0.53 0.00 0.00 RG
-545.00 265.00 m 545.00 266.66 543.66 268.00 542.00 268.00 c
-540.34 268.00 539.00 266.66 539.00 265.00 c
-539.00 263.34 540.34 262.00 542.00 262.00 c
-543.66 262.00 545.00 263.34 545.00 265.00 c
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 262.00 Td
-(RST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 266.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 265.000 m
-539.000 265.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 252.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 256.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 255.000 m
-545.000 255.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 242.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 246.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 245.000 m
-545.000 245.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 232.000 m
-548.000 235.000 l
-545.000 238.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 232.00 Td
-(CLK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 236.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 235.000 m
-545.000 235.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 222.00 Td
-(CS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 226.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 225.000 m
-545.000 225.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-582.36 182.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 186.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 185.000 m
-605.000 185.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-582.36 212.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 216.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 215.000 m
-605.000 215.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-550.000 295.000 m
-600.000 295.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-605.000 290.000 m
-605.000 180.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-600.000 175.000 m
-550.000 175.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-545.000 180.000 m
-545.000 290.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 290.000 m
-545.000 295.000 545.000 295.000 550.000 295.000 c
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-600.000 295.000 m
-605.000 295.000 605.000 295.000 605.000 290.000 c
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-605.000 180.000 m
-605.000 175.000 605.000 175.000 600.000 175.000 c
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 180.000 m
-545.000 175.000 545.000 175.000 550.000 175.000 c
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 737.72 681.45 Tm
-(MCU_RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-820.000 685.000 m
-815.000 680.000 l
-800.000 680.000 l
-800.000 690.000 l
-815.000 690.000 l
-820.000 685.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-1115.00 362.67 Td
-(100uF) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-1115.00 371.67 Td
-(C1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1113.000 373.000 m
-1097.000 373.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-1105.000 365.000 m
-1105.000 355.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1105.000 385.000 m
-1105.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1097.000 377.000 m
-1113.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-1105.000 385.000 m
-1105.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1105.000 373.000 m
-1105.000 365.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-1065.00 362.67 Td
-(100uF) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-1065.00 371.67 Td
-(C2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1063.000 373.000 m
-1047.000 373.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-1055.000 365.000 m
-1055.000 355.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1055.000 385.000 m
-1055.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1047.000 377.000 m
-1063.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-1055.000 385.000 m
-1055.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1055.000 373.000 m
-1055.000 365.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1092.00 329.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1105.000 345.000 m
-1105.000 355.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1096.000 345.000 m
-1114.000 345.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1099.000 343.000 m
-1111.000 343.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1102.000 341.000 m
-1108.000 341.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1104.000 339.000 m
-1106.000 339.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1043.00 407.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1055.000 405.000 m
-1055.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1050.000 405.000 m
-1060.000 405.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1042.00 327.76 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1055.000 345.000 m
-1055.000 355.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1046.000 345.000 m
-1064.000 345.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1049.000 343.000 m
-1061.000 343.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1052.000 341.000 m
-1058.000 341.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1054.000 339.000 m
-1056.000 339.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1095.00 407.00 Tm
-(+5V) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1105.000 405.000 m
-1105.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1100.000 405.000 m
-1110.000 405.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 102.98 611.40 Tm
-(SCL) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 615.000 m
-140.000 610.000 l
-125.000 610.000 l
-125.000 620.000 l
-140.000 620.000 l
-145.000 615.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 101.11 601.40 Tm
-(SDA) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 605.000 m
-140.000 600.000 l
-125.000 600.000 l
-125.000 610.000 l
-140.000 610.000 l
-145.000 605.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-820.000 685.000 m
-840.000 685.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 77.72 661.45 Tm
-(MCU_RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-160.000 665.000 m
-155.000 660.000 l
-140.000 660.000 l
-140.000 670.000 l
-155.000 670.000 l
-160.000 665.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-160.000 715.000 m
-165.000 715.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-621.000 289.000 m
-629.000 281.000 l
-629.000 289.000 m
-621.000 281.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-251.000 439.000 m
-259.000 431.000 l
-259.000 439.000 m
-251.000 431.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-251.000 449.000 m
-259.000 441.000 l
-259.000 449.000 m
-251.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-341.000 449.000 m
-349.000 441.000 l
-349.000 449.000 m
-341.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-251.000 409.000 m
-259.000 401.000 l
-259.000 409.000 m
-251.000 401.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-171.000 479.000 m
-179.000 471.000 l
-179.000 479.000 m
-171.000 471.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 432.69 661.28 Tm
-(ADC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-410.000 665.000 m
-415.000 670.000 l
-430.000 670.000 l
-430.000 660.000 l
-415.000 660.000 l
-410.000 665.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-213.86 728.26 Td
-(PRO_MICRO_NRF52840_29P) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-213.86 737.11 Td
-(PRO-MICRO) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-180.00 725.00 80.00 -160.00 re
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-234.94 702.00 Td
-(BATIN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 706.00 Td
-(25) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 705.000 m
-260.000 705.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-240.95 692.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 696.00 Td
-(24) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 695.000 m
-260.000 695.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-243.04 682.00 Td
-(RST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 686.00 Td
-(23) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 685.000 m
-260.000 685.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-226.28 672.00 Td
-(3.3v Out) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 676.00 Td
-(22) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 675.000 m
-260.000 675.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 632.00 Td
-(P1.15) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 636.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 635.000 m
-260.000 635.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 662.00 Td
-(P0.31) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 666.00 Td
-(21) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 665.000 m
-260.000 665.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 692.00 Td
-(P0.08) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 696.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 695.000 m
-180.000 695.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 622.00 Td
-(P1.13) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 626.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 625.000 m
-260.000 625.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 652.00 Td
-(P0.29) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 656.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 655.000 m
-260.000 655.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 612.00 Td
-(P1.11) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 616.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 615.000 m
-260.000 615.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 642.00 Td
-(P0.02) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 646.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 645.000 m
-260.000 645.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 682.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 686.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 685.000 m
-180.000 685.000 l
-S
-1.00 w
-0.55 0.14 0.14 RG
-180.00 705.00 m 180.00 706.66 178.66 708.00 177.00 708.00 c
-175.34 708.00 174.00 706.66 174.00 705.00 c
-174.00 703.34 175.34 702.00 177.00 702.00 c
-178.66 702.00 180.00 703.34 180.00 705.00 c
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 702.00 Td
-(P0.06) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 706.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 705.000 m
-174.000 705.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 592.00 Td
-(P0.09) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 596.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 595.000 m
-260.000 595.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 602.00 Td
-(P0.10) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 606.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 605.000 m
-260.000 605.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 602.00 Td
-(P1.04) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-162.57 606.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 605.000 m
-180.000 605.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 672.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 676.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 675.000 m
-180.000 675.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 662.00 Td
-(P0.17) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 666.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 665.000 m
-180.000 665.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 652.00 Td
-(P0.20) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 656.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 655.000 m
-180.000 655.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 642.00 Td
-(P0.22) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 646.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 645.000 m
-180.000 645.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 632.00 Td
-(P0.24) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 636.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 635.000 m
-180.000 635.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 622.00 Td
-(P1.00) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-162.57 626.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 625.000 m
-180.000 625.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 612.00 Td
-(P0.11) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-162.57 616.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 615.000 m
-180.000 615.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 592.00 Td
-(P1.06) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-162.57 596.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 595.000 m
-180.000 595.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 193.00 568.00 Tm
-(P1.01) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 189.00 547.57 Tm
-(27) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-190.000 545.000 m
-190.000 565.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 203.00 568.00 Tm
-(P1.02) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 199.00 547.57 Tm
-(28) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-200.000 545.000 m
-200.000 565.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 213.00 568.00 Tm
-(P1.07) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 209.00 547.57 Tm
-(29) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-210.000 545.000 m
-210.000 565.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-234.94 712.00 Td
-(BATIN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 716.00 Td
-(26) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 715.000 m
-260.000 715.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 712.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 716.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 715.000 m
-180.000 715.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-864.25 253.00 Td
-(RA-02_C9900010926) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-864.25 262.00 Td
-(RA-02) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-837.000 250.000 m
-903.000 250.000 l
-904.105 250.000 905.000 249.105 905.000 248.000 c
-905.000 162.000 l
-905.000 160.895 903.895 160.000 903.000 160.000 c
-837.000 160.000 l
-835.895 160.000 835.000 161.105 835.000 162.000 c
-835.000 248.000 l
-835.000 249.105 836.105 250.000 837.000 250.000 c
-S
-1.00 w
-0.53 0.00 0.00 RG
-0.53 0.00 0.00 rg
-[] 0 d
-841.50 245.00 m 841.50 245.83 840.83 246.50 840.00 246.50 c
-839.17 246.50 838.50 245.83 838.50 245.00 c
-838.50 244.17 839.17 243.50 840.00 243.50 c
-840.83 243.50 841.50 244.17 841.50 245.00 c
-B
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-838.70 236.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-828.78 241.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-825.000 240.000 m
-835.000 240.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-838.70 226.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-828.78 231.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-825.000 230.000 m
-835.000 230.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 216.00 Td
-(3.3V) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 221.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 220.000 m
-835.000 220.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 206.00 Td
-(RESET) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 211.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 210.000 m
-835.000 210.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 196.00 Td
-(DIO0) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 201.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 200.000 m
-835.000 200.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 186.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 191.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 190.000 m
-835.000 190.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 176.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 181.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 180.000 m
-835.000 180.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 166.00 Td
-(DIO3) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 171.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 170.000 m
-835.000 170.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-880.66 166.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-905.50 171.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-915.000 170.000 m
-905.000 170.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-877.79 176.00 Td
-(DIO4) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 181.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 180.000 m
-905.000 180.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-877.79 186.00 Td
-(DIO5) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 191.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 190.000 m
-905.000 190.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-882.64 196.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 201.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 200.000 m
-905.000 200.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-876.71 206.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 211.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 210.000 m
-905.000 210.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-876.71 216.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 221.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 220.000 m
-905.000 220.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-882.27 226.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 231.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 230.000 m
-905.000 230.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-880.66 236.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-905.50 241.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-915.000 240.000 m
-905.000 240.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 773.85 196.40 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-825.000 200.000 m
-820.000 195.000 l
-805.000 195.000 l
-805.000 205.000 l
-820.000 205.000 l
-825.000 200.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 937.00 226.60 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-915.000 230.000 m
-920.000 235.000 l
-935.000 235.000 l
-935.000 225.000 l
-920.000 225.000 l
-915.000 230.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 936.71 196.60 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-915.000 200.000 m
-920.000 205.000 l
-935.000 205.000 l
-935.000 195.000 l
-920.000 195.000 l
-915.000 200.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 937.00 216.60 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-915.000 220.000 m
-920.000 225.000 l
-935.000 225.000 l
-935.000 215.000 l
-920.000 215.000 l
-915.000 220.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 937.00 206.60 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-915.000 210.000 m
-920.000 215.000 l
-935.000 215.000 l
-935.000 205.000 l
-920.000 205.000 l
-915.000 210.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 775.06 206.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-825.000 210.000 m
-820.000 205.000 l
-805.000 205.000 l
-805.000 215.000 l
-820.000 215.000 l
-825.000 210.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 784.19 186.40 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-825.000 190.000 m
-820.000 185.000 l
-805.000 185.000 l
-805.000 195.000 l
-820.000 195.000 l
-825.000 190.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 931.50 166.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-925.000 170.000 m
-915.000 170.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-925.000 161.000 m
-925.000 179.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-927.000 164.000 m
-927.000 176.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-929.000 167.000 m
-929.000 173.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-931.000 169.000 m
-931.000 171.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 931.50 236.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-925.000 240.000 m
-915.000 240.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-925.000 231.000 m
-925.000 249.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-927.000 234.000 m
-927.000 246.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-929.000 237.000 m
-929.000 243.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-931.000 239.000 m
-931.000 241.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 782.50 236.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-815.000 240.000 m
-825.000 240.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-815.000 249.000 m
-815.000 231.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-813.000 246.000 m
-813.000 234.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-811.000 243.000 m
-811.000 237.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-809.000 241.000 m
-809.000 239.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-825.000 240.000 m
-825.000 230.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 789.82 216.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-815.000 220.000 m
-825.000 220.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-815.000 215.000 m
-815.000 225.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-911.000 194.000 m
-919.000 186.000 l
-919.000 194.000 m
-911.000 186.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-911.000 184.000 m
-919.000 176.000 l
-919.000 184.000 m
-911.000 176.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-821.000 184.000 m
-829.000 176.000 l
-829.000 184.000 m
-821.000 176.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-821.000 174.000 m
-829.000 166.000 l
-829.000 174.000 m
-821.000 166.000 l
-S
-0.80 0.00 0.00 rg
-652.50 420.00 m 652.50 421.38 651.38 422.50 650.00 422.50 c
-648.62 422.50 647.50 421.38 647.50 420.00 c
-647.50 418.62 648.62 417.50 650.00 417.50 c
-651.38 417.50 652.50 418.62 652.50 420.00 c
-f
-0.80 0.00 0.00 rg
-942.50 415.00 m 942.50 416.38 941.38 417.50 940.00 417.50 c
-938.62 417.50 937.50 416.38 937.50 415.00 c
-937.50 413.62 938.62 412.50 940.00 412.50 c
-941.38 412.50 942.50 413.62 942.50 415.00 c
-f
-0.80 0.00 0.00 rg
-942.50 395.00 m 942.50 396.38 941.38 397.50 940.00 397.50 c
-938.62 397.50 937.50 396.38 937.50 395.00 c
-937.50 393.62 938.62 392.50 940.00 392.50 c
-941.38 392.50 942.50 393.62 942.50 395.00 c
-f
-0.80 0.00 0.00 rg
-942.50 385.00 m 942.50 386.38 941.38 387.50 940.00 387.50 c
-938.62 387.50 937.50 386.38 937.50 385.00 c
-937.50 383.62 938.62 382.50 940.00 382.50 c
-941.38 382.50 942.50 383.62 942.50 385.00 c
-f
-0.80 0.00 0.00 rg
-942.50 375.00 m 942.50 376.38 941.38 377.50 940.00 377.50 c
-938.62 377.50 937.50 376.38 937.50 375.00 c
-937.50 373.62 938.62 372.50 940.00 372.50 c
-941.38 372.50 942.50 373.62 942.50 375.00 c
-f
-0.80 0.00 0.00 rg
-652.50 490.00 m 652.50 491.38 651.38 492.50 650.00 492.50 c
-648.62 492.50 647.50 491.38 647.50 490.00 c
-647.50 488.62 648.62 487.50 650.00 487.50 c
-651.38 487.50 652.50 488.62 652.50 490.00 c
-f
-0.80 0.00 0.00 rg
-942.50 465.00 m 942.50 466.38 941.38 467.50 940.00 467.50 c
-938.62 467.50 937.50 466.38 937.50 465.00 c
-937.50 463.62 938.62 462.50 940.00 462.50 c
-941.38 462.50 942.50 463.62 942.50 465.00 c
-f
-0.80 0.00 0.00 rg
-412.50 665.00 m 412.50 666.38 411.38 667.50 410.00 667.50 c
-408.62 667.50 407.50 666.38 407.50 665.00 c
-407.50 663.62 408.62 662.50 410.00 662.50 c
-411.38 662.50 412.50 663.62 412.50 665.00 c
-f
-0.80 0.00 0.00 rg
-162.50 680.00 m 162.50 681.38 161.38 682.50 160.00 682.50 c
-158.62 682.50 157.50 681.38 157.50 680.00 c
-157.50 678.62 158.62 677.50 160.00 677.50 c
-161.38 677.50 162.50 678.62 162.50 680.00 c
-f
-0.80 0.00 0.00 rg
-627.50 185.00 m 627.50 186.38 626.38 187.50 625.00 187.50 c
-623.62 187.50 622.50 186.38 622.50 185.00 c
-622.50 183.62 623.62 182.50 625.00 182.50 c
-626.38 182.50 627.50 183.62 627.50 185.00 c
-f
-0.80 0.00 0.00 rg
-627.50 195.00 m 627.50 196.38 626.38 197.50 625.00 197.50 c
-623.62 197.50 622.50 196.38 622.50 195.00 c
-622.50 193.62 623.62 192.50 625.00 192.50 c
-626.38 192.50 627.50 193.62 627.50 195.00 c
-f
-0.80 0.00 0.00 rg
-627.50 205.00 m 627.50 206.38 626.38 207.50 625.00 207.50 c
-623.62 207.50 622.50 206.38 622.50 205.00 c
-622.50 203.62 623.62 202.50 625.00 202.50 c
-626.38 202.50 627.50 203.62 627.50 205.00 c
-f
-0.80 0.00 0.00 rg
-827.50 240.00 m 827.50 241.38 826.38 242.50 825.00 242.50 c
-823.62 242.50 822.50 241.38 822.50 240.00 c
-822.50 238.62 823.62 237.50 825.00 237.50 c
-826.38 237.50 827.50 238.62 827.50 240.00 c
-f
-q
-102.00 0 0 20.00 706.00 30.50 cm
-/I0 Do
-Q
-endstream
-endobj
-1 0 obj
-<>
-endobj
-5 0 obj
-<<
-/Descent -209
-/CapHeight 727
-/StemV 0
-/Type /FontDescriptor
-/Flags 32
-/FontBBox [-559 -303 1446 1050]
-/FontName /Verdana
-/ItalicAngle 0
-/Ascent 1005
->>
-endobj
-6 0 obj
-<>
-endobj
-7 0 obj
-<<
-/Type /Font
-/BaseFont /Times-Roman
-/Subtype /Type1
-/Encoding /WinAnsiEncoding
-/FirstChar 32
-/LastChar 255
->>
-endobj
-8 0 obj
-<<
-/Descent -325
-/CapHeight 500
-/StemV 80
-/Type /FontDescriptor
-/Flags 32
-/FontBBox [-665 -325 2000 1006]
-/FontName /Arial
-/ItalicAngle 0
-/Ascent 1006
->>
-endobj
-9 0 obj
-<>
-endobj
-10 0 obj
-<<
-/Type /XObject
-/Subtype /Image
-/Width 520
-/Height 105
-/ColorSpace /DeviceRGB
-/BitsPerComponent 8
-/DecodeParms <>
-/SMask 11 0 R
-/Length 6251
-/Filter /FlateDecode
->>
-stream
-xí�K�ÉqÇ£ª{†$V>ì:ùdm]uXbÅ™æ�Zð‰>Ù€lËòh€Ëy�-Ða÷$`!q‡Mr…õÁ€N|ÖÑGëØ^_ÎLU¸ªz8ì™îÊügfdfõ0~° ÃÛS•••�‚EQe�"÷ EQ”a¡ŠAQE¹ÀÛ¢&\ÔíãÖógnþ—_ÿ»@Q´ÿ�æJ¦k#úôûoËü(Š¢œs•ßd—AO%1ÛÜGó·Å˜fª!Eyk¸‚ònç!×�&¨…®è”Ä쯯àŒ)Š¢,r¥ÄÜö—lì´óUÓ³¿¹Ró¦(вÈpÛ\DUánGòüo¯Èì)Š¢,²ö¢mçó(î.Á@QÒ³¿\û9TEYd½…ÚdŸ—#‹Óܼªésu.)ŠrUXWqvkŸG< á7yöWƒ�¢(J k)˶÷y€ãnôÔsÕ
Š¢¬?ë'ȶ÷ºì„¡Rtøƒ�OQÅÆ:‰°oNùë×s€™žkºƒ¢(kËÚȯɯÏ`U7(Š²Æ¬‡ðj£�Y8µn+jŠá›RÝ (Êš²’K@+´Uñ¨à³¸Ö™ñˆ¸Ùš4ÚBd^T7(вެ�Ø
-W㊞ºg)ß}À§]
¾4TIQ”µcè2+D+H åí‡]ÉnoŽh6ú<+Š¢œ“S`½?åw~‡NŽ©ì*cÏ{!´ÿæßqÝë~j¡`z&íÃñVêPRe½È °�øË4—ÑÓhºß¦ÑyÏ4¹AQ”5!�´šìvñ¦Qo˜¤_Âí}¯Æ?cšý…êEQÖ€¢*Q®ò Íþ>‘ä½yÀ×ÈÍÏû¥(Šƒ¸¢êÖ>�?-h–ÃQãêV₞ɡôÁ”Ç7âߦêb…õ~ýÙ>HRǸ¦�Ú!ŒÐ<ª²¹^}v~Ùü[TTíÿaŽY�UmþA¤óËX³³Ó$¤©‡�ñ;9lÒìφ²"wö9$ØÊÝ3;Ây¦Fð�ÂÀQµ®�¤ª¨<¢íb~ºx�-Q.š²úéõ}úý|Šá Ó±Ãïu
-=9àÔ,jšiãŠõäîc>9‰R#À bóɪ«Æ|©£™á®X�ðñªýË_4eõÓb í—'÷™6~ÿª /†¡¶v¹Kßó!I€€"NžbeÀj‰´�áÎï”ÓŽaž ?ûÍfòƒ”¼bê꧃1N§‡“�Ò3°Èº¸w•ErU±4¯–;q5Ž<‚LÇcàçÉU”æóbWL¿n%_œ„ìÑt ÞÌ<Ša0ê\qb{—‹ôûKj³_ûÛª§YÃé=Àß~Äã
-úe$CSæŠé½Õ#¦Ï†$_ný�^¡?.êv£š]>êŽAÁÉÕ6±<¡Ãþ0tϤ"/R®ÛÛ>X$¥%¤‹˜AZ�Ú1c§è{÷øËod¸¯6Ç^Sr)k-䔎ŠzD/⇺lí³ÓÞ,†Æ¸bzÃs°&§gÁŒGè“ÇL§‰ïÙ1H¥®Xɲ¿´šÔUü®S]ŸÒ‹¿RèåB·róÄ“ºýwLw¨X|Òæ¥”]”î2C®Iç‘»ÄÓ2·wÙÍ,!S*¢N†àfÀÌã�©&»€Ybd]Îÿ]9U`~·qQ]eÞÄ3ß}ÄGØ‘ÑJRºYÒQšà¶¾ììr�ÖŒà.ïì¥ÍŒÈuNO7ø9ÇÄ�(þ—óŽ‚÷0ù'O˜�»¼vJ¬g�{Ûdº ±•Ù¥wæÏq9”ì
Åd+,æÓ©©QBV•Ðð�=>qUñº6F÷BÝ%1Ú·»‚ÎD؈û_Î㸤çý‘gVõPW)BØyÔ2„4±q�a¢‰ÊJÔŒ$¹Ÿpç€kë†Á8ªÖq]ž„t qÝà¿’.!êy-»8Í�þ‰”ˆ}ÀŽ,ÁwŽè§È·P²ƒ%¦?üC>4pT·»ºa®£—Ý߇8Ä'ßóZNšmÈÇÅ19¦‹� mƒ>¤¼QeM‡i÷—ˆ$-:„÷1Ç̱m¤ t^"ýµú\Ë©
8º4R!t� ó"ÎÔÝù!W'öŸ
êÅ}g�«‚ª’æ>�fd5ÓhDÿu�þéO3å‚|ÌÿñïmÅâç0¨I“±ÍÓ?x¤]¯“€–rü†
Ùù÷¹–›fR¡é4Hö•48•éá BK·rYA‹´1ôÊcúL:š{%·»õo˜¿¢;ógÏZp¿®é¥D1œ[{P|òÊÍ=²`2(«ºòµ£¼K˜êßúþÚï^ƒ6�ègrŽ_ç¹9‘ÂN›×ÙˆïÅ? “¬Åþš9,zYÌDí†4é<*W)*¤¡‘/w¥Óúäs8‘�y«+<¸¦.èEØ ºåšžË¹òœ/ä”Ãu%·ÕN´¯¼›ƒ‚ÉQ¨¼A>ø9Ï´ch�ÿ@‘9â3’hvsß~ÈÖ~Iá�‰žž7¾ó˜k[’|Êœ9Ó)ÿ«–sàé&*¯Ã¾”[<’°#eó„¢í
-úŸÙœ¿ƒÅÓ“(mŽ!èkz‘¾P‡\¾’`\ø¨ÌžhëõOŽèa^‚«Q]B’v™lª:P]}ëÇüµÿƒ~ùÕˆ~黨ðzyfdu³Û…�Œ’óëj´•´Éz'Îñéˆ>—ö Ëøƒ)o\nµÁôó`i%å4ˆNk4G Úå÷ˆRI¯ ˜à]/¢[|M·=.ƒ§-†°u»¾¡V'’W3AÖãŸÌ¯
¯¢_à:œ<`I�¥÷È-jÌÏ;FöLÃõÅP]£—ßK»cH¢®&Ϙ~
üÎW1„7¬æ8Y¨nW=!ƒêl