diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml index c048b7ac2..e93796614 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@v6 + uses: actions/upload-artifact@v7 with: name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }} overwrite: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0142c57a2..8f7670fa5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,16 +4,16 @@ - Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback - is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc... + is appreciated." This will allow other devs to potentially save you time by not accidentally duplicating work etc... - Please do not check in files that don't have real changes - Please do not reformat lines that you didn't have to change the code on -- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the linux version), +- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the Linux version), because it automatically follows our indentation rules and its auto reformatting will not cause spurious changes to lines. - If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description. - If your other co-developers have comments on your PR please tweak as needed. - Please also enable "Allow edits by maintainers". - Please do not submit untested code. -- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and commnunity members can help test your changes. +- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and community members can help test your changes. - If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord ## 🤝 Attestations diff --git a/.github/workflows/build_debian_src.yml b/.github/workflows/build_debian_src.yml index de114be1c..b3744493b 100644 --- a/.github/workflows/build_debian_src.yml +++ b/.github/workflows/build_debian_src.yml @@ -42,7 +42,7 @@ jobs: sudo mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} id: gpg @@ -64,7 +64,7 @@ jobs: PKG_VERSION: ${{ steps.version.outputs.deb }} - name: Store binaries as an artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 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 23690766a..c4c7a54e0 100644 --- a/.github/workflows/build_firmware.yml +++ b/.github/workflows/build_firmware.yml @@ -111,7 +111,7 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY - name: Store binaries as an artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 id: upload-firmware with: name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }} @@ -127,7 +127,7 @@ jobs: release/device-*.bat - name: Store manifests as an artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 id: upload-manifest with: name: manifest-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }} diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml index 9cc0bac78..0a1744edb 100644 --- a/.github/workflows/build_one_target.yml +++ b/.github/workflows/build_one_target.yml @@ -98,7 +98,7 @@ jobs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: path: ./ pattern: firmware-*-* @@ -111,7 +111,7 @@ jobs: run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat - name: Repackage in single firmware zip - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }} overwrite: true @@ -127,7 +127,7 @@ jobs: ./Meshtastic_nRF52_factory_erase*.uf2 retention-days: 30 - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: firmware-*-${{ needs.version.outputs.long }} merge-multiple: true @@ -146,7 +146,7 @@ jobs: run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output - name: Repackage in single elfs zip - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip overwrite: true diff --git a/.github/workflows/daily_packaging.yml b/.github/workflows/daily_packaging.yml index 392faeb8a..7df688055 100644 --- a/.github/workflows/daily_packaging.yml +++ b/.github/workflows/daily_packaging.yml @@ -35,8 +35,8 @@ jobs: series: - jammy # 22.04 LTS - noble # 24.04 LTS - - plucky # 25.04 - questing # 25.10 + - resolute # 26.04 LTS uses: ./.github/workflows/package_ppa.yml with: ppa_repo: ppa:meshtastic/daily diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 8d19af894..2bde9e729 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -60,13 +60,13 @@ jobs: - name: Docker login if: ${{ inputs.push }} - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: meshtastic password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Docker setup uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/docker_manifest.yml b/.github/workflows/docker_manifest.yml index 396ddb68e..37108101e 100644 --- a/.github/workflows/docker_manifest.yml +++ b/.github/workflows/docker_manifest.yml @@ -139,7 +139,7 @@ jobs: id: tags - name: Docker login - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: meshtastic password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }} diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 876d7e4ce..39da22ae0 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -96,7 +96,6 @@ jobs: pio_platform: ${{ matrix.check.platform }} pio_env: ${{ matrix.check.board }} pio_target: check - pio_opts: --fail-on-defect=low build: needs: [setup, version] @@ -178,7 +177,7 @@ jobs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: path: ./ pattern: firmware-${{matrix.arch}}-* @@ -188,7 +187,7 @@ jobs: run: ls -R - name: Repackage in single firmware zip - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} overwrite: true @@ -206,7 +205,7 @@ jobs: ./Meshtastic_nRF52_factory_erase*.uf2 retention-days: 30 - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true @@ -225,7 +224,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@v6 + uses: actions/upload-artifact@v7 with: name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }} overwrite: true @@ -251,13 +250,13 @@ jobs: filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head) fetch-depth: 0 - name: Download the current manifests - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: path: ./manifests-new/ pattern: manifest-* merge-multiple: true - name: Upload combined manifests for later commit and global stats crunching. - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 id: upload-manifest with: name: manifests-${{ github.sha }} @@ -325,14 +324,14 @@ jobs: body: ${{ steps.release_notes.outputs.notes }} - name: Download source deb - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 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@v7 + uses: actions/download-artifact@v8 with: pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} merge-multiple: true @@ -356,7 +355,7 @@ jobs: }' > firmware-${{ needs.version.outputs.long }}.json - name: Save Release manifest artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: manifest-${{ needs.version.outputs.long }} overwrite: true @@ -397,7 +396,7 @@ jobs: with: python-version: 3.x - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true @@ -414,7 +413,7 @@ jobs: - name: Zip firmware run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true @@ -455,14 +454,14 @@ jobs: python-version: 3.x - name: Get firmware artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} merge-multiple: true path: ./publish - name: Get manifest artifact - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: manifest-${{ needs.version.outputs.long }} path: ./publish diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index bd3f6d4eb..ad8534984 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -147,7 +147,7 @@ jobs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: path: ./ pattern: firmware-${{matrix.arch}}-* @@ -160,7 +160,7 @@ jobs: run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat - name: Repackage in single firmware zip - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} overwrite: true @@ -176,7 +176,7 @@ jobs: ./Meshtastic_nRF52_factory_erase*.uf2 retention-days: 30 - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true @@ -195,7 +195,7 @@ jobs: run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output - name: Repackage in single elfs zip - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }} overwrite: true @@ -235,14 +235,14 @@ jobs: Autogenerated by github action, developer should edit as required before publishing... - name: Download source deb - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 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@v7 + uses: actions/download-artifact@v8 with: pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} merge-multiple: true @@ -292,7 +292,7 @@ jobs: with: python-version: 3.x - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true @@ -309,7 +309,7 @@ jobs: - name: Zip firmware run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true @@ -347,7 +347,7 @@ jobs: with: python-version: 3.x - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} merge-multiple: true diff --git a/.github/workflows/package_obs.yml b/.github/workflows/package_obs.yml index 63f1fe8a0..395b721a5 100644 --- a/.github/workflows/package_obs.yml +++ b/.github/workflows/package_obs.yml @@ -58,7 +58,7 @@ jobs: id: version - name: Download artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 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 82ffe66e9..8fe675cb1 100644 --- a/.github/workflows/package_pio_deps.yml +++ b/.github/workflows/package_pio_deps.yml @@ -54,9 +54,19 @@ jobs: PLATFORMIO_LIBDEPS_DIR: pio/libdeps PLATFORMIO_PACKAGES_DIR: pio/packages PLATFORMIO_CORE_DIR: pio/core + PLATFORMIO_SETTING_ENABLE_TELEMETRY: 0 + PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL: 3650 + PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD: 10240 + + - name: Mangle platformio cache + # Add "1" to epoch-timestamps of all downloads in the cache. + # This is a hack to prevent internet access at build-time. + run: | + cp pio/core/.cache/downloads/usage.db pio/core/.cache/downloads/usage.db.bak + jq -c 'with_entries(.value |= (. | tostring + "1" | tonumber))' pio/core/.cache/downloads/usage.db.bak > pio/core/.cache/downloads/usage.db - name: Store binaries as an artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 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 9a463dbea..86e655809 100644 --- a/.github/workflows/package_ppa.yml +++ b/.github/workflows/package_ppa.yml @@ -46,7 +46,7 @@ jobs: sudo apt-get install -y dput - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} id: gpg @@ -60,7 +60,7 @@ jobs: id: version - name: Download artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 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 6306d777f..3556226ba 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -50,7 +50,7 @@ jobs: - name: Download test artifacts if: needs.native-tests.result != 'skipped' - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: platformio-test-report-${{ steps.version.outputs.long }} merge-multiple: true diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml index 7f925b67c..a85979c72 100644 --- a/.github/workflows/release_channels.yml +++ b/.github/workflows/release_channels.yml @@ -23,8 +23,8 @@ jobs: series: - jammy # 22.04 LTS - noble # 24.04 LTS - - plucky # 25.04 - questing # 25.10 + - resolute # 26.04 LTS uses: ./.github/workflows/package_ppa.yml with: ppa_repo: |- diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml index d93449d6d..95e5c2c3d 100644 --- a/.github/workflows/sec_sast_semgrep_cron.yml +++ b/.github/workflows/sec_sast_semgrep_cron.yml @@ -33,7 +33,7 @@ jobs: # step 3 - name: save report as pipeline artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: report.sarif overwrite: true diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml index fc0702bd8..9255975a8 100644 --- a/.github/workflows/stale_bot.yml +++ b/.github/workflows/stale_bot.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Stale PR+Issues - uses: actions/stale@v10.1.1 + uses: actions/stale@v10.2.0 with: days-before-stale: 45 stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days. diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index b527c2fd9..9d1b475a0 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -59,7 +59,7 @@ jobs: id: version - name: Save coverage information - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: always() # run this step even if previous step failed with: name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }} @@ -94,7 +94,7 @@ jobs: - name: Save test results if: always() # run this step even if previous step failed - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: platformio-test-report-${{ steps.version.outputs.long }} overwrite: true @@ -108,7 +108,7 @@ jobs: sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative. - name: Save coverage information - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: always() # run this step even if previous step failed with: name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }} @@ -137,20 +137,20 @@ jobs: id: version - name: Download test artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: platformio-test-report-${{ steps.version.outputs.long }} merge-multiple: true - name: Test Report - uses: dorny/test-reporter@v2.5.0 + uses: dorny/test-reporter@v2.6.0 with: name: PlatformIO Tests path: testreport.xml reporter: java-junit - name: Download coverage artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }} path: code-coverage-report @@ -163,7 +163,7 @@ jobs: genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report - name: Save Code Coverage Report - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: code-coverage-report-${{ steps.version.outputs.long }} path: code-coverage-report diff --git a/.gitignore b/.gitignore index d6d97c6c4..43cee78db 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ __pycache__ *~ venv/ +.venv/ release/ .vscode/extensions.json /compile_commands.json diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 03a60cad0..201b129e7 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,24 +4,24 @@ cli: plugins: sources: - id: trunk - ref: v1.7.4 + ref: v1.7.5 uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.501 - - renovate@43.10.3 + - checkov@3.2.506 + - renovate@43.52.0 - prettier@3.8.1 - - trufflehog@3.93.3 + - trufflehog@3.93.6 - yamllint@1.38.0 - - bandit@1.9.3 - - trivy@0.69.1 + - bandit@1.9.4 + - trivy@0.69.3 - taplo@0.10.0 - - ruff@0.15.1 - - isort@7.0.0 - - markdownlint@0.47.0 + - ruff@0.15.4 + - isort@8.0.1 + - markdownlint@0.48.0 - oxipng@10.1.0 - svgo@4.0.0 - - actionlint@1.7.10 + - actionlint@1.7.11 - flake8@7.3.0 - hadolint@2.14.0 - shfmt@3.6.0 diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 000000000..12479b36d --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,26 @@ +# Lightweight container for running native PlatformIO tests on non-Linux hosts +FROM python:3.14-slim-trixie + +ENV DEBIAN_FRONTEND=noninteractive +ENV PIP_ROOT_USER_ACTION=ignore + +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install --no-install-recommends -y \ + g++ git ca-certificates pkg-config \ + libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \ + libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \ + libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \ + && apt-get clean && rm -rf /var/lib/apt/lists/* \ + && pip install --no-cache-dir platformio==6.1.19 \ + && useradd --create-home --shell /usr/sbin/nologin meshtastic + +WORKDIR /firmware +RUN chown -R meshtastic:meshtastic /firmware + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD platformio --version || exit 1 + +USER meshtastic + +# Run tests by default; override with docker run args for specific filters +CMD ["platformio", "test", "-e", "coverage", "-v"] diff --git a/bin/check-all.sh b/bin/check-all.sh index 29d6b5532..9c7fc694d 100755 --- a/bin/check-all.sh +++ b/bin/check-all.sh @@ -23,4 +23,4 @@ for BOARD in $BOARDS; do CHECK="${CHECK} -e ${BOARD}" done -pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=medium --fail-on-defect=high +pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=low --fail-on-defect=medium --fail-on-defect=high diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 3c996051e..38fc057a0 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -187,6 +187,7 @@ Logging: LogLevel: info # debug, info, warn, error # TraceFile: /var/log/meshtasticd.json # JSONFile: /packets.json # File location for JSON output of decoded packets +# JSONFileRotate: 60 # Rotate JSON file every N minutes, or 0 for no rotation # JSONFilter: position # filter for packets to save to JSON file # AsciiLogs: true # default if not specified is !isatty() on stdout @@ -214,3 +215,4 @@ General: AvailableDirectory: /etc/meshtasticd/available.d/ # MACAddress: AA:BB:CC:DD:EE:FF # MACAddressSource: eth0 +# APIPort: 4403 diff --git a/bin/config.d/lora-RAK6421-13300-slot1.yaml b/bin/config.d/lora-RAK6421-13300-slot1.yaml index 6f65f9ccd..628198887 100644 --- a/bin/config.d/lora-RAK6421-13300-slot1.yaml +++ b/bin/config.d/lora-RAK6421-13300-slot1.yaml @@ -6,6 +6,9 @@ Lora: Reset: 16 # IO4 Busy: 24 # IO5 # Ant_sw: 13 # IO3 + Enable_Pins: + - 12 + - 13 DIO3_TCXO_VOLTAGE: true DIO2_AS_RF_SWITCH: true spidev: spidev0.0 diff --git a/bin/config.d/lora-RAK6421-13300-slot2.yaml b/bin/config.d/lora-RAK6421-13300-slot2.yaml index cbc794d39..f890f0467 100644 --- a/bin/config.d/lora-RAK6421-13300-slot2.yaml +++ b/bin/config.d/lora-RAK6421-13300-slot2.yaml @@ -4,5 +4,8 @@ Lora: Reset: 24 # IO4 Busy: 19 # IO5 # Ant_sw: 23 # IO3 + Enable_Pins: + - 26 + - 23 spidev: spidev0.1 # CS: 7 \ No newline at end of file diff --git a/bin/config.d/lora-RAK6421-13302-slot1.yaml b/bin/config.d/lora-RAK6421-13302-slot1.yaml new file mode 100644 index 000000000..7922f5182 --- /dev/null +++ b/bin/config.d/lora-RAK6421-13302-slot1.yaml @@ -0,0 +1,16 @@ +Lora: + + ### RAK13300in Slot 1 + Module: sx1262 + IRQ: 22 #IO6 + Reset: 16 # IO4 + Busy: 24 # IO5 + # Ant_sw: 13 # IO3 + Enable_Pins: + - 12 + - 13 + DIO3_TCXO_VOLTAGE: true + DIO2_AS_RF_SWITCH: true + spidev: spidev0.0 + # CS: 8 + TX_GAIN_LORA: [8] \ No newline at end of file diff --git a/bin/config.d/lora-RAK6421-13302-slot2.yaml b/bin/config.d/lora-RAK6421-13302-slot2.yaml new file mode 100644 index 000000000..362df93a6 --- /dev/null +++ b/bin/config.d/lora-RAK6421-13302-slot2.yaml @@ -0,0 +1,12 @@ +Lora: + ### RAK13300in Slot 2 pins + IRQ: 18 #IO6 + Reset: 24 # IO4 + Busy: 19 # IO5 + # Ant_sw: 23 # IO3 + Enable_Pins: + - 26 + - 23 + spidev: spidev0.1 + # CS: 7 + TX_GAIN_LORA: [8] \ No newline at end of file diff --git a/bin/config.d/lora-hat-rak-6421-pi-hat.yaml b/bin/config.d/lora-hat-rak-6421-pi-hat.yaml index 066e36a10..10b72d9d2 100644 --- a/bin/config.d/lora-hat-rak-6421-pi-hat.yaml +++ b/bin/config.d/lora-hat-rak-6421-pi-hat.yaml @@ -5,7 +5,11 @@ Lora: IRQ: 22 #IO6 Reset: 16 # IO4 Busy: 24 # IO5 - # Ant_sw: 13 # IO3 + Enable_Pins: + - 12 + - 13 DIO3_TCXO_VOLTAGE: true DIO2_AS_RF_SWITCH: true spidev: spidev0.0 + # GPIO_DETECT_PA: 13 + TX_GAIN_LORA: [8] \ No newline at end of file diff --git a/bin/config.d/lora-lyra-ultra_1w.yaml b/bin/config.d/lora-lyra-ultra_1w.yaml new file mode 100644 index 000000000..0bdc05fef --- /dev/null +++ b/bin/config.d/lora-lyra-ultra_1w.yaml @@ -0,0 +1,16 @@ +# For use with Armbian luckfox-lyra-ultra-w +# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config +# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat +# 1 Watt Lyra Ultra hat +Lora: + Module: sx1262 + DIO2_AS_RF_SWITCH: true + DIO3_TCXO_VOLTAGE: true + CS: 10 + IRQ: 5 + Busy: 11 + Reset: 9 + RXen: 14 + + spidev: spidev0.0 #pins are (CS=10, CLK=8, MOSI=6, MISO=7) + spiSpeed: 2000000 diff --git a/bin/config.d/lora-lyra-ultra_2w.yaml b/bin/config.d/lora-lyra-ultra_2w.yaml new file mode 100644 index 000000000..a1fe6d7f7 --- /dev/null +++ b/bin/config.d/lora-lyra-ultra_2w.yaml @@ -0,0 +1,17 @@ +# For use with Armbian luckfox-lyra-ultra-w +# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config +# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat +# 2 Watt Lyra Ultra hat +Lora: + Module: sx1262 + DIO2_AS_RF_SWITCH: true + DIO3_TCXO_VOLTAGE: true + SX126X_MAX_POWER: 8 + CS: 10 + IRQ: 5 + Busy: 11 + Reset: 9 + RXen: 14 + + spidev: spidev0.0 #pins are (CS=10, CLK=8, MOSI=6, MISO=7) + spiSpeed: 2000000 diff --git a/bin/config.d/lora-lyra-ws-raspberry-pi-pico-hat.yaml b/bin/config.d/lora-lyra-ws-raspberry-pi-pico-hat.yaml new file mode 100644 index 000000000..8425fc385 --- /dev/null +++ b/bin/config.d/lora-lyra-ws-raspberry-pi-pico-hat.yaml @@ -0,0 +1,25 @@ +# For use with Armbian luckfox-lyra // luckfox-lyra-plus +# Enable overlay 'luckfox-lyra-plus-spi0-cs0_rmio13-spidev' with armbian-config +# Waveshare LoRa HAT for Raspberry Pi Pico +# https://www.waveshare.com/wiki/Pico-LoRa-SX1262 +Lora: + Module: sx1262 + DIO2_AS_RF_SWITCH: true + DIO3_TCXO_VOLTAGE: true + spidev: spidev0.0 + CS: # GPIO0_B5 + pin: 13 + gpiochip: 0 + line: 13 + IRQ: # GPIO1_C2 + pin: 50 + gpiochip: 1 + line: 18 + Busy: # GPIO0_B4 + pin: 12 + gpiochip: 0 + line: 12 + Reset: # GPIO0_A2 + pin: 2 + gpiochip: 0 + line: 2 diff --git a/bin/config.d/lora-usb-umesh-1262-30dbm.yaml b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml index 7726eccd1..8b32c5af2 100644 --- a/bin/config.d/lora-usb-umesh-1262-30dbm.yaml +++ b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml @@ -13,8 +13,7 @@ Lora: # USB_Serialnum: 12345678 SX126X_MAX_POWER: 22 # Reduce output power to improve EMI - NUM_PA_POINTS: 22 - TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 + TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7] # Note: This module integrates an additional PA to achieve higher output power. # The 'power' parameter here does not represent the actual RF output. # TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). diff --git a/bin/config.d/lora-usb-umesh-1268-30dbm.yaml b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml index c054a92f9..df772184c 100644 --- a/bin/config.d/lora-usb-umesh-1268-30dbm.yaml +++ b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml @@ -13,8 +13,7 @@ Lora: # USB_Serialnum: 12345678 SX126X_MAX_POWER: 22 # Reduce output power to improve EMI - NUM_PA_POINTS: 22 - TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 + TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7] # Note: This module integrates an additional PA to achieve higher output power. # The 'power' parameter here does not represent the actual RF output. # TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). diff --git a/bin/test-native-docker.sh b/bin/test-native-docker.sh new file mode 100755 index 000000000..b42c940c5 --- /dev/null +++ b/bin/test-native-docker.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Run native PlatformIO tests inside Docker (for macOS / non-Linux hosts). +# +# Usage: +# ./bin/test-native-docker.sh # run all native tests +# ./bin/test-native-docker.sh -f test_transmit_history # run specific test filter +# ./bin/test-native-docker.sh --rebuild # force rebuild the image +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +IMAGE_NAME="meshtastic-native-test" + +REBUILD=false +EXTRA_ARGS=() + +for arg in "$@"; do + if [[ "$arg" == "--rebuild" ]]; then + REBUILD=true + else + EXTRA_ARGS+=("$arg") + fi +done + +if $REBUILD || ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then + echo "Building test image (first run may take a few minutes)..." + docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/Dockerfile.test" "$ROOT_DIR" +fi + +# Disable BUILD_EPOCH to avoid full rebuilds between test runs (matches CI) +sed_cmd='s/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/' + +# Default: run all tests. Pass extra args (e.g. -f test_transmit_history) through. +if [[ ${#EXTRA_ARGS[@]} -eq 0 ]]; then + CMD=("platformio" "test" "-e" "coverage" "-v") +else + CMD=("platformio" "test" "-e" "coverage" "-v" "${EXTRA_ARGS[@]}") +fi + +exec docker run --rm \ + -v "$ROOT_DIR:/src:ro" \ + "$IMAGE_NAME" \ + bash -c "rm -rf /tmp/fw-test && cp -a /src /tmp/fw-test && cd /tmp/fw-test && sed -i '${sed_cmd}' platformio.ini && ${CMD[*]}" diff --git a/boards/mini-epaper-s3.json b/boards/mini-epaper-s3.json new file mode 100644 index 000000000..5140f88be --- /dev/null +++ b/boards/mini-epaper-s3.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_ESP32S3_DEV", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": ["wifi"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "LilyGo Mini-Epaper-S3 (4 MB Flash, 2MB PSRAM)", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://www.lilygo.cc", + "vendor": "LilyGo" +} diff --git a/branding/README.md b/branding/README.md index 3a558bf20..b59936140 100644 --- a/branding/README.md +++ b/branding/README.md @@ -12,6 +12,6 @@ Ex: - `logo_320x480.png` - `logo_320x240.png` -This file is copied to `data/boot/logo.png` before filesytem image compilation. +This file is copied to `data/boot/logo.png` before filesystem image compilation. For additional examples see the [`event/defcon33` branch](https://github.com/meshtastic/firmware/tree/event/defcon33). diff --git a/debian/ci_pack_sdeb.sh b/debian/ci_pack_sdeb.sh index 81e681e0c..30a775295 100755 --- a/debian/ci_pack_sdeb.sh +++ b/debian/ci_pack_sdeb.sh @@ -3,6 +3,9 @@ export DEBEMAIL="jbennett@incomsystems.biz" export PLATFORMIO_LIBDEPS_DIR=pio/libdeps export PLATFORMIO_PACKAGES_DIR=pio/packages export PLATFORMIO_CORE_DIR=pio/core +export PLATFORMIO_SETTING_ENABLE_TELEMETRY=0 +export PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650 +export PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240 # Download libraries to `pio` platformio pkg install -e native-tft diff --git a/debian/rules b/debian/rules index ebb572153..68af9a9a5 100755 --- a/debian/rules +++ b/debian/rules @@ -9,7 +9,10 @@ PIO_ENV:=\ PLATFORMIO_CORE_DIR=pio/core \ PLATFORMIO_LIBDEPS_DIR=pio/libdeps \ - PLATFORMIO_PACKAGES_DIR=pio/packages + PLATFORMIO_PACKAGES_DIR=pio/packages \ + PLATFORMIO_SETTING_ENABLE_TELEMETRY=0 \ + PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650 \ + PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240 # Raspbian armhf builds should be compatible with armv6-hardfloat # https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-1/#rpi1-compiler-flags diff --git a/platformio.ini b/platformio.ini index ccc5c9755..c82cfd11c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -117,7 +117,7 @@ lib_deps = [radiolib_base] lib_deps = # renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib - jgromes/RadioLib@7.5.0 + jgromes/RadioLib@7.6.0 [device-ui_base] lib_deps = @@ -138,7 +138,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library adafruit/Adafruit BME280 Library@2.3.0 # renovate: datasource=custom.pio depName=Adafruit DPS310 packageName=adafruit/library/Adafruit DPS310 - adafruit/Adafruit DPS310@1.1.5 + adafruit/Adafruit DPS310@1.1.6 # renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library adafruit/Adafruit MCP9808 Library@2.0.2 # renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library @@ -150,7 +150,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH adafruit/Adafruit LIS3DH@1.3.0 # renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0 - adafruit/Adafruit AHTX0@2.0.5 + adafruit/Adafruit AHTX0@2.0.6 # renovate: datasource=custom.pio depName=Adafruit LSM6DS packageName=adafruit/library/Adafruit LSM6DS adafruit/Adafruit LSM6DS@4.7.4 # renovate: datasource=custom.pio depName=Adafruit TSL2591 packageName=adafruit/library/Adafruit TSL2591 Library @@ -158,7 +158,7 @@ lib_deps = # renovate: datasource=custom.pio depName=EmotiBit MLX90632 packageName=emotibit/library/EmotiBit MLX90632 emotibit/EmotiBit MLX90632@1.0.8 # renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library - adafruit/Adafruit MLX90614 Library@2.1.5 + adafruit/Adafruit MLX90614 Library@2.1.6 # renovate: datasource=git-refs depName=INA3221 packageName=https://github.com/sgtwilko/INA3221 gitBranch=FixOverflow https://github.com/sgtwilko/INA3221/archive/bb03d7e9bfcc74fc798838a54f4f99738f29fc6a.zip # renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass @@ -180,7 +180,7 @@ lib_deps = # renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150 dfrobot/DFRobot_BMM150@1.0.0 # renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561 - adafruit/Adafruit TSL2561@1.1.2 + adafruit/Adafruit TSL2561@1.1.3 # renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE wollewald/BH1750_WE@1.1.10 diff --git a/protobufs b/protobufs index 44298d374..a229208f2 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 44298d374fd83cfbc36fdb76c6f966e980cadd93 +Subproject commit a229208f29a59cf1d8cfa24cbb7567a08f2d1771 diff --git a/src/AudioThread.h b/src/AudioThread.h index e1ba422bc..1129ee087 100644 --- a/src/AudioThread.h +++ b/src/AudioThread.h @@ -51,9 +51,7 @@ class AudioThread : public concurrency::OSThread i2sRtttl = nullptr; } - if (rtttlFile != nullptr) { - rtttlFile = nullptr; - } + rtttlFile = nullptr; setCPUFast(false); #ifdef T_LORA_PAGER diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 2153dbfd5..b11f37cf0 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -38,7 +38,10 @@ static bool isPowered() return true; #endif - bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); + bool isRouter = ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) + ? 1 + : 0); // If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON // We assume routers might be powered all the time, but from a low current (solar) source @@ -262,7 +265,10 @@ Fsm powerFSM(&stateBOOT); void PowerFSM_setup() { - bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); + bool isRouter = ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) + ? 1 + : 0); bool hasPower = isPowered(); LOG_INFO("PowerFSM init, USB power=%d", hasPower ? 1 : 0); diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index dd2acb599..e24aa3c57 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -35,6 +35,8 @@ void consoleInit() #if defined(SERIAL_HAS_ON_RECEIVE) // onReceive does only exist for HardwareSerial not for USB CDC serial Port.onReceive([sc]() { sc->rxInt(); }); +#else + (void)sc; #endif DEBUG_PORT.rpInit(); // Simply sets up semaphore } diff --git a/src/concurrency/NotifiedWorkerThread.cpp b/src/concurrency/NotifiedWorkerThread.cpp index 0e4e31d9b..29aff32a5 100644 --- a/src/concurrency/NotifiedWorkerThread.cpp +++ b/src/concurrency/NotifiedWorkerThread.cpp @@ -76,8 +76,10 @@ bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrit void NotifiedWorkerThread::checkNotification() { - auto n = notification; - notification = 0; // clear notification + // Atomically read and clear. (This avoids a potential race condition where an interrupt handler could set a new notification + // after checkNotification reads but before it clears, which would cause us to miss that notification until the next one comes + // in.) + auto n = notification.exchange(0); // read+clear atomically: like `n = notification; notification = 0;` but interrupt-safe if (n) { onNotify(n); } diff --git a/src/concurrency/NotifiedWorkerThread.h b/src/concurrency/NotifiedWorkerThread.h index 7a150b0b0..166b9ea65 100644 --- a/src/concurrency/NotifiedWorkerThread.h +++ b/src/concurrency/NotifiedWorkerThread.h @@ -1,6 +1,7 @@ #pragma once #include "OSThread.h" +#include namespace concurrency { @@ -13,7 +14,7 @@ class NotifiedWorkerThread : public OSThread /** * The notification that was most recently used to wake the thread. Read from runOnce() */ - uint32_t notification = 0; + std::atomic notification{0}; public: NotifiedWorkerThread(const char *name) : OSThread(name) {} diff --git a/src/concurrency/Periodic.h b/src/concurrency/Periodic.h index db07145a6..8576be7ea 100644 --- a/src/concurrency/Periodic.h +++ b/src/concurrency/Periodic.h @@ -1,24 +1,29 @@ #pragma once +#include +#include + #include "concurrency/OSThread.h" namespace concurrency { /** - * @brief Periodically invoke a callback. This just provides C-style callback conventions - * rather than a virtual function - FIXME, remove? + * @brief Periodically invoke a callback. + * Supports both legacy function pointers and modern callables. */ class Periodic : public OSThread { - int32_t (*callback)(); - public: // callback returns the period for the next callback invocation (or 0 if we should no longer be called) - Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {} + Periodic(const char *name, int32_t (*cb)()) : OSThread(name), callback(cb) {} + Periodic(const char *name, std::function cb) : OSThread(name), callback(std::move(cb)) {} protected: - int32_t runOnce() override { return callback(); } + int32_t runOnce() override { return callback ? callback() : 0; } + + private: + std::function callback; }; } // namespace concurrency diff --git a/src/configuration.h b/src/configuration.h index 53ae30d51..a5f2cd9a9 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -149,6 +149,12 @@ 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 USE_KCT8103L_PA +// Power Amps are often non-linear, so we can use an array of values for the power curve +#define NUM_PA_POINTS 22 +#define TX_GAIN_LORA 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 7 +#endif + #ifdef RAK13302 #define NUM_PA_POINTS 22 #define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8 @@ -163,6 +169,10 @@ along with this program. If not, see . #define TX_GAIN_LORA 0 #endif +#ifndef HAS_LORA_FEM +#define HAS_LORA_FEM 0 +#endif + // ----------------------------------------------------------------------------- // Feature toggles // ----------------------------------------------------------------------------- @@ -261,6 +271,8 @@ along with this program. If not, see . #define BHI260AP_ADDR 0x28 #define BMM150_ADDR 0x13 #define DA217_ADDR 0x26 +#define BMI270_ADDR 0x68 +#define BMI270_ADDR_ALT 0x69 // ----------------------------------------------------------------------------- // LED diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 3389cce91..75eabc954 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -37,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const { - ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150}; - return firstOfOrNONE(9, types); + ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150, BMI270}; + return firstOfOrNONE(10, types); } ScanI2C::FoundDevice ScanI2C::firstAQI() const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 7b575dd63..cc83a8d7b 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -89,10 +89,12 @@ class ScanI2C DA217, CHSC6X, CST226SE, + BMI270, SEN5X, SFA30, CW2015, - SCD30 + SCD30, + ADS1115 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index c58a58518..06862a2c4 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -652,9 +652,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; - case ICM20948_ADDR: // same as BMX160_ADDR and SEN5X_ADDR - case ICM20948_ADDR_ALT: // same as MPU6050_ADDR - // ICM20948 Register check + case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, and SEN5X_ADDR + case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); #ifdef HAS_ICM20948 type = ICM20948; @@ -665,6 +664,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = ICM20948; logFoundDevice("ICM20948", (uint8_t)addr.address); break; + } else if (registerValue == 0x24) { + type = BMI270; + logFoundDevice("BMI270", (uint8_t)addr.address); + break; + } else if (registerValue == 0xD8) { // BMX160 chip ID at register 0x00 + type = BMX160; + logFoundDevice("BMX160", (uint8_t)addr.address); + break; } else { String prod = ""; prod = readSEN5xProductName(i2cBus, addr.address); @@ -718,11 +725,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) if (len == 5 && memcmp(expectedInfo, info, len) == 0) { LOG_INFO("NXP SE050 crypto chip found"); type = NXP_SE050; - - } else { - LOG_INFO("FT6336U touchscreen found"); - type = FT6336U; + break; } + + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2); + if (registerValue == 0x8583 || registerValue == 0x8580) { + type = ADS1115; + logFoundDevice("ADS1115 ADC", (uint8_t)addr.address); + break; + } + + LOG_INFO("FT6336U touchscreen found"); + type = FT6336U; break; } diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 2beaeb127..1260d8b15 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -93,7 +93,7 @@ static const char *getGPSPowerStateString(GPSPowerState state) #ifdef PIN_GPS_SWITCH // If we have a hardware switch, define a periodic watcher outside of the GPS runOnce thread, since this can be sleeping -// idefinitely +// indefinitely int lastState = LOW; bool firstrun = true; @@ -586,14 +586,14 @@ bool GPS::setup() _serial_gps->write("$PMTK301,2*2E\r\n"); delay(250); } else if (gnssModel == GNSS_MODEL_ATGM336H) { - // Set the intial configuration of the device - these _should_ work for most AT6558 devices + // Set the initial configuration of the device - these _should_ work for most AT6558 devices msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF); _serial_gps->write(UBXscratch, msglen); if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) { LOG_WARN("ATGM336H: Could not set Config"); } - // Set the update frequence to 1Hz + // Set the update frequency to 1Hz msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ); _serial_gps->write(UBXscratch, msglen); if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) { @@ -700,7 +700,7 @@ bool GPS::setup() } else { // 8,9 LOG_INFO("GPS+SBAS+GLONASS+Galileo configured"); } - // Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next + // Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next // commands for the M8 it tends to be more... 1 sec should be enough ;>) delay(1000); } @@ -733,7 +733,7 @@ bool GPS::setup() SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersave for GPS", 500); SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500); - // For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats. + // For M8 we want to enable NMEA version 4.10 so we can see the additional sats. if (gnssModel == GNSS_MODEL_UBLOX8) { clearBuffer(); SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500); @@ -1211,7 +1211,7 @@ int32_t GPS::runOnce() return disable(); // This should trigger when we have a fixed position, and get that first position // 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms - // if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake. + // if not awake we can run super infrequently (once every 5 secs?) to see if we need to wake. return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000; } diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index 6d1f2da6d..04c25caa3 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -12,7 +12,7 @@ GeoCoord::GeoCoord(int32_t lat, int32_t lon, int32_t alt) : _latitude(lat), _lon GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt) { - // Change decimial representation to int32_t. I.e., 12.345 becomes 123450000 + // Change decimal representation to int32_t. I.e., 12.345 becomes 123450000 _latitude = int32_t(lat * 1e+7); _longitude = int32_t(lon * 1e+7); GeoCoord::setCoords(); @@ -20,7 +20,7 @@ GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt) GeoCoord::GeoCoord(double lat, double lon, int32_t alt) : _altitude(alt) { - // Change decimial representation to int32_t. I.e., 12.345 becomes 123450000 + // Change decimal representation to int32_t. I.e., 12.345 becomes 123450000 _latitude = int32_t(lat * 1e+7); _longitude = int32_t(lon * 1e+7); GeoCoord::setCoords(); @@ -467,10 +467,10 @@ int32_t GeoCoord::bearingTo(const GeoCoord &pointB) } /** - * Create a new point bassed on the passed in poin + * Create a new point based on the passed-in point * Ported from http://www.edwilliams.org/avform147.htm#LL * @param bearing - * The bearing in raidans + * The bearing in radians * @param range_meters * range in meters * @return GeoCoord object of point at bearing and range from initial point @@ -593,4 +593,4 @@ double GeoCoord::toRadians(double deg) double GeoCoord::toDegrees(double r) { return r * 180 / PI; -} \ No newline at end of file +} diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index e67bef53e..a0315559f 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -223,7 +223,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd // This delta value works on all platforms timeStartMsec = now; zeroOffsetSecs = tv->tv_sec; - // If this platform has a setable RTC, set it + // If this platform has a settable RTC, set it #ifdef RV3028_RTC if (rtc_found.address == RV3028_RTC) { Melopero_RV3028 rtc; @@ -402,7 +402,7 @@ time_t gm_mktime(const struct tm *tm) #if !MESHTASTIC_EXCLUDE_TZ time_t result = 0; - // First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch. + // First, get us to the start of tm->year, by calculating the number of days since the Unix epoch. int year = 1900 + tm->tm_year; // tm_year is years since 1900 int year_minus_one = year - 1; int days_before_this_year = 0; diff --git a/src/gps/cas.h b/src/gps/cas.h index 725fd07b3..2a30fd586 100644 --- a/src/gps/cas.h +++ b/src/gps/cas.h @@ -37,7 +37,7 @@ static const uint8_t _message_CAS_CFG_RATE_1HZ[] = { // CFG-NAVX (0x06, 0x07) // Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system -// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable +// Quirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable // and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used. static const uint8_t _message_CAS_CFG_NAVX_CONF[] = { 0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings diff --git a/src/gps/ubx.h b/src/gps/ubx.h index 0fe2f01fb..8c32ee151 100644 --- a/src/gps/ubx.h +++ b/src/gps/ubx.h @@ -57,7 +57,7 @@ static const uint8_t _message_CFG_PM2[] PROGMEM = { 0x00, 0x00, 0x00, 0x00 // 0x64, 0x40, 0x01, 0x00 // reserved 11 }; -// Constallation setup, none required for Neo-6 +// Constellation setup, none required for Neo-6 // For Neo-7 GPS & SBAS static const uint8_t _message_GNSS_7[] = { @@ -157,7 +157,7 @@ static const uint8_t _message_NAVX5[] = { 0x00, 0x00, 0x00, 0x00, // Reserved 9 0x00, // Reserved 10 0x00, // Reserved 11 - 0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true) + 0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true) 0x01, // useAOP (AssistNow Autonomous configuration) = 1 (enabled) 0x00, // Reserved 12 0x00, // Reserved 13 @@ -185,7 +185,7 @@ static const uint8_t _message_NAVX5_8[] = { 0x00, // Reserved 4 0x00, 0x00, // Reserved 5 0x00, 0x00, // Reserved 6 - 0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true) + 0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true) 0x01, // aopCfg (AssistNow Autonomous configuration) = 1 (enabled) 0x00, 0x00, // Reserved 7 0x00, 0x00, // aopOrbMaxErr = 0 to reset to firmware default @@ -314,7 +314,7 @@ static const uint8_t _message_DISABLE_TXT_INFO[] = { // This command applies to M8 products static const uint8_t _message_PMS[] = { 0x00, // Version (0) - 0x03, // Power setup value 3 = Agresssive 1Hz + 0x03, // Power setup value 3 = Agressive 1Hz 0x00, 0x00, // period: not applicable, set to 0 0x00, 0x00, // onTime: not applicable, set to 0 0x00, 0x00 // reserved, generated by u-center @@ -337,7 +337,7 @@ static const uint8_t _message_SAVE_10[] = { // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. // BBR will survive a restart, and power off for a while, but modules with small backup // batteries or super caps will not retain the config for a long power off time. -// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after +// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast acquisition after // sleep // VALSET Commands for M10 @@ -462,7 +462,7 @@ Default GNSS configuration is: GPS, Galileo, BDS B1l, with QZSS and SBAS enabled The PMREQ puts the receiver to sleep and wakeup re-acquires really fast and seems to not need the PM config. Lets try without it. PMREQ sort of works with SBAS, but the awake time is too short to re-acquire any SBAS sats. -The defination of "Got Fix" doesn't seem to include SBAS. Much more too this... +The definition of "Got Fix" doesn't seem to include SBAS. Much more too this... Even if it was, it can take minutes (up to 12.5), even under good sat visibility conditions to re-acquire the SBAS data. diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 1678da793..faf72e06d 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -101,7 +101,7 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) return true; } -// End the update process - virtual method, overriden in derived class +// End the update process - virtual method, overridden in derived class void EInkDisplay::endUpdate() { // Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep) @@ -259,6 +259,17 @@ bool EInkDisplay::connect() adafruitDisplay->setRotation(3); adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } +#elif defined(MINI_EPAPER_S3) + spi1 = new SPIClass(HSPI); + spi1->begin(PIN_SPI1_SCK, PIN_SPI1_MISO, PIN_SPI1_MOSI, PIN_EINK_CS); + + // Create GxEPD2 objects + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1); + adafruitDisplay = new GxEPD2_BW(*lowLevel); + + // Init GxEPD2 + adafruitDisplay->init(); + adafruitDisplay->setRotation(1); #elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) // Detect display model, before starting SPI diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index f5418b069..14adeda12 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -93,7 +93,8 @@ class EInkDisplay : public OLEDDisplay SPIClass *hspi = NULL; #endif -#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) +#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) || \ + defined(MINI_EPAPER_S3) SPIClass *spi1 = NULL; #endif diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 892a4a885..a48ba5c93 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -95,7 +95,7 @@ void EInkDynamicDisplay::adjustRefreshCounters() // Trigger the display update by calling base class bool EInkDynamicDisplay::update() { - // Detemine the refresh mode to use, and start the update + // Determine the refresh mode to use, and start the update bool refreshApproved = determineMode(); if (refreshApproved) { EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system @@ -317,7 +317,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious() LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x", frameFlags); } -// Have too many fast-refreshes occured consecutively, since last full refresh? +// Have too many fast-refreshes occurred consecutively, since last full refresh? void EInkDynamicDisplay::checkConsecutiveFastRefreshes() { // If a decision was already reached, don't run the check @@ -561,4 +561,4 @@ void EInkDynamicDisplay::awaitRefresh() } #endif // HAS_EINK_ASYNCFULL -#endif // USE_EINK_DYNAMICDISPLAY \ No newline at end of file +#endif // USE_EINK_DYNAMICDISPLAY diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 65f569831..23186d656 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -469,12 +469,15 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) PMU->enablePowerOutput(XPOWERS_ALDO2); #endif -#if defined(MUZI_BASE) +// some screens seem to need a kick in the pants to turn back on +#if defined(MUZI_BASE) || defined(M5STACK_CARDPUTER_ADV) dispdev->init(); dispdev->setBrightness(brightness); dispdev->flipScreenVertically(); dispdev->resetDisplay(); +#ifdef SCREEN_12V_ENABLE digitalWrite(SCREEN_12V_ENABLE, HIGH); +#endif delay(100); #endif #if !ARCH_PORTDUINO @@ -498,9 +501,11 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2) ui->init(); #endif -#ifdef USE_ST7789 +#if defined(USE_ST7789) && defined(VTFT_LEDA) +#ifdef VTFT_CTRL pinMode(VTFT_CTRL, OUTPUT); digitalWrite(VTFT_CTRL, LOW); +#endif ui->init(); #ifdef ESP_PLATFORM analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); @@ -542,8 +547,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #ifdef USE_ST7789 SPI1.end(); #if defined(ARCH_ESP32) +#ifdef VTFT_LEDA pinMode(VTFT_LEDA, ANALOG); +#endif +#ifdef VTFT_CTRL pinMode(VTFT_CTRL, ANALOG); +#endif pinMode(ST7789_RESET, ANALOG); pinMode(ST7789_RS, ANALOG); pinMode(ST7789_NSS, ANALOG); @@ -911,6 +920,10 @@ int32_t Screen::runOnce() break; case Cmd::STOP_ALERT_FRAME: NotificationRenderer::pauseBanner = false; + // Return from one-off alert mode back to regular frames. + if (!showingNormalScreen && NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) { + setFrames(); + } break; case Cmd::STOP_BOOT_SCREEN: EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 6d6d85e70..68cc8c8b3 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -768,7 +768,11 @@ class Screen : public concurrency::OSThread DebugInfo debugInfo; /// Display device +#ifdef USE_ST7789 + ST7789Spi *dispdev; +#else OLEDDisplay *dispdev; +#endif /// UI helper for rendering to frames and switching between them OLEDDisplayUi *ui; diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 12fac4f34..1c2eb72d4 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1209,8 +1209,8 @@ void TFTDisplay::display(bool fromBlank) bool somethingChanged = false; // Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step - colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8); - colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8); + colorTftMesh = __builtin_bswap16(TFT_MESH); + colorTftBlack = __builtin_bswap16(TFT_BLACK); y = 0; while (y < displayHeight) { diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index 6b6773d21..1ba856e05 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -44,7 +44,7 @@ InputEvent NotificationRenderer::inEvent; int8_t NotificationRenderer::curSelected = 0; char NotificationRenderer::alertBannerMessage[256] = {0}; uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever -uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are seelctable options +uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are selectable options const char **NotificationRenderer::optionsArrayPtr = nullptr; const int *NotificationRenderer::optionsEnumPtr = nullptr; std::function NotificationRenderer::alertBannerCallback = NULL; @@ -372,7 +372,7 @@ void NotificationRenderer::resetBanner() inEvent.inputEvent = INPUT_BROKER_NONE; inEvent.kbchar = 0; curSelected = 0; - alertBannerOptions = 0; // last x lines are seelctable options + alertBannerOptions = 0; // last x lines are selectable options optionsArrayPtr = nullptr; optionsEnumPtr = nullptr; alertBannerCallback = NULL; diff --git a/src/graphics/draw/NotificationRenderer.h b/src/graphics/draw/NotificationRenderer.h index 73c44e725..2cc6d8b13 100644 --- a/src/graphics/draw/NotificationRenderer.h +++ b/src/graphics/draw/NotificationRenderer.h @@ -23,7 +23,7 @@ class NotificationRenderer static uint32_t alertBannerUntil; // 0 is a special case meaning forever static const char **optionsArrayPtr; static const int *optionsEnumPtr; - static uint8_t alertBannerOptions; // last x lines are seelctable options + static uint8_t alertBannerOptions; // last x lines are selectable options static std::function alertBannerCallback; static uint32_t numDigits; static uint32_t currentNumber; diff --git a/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h b/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h index 3ce16e473..e37969edf 100644 --- a/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h +++ b/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h @@ -37,8 +37,8 @@ class DEPG0213BNS800 : public SSD16XX void configWaveform() override; void configUpdateSequence() override; void detachFromUpdate() override; - void finalizeUpdate() override; // Only overriden for a slight optimization + void finalizeUpdate() override; // Only overridden for a slight optimization }; } // namespace NicheGraphics::Drivers -#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS diff --git a/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.h b/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.h index 257fed1a6..761cf772a 100644 --- a/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.h +++ b/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.h @@ -35,8 +35,8 @@ class DEPG0290BNS800 : public SSD16XX void configWaveform() override; void configUpdateSequence() override; void detachFromUpdate() override; - void finalizeUpdate() override; // Only overriden for a slight optimization + void finalizeUpdate() override; // Only overridden for a slight optimization }; } // namespace NicheGraphics::Drivers -#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS diff --git a/src/graphics/niche/Drivers/EInk/README.md b/src/graphics/niche/Drivers/EInk/README.md index eca91c6a8..33833f0cd 100644 --- a/src/graphics/niche/Drivers/EInk/README.md +++ b/src/graphics/niche/Drivers/EInk/README.md @@ -19,7 +19,7 @@ void setupNicheGraphics() SPIClass *hspi = new SPIClass(HSPI); hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); - // Setup Enk driver + // Setup EInk driver Drivers::EInk *driver = new Drivers::DEPG0290BNS800; driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY); diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index 84fd86465..39551b47e 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -3,7 +3,7 @@ /* Base class for InkHUD applets - Must be overriden + Must be overridden An applet is one "program" which may show info on the display. @@ -208,4 +208,4 @@ class Applet : public GFX }; // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp index 4cf83966b..06ddd5bb0 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp @@ -525,7 +525,7 @@ void InkHUD::MapApplet::calculateAllMarkers() } // Determine the conversion factor between metres, and pixels on screen -// May be overriden by derived applet, if custom scale required (fixed map size?) +// May be overridden by derived applet, if custom scale required (fixed map size?) void InkHUD::MapApplet::calculateMapScale() { // Aspect ratio of map and screen @@ -555,4 +555,4 @@ void InkHUD::MapApplet::drawCross(int16_t x, int16_t y, uint8_t size) drawLine(x0, y1, x1, y0, BLACK); } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp index a063c08b5..607fd4ef7 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp @@ -120,9 +120,26 @@ void InkHUD::NodeListApplet::onRender(bool full) // Draw the main node list // ======================== - // Imaginary vertical line dividing left-side and right-side info - // Long-name will crop here - const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops"); + // Leave a small gutter between long-name text and right-side card content + constexpr uint8_t rightContentGap = 2; + + // Truncate with trailing "...", sized using the current font. + auto ellipsizeToWidth = [this](std::string text, uint16_t maxWidth) { + constexpr const char *ellipsis = "..."; + const uint16_t ellipsisW = getTextWidth(ellipsis); + uint16_t textW = getTextWidth(text); + if (maxWidth == 0) + return std::string(); + if (textW <= maxWidth) + return text; + if (ellipsisW > maxWidth) + return std::string(); + while (!text.empty() && (textW + ellipsisW > maxWidth)) { + text.pop_back(); + textW = getTextWidth(text); + } + return text + ellipsis; + }; // Y value (top) of the current card. Increases as we draw. uint16_t cardTopY = headerDivY + padDivH; @@ -141,7 +158,7 @@ void InkHUD::NodeListApplet::onRender(bool full) SignalStrength &signal = card->signal; std::string longName; // handled below std::string shortName; // handled below - std::string distance; // handled below; + std::string distance; // handled below const uint8_t &hopsAway = card->hopsAway; meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum); @@ -185,41 +202,49 @@ void InkHUD::NodeListApplet::onRender(bool full) setFont(fontMedium); printAt(0, lineAY, shortName, LEFT, MIDDLE); - // Print the distance + // Right-side labels and long name are rendered in small font. setFont(fontSmall); - printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE); + uint16_t rightContentW = 0; - // If we have a direct connection to the node, draw the signal indicator + // Bottom row right: distance. + if (!distance.empty()) { + rightContentW = std::max(rightContentW, getTextWidth(distance)); + printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE); + } + + // Top row right: direct-link signal only. if (hopsAway == 0 && signal != SIGNAL_UNKNOWN) { - uint16_t signalW = getTextWidth("Xkm"); // Indicator should be similar width to distance label + uint16_t signalW = getTextWidth("Xkm"); // Indicator width tuned to a short right-side label uint16_t signalH = fontMedium.lineHeight() * 0.75; int16_t signalY = lineAY + (fontMedium.lineHeight() / 2) - (fontMedium.lineHeight() * 0.75); int16_t signalX = width() - signalW; + rightContentW = std::max(rightContentW, signalW); drawSignalIndicator(signalX, signalY, signalW, signalH, signal); - } - // Otherwise, print "hops away" info, if available - else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) { - std::string hopString = to_string(node->hops_away); - hopString += " Hop"; - if (node->hops_away != 1) - hopString += "s"; // Append s for "Hops", rather than "Hop" - + } else if (hopsAway != CardInfo::HOPS_UNKNOWN) { + std::string hopString = to_string(hopsAway) + (hopsAway == 1 ? " Hop" : " Hops"); + rightContentW = std::max(rightContentW, getTextWidth(hopString)); printAt(width() - 1, lineAY, hopString, RIGHT, MIDDLE); } - // Print the long name, cropping to prevent overflow onto the right-side info - setCrop(0, 0, dividerX - 1, height()); - printAt(0, lineBY, longName, LEFT, MIDDLE); + // Give long names as much room as possible while still avoiding right side signal and hop space + const uint16_t longNameMaxW = + (rightContentW + rightContentGap < width()) ? (width() - rightContentW - rightContentGap) : 0; + const std::string longNameShown = ellipsizeToWidth(longName, longNameMaxW); - // GFX effect: "hatch" the right edge of longName area - // If a longName has been cropped, it will appear to fade out, - // creating a soft barrier with the right-side info - const int16_t hatchLeft = dividerX - 1 - (fontSmall.lineHeight()); - const int16_t hatchWidth = fontSmall.lineHeight(); - hatchRegion(hatchLeft, cardTopY, hatchWidth, cardH, 2, WHITE); + // Safety crop + setCrop(0, cardTopY, longNameMaxW, cardH); + printAt(0, lineBY, longNameShown, LEFT, MIDDLE); + + resetCrop(); + + // Draw separator between cards + const int16_t separatorY = cardTopY + cardH - 1; + if (separatorY < height() - 1 && (card + 1) != cards.end()) { + for (int16_t xSep = 0; xSep < width(); xSep += 2) + drawPixel(xSep, separatorY, BLACK); + } // Prepare to draw the next card - resetCrop(); cardTopY += cardH; // Once we've run out of screen, stop drawing cards @@ -288,4 +313,4 @@ void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t } } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h index 8babdba03..baee3f0f4 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h @@ -65,10 +65,10 @@ class NodeListApplet : public Applet, public MeshModule // Card Dimensions // - for rendering and for maxCards calc - uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards + uint8_t cardMarginH = 1; // Gap between cards (minimal to fit more rows) uint16_t cardH = fontMedium.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card }; } // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp index c0850b742..0b9607133 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp @@ -6,7 +6,7 @@ using namespace NicheGraphics; InkHUD::BatteryIconApplet::BatteryIconApplet() { - alwaysRender = true; // render everytime the screen is updated + alwaysRender = true; // render every time the screen is updated // Show at boot, if user has previously enabled the feature if (settings->optionalFeatures.batteryIcon) @@ -91,4 +91,4 @@ void InkHUD::BatteryIconApplet::onRender(bool full) drawRect(sliceL, sliceT, sliceW, sliceH, BLACK); } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h index 306a8d8e3..0ae181a2c 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h @@ -2,7 +2,7 @@ /* -System Applet to render an on-screeen keyboard +System Applet to render an on-screen keyboard */ diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp index b2c58fc60..1f3109413 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp @@ -45,7 +45,7 @@ void InkHUD::LogoApplet::onRender(bool full) int16_t logoCY = Y(0.5 - 0.05); // Invert colors if black-on-white - // Used during shutdown, to resport display health + // Used during shutdown, to report display health // Todo: handle this in InkHUD::Renderer instead if (inverted) { fillScreen(BLACK); @@ -186,4 +186,4 @@ int32_t InkHUD::LogoApplet::runOnce() return OSThread::disable(); } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 26d6f03d3..a07e56665 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -1524,7 +1524,15 @@ void InkHUD::MenuApplet::onButtonShortPress() if (!settings->joystick.enabled) { if (!cursorShown) { cursorShown = true; + // Select the first item that isn't a header cursor = 0; + while (cursor < items.size() && items.at(cursor).isHeader) { + cursor++; + } + if (cursor >= items.size()) { + cursorShown = false; + cursor = 0; + } } else { do { cursor = (cursor + 1) % items.size(); @@ -1576,7 +1584,15 @@ void InkHUD::MenuApplet::onNavUp() if (!cursorShown) { cursorShown = true; - cursor = 0; + // Select the last item that isn't a header + cursor = items.size() - 1; + while (items.at(cursor).isHeader) { + if (cursor == 0) { + cursorShown = false; + break; + } + cursor--; + } } else { do { if (cursor == 0) @@ -1597,7 +1613,15 @@ void InkHUD::MenuApplet::onNavDown() if (!cursorShown) { cursorShown = true; + // Select the first item that isn't a header cursor = 0; + while (cursor < items.size() && items.at(cursor).isHeader) { + cursor++; + } + if (cursor >= items.size()) { + cursorShown = false; + cursor = 0; + } } else { do { cursor = (cursor + 1) % items.size(); @@ -2004,7 +2028,7 @@ void InkHUD::MenuApplet::sendText(NodeNum dest, ChannelIndex channel, const char service->sendToMesh(p, RX_SRC_LOCAL, true); // Send to mesh, cc to phone } -// Free up any heap mmemory we'd used while selecting / sending canned messages +// Free up any heap memory we'd used while selecting / sending canned messages void InkHUD::MenuApplet::freeCannedMessageResources() { cm.selectedMessageItem = nullptr; diff --git a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp index 6963df54a..520070d72 100644 --- a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp @@ -73,7 +73,7 @@ ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshP } } else { // For non-local packets, this applet only reacts to favorited nodes. - meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); + const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); if (!sender || !sender->is_favorite) return ProcessMessage::CONTINUE; diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h index 045e2a6fc..2cd2c4163 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h @@ -7,7 +7,7 @@ Displays a thread-view of incoming and outgoing message for a specific channel The channel for this applet is set in the constructor, when the applet is added to WindowManager in the setupNicheGraphics method. -Several messages are saved to flash at shutdown, to preseve applet between reboots. +Several messages are saved to flash at shutdown, to preserve applet between reboots. This class has its own internal method for saving and loading to fs, which interacts directly with the FSCommon layer. If the amount of flash usage is unacceptable, we could keep these in RAM only. @@ -55,4 +55,4 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule } // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Renderer.h b/src/graphics/niche/InkHUD/Renderer.h index 5cfb79277..1ab94b70b 100644 --- a/src/graphics/niche/InkHUD/Renderer.h +++ b/src/graphics/niche/InkHUD/Renderer.h @@ -53,7 +53,7 @@ class Renderer : protected concurrency::OSThread uint16_t height(); private: - // Make attemps to render / update, once triggered by requestUpdate or forceUpdate + // Make attempts to render / update, once triggered by requestUpdate or forceUpdate int32_t runOnce() override; // Apply the display rotation to handled pixels @@ -95,4 +95,4 @@ class Renderer : protected concurrency::OSThread } // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index fce3c9770..ff324943b 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -649,7 +649,7 @@ void InkHUD::WindowManager::refocusTile() } } -// Seach for any applets which believe they are foreground, but no longer have a valid tile +// Search for any applets which believe they are foreground, but no longer have a valid tile // Tidies up after layout changes at runtime void InkHUD::WindowManager::findOrphanApplets() { @@ -679,4 +679,4 @@ void InkHUD::WindowManager::findOrphanApplets() } } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/docs/README.md b/src/graphics/niche/InkHUD/docs/README.md index 8c30aba58..7cd468d73 100644 --- a/src/graphics/niche/InkHUD/docs/README.md +++ b/src/graphics/niche/InkHUD/docs/README.md @@ -733,7 +733,7 @@ To add support for additional encodings, add to the `AppletFont::Encodings` enum #### Custom Line Height -Some fonts may have a handful of especially tall characters, especially extended-ASCII fonts with diacritcs. Ideally, the font should be modified to help resolve this, but if the problem remains, manual offsets to the automatically determined line height can be specified in the constructor. +Some fonts may have a handful of especially tall characters, especially extended-ASCII fonts with diacritics. Ideally, the font should be modified to help resolve this, but if the problem remains, manual offsets to the automatically determined line height can be specified in the constructor. ```cpp // -2 px of padding above, +1 px of padding below diff --git a/src/graphics/niche/Inputs/TwoButton.cpp b/src/graphics/niche/Inputs/TwoButton.cpp index bd29f981d..1a27e039b 100644 --- a/src/graphics/niche/Inputs/TwoButton.cpp +++ b/src/graphics/niche/Inputs/TwoButton.cpp @@ -59,7 +59,7 @@ void TwoButton::stop() } // Attempt to resolve a GPIO pin for the user button, honoring userPrefs.jsonc and device settings -// This helper method isn't used by the TweButton class itself, it could be moved elsewhere. +// This helper method isn't used by the TwoButton class itself, it could be moved elsewhere. // Intention is to pass this value to TwoButton::setWiring in the setupNicheGraphics method. uint8_t TwoButton::getUserButtonPin() { @@ -308,4 +308,4 @@ int TwoButton::afterLightSleep(esp_sleep_wakeup_cause_t cause) #endif -#endif \ No newline at end of file +#endif diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp index 0d835a3a9..df8de4905 100644 --- a/src/input/ButtonThread.cpp +++ b/src/input/ButtonThread.cpp @@ -271,12 +271,13 @@ int32_t ButtonThread::runOnce() break; } // end multipress event - // Do actual shutdown when button released, otherwise the button release - // may wake the board immediatedly. + // Do actual shutdown when button released, otherwise the button release + // may wake the board immediately. case BUTTON_EVENT_LONG_RELEASED: { LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime); - if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE && + // Require press started after boot holdoff to avoid phantom shutdown from floating pins + if (millis() > 30000 && buttonPressStartTime > 30000 && _longLongPress != INPUT_BROKER_NONE && (millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) { evt.inputEvent = _longLongPress; this->notifyObservers(&evt); @@ -346,4 +347,4 @@ int ButtonThread::afterLightSleep(esp_sleep_wakeup_cause_t cause) void ButtonThread::storeClickCount() { multipressClickCount = userButton.getNumberClicks(); -} \ No newline at end of file +} diff --git a/src/input/CardputerKeyboard.cpp b/src/input/CardputerKeyboard.cpp new file mode 100644 index 000000000..ec1ed383a --- /dev/null +++ b/src/input/CardputerKeyboard.cpp @@ -0,0 +1,200 @@ +#if defined(M5STACK_CARDPUTER_ADV) + +#include "CardputerKeyboard.h" +#include "main.h" + +#define _TCA8418_COLS 8 +#define _TCA8418_ROWS 7 +#define _TCA8418_NUM_KEYS 56 + +#define _TCA8418_MULTI_TAP_THRESHOLD 1500 + +using Key = TCA8418KeyboardBase::TCA8418Key; + +constexpr uint8_t modifierFnKey = 2; +constexpr uint8_t modifierFn = 0b0010; +constexpr uint8_t modifierCtrlKey = 3; +constexpr uint8_t modifierShiftKey = 6; +constexpr uint8_t modifierShift = 0b0001; +constexpr uint8_t modifierOptKey = 7; +constexpr uint8_t modifierAltKey = 11; + +// Num chars per key, Modulus for rotating through characters +static uint8_t CardputerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}; + +static unsigned char CardputerTapMap[_TCA8418_NUM_KEYS][3] = {{'`', '~', Key::ESC}, + {Key::TAB, 0x00, 0x00}, + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00}, + {'1', '!', 0x00}, + {'q', 'Q', Key::REBOOT}, + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00}, + {'2', '@', 0x00}, + {'w', 'W', 0x00}, + {'a', 'A', 0x00}, + {0x00, 0x00, 0x00}, + {'3', '#', 0x00}, + {'e', 'E', 0x00}, + {'s', 'S', 0x00}, + {'z', 'Z', 0x00}, + {'4', '$', 0x00}, + {'r', 'R', 0x00}, + {'d', 'D', 0x00}, + {'x', 'X', 0x00}, + {'5', '%', 0x00}, + {'t', 'T', 0x00}, + {'f', 'F', 0x00}, + {'c', 'C', 0x00}, + {'6', '^', 0x00}, + {'y', 'Y', 0x00}, + {'g', 'G', Key::GPS_TOGGLE}, + {'v', 'V', 0x00}, + {'7', '&', 0x00}, + {'u', 'U', 0x00}, + {'h', 'H', 0x00}, + {'b', 'B', Key::BT_TOGGLE}, + {'8', '*', 0x00}, + {'i', 'I', 0x00}, + {'j', 'J', 0x00}, + {'n', 'N', 0x00}, + {'9', '(', 0x00}, + {'o', 'O', 0x00}, + {'k', 'K', 0x00}, + {'m', 'M', Key::MUTE_TOGGLE}, + {'0', ')', 0x00}, + {'p', 'P', Key::SEND_PING}, + {'l', 'L', 0x00}, + {',', '<', Key::LEFT}, + {'_', '-', 0x00}, + {'[', '{', 0x00}, + {';', ':', Key::UP}, + {'.', '>', Key::DOWN}, + {'=', '+', 0x00}, + {']', '}', 0x00}, + {'\'', '"', 0x00}, + {'/', '?', Key::RIGHT}, + {Key::BSP, 0x00, 0x00}, + {'\\', '|', 0x00}, + {Key::SELECT, 0x00, 0x00}, + {' ', ' ', ' '}}; + +CardputerKeyboard::CardputerKeyboard() + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), + last_tap(0L), char_idx(0), tap_interval(0) +{ + reset(); +} + +void CardputerKeyboard::reset(void) +{ + TCA8418KeyboardBase::reset(); +} + +// handle multi-key presses (shift and alt) +void CardputerKeyboard::trigger() +{ + uint8_t count = keyCount(); + if (count == 0) + return; + for (uint8_t i = 0; i < count; ++i) { + uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i); + uint8_t key = k & 0x7F; + if (k & 0x80) { + pressed(key); + } else { + released(); + state = Idle; + } + } +} + +void CardputerKeyboard::pressed(uint8_t key) +{ + if (state == Init || state == Busy) { + return; + } + + if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { + modifierFlag = 0; + } + + uint8_t next_key = 0; + int row = (key - 1) / 10; + int col = (key - 1) % 10; + + if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { + return; // Invalid key + } + + next_key = row * _TCA8418_COLS + col; + state = Held; + + uint32_t now = millis(); + tap_interval = now - last_tap; + + updateModifierFlag(next_key); + if (isModifierKey(next_key)) { + last_modifier_time = now; + } + + if (tap_interval < 0) { + last_tap = 0; + state = Busy; + return; + } + + if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { + char_idx = 0; + } else { + char_idx += 1; + } + + last_key = next_key; + last_tap = now; +} + +void CardputerKeyboard::released() +{ + if (state != Held) { + return; + } + + if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { + last_key = -1; + state = Idle; + return; + } + + uint32_t now = millis(); + last_tap = now; + + if (inputBroker->menuMode && modifierFlag == 0) { + if (last_key == 0 || last_key == 43 || last_key == 46 || last_key == 47 || + last_key == 51) { // esc, left, up, down, right key + modifierFlag = modifierFn; + } + } + + queueEvent(CardputerTapMap[last_key][modifierFlag % CardputerTapMod[last_key]]); + if (isModifierKey(last_key) == false) + modifierFlag = 0; +} + +void CardputerKeyboard::updateModifierFlag(uint8_t key) +{ + if (key == modifierShiftKey) { + modifierFlag ^= modifierShift; + } else if (key == modifierFnKey) { + modifierFlag ^= modifierFn; + } +} + +bool CardputerKeyboard::isModifierKey(uint8_t key) +{ + return (key == modifierShiftKey || key == modifierFnKey); +} + +#endif \ No newline at end of file diff --git a/src/input/CardputerKeyboard.h b/src/input/CardputerKeyboard.h new file mode 100644 index 000000000..c9de1f36b --- /dev/null +++ b/src/input/CardputerKeyboard.h @@ -0,0 +1,26 @@ +#include "TCA8418KeyboardBase.h" + +class CardputerKeyboard : public TCA8418KeyboardBase +{ + public: + CardputerKeyboard(); + void reset(void); + void trigger(void) override; + virtual ~CardputerKeyboard() {} + + protected: + void pressed(uint8_t key) override; + void released(void) override; + + void updateModifierFlag(uint8_t key); + bool isModifierKey(uint8_t key); + + private: + uint8_t modifierFlag; + uint32_t last_modifier_time; + int8_t last_key; + int8_t next_key; + uint32_t last_tap; + uint8_t char_idx; + int32_t tap_interval; +}; diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index c0a56233f..acf79f149 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -100,13 +100,28 @@ void InputBroker::processInputEventQueue() int InputBroker::handleInputEvent(const InputEvent *event) { - powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release +#if HAS_SCREEN + bool screenWasOff = false; + if (screen) { + screenWasOff = !screen->isScreenOn(); + } +#endif + powerFSM.trigger(EVENT_INPUT); if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule && moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) { externalNotificationModule->stopNow(); + // If this turns off a notification, don't further process the event + return 0; } +#if HAS_SCREEN + if (screen && screenWasOff) { + // If the screen was off, it is in the process of turning on, and we just drop the event + return 0; + } +#endif + this->notifyObservers(event); return 0; } diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 9fcdd845f..847604011 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -70,6 +70,7 @@ class InputBroker : public Observable public: InputBroker(); + bool menuMode = true; void registerSource(Observable *source); void injectInputEvent(const InputEvent *event) { handleInputEvent(event); } #if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040) diff --git a/src/input/MPR121Keyboard.cpp b/src/input/MPR121Keyboard.cpp index ec37cfbaa..80a272d3b 100644 --- a/src/input/MPR121Keyboard.cpp +++ b/src/input/MPR121Keyboard.cpp @@ -177,7 +177,7 @@ void MPR121Keyboard::reset() delay(20); writeRegister(_MPR121_REG_CONFIG2, 0x21); delay(20); - // Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels + // Enter run mode by setting partial filter calibration tracking, disable proximity detection, enable 12 channels writeRegister(_MPR121_REG_ELECTRODE_CONFIG, ECR_CALIBRATION_TRACK_FROM_FULL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH); delay(100); @@ -430,4 +430,4 @@ void MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value) if (writeCallback) { writeCallback(m_addr, data[0], &(data[1]), 1); } -} \ No newline at end of file +} diff --git a/src/input/SeesawRotary.cpp b/src/input/SeesawRotary.cpp index 0a6e6e974..dc57b296b 100644 --- a/src/input/SeesawRotary.cpp +++ b/src/input/SeesawRotary.cpp @@ -59,7 +59,7 @@ int32_t SeesawRotary::runOnce() wasPressed = currentlyPressed; int32_t new_position = ss.getEncoderPosition(); - // did we move arounde? + // did we move around? if (encoder_position != new_position) { if (encoder_position == 0 && new_position != 1) { e.inputEvent = INPUT_BROKER_ALT_PRESS; @@ -80,4 +80,4 @@ int32_t SeesawRotary::runOnce() return 50; } -#endif \ No newline at end of file +#endif diff --git a/src/input/TCA8418Keyboard.cpp b/src/input/TCA8418Keyboard.cpp index 822885a9f..238b9bb51 100644 --- a/src/input/TCA8418Keyboard.cpp +++ b/src/input/TCA8418Keyboard.cpp @@ -88,7 +88,7 @@ void TCA8418Keyboard::pressed(uint8_t key) // Check if the key is the same as the last one or if the time interval has passed if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { char_idx = 0; // Reset char index if new key or long press - should_backspace = false; // dont backspace on new key + should_backspace = false; // don't backspace on new key } else { char_idx += 1; // Cycle through characters if same key pressed should_backspace = true; // allow backspace on same key diff --git a/src/input/TouchScreenBase.cpp b/src/input/TouchScreenBase.cpp index c2755980e..fceac74ba 100644 --- a/src/input/TouchScreenBase.cpp +++ b/src/input/TouchScreenBase.cpp @@ -43,7 +43,7 @@ int32_t TouchScreenBase::runOnce() // process touch events int16_t x, y; bool touched = getTouch(x, y); - if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turing off the screen + if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turning off the screen touched = false; if (touched) { this->setInterval(20); @@ -123,7 +123,7 @@ int32_t TouchScreenBase::runOnce() } } #else - // fire TAP event when no 2nd tap occured within time + // fire TAP event when no 2nd tap occurred within time if (_tapped) { _tapped = false; e.touchEvent = static_cast(TOUCH_ACTION_TAP); diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index d744ee2ca..510fb1e31 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -7,6 +7,8 @@ #include "TDeckProKeyboard.h" #elif defined(T_LORA_PAGER) #include "TLoraPagerKeyboard.h" +#elif defined(M5STACK_CARDPUTER_ADV) +#include "CardputerKeyboard.h" #elif defined(HACKADAY_COMMUNICATOR) #include "HackadayCommunicatorKeyboard.h" #else @@ -22,6 +24,8 @@ KbI2cBase::KbI2cBase(const char *name) TCAKeyboard(*(new TDeckProKeyboard())) #elif defined(T_LORA_PAGER) TCAKeyboard(*(new TLoraPagerKeyboard())) +#elif defined(M5STACK_CARDPUTER_ADV) + TCAKeyboard(*(new CardputerKeyboard())) #elif defined(HACKADAY_COMMUNICATOR) TCAKeyboard(*(new HackadayCommunicatorKeyboard())) #else @@ -487,7 +491,7 @@ int32_t KbI2cBase::runOnce() e.kbchar = 0; break; case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker)) - // toggle moddifiers button. + // toggle modifiers button. is_sym = !is_sym; e.inputEvent = INPUT_BROKER_ANYKEY; e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the diff --git a/src/main.cpp b/src/main.cpp index f70b3975e..8a46b3f5b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include "PowerMon.h" #include "RadioLibInterface.h" #include "ReliableRouter.h" +#include "TransmitHistory.h" #include "airtime.h" #include "buzz.h" #include "power/PowerHAL.h" @@ -192,8 +193,6 @@ bool kb_found = false; // global bool to record that on-screen keyboard (OSK) is present bool osk_found = false; -unsigned long last_listen = 0; - // The I2C address of the RTC Module (if found) ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE; // The I2C address of the Accelerometer (if found) @@ -248,7 +247,6 @@ uint32_t timeLastPowered = 0; static OSThread *powerFSMthread; OSThread *ambientLightingThread; -RadioInterface *rIf = NULL; RadioLibHal *RadioLibHAL = NULL; /** @@ -391,8 +389,8 @@ void setup() #if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM) #ifndef SENSECAP_INDICATOR - // use PSRAM for malloc calls > 256 bytes - heap_caps_malloc_extmem_enable(256); + // use PSRAM for malloc calls > 2048 bytes + heap_caps_malloc_extmem_enable(2048); #endif #endif @@ -706,6 +704,9 @@ void setup() // We do this as early as possible because this loads preferences from flash // but we need to do this after main cpu init (esp32setup), because we need the random seed set nodeDB = new NodeDB; + + // Initialize transmit history to persist broadcast throttle timers across reboots + TransmitHistory::getInstance()->loadFromDisk(); #if HAS_TFT if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { tftSetup(); @@ -1121,10 +1122,19 @@ void loop() #endif power->powerCommandsCheck(); - if (RadioLibInterface::instance != nullptr && !Throttle::isWithinTimespanMs(last_listen, 1000 * 60) && - !(RadioLibInterface::instance->isSending() || RadioLibInterface::instance->isActivelyReceiving())) { - RadioLibInterface::instance->startReceive(); - LOG_DEBUG("attempting AGC reset"); + if (RadioLibInterface::instance != nullptr) { + static uint32_t lastRadioMissedIrqPoll; + if (!Throttle::isWithinTimespanMs(lastRadioMissedIrqPoll, 1000)) { + lastRadioMissedIrqPoll = millis(); + RadioLibInterface::instance->pollMissedIrqs(); + } + + // Periodic AGC reset — warm sleep + recalibrate to prevent stuck AGC gain + static uint32_t lastAgcReset; + if (!Throttle::isWithinTimespanMs(lastAgcReset, AGC_RESET_INTERVAL_MS)) { + lastAgcReset = millis(); + RadioLibInterface::instance->resetAGC(); + } } #ifdef DEBUG_STACK diff --git a/src/main.h b/src/main.h index 619eb184e..91e27951f 100644 --- a/src/main.h +++ b/src/main.h @@ -33,7 +33,6 @@ extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; extern bool kb_found; extern bool osk_found; -extern unsigned long last_listen; extern ScanI2C::DeviceAddress rtc_found; extern ScanI2C::DeviceAddress accelerometer_found; extern ScanI2C::FoundDevice rgb_found; diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 4dcd94e3b..1583567fe 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -22,6 +22,8 @@ const char *Channels::serialChannel = "serial"; const char *Channels::mqttChannel = "mqtt"; #endif +meshtastic_Channel dummyChannel = {.index = -1}; + uint8_t xorHash(const uint8_t *p, size_t len) { uint8_t code = 0; @@ -309,13 +311,7 @@ meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex) return *ch; } else { LOG_ERROR("Invalid channel index %d > %d, malformed packet received?", chIndex, channelFile.channels_count); - - static meshtastic_Channel *ch = (meshtastic_Channel *)malloc(sizeof(meshtastic_Channel)); - memset(ch, 0, sizeof(meshtastic_Channel)); - // ch.index -1 means we don't know the channel locally and need to look it up by settings.name - // not sure this is handled right everywhere - ch->index = -1; - return *ch; + return dummyChannel; } } diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp index 1bd0340f8..3ecd766f1 100644 --- a/src/mesh/Default.cpp +++ b/src/mesh/Default.cpp @@ -38,11 +38,13 @@ uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultVa uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes) { // If we are a router, we don't scale the value. It's already significantly higher. - if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) + if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) return getConfiguredOrDefaultMs(configured, defaultValue); // Additionally if we're a tracker or sensor, we want priority to send position and telemetry - if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_TRACKER)) + if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_TRACKER, + meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) return getConfiguredOrDefaultMs(configured, defaultValue); return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes); diff --git a/src/mesh/Default.h b/src/mesh/Default.h index e206d8277..4cfbdbcc8 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -1,5 +1,8 @@ #pragma once +#include #include +#include +#include #include #include #define ONE_DAY 24 * 60 * 60 @@ -42,7 +45,10 @@ #define default_mqtt_tls_enabled false #define IF_ROUTER(routerVal, normalVal) \ - ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal)) + ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || \ + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) \ + ? (routerVal) \ + : (normalVal)) class Default { @@ -63,25 +69,39 @@ class Default if (numOnlineNodes <= 40) { return 1.0; } else { - float throttlingFactor = 0.075; - if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW) - throttlingFactor = 0.04; - else if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST) - throttlingFactor = 0.02; - else if (config.lora.use_preset && - IS_ONE_OF(config.lora.modem_preset, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST, - meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO, - meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW)) - throttlingFactor = 0.01; + // Resolve SF and BW from preset or manual config + // When use_preset is true, config.lora.spread_factor and bandwidth may be 0 + // because applyModemConfig() sets them on RadioInterface, not on config.lora + float bwKHz; + uint8_t sf; + uint8_t cr; + if (config.lora.use_preset) { + modemPresetToParams(config.lora.modem_preset, false, bwKHz, sf, cr); + } else { + sf = config.lora.spread_factor; + bwKHz = bwCodeToKHz(config.lora.bandwidth); + } + + // Guard against invalid values + sf = clampSpreadFactor(sf); + bwKHz = clampBandwidthKHz(bwKHz); + + // throttlingFactor = 2^SF / (BW_in_kHz * scaling_divisor) + // With scaling_divisor=100: + // In SF11 and BW=250khz (longfast), this gives 0.08192 rather than the original 0.075 + // In SF10 and BW=250khz (mediumslow), this gives 0.04096 rather than the original 0.04 + // In SF9 and BW=250khz (mediumfast), this gives 0.02048 rather than the original 0.02 + // In SF7 and BW=250khz (shortfast), this gives 0.00512 rather than the original 0.01 + float throttlingFactor = static_cast(pow_of_2(sf)) / (bwKHz * 100.0f); #if USERPREFS_EVENT_MODE - // If we are in event mode, scale down the throttling factor - throttlingFactor = 0.04; + // If we are in event mode, scale down the throttling factor by 4 + throttlingFactor = static_cast(pow_of_2(sf)) / (bwKHz * 25.0f); #endif // Scaling up traffic based on number of nodes over 40 int nodesOverForty = (numOnlineNodes - 40); - return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default) + return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by throttle factor } } -}; +}; \ No newline at end of file diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 7c73b56cd..4fec06da4 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -263,6 +263,7 @@ template void LR11x0Interface::startReceive() // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); + checkRxDoneIrqFlag(); #endif } @@ -298,6 +299,38 @@ template bool LR11x0Interface::isActivelyReceiving() RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED); } +#ifdef LR11X0_AGC_RESET +template void LR11x0Interface::resetAGC() +{ + // Safety: don't reset mid-packet + if (sendingPacket != NULL || (isReceiving && isActivelyReceiving())) + return; + + LOG_DEBUG("LR11x0 AGC reset: warm sleep + Calibrate(0x3F)"); + + // 1. Warm sleep — powers down the analog frontend, resetting AGC state + lora.sleep(true, 0); + + // 2. Wake to RC standby for stable calibration + lora.standby(RADIOLIB_LR11X0_STANDBY_RC, true); + + // 3. Calibrate all blocks (PLL, ADC, image, RC oscillators) + // calibrate() is protected on LR11x0, so use raw SPI (same as internal implementation) + uint8_t calData = RADIOLIB_LR11X0_CALIBRATE_ALL; + module.SPIwriteStream(RADIOLIB_LR11X0_CMD_CALIBRATE, &calData, 1, true, true); + + // 4. Re-calibrate image rejection for actual operating frequency + // Calibrate(0x3F) defaults to 902-928 MHz which is wrong for other regions. + lora.calibrateImageRejection(getFreq() - 4.0f, getFreq() + 4.0f); + + // 5. Re-apply RX boosted gain mode + lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain); + + // 6. Resume receiving + startReceive(); +} +#endif + template bool LR11x0Interface::sleep() { // \todo Display actual typename of the adapter, not just `LR11x0` diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 840184bbf..1a6b92520 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -27,6 +27,10 @@ template class LR11x0Interface : public RadioLibInterface bool isIRQPending() override { return lora.getIrqFlags() != 0; } +#ifdef LR11X0_AGC_RESET + void resetAGC() override; +#endif + protected: /** * Specific module instance diff --git a/src/mesh/LoRaFEMInterface.cpp b/src/mesh/LoRaFEMInterface.cpp new file mode 100644 index 000000000..bad795c3a --- /dev/null +++ b/src/mesh/LoRaFEMInterface.cpp @@ -0,0 +1,228 @@ +#if HAS_LORA_FEM +#include "LoRaFEMInterface.h" + +#if defined(ARCH_ESP32) +#include +#include +#endif + +LoRaFEMInterface loraFEMInterface; +void LoRaFEMInterface::init(void) +{ + setLnaCanControl(false); // Default is uncontrollable +#ifdef HELTEC_V4 + pinMode(LORA_PA_POWER, OUTPUT); + digitalWrite(LORA_PA_POWER, HIGH); + rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER); + delay(1); + rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD); + pinMode(LORA_KCT8103L_PA_CSD, INPUT); // detect which FEM is used + delay(1); + if (digitalRead(LORA_KCT8103L_PA_CSD) == HIGH) { + // FEM is KCT8103L + fem_type = KCT8103L_PA; + rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX); + pinMode(LORA_KCT8103L_PA_CSD, OUTPUT); + digitalWrite(LORA_KCT8103L_PA_CSD, HIGH); + pinMode(LORA_KCT8103L_PA_CTX, OUTPUT); + digitalWrite(LORA_KCT8103L_PA_CTX, HIGH); + setLnaCanControl(true); + } else if (digitalRead(LORA_KCT8103L_PA_CSD) == LOW) { + // FEM is GC1109 + fem_type = GC1109_PA; + // LORA_GC1109_PA_EN and LORA_KCT8103L_PA_CSD are the same pin and do not need to be repeatedly turned off and held. + // rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN); + pinMode(LORA_GC1109_PA_EN, OUTPUT); + digitalWrite(LORA_GC1109_PA_EN, HIGH); + pinMode(LORA_GC1109_PA_TX_EN, OUTPUT); + digitalWrite(LORA_GC1109_PA_TX_EN, LOW); + } else { + fem_type = OTHER_FEM_TYPES; + } +#elif defined(USE_GC1109_PA) + fem_type = GC1109_PA; + pinMode(LORA_PA_POWER, OUTPUT); + digitalWrite(LORA_PA_POWER, HIGH); +#if defined(ARCH_ESP32) + rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER); + rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN); + rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_TX_EN); +#endif + delay(1); + pinMode(LORA_GC1109_PA_EN, OUTPUT); + digitalWrite(LORA_GC1109_PA_EN, HIGH); + pinMode(LORA_GC1109_PA_TX_EN, OUTPUT); + digitalWrite(LORA_GC1109_PA_TX_EN, LOW); +#elif defined(USE_KCT8103L_PA) + fem_type = KCT8103L_PA; + pinMode(LORA_PA_POWER, OUTPUT); + digitalWrite(LORA_PA_POWER, HIGH); +#if defined(ARCH_ESP32) + rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER); + rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD); + rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX); +#endif + delay(1); + pinMode(LORA_KCT8103L_PA_CSD, OUTPUT); + digitalWrite(LORA_KCT8103L_PA_CSD, HIGH); + pinMode(LORA_KCT8103L_PA_CTX, OUTPUT); + digitalWrite(LORA_KCT8103L_PA_CTX, HIGH); + setLnaCanControl(true); +#endif +} + +void LoRaFEMInterface::setSleepModeEnable(void) +{ +#ifdef HELTEC_V4 + if (fem_type == GC1109_PA) { + /* + * Do not switch the power on and off frequently. + * After turning off LORA_GC1109_PA_EN, the power consumption has dropped to the uA level. + */ + digitalWrite(LORA_GC1109_PA_EN, LOW); + digitalWrite(LORA_GC1109_PA_TX_EN, LOW); + } else if (fem_type == KCT8103L_PA) { + // shutdown the PA + digitalWrite(LORA_KCT8103L_PA_CSD, LOW); + } +#elif defined(USE_GC1109_PA) + digitalWrite(LORA_GC1109_PA_EN, LOW); + digitalWrite(LORA_GC1109_PA_TX_EN, LOW); +#elif defined(USE_KCT8103L_PA) + // shutdown the PA + digitalWrite(LORA_KCT8103L_PA_CSD, LOW); +#endif +} + +void LoRaFEMInterface::setTxModeEnable(void) +{ +#ifdef HELTEC_V4 + if (fem_type == GC1109_PA) { + digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled + digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care) + } else if (fem_type == KCT8103L_PA) { + digitalWrite(LORA_KCT8103L_PA_CSD, HIGH); + digitalWrite(LORA_KCT8103L_PA_CTX, HIGH); + } +#elif defined(USE_GC1109_PA) + digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled + digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care) +#elif defined(USE_KCT8103L_PA) + digitalWrite(LORA_KCT8103L_PA_CSD, HIGH); + digitalWrite(LORA_KCT8103L_PA_CTX, HIGH); +#endif +} + +void LoRaFEMInterface::setRxModeEnable(void) +{ +#ifdef HELTEC_V4 + if (fem_type == GC1109_PA) { + digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled + digitalWrite(LORA_GC1109_PA_TX_EN, LOW); + } else if (fem_type == KCT8103L_PA) { + digitalWrite(LORA_KCT8103L_PA_CSD, HIGH); + if (lna_enabled) { + digitalWrite(LORA_KCT8103L_PA_CTX, LOW); + } else { + digitalWrite(LORA_KCT8103L_PA_CTX, HIGH); + } + } +#elif defined(USE_GC1109_PA) + digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled + digitalWrite(LORA_GC1109_PA_TX_EN, LOW); +#elif defined(USE_KCT8103L_PA) + digitalWrite(LORA_KCT8103L_PA_CSD, HIGH); + if (lna_enabled) { + digitalWrite(LORA_KCT8103L_PA_CTX, LOW); + } else { + digitalWrite(LORA_KCT8103L_PA_CTX, HIGH); + } +#endif +} + +void LoRaFEMInterface::setRxModeEnableWhenMCUSleep(void) +{ + +#ifdef HELTEC_V4 + // Keep GC1109 FEM powered during deep sleep so LNA remains active for RX wake. + // Set PA_POWER and PA_EN HIGH (overrides SX126xInterface::sleep() shutdown), + // then latch with RTC hold so the state survives deep sleep. + digitalWrite(LORA_PA_POWER, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER); + if (fem_type == GC1109_PA) { + digitalWrite(LORA_GC1109_PA_EN, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN); + gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN); + } else if (fem_type == KCT8103L_PA) { + digitalWrite(LORA_KCT8103L_PA_CSD, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD); + if (lna_enabled) { + digitalWrite(LORA_KCT8103L_PA_CTX, LOW); + } else { + digitalWrite(LORA_KCT8103L_PA_CTX, HIGH); + } + rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX); + } +#elif defined(USE_GC1109_PA) + digitalWrite(LORA_PA_POWER, HIGH); + digitalWrite(LORA_GC1109_PA_EN, HIGH); +#if defined(ARCH_ESP32) + rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER); + rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN); + gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN); +#endif +#elif defined(USE_KCT8103L_PA) + digitalWrite(LORA_KCT8103L_PA_CSD, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD); + if (lna_enabled) { + digitalWrite(LORA_KCT8103L_PA_CTX, LOW); + } else { + digitalWrite(LORA_KCT8103L_PA_CTX, HIGH); + } + rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX); +#endif +} + +void LoRaFEMInterface::setLNAEnable(bool enabled) +{ + lna_enabled = enabled; +} + +int8_t LoRaFEMInterface::powerConversion(int8_t loraOutputPower) +{ +#ifdef HELTEC_V4 + const uint16_t gc1109_tx_gain[] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7}; + const uint16_t kct8103l_tx_gain[] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7}; + const uint16_t *tx_gain; + uint16_t tx_gain_num; + if (fem_type == GC1109_PA) { + tx_gain = gc1109_tx_gain; + tx_gain_num = sizeof(gc1109_tx_gain) / sizeof(gc1109_tx_gain[0]); + } else if (fem_type == KCT8103L_PA) { + tx_gain = kct8103l_tx_gain; + tx_gain_num = sizeof(kct8103l_tx_gain) / sizeof(kct8103l_tx_gain[0]); + } else { + return loraOutputPower; + } +#else +#ifdef ARCH_PORTDUINO + const uint16_t *tx_gain = portduino_config.tx_gain_lora; + uint16_t tx_gain_num = portduino_config.num_pa_points; +#else + const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; + uint16_t tx_gain_num = NUM_PA_POINTS; +#endif +#endif + for (int radio_dbm = 0; radio_dbm < tx_gain_num; radio_dbm++) { + if (((radio_dbm + tx_gain[radio_dbm]) > loraOutputPower) || + ((radio_dbm == (tx_gain_num - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= loraOutputPower))) { + // we've exceeded the power limit, or hit the max we can do + LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", loraOutputPower, tx_gain[radio_dbm]); + loraOutputPower -= tx_gain[radio_dbm]; + break; + } + } + return loraOutputPower; +} + +#endif \ No newline at end of file diff --git a/src/mesh/LoRaFEMInterface.h b/src/mesh/LoRaFEMInterface.h new file mode 100644 index 000000000..0a7c810ef --- /dev/null +++ b/src/mesh/LoRaFEMInterface.h @@ -0,0 +1,30 @@ +#pragma once +#if HAS_LORA_FEM +#include "configuration.h" +#include + +typedef enum { GC1109_PA, KCT8103L_PA, OTHER_FEM_TYPES } LoRaFEMType; + +class LoRaFEMInterface +{ + public: + LoRaFEMInterface() {} + virtual ~LoRaFEMInterface() {} + void init(void); + void setSleepModeEnable(void); + void setTxModeEnable(void); + void setRxModeEnable(void); + void setRxModeEnableWhenMCUSleep(void); + void setLNAEnable(bool enabled); + int8_t powerConversion(int8_t loraOutputPower); + bool isLnaCanControl(void) { return lna_can_control; } + void setLnaCanControl(bool can_control) { lna_can_control = can_control; } + + private: + LoRaFEMType fem_type; + bool lna_enabled = false; + bool lna_can_control = false; +}; +extern LoRaFEMInterface loraFEMInterface; + +#endif \ No newline at end of file diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index 8febe51bd..e902a1922 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -107,6 +107,18 @@ class MeshModule /* We allow modules to ignore a request without sending an error if they have a specific reason for it. */ bool ignoreRequest = false; + /** + * Check if the current request is a multi-hop broadcast. Modules should call this in allocReply() + * and return NULL to prevent reply storms from broadcast requests that have already been relayed. + */ + bool isMultiHopBroadcastRequest() + { + if (currentRequest && isBroadcast(currentRequest->to) && currentRequest->hop_limit < currentRequest->hop_start) { + return true; + } + return false; + } + /** If a bound channel name is set, we will only accept received packets that come in on that channel. * A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface * are allowed on any channel (this lets the local user do anything). diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index cbea85c62..4aad40c69 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -184,6 +184,29 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p) } } + if (backPacket->tx_after) { + // Check if there's a late packet at the queue end + auto now = millis(); + if (backPacket->tx_after < now && (!p->tx_after || backPacket->tx_after > p->tx_after)) { + int32_t dt = (int32_t)(backPacket->tx_after - now); + if (p->tx_after) { + LOG_WARN("Dropping late packet 0x%08x with TX delay %dms to make room in the TX queue for packet 0x%08x with " + "TX delay %ums", + backPacket->id, dt, p->id, p->tx_after - now); + + } else { + LOG_WARN("Dropping late packet 0x%08x with TX delay %dms to make room in the TX queue for packet 0x%08x " + "with no TX delay", + backPacket->id, dt, p->id); + } + queue.pop_back(); + packetPool.release(backPacket); + // Insert the new packet in the correct order + enqueue(p); + return true; + } + } + // If the back packet's priority is not lower, no replacement occurs return false; } \ No newline at end of file diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index bbb0ee00f..07d956878 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -24,6 +24,45 @@ extern const RegionInfo *myRegion; extern void initRegion(); +// Valid LoRa spread factor range and defaults +constexpr uint8_t LORA_SF_MIN = 7; +constexpr uint8_t LORA_SF_MAX = 12; +constexpr uint8_t LORA_SF_DEFAULT = 11; // LONG_FAST default + +// Valid LoRa coding rate range and default +constexpr uint8_t LORA_CR_MIN = 4; +constexpr uint8_t LORA_CR_MAX = 8; +constexpr uint8_t LORA_CR_DEFAULT = 5; // LONG_FAST default + +// Default bandwidth in kHz (LONG_FAST) +constexpr float LORA_BW_DEFAULT_KHZ = 250.0f; + +/// Clamp spread factor to the valid LoRa range [7, 12]. +/// Out-of-range values (including 0 from unset preset mode) return LORA_SF_DEFAULT. +static inline uint8_t clampSpreadFactor(uint8_t sf) +{ + if (sf < LORA_SF_MIN || sf > LORA_SF_MAX) + return LORA_SF_DEFAULT; + return sf; +} + +/// Clamp coding rate to the valid LoRa range [4, 8]. +/// Out-of-range values return LORA_CR_DEFAULT. +static inline uint8_t clampCodingRate(uint8_t cr) +{ + if (cr < LORA_CR_MIN || cr > LORA_CR_MAX) + return LORA_CR_DEFAULT; + return cr; +} + +/// Ensure bandwidth is positive. Non-positive values return LORA_BW_DEFAULT_KHZ. +static inline float clampBandwidthKHz(float bwKHz) +{ + if (bwKHz <= 0.0f) + return LORA_BW_DEFAULT_KHZ; + return bwKHz; +} + static inline float bwCodeToKHz(uint16_t bwCode) { if (bwCode == 31) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index c1b3839bb..952a6d2be 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -87,7 +87,9 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio - bool isPreferredRebroadcaster = config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER; + bool isPreferredRebroadcaster = + IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, + meshtastic_Config_DeviceConfig_Role_CLIENT_BASE); if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) { LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo"); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index a52fa4478..529f50003 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -143,9 +143,8 @@ uint32_t get_st7789_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_ digitalWrite(rst, HIGH); delay(10); - uint32_t ID = 0; - ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); - ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); // ST7789 needs twice + readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); + uint32_t ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); // ST7789 needs twice return ID; } @@ -322,9 +321,9 @@ NodeDB::NodeDB() // Uncomment below to always enable UDP broadcasts // config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST; - // If we are setup to broadcast on the default channel, ensure that the telemetry intervals are coerced to the minimum value - // of 30 minutes or more - if (channels.isDefaultChannel(channels.getPrimaryIndex())) { + // If we are setup to broadcast on any default channel slot (with default frequency slot semantics), + // ensure that the telemetry intervals are coerced to the minimum value of 30 minutes or more. + if (channels.hasDefaultChannel()) { LOG_DEBUG("Coerce telemetry to min of 30 minutes on defaults"); moduleConfig.telemetry.device_update_interval = Default::getConfiguredOrMinimumValue( moduleConfig.telemetry.device_update_interval, min_default_telemetry_interval_secs); @@ -569,6 +568,11 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off) config.lora.override_duty_cycle = false; config.lora.config_ok_to_mqtt = false; +#if HAS_LORA_FEM + config.lora.fem_lna_mode = meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED; +#else + config.lora.fem_lna_mode = meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT; +#endif #if HAS_TFT // For the devices that support MUI, default to that config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR; @@ -668,7 +672,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) #endif config.position.broadcast_smart_minimum_distance = 100; config.position.broadcast_smart_minimum_interval_secs = default_broadcast_smart_minimum_interval_secs; - if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER) + if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && + config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; config.security.serial_enabled = true; config.security.admin_channel_enabled = false; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 9050ee89d..a02f96ac5 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -122,6 +122,8 @@ void PhoneAPI::close() } packetForPhone = NULL; filesManifest.clear(); + filesManifest.shrink_to_fit(); + lastPortNumToRadio.clear(); fromRadioNum = 0; config_nonce = 0; config_state = 0; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 0c12401ca..b3aa72f7a 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -301,6 +301,7 @@ void RF95Interface::startReceive() // Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); + checkRxDoneIrqFlag(); } bool RF95Interface::isChannelActive() diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index e8202d9b0..4defd00ed 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -16,6 +16,7 @@ #include "configuration.h" #include "detect/LoRaRadioType.h" #include "main.h" +#include "meshUtils.h" // for pow_of_2 #include "sleep.h" #include #include @@ -31,12 +32,6 @@ #include "STM32WLE5JCInterface.h" #endif -// Calculate 2^n without calling pow() -uint32_t pow_of_2(uint32_t n) -{ - return 1 << n; -} - #define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \ { \ meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \ @@ -920,6 +915,12 @@ void RadioInterface::limitPower(int8_t loraMaxPower) power = maxPower; } +#if HAS_LORA_FEM + if (!devicestate.owner.is_licensed) { + power = loraFEMInterface.powerConversion(power); + } +#else +// todo:All entries containing "lora fem" are grouped together above. #ifdef ARCH_PORTDUINO size_t num_pa_points = portduino_config.num_pa_points; const uint16_t *tx_gain = portduino_config.tx_gain_lora; @@ -945,7 +946,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower) } } } - +#endif if (power > loraMaxPower) // Clamp power to maximum defined level power = loraMaxPower; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 1fe3dd7b0..05825dce1 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -8,6 +8,10 @@ #include "error.h" #include +#if HAS_LORA_FEM +#include "LoRaFEMInterface.h" +#endif + // Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file. typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index af6ab30c1..30cd587da 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -517,12 +517,31 @@ void RadioLibInterface::handleReceiveInterrupt() void RadioLibInterface::startReceive() { - // Note the updated timestamp, to avoid unneeded AGC resets - last_listen = millis(); isReceiving = true; powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); } +void RadioLibInterface::pollMissedIrqs() +{ + // RadioLibInterface::enableInterrupt uses EDGE-TRIGGERED interrupts. Poll as a backup to catch missed edges. + if (isReceiving) { + checkRxDoneIrqFlag(); + } +} + +void RadioLibInterface::resetAGC() +{ + // Base implementation: no-op. Override in chip-specific subclasses. +} + +void RadioLibInterface::checkRxDoneIrqFlag() +{ + if (iface->checkIrq(RADIOLIB_IRQ_RX_DONE)) { + LOG_WARN("caught missed RX_DONE"); + notify(ISR_RX, true); + } +} + void RadioLibInterface::configHardwareForSend() { powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn); diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 833c88710..ca3d78503 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -19,6 +19,8 @@ // In addition to the default Rx flags, we need the PREAMBLE_DETECTED flag to detect whether we are actively receiving #define MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS (RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1 << RADIOLIB_IRQ_PREAMBLE_DETECTED)) +#define AGC_RESET_INTERVAL_MS (60 * 1000) // 60 seconds + /** * We need to override the RadioLib ArduinoHal class to add mutex protection for SPI bus access */ @@ -112,6 +114,18 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void enableInterrupt(void (*)()) = 0; + /** + * Poll as a backup to catch missed edge-triggered interrupts. + */ + void pollMissedIrqs(); + + /** + * Reset AGC by power-cycling the analog frontend. + * Subclasses override with chip-specific calibration sequences. + * Safe to call periodically — skips if currently sending or receiving. + */ + virtual void resetAGC(); + /** * Debugging counts */ @@ -264,4 +278,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override; + + void checkRxDoneIrqFlag(); }; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 32544a051..2e4c4d7d9 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -16,6 +16,7 @@ #endif #include "Default.h" #if ARCH_PORTDUINO +#include "Throttle.h" #include "platform/portduino/PortduinoGlue.h" #endif #if ENABLE_JSON_LOGGING || ARCH_PORTDUINO @@ -532,6 +533,25 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p) if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) { LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str()); } else if (portduino_config.JSONFilename != "") { + if (portduino_config.JSONFileRotate != 0) { + static uint32_t fileage = 0; + + if (portduino_config.JSONFileRotate != 0 && + (fileage == 0 || !Throttle::isWithinTimespanMs(fileage, portduino_config.JSONFileRotate * 60 * 1000))) { + time_t timestamp = time(NULL); + struct tm *timeinfo; + char buffer[80]; + timeinfo = localtime(×tamp); + strftime(buffer, 80, "%Y%m%d-%H%M%S", timeinfo); + + std::string datetime(buffer); + if (JSONFile.is_open()) { + JSONFile.close(); + } + JSONFile.open(portduino_config.JSONFilename + "_" + datetime, std::ios::out | std::ios::app); + fileage = millis(); + } + } if (portduino_config.JSONFilter == (_meshtastic_PortNum)0 || portduino_config.JSONFilter == p->decoded.portnum) { JSONFile << MeshPacketSerializer::JsonSerialize(p, false) << std::endl; } diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 9dfc46bee..4dd90b6e6 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -6,6 +6,10 @@ #ifdef ARCH_PORTDUINO #include "PortduinoGlue.h" #endif +#if defined(ARCH_ESP32) +#include +#include +#endif #include "Throttle.h" @@ -52,22 +56,12 @@ template bool SX126xInterface::init() pinMode(SX126X_POWER_EN, OUTPUT); #endif -#if defined(USE_GC1109_PA) - // GC1109 FEM chip initialization - // See variant.h for full pin mapping and control logic documentation - - // VFEM_Ctrl (LORA_PA_POWER): Power enable for GC1109 LDO (always on) - pinMode(LORA_PA_POWER, OUTPUT); - digitalWrite(LORA_PA_POWER, HIGH); - - // CSD (LORA_PA_EN): Chip enable - must be HIGH to enable GC1109 for both RX and TX - pinMode(LORA_PA_EN, OUTPUT); - digitalWrite(LORA_PA_EN, HIGH); - - // CPS (LORA_PA_TX_EN): PA mode select - HIGH enables full PA during TX, LOW for RX (don't care) - // Note: TX/RX path switching (CTX) is handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH - pinMode(LORA_PA_TX_EN, OUTPUT); - digitalWrite(LORA_PA_TX_EN, LOW); // Start in RX-ready state +#if HAS_LORA_FEM + loraFEMInterface.init(); + // Apply saved FEM LNA mode from config + if (loraFEMInterface.isLnaCanControl()) { + loraFEMInterface.setLNAEnable(config.lora.fem_lna_mode == meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ENABLED); + } #endif #ifdef RF95_FAN_EN @@ -171,6 +165,14 @@ template bool SX126xInterface::init() LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", result); } + // Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity. + // Sets bit 0 of register 0x8B5. + if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) == RADIOLIB_ERR_NONE) { + LOG_INFO("Applied SX1262 register 0x8B5 patch for RX improvement"); + } else { + LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for RX improvement"); + } + #if 0 // Read/write a register we are not using (only used for FSK mode) to test SPI comms uint8_t crcLSB = 0; @@ -328,6 +330,7 @@ template void SX126xInterface::startReceive() // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); + checkRxDoneIrqFlag(); #endif } @@ -387,25 +390,77 @@ template bool SX126xInterface::sleep() digitalWrite(SX126X_POWER_EN, LOW); #endif -#if defined(USE_GC1109_PA) - /* - * Do not switch the power on and off frequently. - * After turning off LORA_PA_EN, the power consumption has dropped to the uA level. - * // digitalWrite(LORA_PA_POWER, LOW); - */ - digitalWrite(LORA_PA_EN, LOW); - digitalWrite(LORA_PA_TX_EN, LOW); +#if HAS_LORA_FEM + loraFEMInterface.setSleepModeEnable(); #endif + return true; } +template void SX126xInterface::resetAGC() +{ + // Safety: don't reset mid-packet + if (sendingPacket != NULL || (isReceiving && isActivelyReceiving())) + return; + + LOG_DEBUG("SX126x AGC reset: warm sleep + Calibrate(0x7F)"); + + // 1. Warm sleep — powers down the entire analog frontend, resetting AGC state. + // A plain standby→startReceive cycle does NOT reset the AGC. + lora.sleep(true); + + // 2. Wake to RC standby for stable calibration + lora.standby(RADIOLIB_SX126X_STANDBY_RC, true); + + // 3. Calibrate all blocks (ADC, PLL, image, RC oscillators) + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + module.SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + + // 4. Wait for calibration to complete (BUSY pin goes low) + module.hal->delay(5); + uint32_t start = millis(); + while (module.hal->digitalRead(module.getGpio())) { + if (millis() - start > 50) + break; + module.hal->yield(); + } + + if (module.hal->digitalRead(module.getGpio())) { + LOG_WARN("SX126x AGC reset: calibration did not complete within 50ms"); + startReceive(); + return; + } + + // 5. Re-calibrate image rejection for actual operating frequency + // Calibrate(0x7F) defaults to 902-928 MHz which is wrong for other regions. + lora.calibrateImage(getFreq()); + + // Re-apply settings that calibration may have reset + + // DIO2 as RF switch +#ifdef SX126X_DIO2_AS_RF_SWITCH + lora.setDio2AsRfSwitch(true); +#elif defined(ARCH_PORTDUINO) + if (portduino_config.dio2_as_rf_switch) + lora.setDio2AsRfSwitch(true); +#endif + + // RX boosted gain mode + lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain); + + // 6. Resume receiving + startReceive(); +} + /** Control PA mode for GC1109 FEM - CPS pin selects full PA (txon=true) or bypass mode (txon=false) */ template void SX126xInterface::setTransmitEnable(bool txon) { -#if defined(USE_GC1109_PA) - digitalWrite(LORA_PA_POWER, HIGH); // Ensure LDO is on - digitalWrite(LORA_PA_EN, HIGH); // CSD=1: Chip enabled - digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care) +#if HAS_LORA_FEM + if (txon) { + loraFEMInterface.setTxModeEnable(); + } else { + loraFEMInterface.setRxModeEnable(); + } #endif } diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index b8f16ac6d..67625e115 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -28,6 +28,8 @@ template class SX126xInterface : public RadioLibInterface bool isIRQPending() override { return lora.getIrqFlags() != 0; } + void resetAGC() override; + void setTCXOVoltage(float voltage) { tcxoVoltage = voltage; } protected: diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 9fcedfe49..0e882ef05 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -271,6 +271,7 @@ template void SX128xInterface::startReceive() // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); + checkRxDoneIrqFlag(); #endif } diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 97e231f23..c724871cb 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -52,6 +52,9 @@ class StreamAPI : public PhoneAPI virtual int32_t runOncePart(); virtual int32_t runOncePart(char *buf, uint16_t bufLen); + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override = 0; + private: /** * Read any rx chars from the link and call handleToRadio @@ -73,9 +76,6 @@ class StreamAPI : public PhoneAPI virtual void onConnectionChanged(bool connected) override; - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override = 0; - /** * Send the current txBuffer over our stream */ diff --git a/src/mesh/TransmitHistory.cpp b/src/mesh/TransmitHistory.cpp new file mode 100644 index 000000000..3dbabc635 --- /dev/null +++ b/src/mesh/TransmitHistory.cpp @@ -0,0 +1,202 @@ +#include "TransmitHistory.h" +#include "FSCommon.h" +#include "RTC.h" +#include "SPILock.h" +#include + +#ifdef FSCom + +TransmitHistory *transmitHistory = nullptr; + +TransmitHistory *TransmitHistory::getInstance() +{ + if (!transmitHistory) { + transmitHistory = new TransmitHistory(); + } + return transmitHistory; +} + +void TransmitHistory::loadFromDisk() +{ + spiLock->lock(); + auto file = FSCom.open(FILENAME, FILE_O_READ); + if (file) { + FileHeader header{}; + if (file.read((uint8_t *)&header, sizeof(header)) == sizeof(header) && header.magic == MAGIC && + header.version == VERSION && header.count <= MAX_ENTRIES) { + for (uint8_t i = 0; i < header.count; i++) { + Entry entry{}; + if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry)) { + if (entry.epochSeconds > 0) { + history[entry.key] = entry.epochSeconds; + // Seed in-memory millis so throttle works even without RTC/GPS. + // Treating stored entries as "just sent" is safe — worst case the + // node waits one full interval before its first broadcast. + lastMillis[entry.key] = millis(); + } + } + } + LOG_INFO("TransmitHistory: loaded %u entries from disk", header.count); + } else { + LOG_WARN("TransmitHistory: invalid file header, starting fresh"); + } + file.close(); + } else { + LOG_INFO("TransmitHistory: no history file found, starting fresh"); + } + spiLock->unlock(); + dirty = false; +} + +void TransmitHistory::setLastSentToMesh(uint16_t key) +{ + lastMillis[key] = millis(); + uint32_t now = getTime(); + if (now >= 2) { + history[key] = now; + dirty = true; + // Don't flush to disk on every transmit — flash has limited write endurance. + // The in-memory lastMillis map handles throttle during normal operation. + // Disk is flushed: before deep sleep (sleep.cpp) and periodically here, + // throttled to at most once per 5 minutes. Always save the first time + // after boot so a crash-reboot loop can't avoid persisting. + if (lastDiskSave == 0 || !Throttle::isWithinTimespanMs(lastDiskSave, SAVE_INTERVAL_MS)) { + if (saveToDisk()) { + lastDiskSave = millis(); + } + } + } +} + +uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const +{ + auto it = history.find(key); + if (it != history.end()) { + return it->second; + } + return 0; +} + +uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const +{ + // Prefer runtime millis value (accurate within this boot) + auto mit = lastMillis.find(key); + if (mit != lastMillis.end()) { + return mit->second; + } + + // Fall back to epoch conversion (loaded from disk after reboot) + uint32_t storedEpoch = getLastSentToMeshEpoch(key); + if (storedEpoch == 0) { + return 0; // No stored time — module has never sent + } + + uint32_t now = getTime(); + if (now < 2) { + // No valid RTC time yet — can't convert to millis. Return 0 so throttle doesn't block. + return 0; + } + + if (storedEpoch > now) { + // Stored time is in the future (clock went backwards?) — treat as stale + return 0; + } + + uint32_t secondsAgo = now - storedEpoch; + uint32_t msAgo = secondsAgo * 1000; + + // Guard against overflow: if the transmit was very long ago, just return 0 (won't throttle) + if (secondsAgo > 86400 || msAgo / 1000 != secondsAgo) { + return 0; + } + + // Convert to a millis()-relative timestamp: millis() - msAgo + // This gives a value that, when passed to Throttle::isWithinTimespanMs(value, interval), + // correctly reports whether the transmit was within interval ms. + return millis() - msAgo; +} + +bool TransmitHistory::saveToDisk() +{ + if (!dirty) { + return true; + } + + spiLock->lock(); + + FSCom.mkdir("/prefs"); + + // Remove old file first + if (FSCom.exists(FILENAME)) { + FSCom.remove(FILENAME); + } + + auto file = FSCom.open(FILENAME, FILE_O_WRITE); + if (file) { + FileHeader header{}; + header.magic = MAGIC; + header.version = VERSION; + header.count = (uint8_t)min((size_t)MAX_ENTRIES, history.size()); + + file.write((uint8_t *)&header, sizeof(header)); + + uint8_t written = 0; + for (auto &pair : history) { + if (written >= MAX_ENTRIES) + break; + Entry entry{}; + entry.key = pair.first; + entry.epochSeconds = pair.second; + file.write((uint8_t *)&entry, sizeof(entry)); + written++; + } + file.flush(); + file.close(); + LOG_DEBUG("TransmitHistory: saved %u entries to disk", written); + dirty = false; + spiLock->unlock(); + return true; + } else { + LOG_WARN("TransmitHistory: failed to open file for writing"); + } + + spiLock->unlock(); + return false; +} + +#else +// No filesystem available — provide stub with in-memory tracking +TransmitHistory *transmitHistory = nullptr; + +TransmitHistory *TransmitHistory::getInstance() +{ + if (!transmitHistory) { + transmitHistory = new TransmitHistory(); + } + return transmitHistory; +} + +void TransmitHistory::loadFromDisk() {} + +void TransmitHistory::setLastSentToMesh(uint16_t key) +{ + lastMillis[key] = millis(); +} + +uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const +{ + return 0; +} + +uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const +{ + auto mit = lastMillis.find(key); + return (mit != lastMillis.end()) ? mit->second : 0; +} + +bool TransmitHistory::saveToDisk() +{ + return true; +} + +#endif diff --git a/src/mesh/TransmitHistory.h b/src/mesh/TransmitHistory.h new file mode 100644 index 000000000..01201eaac --- /dev/null +++ b/src/mesh/TransmitHistory.h @@ -0,0 +1,88 @@ +#pragma once + +#include "configuration.h" +#include +#include + +/** + * TransmitHistory persists the last broadcast transmit time (epoch seconds) per portnum + * to the filesystem so that throttle checks survive reboots/crashes. + * + * On boot, modules call getLastSentToMeshMillis() to recover a millis()-relative timestamp + * from the stored epoch time, which plugs directly into existing throttle logic. + * + * On every broadcast transmit, modules call setLastSentToMesh() which updates the + * in-memory cache and flushes to disk. + * + * Keys are meshtastic_PortNum values (one entry per portnum). + */ + +#include "mesh/generated/meshtastic/portnums.pb.h" + +class TransmitHistory +{ + public: + static TransmitHistory *getInstance(); + + /** + * Load persisted transmit times from disk. Call once during init after filesystem is ready. + */ + void loadFromDisk(); + + /** + * Record that a broadcast was sent for the given key right now. + * Stores epoch seconds and flushes to disk. + */ + void setLastSentToMesh(uint16_t key); + + /** + * Get the last transmit epoch seconds for a given key, or 0 if unknown. + */ + uint32_t getLastSentToMeshEpoch(uint16_t key) const; + + /** + * Convert a stored epoch timestamp into a millis()-relative timestamp suitable + * for use with Throttle::isWithinTimespanMs(). + * + * Returns 0 if no valid time is stored or if the stored time is in the future + * (which shouldn't happen but guards against clock weirdness). + * + * Example: if the stored epoch is 300 seconds ago, and millis() is currently 10000, + * this returns 10000 - 300000 (wrapped appropriately for uint32_t arithmetic). + */ + uint32_t getLastSentToMeshMillis(uint16_t key) const; + + /** + * Flush dirty entries to disk. Called periodically or on demand. + * + * @return true if the data is persisted (or there was nothing to write), false on write/open failure. + */ + bool saveToDisk(); + + private: + TransmitHistory() = default; + + static constexpr const char *FILENAME = "/prefs/transmit_history.dat"; + static constexpr uint32_t MAGIC = 0x54485354; // "THST" + static constexpr uint8_t VERSION = 1; + static constexpr uint8_t MAX_ENTRIES = 16; + static constexpr uint32_t SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes + + struct __attribute__((packed)) Entry { + uint16_t key; + uint32_t epochSeconds; + }; + + struct __attribute__((packed)) FileHeader { + uint32_t magic; + uint8_t version; + uint8_t count; + }; + + std::map history; // key -> epoch seconds (for disk persistence) + std::map lastMillis; // key -> millis() value (for runtime throttle) + bool dirty = false; + uint32_t lastDiskSave = 0; // millis() of last disk flush +}; + +extern TransmitHistory *transmitHistory; diff --git a/src/mesh/api/PacketAPI.h b/src/mesh/api/PacketAPI.h index 357eb05c2..aececf85e 100644 --- a/src/mesh/api/PacketAPI.h +++ b/src/mesh/api/PacketAPI.h @@ -15,11 +15,11 @@ class PacketAPI : public PhoneAPI, public concurrency::OSThread static PacketAPI *create(PacketServer *_server); virtual ~PacketAPI(){}; virtual int32_t runOnce(); + // Check the current underlying physical queue to see if the client is fetching packets + bool checkIsConnected() override; protected: explicit PacketAPI(PacketServer *_server); - // Check the current underlying physical queue to see if the client is fetching packets - bool checkIsConnected() override; void onNowHasData(uint32_t fromRadioNum) override {} void onConnectionChanged(bool connected) override {} diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp index 1a506421c..7bb1a8108 100644 --- a/src/mesh/api/ServerAPI.cpp +++ b/src/mesh/api/ServerAPI.cpp @@ -31,6 +31,7 @@ template int32_t ServerAPI::runOnce() return StreamAPI::runOncePart(); } else { LOG_INFO("Client dropped connection, suspend API service"); + close(); enabled = false; // we no longer need to run return 0; } @@ -45,6 +46,11 @@ template void APIServerPort::init() template int32_t APIServerPort::runOnce() { + // Clean up previous connection if its client already disconnected + if (openAPI && !openAPI->checkIsConnected()) { + openAPI.reset(); + } + #ifdef ARCH_ESP32 #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) auto client = U::accept(); @@ -70,10 +76,10 @@ template int32_t APIServerPort::runOnce() } #endif LOG_INFO("Force close previous TCP connection"); - delete openAPI; + openAPI.reset(); } - openAPI = new T(client); + openAPI.reset(new T(client)); } #if RAK_4631 diff --git a/src/mesh/api/ServerAPI.h b/src/mesh/api/ServerAPI.h index 111314476..2da77c8e9 100644 --- a/src/mesh/api/ServerAPI.h +++ b/src/mesh/api/ServerAPI.h @@ -1,6 +1,7 @@ #pragma once #include "StreamAPI.h" +#include #define SERVER_API_DEFAULT_PORT 4403 @@ -21,15 +22,15 @@ template class ServerAPI : public StreamAPI, private concurrency::OSTh /// override close to also shutdown the TCP link virtual void close(); + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override; + protected: /// We override this method to prevent publishing EVENT_SERIAL_CONNECTED/DISCONNECTED for wifi links (we want the board to /// stay in the POWERED state to prevent disabling wifi) virtual void onConnectionChanged(bool connected) override {} virtual int32_t runOnce() override; // Check for dropped client connections - - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override; }; /** @@ -42,7 +43,7 @@ template class APIServerPort : public U, private concurrency: * FIXME: We currently only allow one open TCP connection at a time, because we depend on the loop() call in this class to * delegate to the worker. Once coroutines are implemented we can relax this restriction. */ - T *openAPI = NULL; + std::unique_ptr openAPI; #if defined(RAK_4631) || defined(RAK11310) // Track wait time for RAK13800 Ethernet requests int32_t waitTime = 100; diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 3dd92997d..d529336c8 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -81,7 +81,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13, /* Traffic management module config */ - meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14 + meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14, + /* TAK module config */ + meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG = 15 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -395,8 +397,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index f4c33bd79..ce766878b 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 679 +#define meshtastic_ChannelSet_size 682 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp index 52a591f33..c554ca43c 100644 --- a/src/mesh/generated/meshtastic/config.pb.cpp +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -67,6 +67,8 @@ PB_BIND(meshtastic_Config_SessionkeyConfig, meshtastic_Config_SessionkeyConfig, + + diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index c669de6a8..c82dd5ff5 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -318,6 +318,15 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset { meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO = 9 } meshtastic_Config_LoRaConfig_ModemPreset; +typedef enum _meshtastic_Config_LoRaConfig_FEM_LNA_Mode { + /* FEM_LNA is present but disabled */ + meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED = 0, + /* FEM_LNA is present and enabled */ + meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ENABLED = 1, + /* FEM_LNA is not present on the device */ + meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT = 2 +} meshtastic_Config_LoRaConfig_FEM_LNA_Mode; + typedef enum _meshtastic_Config_BluetoothConfig_PairingMode { /* Device generates a random PIN that will be shown on the screen of the device for pairing */ meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN = 0, @@ -579,6 +588,8 @@ typedef struct _meshtastic_Config_LoRaConfig { bool ignore_mqtt; /* Sets the ok_to_mqtt bit on outgoing packets */ bool config_ok_to_mqtt; + /* Set where LORA FEM is enabled, disabled, or not present */ + meshtastic_Config_LoRaConfig_FEM_LNA_Mode fem_lna_mode; } meshtastic_Config_LoRaConfig; typedef struct _meshtastic_Config_BluetoothConfig { @@ -698,6 +709,10 @@ extern "C" { #define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO #define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO+1)) +#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED +#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MAX meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT +#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_FEM_LNA_Mode)(meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT+1)) + #define _meshtastic_Config_BluetoothConfig_PairingMode_MIN meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN #define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN #define _meshtastic_Config_BluetoothConfig_PairingMode_ARRAYSIZE ((meshtastic_Config_BluetoothConfig_PairingMode)(meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN+1)) @@ -721,6 +736,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_modem_preset_ENUMTYPE meshtastic_Config_LoRaConfig_ModemPreset #define meshtastic_Config_LoRaConfig_region_ENUMTYPE meshtastic_Config_LoRaConfig_RegionCode +#define meshtastic_Config_LoRaConfig_fem_lna_mode_ENUMTYPE meshtastic_Config_LoRaConfig_FEM_LNA_Mode #define meshtastic_Config_BluetoothConfig_mode_ENUMTYPE meshtastic_Config_BluetoothConfig_PairingMode @@ -735,7 +751,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0, 0} -#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} +#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_default {0} @@ -746,7 +762,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0, 0} -#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} +#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SessionkeyConfig_init_zero {0} @@ -832,6 +848,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_ignore_incoming_tag 103 #define meshtastic_Config_LoRaConfig_ignore_mqtt_tag 104 #define meshtastic_Config_LoRaConfig_config_ok_to_mqtt_tag 105 +#define meshtastic_Config_LoRaConfig_fem_lna_mode_tag 106 #define meshtastic_Config_BluetoothConfig_enabled_tag 1 #define meshtastic_Config_BluetoothConfig_mode_tag 2 #define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3 @@ -983,7 +1000,8 @@ X(a, STATIC, SINGULAR, FLOAT, override_frequency, 14) \ X(a, STATIC, SINGULAR, BOOL, pa_fan_disabled, 15) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \ X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) \ -X(a, STATIC, SINGULAR, BOOL, config_ok_to_mqtt, 105) +X(a, STATIC, SINGULAR, BOOL, config_ok_to_mqtt, 105) \ +X(a, STATIC, SINGULAR, UENUM, fem_lna_mode, 106) #define meshtastic_Config_LoRaConfig_CALLBACK NULL #define meshtastic_Config_LoRaConfig_DEFAULT NULL @@ -1040,7 +1058,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define meshtastic_Config_BluetoothConfig_size 10 #define meshtastic_Config_DeviceConfig_size 100 #define meshtastic_Config_DisplayConfig_size 36 -#define meshtastic_Config_LoRaConfig_size 85 +#define meshtastic_Config_LoRaConfig_size 88 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 204 #define meshtastic_Config_PositionConfig_size 62 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 5bbd87ffd..1d6cd32f9 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ 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 2419 +#define meshtastic_BackupPreferences_size 2429 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 7a5874b64..8425c122a 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -93,6 +93,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* The part of the config that is specific to the Traffic Management module */ bool has_traffic_management; meshtastic_ModuleConfig_TrafficManagementConfig traffic_management; + /* TAK Config */ + bool has_tak; + meshtastic_ModuleConfig_TAKConfig tak; } meshtastic_LocalModuleConfig; @@ -102,9 +105,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default, false, meshtastic_ModuleConfig_TAKConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero, false, meshtastic_ModuleConfig_TAKConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -132,6 +135,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_paxcounter_tag 14 #define meshtastic_LocalModuleConfig_statusmessage_tag 15 #define meshtastic_LocalModuleConfig_traffic_management_tag 16 +#define meshtastic_LocalModuleConfig_tak_tag 17 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -171,7 +175,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) \ -X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16) +X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16) \ +X(a, STATIC, OPTIONAL, MESSAGE, tak, 17) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -189,6 +194,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16) #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig #define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig #define meshtastic_LocalModuleConfig_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig +#define meshtastic_LocalModuleConfig_tak_MSGTYPE meshtastic_ModuleConfig_TAKConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -199,8 +205,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 751 -#define meshtastic_LocalModuleConfig_size 813 +#define meshtastic_LocalConfig_size 754 +#define meshtastic_LocalModuleConfig_size 820 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index aeae4bd84..773cc8ed6 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -300,6 +300,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_TBEAM_1_WATT = 122, /* LilyGo T5 S3 ePaper Pro (V1 and V2) */ meshtastic_HardwareModel_T5_S3_EPAPER_PRO = 123, + /* LilyGo T-Beam BPF (144-148Mhz) */ + meshtastic_HardwareModel_TBEAM_BPF = 124, + /* LilyGo T-Mini E-paper S3 Kit */ + meshtastic_HardwareModel_MINI_EPAPER_S3 = 125, /* ------------------------------------------------------------------------------------------------------------------------------------------ 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. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index b9705b0bc..f2fe5d967 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -57,6 +57,9 @@ PB_BIND(meshtastic_ModuleConfig_AmbientLightingConfig, meshtastic_ModuleConfig_A PB_BIND(meshtastic_ModuleConfig_StatusMessageConfig, meshtastic_ModuleConfig_StatusMessageConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_TAKConfig, meshtastic_ModuleConfig_TAKConfig, AUTO) + + PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 9312b26af..4fff604dd 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -4,6 +4,7 @@ #ifndef PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED #include +#include "meshtastic/atak.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -450,6 +451,16 @@ typedef struct _meshtastic_ModuleConfig_StatusMessageConfig { char node_status[80]; } meshtastic_ModuleConfig_StatusMessageConfig; +/* TAK team/role configuration */ +typedef struct _meshtastic_ModuleConfig_TAKConfig { + /* Team color. + Default Unspecifed_Color -> firmware uses Cyan */ + meshtastic_Team team; + /* Member role. + Default Unspecifed -> firmware uses TeamMember */ + meshtastic_MemberRole role; +} meshtastic_ModuleConfig_TAKConfig; + /* A GPIO pin definition for remote hardware module */ typedef struct _meshtastic_RemoteHardwarePin { /* GPIO Pin number (must match Arduino) */ @@ -505,6 +516,8 @@ typedef struct _meshtastic_ModuleConfig { meshtastic_ModuleConfig_StatusMessageConfig statusmessage; /* Traffic management module config for mesh network optimization */ meshtastic_ModuleConfig_TrafficManagementConfig traffic_management; + /* TAK team/role configuration for TAK_TRACKER */ + meshtastic_ModuleConfig_TAKConfig tak; } payload_variant; } meshtastic_ModuleConfig; @@ -562,6 +575,9 @@ extern "C" { +#define meshtastic_ModuleConfig_TAKConfig_team_ENUMTYPE meshtastic_Team +#define meshtastic_ModuleConfig_TAKConfig_role_ENUMTYPE meshtastic_MemberRole + #define meshtastic_RemoteHardwarePin_type_ENUMTYPE meshtastic_RemoteHardwarePinType @@ -583,6 +599,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""} +#define meshtastic_ModuleConfig_TAKConfig_init_default {_meshtastic_Team_MIN, _meshtastic_MemberRole_MIN} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}} #define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero} @@ -601,6 +618,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""} +#define meshtastic_ModuleConfig_TAKConfig_init_zero {_meshtastic_Team_MIN, _meshtastic_MemberRole_MIN} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} /* Field tags (for use in manual encoding/decoding) */ @@ -720,6 +738,8 @@ extern "C" { #define meshtastic_ModuleConfig_AmbientLightingConfig_green_tag 4 #define meshtastic_ModuleConfig_AmbientLightingConfig_blue_tag 5 #define meshtastic_ModuleConfig_StatusMessageConfig_node_status_tag 1 +#define meshtastic_ModuleConfig_TAKConfig_team_tag 1 +#define meshtastic_ModuleConfig_TAKConfig_role_tag 2 #define meshtastic_RemoteHardwarePin_gpio_pin_tag 1 #define meshtastic_RemoteHardwarePin_name_tag 2 #define meshtastic_RemoteHardwarePin_type_tag 3 @@ -741,6 +761,7 @@ extern "C" { #define meshtastic_ModuleConfig_paxcounter_tag 13 #define meshtastic_ModuleConfig_statusmessage_tag 14 #define meshtastic_ModuleConfig_traffic_management_tag 15 +#define meshtastic_ModuleConfig_tak_tag 16 /* Struct field encoding specification for nanopb */ #define meshtastic_ModuleConfig_FIELDLIST(X, a) \ @@ -758,7 +779,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_var X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,tak,payload_variant.tak), 16) #define meshtastic_ModuleConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DEFAULT NULL #define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -776,6 +798,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_v #define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig #define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig #define meshtastic_ModuleConfig_payload_variant_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig +#define meshtastic_ModuleConfig_payload_variant_tak_MSGTYPE meshtastic_ModuleConfig_TAKConfig #define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -962,6 +985,12 @@ X(a, STATIC, SINGULAR, STRING, node_status, 1) #define meshtastic_ModuleConfig_StatusMessageConfig_CALLBACK NULL #define meshtastic_ModuleConfig_StatusMessageConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_TAKConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, team, 1) \ +X(a, STATIC, SINGULAR, UENUM, role, 2) +#define meshtastic_ModuleConfig_TAKConfig_CALLBACK NULL +#define meshtastic_ModuleConfig_TAKConfig_DEFAULT NULL + #define meshtastic_RemoteHardwarePin_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, gpio_pin, 1) \ X(a, STATIC, SINGULAR, STRING, name, 2) \ @@ -986,6 +1015,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_TelemetryConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_CannedMessageConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_AmbientLightingConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_StatusMessageConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_TAKConfig_msg; extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ @@ -1006,6 +1036,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_CannedMessageConfig_fields &meshtastic_ModuleConfig_CannedMessageConfig_msg #define meshtastic_ModuleConfig_AmbientLightingConfig_fields &meshtastic_ModuleConfig_AmbientLightingConfig_msg #define meshtastic_ModuleConfig_StatusMessageConfig_fields &meshtastic_ModuleConfig_StatusMessageConfig_msg +#define meshtastic_ModuleConfig_TAKConfig_fields &meshtastic_ModuleConfig_TAKConfig_msg #define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ @@ -1025,6 +1056,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_StatusMessageConfig_size 81 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_TelemetryConfig_size 55 +#define meshtastic_ModuleConfig_TAKConfig_size 4 #define meshtastic_ModuleConfig_TrafficManagementConfig_size 52 #define meshtastic_ModuleConfig_size 227 #define meshtastic_RemoteHardwarePin_size 21 diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 281ece464..6b3aa4859 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -46,10 +46,6 @@ using namespace httpsserver; #include "mesh/http/ContentHandler.h" -#include -#include -HTTPClient httpClient; - #define DEST_FS_USES_LITTLEFS // We need to specify some content-type mapping, so the resources get delivered with the @@ -344,11 +340,6 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) res->print(jsonString.c_str()); delete value; - - // Clean up the fileList to prevent memory leak - for (auto *val : fileList) { - delete val; - } } void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) @@ -543,6 +534,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res) if (name != "file") { LOG_DEBUG("Skip unexpected field"); res->println("

No file found.

"); + delete parser; return; } @@ -550,6 +542,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res) if (filename == "") { LOG_DEBUG("Skip unexpected field"); res->println("

No file found.

"); + delete parser; return; } @@ -783,11 +776,6 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res) std::string jsonString = value->Stringify(); res->print(jsonString.c_str()); delete value; - - // Clean up the nodesArray to prevent memory leak - for (auto *val : nodesArray) { - delete val; - } } /* @@ -941,10 +929,5 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) std::string jsonString = value->Stringify(); res->print(jsonString.c_str()); delete value; - - // Clean up the networkObjs to prevent memory leak - for (auto *val : networkObjs) { - delete val; - } } #endif \ No newline at end of file diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index 6efdb59b7..ed182ad76 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -27,10 +27,10 @@ class HttpAPI : public PhoneAPI public: HttpAPI() { api_type = TYPE_HTTP; } + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this private: // Nothing here yet protected: - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this }; \ No newline at end of file diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 3a264fa5a..90fd8b084 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,12 @@ static const int32_t ACTIVE_INTERVAL_MS = 50; static const int32_t MEDIUM_INTERVAL_MS = 200; static const int32_t IDLE_INTERVAL_MS = 1000; +// Maximum concurrent HTTPS connections (reduced from default 4 to save memory) +static const uint8_t MAX_HTTPS_CONNECTIONS = 2; + +// Minimum free heap required for SSL handshake (~40KB for mbedTLS contexts) +static const uint32_t MIN_HEAP_FOR_SSL = 40000; + static SSLCert *cert; static HTTPSServer *secureServer; static HTTPServer *insecureServer; @@ -67,8 +74,20 @@ static void handleWebResponse() if (isWifiAvailable()) { if (isWebServerReady) { - if (secureServer) - secureServer->loop(); + // Check heap before HTTPS processing - SSL requires significant memory + if (secureServer) { + uint32_t freeHeap = ESP.getFreeHeap(); + if (freeHeap >= MIN_HEAP_FOR_SSL) { + secureServer->loop(); + } else { + // Skip HTTPS when memory is low to prevent SSL setup failures + static uint32_t lastHeapWarning = 0; + if (lastHeapWarning == 0 || !Throttle::isWithinTimespanMs(lastHeapWarning, 30000)) { + LOG_WARN("Low heap (%u bytes), skipping HTTPS processing", freeHeap); + lastHeapWarning = millis(); + } + } + } insecureServer->loop(); } } @@ -229,7 +248,7 @@ void initWebServer() LOG_DEBUG("Init Web Server"); // We can now use the new certificate to setup our server as usual. - secureServer = new HTTPSServer(cert); + secureServer = new HTTPSServer(cert, 443, MAX_HTTPS_CONNECTIONS); insecureServer = new HTTPServer(); registerHandlers(insecureServer, secureServer); diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h index 5a4adedaa..74b094f8c 100644 --- a/src/mesh/raspihttp/PiWebServer.h +++ b/src/mesh/raspihttp/PiWebServer.h @@ -29,12 +29,13 @@ class HttpAPI : public PhoneAPI public: HttpAPI() { api_type = TYPE_HTTP; } + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this + private: // Nothing here yet protected: - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this }; class PiWebServerThread diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index d79b63ceb..f88b48d62 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -56,7 +56,7 @@ class UdpMulticastHandler final isRunning = false; } - void onReceive(AsyncUDPPacket packet) + void onReceive(AsyncUDPPacket &packet) { if (!isRunning) { return; @@ -105,8 +105,7 @@ class UdpMulticastHandler final LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id); uint8_t buffer[meshtastic_MeshPacket_size]; size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp); - udp.writeTo(buffer, encodedLength, udpIpAddress, UDP_MULTICAST_DEFAUL_PORT); - return true; + return udp.writeTo(buffer, encodedLength, udpIpAddress, UDP_MULTICAST_DEFAUL_PORT); } private: diff --git a/src/meshUtils.h b/src/meshUtils.h index 67446f91f..da3a4593b 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -38,4 +38,10 @@ const std::string vformat(const char *const zcFormat, ...); // Get actual string length for nanopb char array fields. size_t pb_string_length(const char *str, size_t max_len); +/// Calculate 2^n without calling pow() - used for spreading factor and other calculations +inline uint32_t pow_of_2(uint32_t n) +{ + return 1 << n; +} + #define IS_ONE_OF(item, ...) isOneOf(item, sizeof((int[]){__VA_ARGS__}) / sizeof(int), __VA_ARGS__) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 419d2b773..8f0296227 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -23,6 +23,7 @@ #endif #include "Default.h" +#include "MeshRadio.h" #include "TypeConversions.h" #if !MESHTASTIC_EXCLUDE_MQTT @@ -652,9 +653,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) } config.device = c.payload_variant.device; if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_NONE && - config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) { + (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) { config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL; - const char *warning = "Rebroadcast mode can't be set to NONE for a router"; + const char *warning = "Rebroadcast mode can't be set to NONE for a router role"; LOG_WARN(warning); sendWarning(warning); } @@ -756,20 +758,14 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) LOG_INFO("Set config: LoRa"); config.has_lora = true; - if (validatedLora.coding_rate < 4 || validatedLora.coding_rate > 8) { - LOG_WARN("Invalid coding_rate %d, setting to 5", validatedLora.coding_rate); - validatedLora.coding_rate = 5; + if (validatedLora.coding_rate != clampCodingRate(validatedLora.coding_rate)) { + LOG_WARN("Invalid coding_rate %d, setting to %d", validatedLora.coding_rate, LORA_CR_DEFAULT); + validatedLora.coding_rate = LORA_CR_DEFAULT; } - if (validatedLora.spread_factor < 7 || validatedLora.spread_factor > 12) { - LOG_WARN("Invalid spread_factor %d, setting to 11", validatedLora.spread_factor); - validatedLora.spread_factor = 11; - } - - if (validatedLora.bandwidth == 0) { - int originalBandwidth = validatedLora.bandwidth; - validatedLora.bandwidth = myRegion->wideLora ? 800 : 250; - LOG_WARN("Invalid bandwidth %d, setting to default", originalBandwidth); + if (validatedLora.spread_factor != clampSpreadFactor(validatedLora.spread_factor)) { + LOG_WARN("Invalid spread_factor %d, setting to %d", validatedLora.spread_factor, LORA_SF_DEFAULT); + validatedLora.spread_factor = LORA_SF_DEFAULT; } // If no lora radio parameters change, don't need to reboot @@ -800,6 +796,17 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) } #endif config.lora = validatedLora; + +#if HAS_LORA_FEM + // Apply FEM LNA mode from config (only meaningful on hardware that supports it) + if (loraFEMInterface.isLnaCanControl()) { + loraFEMInterface.setLNAEnable(config.lora.fem_lna_mode == meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ENABLED); + } else if (config.lora.fem_lna_mode != meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT) { + // Hardware FEM does not support LNA control; normalize stored config to match actual capability + LOG_WARN("FEM LNA mode configured but current FEM does not support LNA control; normalizing to NOT_PRESENT"); + config.lora.fem_lna_mode = meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT; + } +#endif // 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) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index c7eb1b15b..ae25de0cb 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -132,7 +132,7 @@ CannedMessageModule::CannedMessageModule() this->loadProtoForModule(); if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE) { LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled"); - this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED; + this->updateState(CANNED_MESSAGE_RUN_STATE_DISABLED); disable(); } else { LOG_INFO("CannedMessageModule is enabled"); @@ -164,8 +164,7 @@ void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChan currentMessageIndex = selectDestination; // This triggers the canned message list - runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; - requestFocus(); + updateState(CANNED_MESSAGE_RUN_STATE_ACTIVE, true); UIFrameEvent e; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; notifyObservers(&e); @@ -194,8 +193,7 @@ void CannedMessageModule::LaunchFreetextWithDestination(NodeNum newDest, uint8_t lastChannel = channel; lastDestSet = true; - runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; - requestFocus(); + updateState(CANNED_MESSAGE_RUN_STATE_FREETEXT, true); UIFrameEvent e; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; notifyObservers(&e); @@ -390,11 +388,10 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) // Matrix keypad: If matrix key, trigger action select for canned message if (event->inputEvent == INPUT_BROKER_MATRIXKEY) { - runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; + updateState(CANNED_MESSAGE_RUN_STATE_ACTION_SELECT, true); payload = INPUT_BROKER_MATRIXKEY; currentMessageIndex = event->kbchar - 1; lastTouchMillis = millis(); - requestFocus(); return 1; } @@ -432,8 +429,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } // Printable char (ASCII) opens free text compose if (event->kbchar >= 32 && event->kbchar <= 126) { - runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; - requestFocus(); + updateState(CANNED_MESSAGE_RUN_STATE_FREETEXT, true); UIFrameEvent e; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; notifyObservers(&e); @@ -457,6 +453,20 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) return 0; } +void CannedMessageModule::updateState(cannedMessageModuleRunState newState, bool shouldRequestFocus) +{ + runState = newState; + if (runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + inputBroker->menuMode = + false; // Allow any key input to be sent to the message composer instead of being interpreted as menu navigation + } else { + inputBroker->menuMode = true; // Re-enable menu navigation for destination selection + } + if (shouldRequestFocus) { + requestFocus(); + } +} + bool CannedMessageModule::isUpEvent(const InputEvent *event) { return event->inputEvent == INPUT_BROKER_UP || @@ -481,15 +491,18 @@ bool CannedMessageModule::handleTabSwitch(const InputEvent *event) if (event->kbchar != 0x09) return false; - runState = (runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) ? CANNED_MESSAGE_RUN_STATE_FREETEXT - : CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION; + updateState((runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) ? CANNED_MESSAGE_RUN_STATE_FREETEXT + : CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION); destIndex = 0; scrollIndex = 0; // RESTORE THIS! if (runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) updateDestinationSelectionList(); - requestFocus(); + + updateState((runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) ? CANNED_MESSAGE_RUN_STATE_FREETEXT + : CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION, + true); UIFrameEvent e; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; @@ -595,7 +608,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event } } - runState = returnToCannedList ? CANNED_MESSAGE_RUN_STATE_ACTIVE : CANNED_MESSAGE_RUN_STATE_FREETEXT; + updateState(returnToCannedList ? CANNED_MESSAGE_RUN_STATE_ACTIVE : CANNED_MESSAGE_RUN_STATE_FREETEXT, true); returnToCannedList = false; screen->forceDisplay(true); return 1; @@ -603,7 +616,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event // CANCEL if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG) { - runState = returnToCannedList ? CANNED_MESSAGE_RUN_STATE_ACTIVE : CANNED_MESSAGE_RUN_STATE_FREETEXT; + updateState(returnToCannedList ? CANNED_MESSAGE_RUN_STATE_ACTIVE : CANNED_MESSAGE_RUN_STATE_FREETEXT, true); returnToCannedList = false; searchQuery = ""; @@ -634,7 +647,7 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo // Handle Cancel key: go inactive, clear UI state if (runState != CANNED_MESSAGE_RUN_STATE_INACTIVE && (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG)) { - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); freetext = ""; cursor = 0; payload = 0; @@ -652,10 +665,10 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo // Handle up/down navigation if (isUp && messagesCount > 0) { - runState = CANNED_MESSAGE_RUN_STATE_ACTION_UP; + updateState(CANNED_MESSAGE_RUN_STATE_ACTION_UP); handled = true; } else if (isDown && messagesCount > 0) { - runState = CANNED_MESSAGE_RUN_STATE_ACTION_DOWN; + updateState(CANNED_MESSAGE_RUN_STATE_ACTION_DOWN); handled = true; } else if (isSelect) { const char *current = messages[currentMessageIndex]; @@ -663,7 +676,7 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo // [Select Destination] triggers destination selection UI if (strcmp(current, "[Select Destination]") == 0) { returnToCannedList = true; - runState = CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION; + updateState(CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION, true); destIndex = 0; scrollIndex = 0; updateDestinationSelectionList(); // Make sure list is fresh @@ -674,7 +687,7 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo // [Exit] returns to the main/inactive screen if (strcmp(current, "[Exit]") == 0) { // Set runState to inactive so we return to main UI - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); currentMessageIndex = -1; // Notify UI to regenerate frame set and redraw @@ -688,8 +701,7 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo // [Free Text] triggers the free text input (virtual keyboard) #if defined(USE_VIRTUAL_KEYBOARD) if (strcmp(current, "[-- Free Text --]") == 0) { - runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; - requestFocus(); + updateState(CANNED_MESSAGE_RUN_STATE_FREETEXT, true); UIFrameEvent e; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; notifyObservers(&e); @@ -708,7 +720,7 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo if (!text.empty()) { this->freetext = text.c_str(); this->payload = CANNED_MESSAGE_RUN_STATE_FREETEXT; - runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; + updateState(CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE); currentMessageIndex = -1; UIFrameEvent e; @@ -725,7 +737,7 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo graphics::NotificationRenderer::resetBanner(); // Return to inactive state - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); this->currentMessageIndex = -1; this->freetext = ""; this->cursor = 0; @@ -755,12 +767,12 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo graphics::menuHandler::showConfirmationBanner("Send message?", [this, savedIndex]() { this->currentMessageIndex = savedIndex; this->payload = this->runState; - this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; + this->updateState(CANNED_MESSAGE_RUN_STATE_ACTION_SELECT); this->setIntervalFromNow(0); }); #else payload = runState; - runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; + updateState(CANNED_MESSAGE_RUN_STATE_ACTION_SELECT); #endif // Do not immediately set runState; wait for confirmation handled = true; @@ -786,7 +798,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) #if defined(USE_VIRTUAL_KEYBOARD) // Cancel (dismiss freetext screen) if (event->inputEvent == INPUT_BROKER_LEFT) { - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); freetext = ""; cursor = 0; payload = 0; @@ -832,7 +844,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) } // Touch enter/submit else if (keyTapped == "↵") { - runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; // Send the message! + updateState(CANNED_MESSAGE_RUN_STATE_ACTION_SELECT); // Send the message! payload = CANNED_MESSAGE_RUN_STATE_FREETEXT; currentMessageIndex = -1; shift = false; @@ -858,8 +870,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) // All hardware keys fall through to here (CardKB, physical, etc.) if (event->kbchar == INPUT_BROKER_MSG_EMOTE_LIST) { - runState = CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER; - requestFocus(); + updateState(CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER); screen->forceDisplay(); return true; } @@ -876,7 +887,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) payload = CANNED_MESSAGE_RUN_STATE_FREETEXT; currentMessageIndex = -1; - runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; + updateState(CANNED_MESSAGE_RUN_STATE_ACTION_SELECT); lastTouchMillis = millis(); runOnce(); return true; @@ -911,7 +922,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) // Cancel (dismiss freetext screen) if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG || (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() == 0)) { - runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); freetext = ""; cursor = 0; payload = 0; @@ -979,14 +990,14 @@ int CannedMessageModule::handleEmotePickerInput(const InputEvent *event) freetext = freetext.substring(0, cursor) + emoteInsert + freetext.substring(cursor); } cursor += emoteInsert.length(); - runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; + updateState(CANNED_MESSAGE_RUN_STATE_FREETEXT, true); screen->forceDisplay(); return 1; } // Cancel returns to freetext if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG) { - runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; + updateState(CANNED_MESSAGE_RUN_STATE_FREETEXT, true); screen->forceDisplay(); return 1; } @@ -1072,12 +1083,14 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha } else { sm.dest = dest; sm.type = MessageType::DM_TO_US; - // Only add as favorite if our role is NOT CLIENT_BASE - if (config.device.role != 12) { + // Only add as favorite if our role is not router-like (ROUTER, ROUTER_LATE, CLIENT_BASE) + if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && + config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && + config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) { LOG_INFO("Proactively adding %x as favorite node", dest); nodeDB->set_favorite(true, dest); } else { - LOG_DEBUG("Not favoriting node %x as we are CLIENT_BASE role", dest); + LOG_DEBUG("Not favoriting node %x because role is router-like", dest); } } sm.ackStatus = AckStatus::NONE; @@ -1093,9 +1106,8 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha playComboTune(); - this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE); this->payload = wantReplies ? 1 : 0; - requestFocus(); // Tell Screen to switch to TextMessage frame via UIFrameEvent UIFrameEvent e; @@ -1146,7 +1158,7 @@ int32_t CannedMessageModule::runOnce() } else { // Empty message, just go inactive LOG_INFO("Empty freetext detected in delayed processing, returning to inactive state"); - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); } UIFrameEvent e; @@ -1163,7 +1175,7 @@ int32_t CannedMessageModule::runOnce() this->payload != CANNED_MESSAGE_RUN_STATE_FREETEXT) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION)) { - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; this->currentMessageIndex = -1; this->freetext = ""; @@ -1172,7 +1184,7 @@ int32_t CannedMessageModule::runOnce() } // Handle SENDING_ACTIVE state transition after virtual keyboard message else if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload == 0) { - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); this->currentMessageIndex = -1; this->freetext = ""; this->cursor = 0; @@ -1184,7 +1196,7 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = -1; this->freetext = ""; this->cursor = 0; - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); // Clean up virtual keyboard if it exists during timeout if (graphics::NotificationRenderer::virtualKeyboard) { @@ -1198,7 +1210,7 @@ int32_t CannedMessageModule::runOnce() if (this->payload == 0) { // [Exit] button pressed - return to inactive state LOG_INFO("Processing [Exit] action - returning to inactive state"); - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); } else if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) { if (this->freetext.length() > 0) { sendText(this->dest, this->channel, this->freetext.c_str(), true); @@ -1213,15 +1225,15 @@ int32_t CannedMessageModule::runOnce() this->notifyObservers(&e); // Now deactivate this module - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); return INT32_MAX; // don't fall back into canned list } else { - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); } } else { if (strcmp(this->messages[this->currentMessageIndex], "[Select Destination]") == 0) { - this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_ACTIVE); return INT32_MAX; } if ((this->messagesCount > this->currentMessageIndex) && (strlen(this->messages[this->currentMessageIndex]) > 0)) { @@ -1240,12 +1252,12 @@ int32_t CannedMessageModule::runOnce() this->notifyObservers(&e); // Now deactivate this module - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); return INT32_MAX; // don't fall back into canned list } } else { - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_INACTIVE); } } // fallback clean-up if nothing above returned @@ -1279,14 +1291,14 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = getPrevIndex(); this->freetext = ""; this->cursor = 0; - this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_ACTIVE); } } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_DOWN) { if (this->messagesCount > 0) { this->currentMessageIndex = this->getNextIndex(); this->freetext = ""; this->cursor = 0; - this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; + this->updateState(CANNED_MESSAGE_RUN_STATE_ACTIVE); } } else if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) { switch (this->payload) { diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 65715dd22..f6cb4d011 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -183,6 +183,8 @@ class CannedMessageModule : public SinglePortModule, public Observable @@ -133,11 +134,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() // Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient) uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline()); - if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) { + uint32_t lastNodeInfo = transmitHistory ? transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP) : 0; + if (!shorterTimeout && lastNodeInfo && Throttle::isWithinTimespanMs(lastNodeInfo, timeoutMs)) { LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; - } else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) { + } else if (shorterTimeout && lastNodeInfo && Throttle::isWithinTimespanMs(lastNodeInfo, 60 * 1000)) { // For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago"); ignoreRequest = true; @@ -159,7 +161,8 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() strcpy(u.id, nodeDB->getNodeId().c_str()); LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); return allocDataProtobuf(u); } } diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h index d16fbeac2..0c0dec849 100644 --- a/src/modules/NodeInfoModule.h +++ b/src/modules/NodeInfoModule.h @@ -42,7 +42,6 @@ class NodeInfoModule : public ProtobufModule, private concurren virtual int32_t runOnce() override; private: - uint32_t lastSentToMesh = 0; // Last time we sent our NodeInfo to the mesh bool shorterTimeout = false; bool suppressReplyForCurrentRequest = false; std::map lastNodeInfoSeen; diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index f7116e701..e9a21a22b 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -6,6 +6,7 @@ #include "NodeDB.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "TypeConversions.h" #include "airtime.h" #include "configuration.h" @@ -27,6 +28,15 @@ PositionModule::PositionModule() isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others nodeStatusObserver.observe(&nodeStatus->onNewStatus); + // Seed throttle timer from persisted transmit history so we don't re-broadcast immediately after reboot + if (transmitHistory) { + uint32_t restored = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP); + if (restored != 0) { + lastGpsSend = restored; + LOG_INFO("Position: restored lastGpsSend from transmit history"); + } + } + if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) { setIntervalFromNow(setStartDelay()); @@ -438,6 +448,8 @@ int32_t PositionModule::runOnce() lastGpsLatitude = node->position.latitude_i; lastGpsLongitude = node->position.longitude_i; + if (transmitHistory) + transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP); sendOurPosition(); if (config.device.role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) { sendLostAndFoundText(); diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 135d9f6eb..6df0e18f0 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -588,7 +588,8 @@ StoreForwardModule::StoreForwardModule() if (moduleConfig.store_forward.enabled) { // Router - if ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || moduleConfig.store_forward.is_server)) { + if ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE || moduleConfig.store_forward.is_server)) { LOG_INFO("Init Store & Forward Module in Server mode"); if (memGet.getPsramSize() > 0) { if (memGet.getFreePsram() >= 1024 * 1024) { diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 5ffe4d992..ca853d051 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -11,6 +11,7 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "UnitConversions.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" @@ -19,6 +20,8 @@ #include "sleep.h" #include +static constexpr uint16_t TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY = 0x8004; + // Sensors #include "Sensor/AddI2CSensorTemplate.h" #include "Sensor/PMSA003ISensor.h" @@ -108,11 +111,13 @@ int32_t AirQualityTelemetryModule::runOnce() // Wake up the sensors that need it LOG_INFO("Waking up sensors..."); + uint32_t lastTelemetry = + transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY) : 0; for (TelemetrySensor *sensor : sensors) { if (!sensor->canSleep()) { LOG_DEBUG("%s sensor doesn't have sleep feature. Skipping", sensor->sensorName); - } else if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh - sensor->wakeUpTimeMs(), + } else if (((lastTelemetry == 0) || + !Throttle::isWithinTimespanMs(lastTelemetry - sensor->wakeUpTimeMs(), Default::getConfiguredOrDefaultMsScaled( moduleConfig.telemetry.air_quality_interval, default_telemetry_broadcast_interval_secs, numOnlineNodes))) && @@ -131,14 +136,15 @@ int32_t AirQualityTelemetryModule::runOnce() } } - if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( - moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + if (((lastTelemetry == 0) || + !Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet @@ -321,6 +327,10 @@ bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 197491f2d..04936d8c1 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -4,6 +4,8 @@ #pragma once +#include "BaseTelemetryModule.h" + #ifndef AIR_QUALITY_TELEMETRY_MODULE_ENABLE #define AIR_QUALITY_TELEMETRY_MODULE_ENABLE 0 #endif @@ -17,6 +19,7 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public ScanI2CConsumer, + public BaseTelemetryModule, public ProtobufModule { CallbackObserver nodeStatusObserver = @@ -65,7 +68,6 @@ class AirQualityTelemetryModule : private concurrency::OSThread, meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute // uint32_t sendToPhoneIntervalMs = 1000; // Send to phone every minute - uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; }; diff --git a/src/modules/Telemetry/BaseTelemetryModule.h b/src/modules/Telemetry/BaseTelemetryModule.h new file mode 100644 index 000000000..d986f41a9 --- /dev/null +++ b/src/modules/Telemetry/BaseTelemetryModule.h @@ -0,0 +1,15 @@ +#pragma once + +#include "NodeDB.h" +#include "configuration.h" + +class BaseTelemetryModule +{ + protected: + bool isSensorOrRouterRole() const + { + return config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE; + } +}; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 066b9361d..1c2d18c71 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -7,6 +7,7 @@ #include "RTC.h" #include "RadioLibInterface.h" #include "Router.h" +#include "TransmitHistory.h" #include "configuration.h" #include "main.h" #include "memGet.h" @@ -15,21 +16,23 @@ #include #define MAGIC_USB_BATTERY_LEVEL 101 +static constexpr uint16_t TX_HISTORY_KEY_DEVICE_TELEMETRY = 0x8001; int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); - bool isImpoliteRole = - IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_ROUTER); - if (((lastSentToMesh == 0) || - ((uptimeLastMs - lastSentToMesh) >= - Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_DEVICE_TELEMETRY) : 0; + bool isImpoliteRole = isSensorOrRouterRole(); + if (((lastTelemetry == 0) || + ((uptimeLastMs - lastTelemetry) >= 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 && moduleConfig.telemetry.device_telemetry_enabled) { sendTelemetry(); - lastSentToMesh = uptimeLastMs; + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_DEVICE_TELEMETRY); } else if (service->isToPhoneQueueEmpty()) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) @@ -60,6 +63,10 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & meshtastic_MeshPacket *DeviceTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/DeviceTelemetry.h b/src/modules/Telemetry/DeviceTelemetry.h index a1d55a596..f37afee70 100644 --- a/src/modules/Telemetry/DeviceTelemetry.h +++ b/src/modules/Telemetry/DeviceTelemetry.h @@ -1,11 +1,14 @@ #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BaseTelemetryModule.h" #include "NodeDB.h" #include "ProtobufModule.h" #include #include -class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModule +class DeviceTelemetryModule : private concurrency::OSThread, + public BaseTelemetryModule, + public ProtobufModule { CallbackObserver nodeStatusObserver = CallbackObserver(this, &DeviceTelemetryModule::handleStatusUpdate); @@ -48,7 +51,6 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t sendStatsToPhoneIntervalMs = 15 * SECONDS_IN_MINUTE * 1000; // Send stats to phone every 15 minutes uint32_t lastSentStatsToPhone = 0; - uint32_t lastSentToMesh = 0; void refreshUptime() { diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 373c772ed..94f47f49d 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -10,6 +10,7 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "UnitConversions.h" #include "buzz.h" #include "graphics/SharedUIDisplay.h" @@ -171,6 +172,7 @@ void applyTemperatureOffset(meshtastic_EnvironmentMetrics *metrics) metrics->soil_temperature += offsetC; } } // namespace +static constexpr uint16_t TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY = 0x8002; void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { @@ -347,7 +349,8 @@ int32_t EnvironmentTelemetryModule::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(); + if (rak9154Sensor.hasSensor()) + result = rak9154Sensor.runOnce(); #endif #endif refreshLocalMeasurementPacket(); @@ -369,14 +372,17 @@ int32_t EnvironmentTelemetryModule::runOnce() } refreshLocalMeasurementPacket(); - if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( - moduleConfig.telemetry.environment_update_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + uint32_t lastTelemetry = + transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY) : 0; + if (((lastTelemetry == 0) || + !Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet @@ -619,9 +625,11 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m } #endif #ifdef HAS_RAKPROT - get_metrics = rak9154Sensor.getMetrics(m); - valid = valid || get_metrics; - hasSensor = true; + if (rak9154Sensor.hasSensor()) { + get_metrics = rak9154Sensor.getMetrics(m); + valid = valid || get_metrics; + hasSensor = true; + } #endif if (valid) applyTemperatureOffset(&m->variant.environment_metrics); @@ -632,6 +640,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index 049ed6b77..0b7e0f4cb 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -4,6 +4,8 @@ #pragma once +#include "BaseTelemetryModule.h" + #ifndef ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE #define ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE 0 #endif @@ -17,6 +19,7 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public ScanI2CConsumer, + public BaseTelemetryModule, public ProtobufModule { CallbackObserver nodeStatusObserver = @@ -65,7 +68,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, bool firstTime = 1; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute - uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; }; diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index bb3555062..ae6b366bd 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -10,6 +10,7 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "UnitConversions.h" #include "main.h" #include "power.h" @@ -33,6 +34,8 @@ MLX90614Sensor mlx90614Sensor; #endif #include +static constexpr uint16_t TX_HISTORY_KEY_HEALTH_TELEMETRY = 0x8003; + int32_t HealthTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { @@ -69,14 +72,16 @@ int32_t HealthTelemetryModule::runOnce() return disable(); } - if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( - moduleConfig.telemetry.health_update_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_HEALTH_TELEMETRY) : 0; + if (((lastTelemetry == 0) || + !Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.health_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_HEALTH_TELEMETRY); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet @@ -192,6 +197,10 @@ bool HealthTelemetryModule::getHealthTelemetry(meshtastic_Telemetry *m) meshtastic_MeshPacket *HealthTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/HealthTelemetry.h b/src/modules/Telemetry/HealthTelemetry.h index 01e4c2372..4d0722201 100644 --- a/src/modules/Telemetry/HealthTelemetry.h +++ b/src/modules/Telemetry/HealthTelemetry.h @@ -4,12 +4,15 @@ #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BaseTelemetryModule.h" #include "NodeDB.h" #include "ProtobufModule.h" #include #include -class HealthTelemetryModule : private concurrency::OSThread, public ProtobufModule +class HealthTelemetryModule : private concurrency::OSThread, + public BaseTelemetryModule, + public ProtobufModule { CallbackObserver nodeStatusObserver = CallbackObserver(this, &HealthTelemetryModule::handleStatusUpdate); @@ -52,7 +55,6 @@ class HealthTelemetryModule : private concurrency::OSThread, public ProtobufModu bool firstTime = 1; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute - uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; uint32_t sensor_read_error_count = 0; }; diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 9047c7cd4..d02aed9c2 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -10,6 +10,7 @@ #include "PowerTelemetry.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "graphics/SharedUIDisplay.h" #include "main.h" #include "power.h" @@ -22,6 +23,8 @@ #include "graphics/ScreenFonts.h" #include +static constexpr uint16_t TX_HISTORY_KEY_POWER_TELEMETRY = 0x8005; + namespace graphics { extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, @@ -88,10 +91,12 @@ int32_t PowerTelemetryModule::runOnce() if (!moduleConfig.telemetry.power_measurement_enabled) return disable(); - if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, sendToMeshIntervalMs)) && + uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_POWER_TELEMETRY) : 0; + if (((lastTelemetry == 0) || !Throttle::isWithinTimespanMs(lastTelemetry, sendToMeshIntervalMs)) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_POWER_TELEMETRY); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet @@ -217,6 +222,10 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) meshtastic_MeshPacket *PowerTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h index b9ec6edc1..134b40b6b 100644 --- a/src/modules/Telemetry/PowerTelemetry.h +++ b/src/modules/Telemetry/PowerTelemetry.h @@ -5,12 +5,15 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BaseTelemetryModule.h" #include "NodeDB.h" #include "ProtobufModule.h" #include #include -class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModule +class PowerTelemetryModule : private concurrency::OSThread, + public BaseTelemetryModule, + public ProtobufModule { CallbackObserver nodeStatusObserver = CallbackObserver(this, &PowerTelemetryModule::handleStatusUpdate); @@ -51,7 +54,6 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModul bool firstTime = 1; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute - uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; uint32_t sensor_read_error_count = 0; }; diff --git a/src/modules/Telemetry/Sensor/RAK9154Sensor.h b/src/modules/Telemetry/Sensor/RAK9154Sensor.h index c96139f9c..34d0fba73 100644 --- a/src/modules/Telemetry/Sensor/RAK9154Sensor.h +++ b/src/modules/Telemetry/Sensor/RAK9154Sensor.h @@ -18,6 +18,7 @@ class RAK9154Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor public: RAK9154Sensor(); + bool hasSensor() { return true; } // Not an I2C sensor; always available when HAS_RAKPROT is defined virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual uint16_t getBusVoltageMv() override; diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.cpp b/src/modules/Telemetry/Sensor/T1000xSensor.cpp index b123450ec..1e2a77e8b 100644 --- a/src/modules/Telemetry/Sensor/T1000xSensor.cpp +++ b/src/modules/Telemetry/Sensor/T1000xSensor.cpp @@ -73,11 +73,15 @@ float T1000xSensor::getTemp() float Vout = 0, Rt = 0, temp = 0; float Temp = 0; + // P0.4 is a sensor power enable GPIO, not a VCC ADC pin. + // Read BATTERY_PIN (with voltage divider) and cap at NTC_REF_VCC to estimate the sensor rail voltage. for (uint32_t i = 0; i < T1000X_SENSE_SAMPLES; i++) { - vcc_vot += analogRead(T1000X_VCC_PIN); + vcc_vot += analogRead(BATTERY_PIN); } vcc_vot = vcc_vot / T1000X_SENSE_SAMPLES; - vcc_vot = 2 * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vcc_vot; + vcc_vot = ADC_MULTIPLIER * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vcc_vot; + if (vcc_vot > NTC_REF_VCC) + vcc_vot = NTC_REF_VCC; for (uint32_t i = 0; i < T1000X_SENSE_SAMPLES; i++) { ntc_vot += analogRead(T1000X_NTC_PIN); diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index 6c53bbd72..47deaa936 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -1,6 +1,6 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" diff --git a/src/motion/AccelerometerThread.h b/src/motion/AccelerometerThread.h old mode 100755 new mode 100644 index f08ee00f9..9724192c2 --- a/src/motion/AccelerometerThread.h +++ b/src/motion/AccelerometerThread.h @@ -10,6 +10,9 @@ #ifdef HAS_BMA423 #include "BMA423Sensor.h" #endif +#ifdef HAS_BMI270 +#include "BMI270Sensor.h" +#endif #include "BMM150Sensor.h" #include "BMX160Sensor.h" #include "ICM20948Sensor.h" @@ -111,6 +114,11 @@ class AccelerometerThread : public concurrency::OSThread case ScanI2C::DeviceType::BMM150: sensor = new BMM150Sensor(device); break; +#ifdef HAS_BMI270 + case ScanI2C::DeviceType::BMI270: + sensor = new BMI270Sensor(device); + break; +#endif #ifdef HAS_QMA6100P case ScanI2C::DeviceType::QMA6100P: sensor = new QMA6100PSensor(device); diff --git a/src/motion/BMA423Sensor.cpp b/src/motion/BMA423Sensor.cpp old mode 100755 new mode 100644 diff --git a/src/motion/BMA423Sensor.h b/src/motion/BMA423Sensor.h old mode 100755 new mode 100644 diff --git a/src/motion/BMI270Sensor.cpp b/src/motion/BMI270Sensor.cpp new file mode 100644 index 000000000..bc547529d --- /dev/null +++ b/src/motion/BMI270Sensor.cpp @@ -0,0 +1,605 @@ +#include "BMI270Sensor.h" + +#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && defined(HAS_BMI270) + +#include + +// BMI270 registers used +#define BMI270_REG_ACC_X_LSB 0x0C +#define BMI270_REG_INTERNAL_STATUS 0x21 +#define BMI270_REG_ACC_CONF 0x40 +#define BMI270_REG_ACC_RANGE 0x41 +#define BMI270_REG_INIT_CTRL 0x59 +#define BMI270_REG_INIT_ADDR_0 0x5B +#define BMI270_REG_INIT_ADDR_1 0x5C +#define BMI270_REG_INIT_DATA 0x5E +#define BMI270_REG_PWR_CONF 0x7C +#define BMI270_REG_PWR_CTRL 0x7D +#define BMI270_REG_CMD 0x7E + +// Commands and configuration values +#define BMI270_CMD_SOFTRESET 0xB6 +#define BMI270_PWR_CONF_ADV_POWER_SAVE_DISABLED 0x00 +#define BMI270_PWR_CTRL_ACC_EN 0x04 +#define BMI270_ACC_ODR_50HZ 0x07 +#define BMI270_ACC_BWP_NORMAL 0x20 +#define BMI270_ACC_FILTER_PERF 0x80 +#define BMI270_ACC_RANGE_2G 0x00 +#define BMI270_INIT_OK 0x01 + +// BMI270 config file - 8192 bytes from official Bosch BMI270 API +static const uint8_t bmi270_config_file[] PROGMEM = { + 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x3d, 0xb1, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x91, 0x03, 0x80, 0x2e, 0xbc, 0xb0, 0x80, + 0x2e, 0xa3, 0x03, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x00, 0xb0, 0x50, 0x30, 0x21, 0x2e, 0x59, 0xf5, 0x10, 0x30, 0x21, 0x2e, + 0x6a, 0xf5, 0x80, 0x2e, 0x3b, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x19, 0x01, 0x00, 0x22, 0x00, 0x75, 0x00, 0x00, 0x10, 0x00, + 0x10, 0xd1, 0x00, 0xb3, 0x43, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0xe0, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x19, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xe0, 0xaa, 0x38, 0x05, 0xe0, 0x90, 0x30, 0xfa, 0x00, + 0x96, 0x00, 0x4b, 0x09, 0x11, 0x00, 0x11, 0x00, 0x02, 0x00, 0x2d, 0x01, 0xd4, 0x7b, 0x3b, 0x01, 0xdb, 0x7a, 0x04, 0x00, 0x3f, + 0x7b, 0xcd, 0x6c, 0xc3, 0x04, 0x85, 0x09, 0xc3, 0x04, 0xec, 0xe6, 0x0c, 0x46, 0x01, 0x00, 0x27, 0x00, 0x19, 0x00, 0x96, 0x00, + 0xa0, 0x00, 0x01, 0x00, 0x0c, 0x00, 0xf0, 0x3c, 0x00, 0x01, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x32, + 0x00, 0x05, 0x00, 0xee, 0x06, 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x04, 0x00, 0xa8, 0x05, 0xee, 0x06, 0x00, 0x04, 0xbc, 0x02, + 0xb3, 0x00, 0x85, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xb4, 0x00, 0x01, 0x00, 0xb9, 0x00, 0x01, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x00, 0x04, 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, 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, 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, 0x80, 0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0xde, 0x00, 0xeb, 0x00, 0xda, 0x00, 0x00, 0x0c, 0xff, 0x0f, 0x00, 0x04, 0xc0, + 0x00, 0x5b, 0xf5, 0xc9, 0x01, 0x1e, 0xf2, 0x80, 0x00, 0x3f, 0xff, 0x19, 0xf4, 0x58, 0xf5, 0x66, 0xf5, 0x64, 0xf5, 0xc0, 0xf1, + 0xf0, 0x00, 0xe0, 0x00, 0xcd, 0x01, 0xd3, 0x01, 0xdb, 0x01, 0xff, 0x7f, 0xff, 0x01, 0xe4, 0x00, 0x74, 0xf7, 0xf3, 0x00, 0xfa, + 0x00, 0xff, 0x3f, 0xca, 0x03, 0x6c, 0x38, 0x56, 0xfe, 0x44, 0xfd, 0xbc, 0x02, 0xf9, 0x06, 0x00, 0xfc, 0x12, 0x02, 0xae, 0x01, + 0x58, 0xfa, 0x9a, 0xfd, 0x77, 0x05, 0xbb, 0x02, 0x96, 0x01, 0x95, 0x01, 0x7f, 0x01, 0x82, 0x01, 0x89, 0x01, 0x87, 0x01, 0x88, + 0x01, 0x8a, 0x01, 0x8c, 0x01, 0x8f, 0x01, 0x8d, 0x01, 0x92, 0x01, 0x91, 0x01, 0xdd, 0x00, 0x9f, 0x01, 0x7e, 0x01, 0xdb, 0x00, + 0xb6, 0x01, 0x70, 0x69, 0x26, 0xd3, 0x9c, 0x07, 0x1f, 0x05, 0x9d, 0x00, 0x00, 0x08, 0xbc, 0x05, 0x37, 0xfa, 0xa2, 0x01, 0xaa, + 0x01, 0xa1, 0x01, 0xa8, 0x01, 0xa0, 0x01, 0xa8, 0x05, 0xb4, 0x01, 0xb4, 0x01, 0xce, 0x00, 0xd0, 0x00, 0xfc, 0x00, 0xc5, 0x01, + 0xff, 0xfb, 0xb1, 0x00, 0x00, 0x38, 0x00, 0x30, 0xfd, 0xf5, 0xfc, 0xf5, 0xcd, 0x01, 0xa0, 0x00, 0x5f, 0xff, 0x00, 0x40, 0xff, + 0x00, 0x00, 0x80, 0x6d, 0x0f, 0xeb, 0x00, 0x7f, 0xff, 0xc2, 0xf5, 0x68, 0xf7, 0xb3, 0xf1, 0x67, 0x0f, 0x5b, 0x0f, 0x61, 0x0f, + 0x80, 0x0f, 0x58, 0xf7, 0x5b, 0xf7, 0x83, 0x0f, 0x86, 0x00, 0x72, 0x0f, 0x85, 0x0f, 0xc6, 0xf1, 0x7f, 0x0f, 0x6c, 0xf7, 0x00, + 0xe0, 0x00, 0xff, 0xd1, 0xf5, 0x87, 0x0f, 0x8a, 0x0f, 0xff, 0x03, 0xf0, 0x3f, 0x8b, 0x00, 0x8e, 0x00, 0x90, 0x00, 0xb9, 0x00, + 0x2d, 0xf5, 0xca, 0xf5, 0xcb, 0x01, 0x20, 0xf2, 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, 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, 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, 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, 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, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x30, 0x50, 0x98, + 0x2e, 0xd7, 0x0e, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x00, 0x30, 0xf0, 0x7f, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x00, 0x2e, + 0x01, 0x80, 0x08, 0xa2, 0xfb, 0x2f, 0x98, 0x2e, 0xba, 0x03, 0x21, 0x2e, 0x19, 0x00, 0x01, 0x2e, 0xee, 0x00, 0x00, 0xb2, 0x07, + 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x03, 0x2f, 0x01, 0x50, 0x03, 0x52, 0x98, 0x2e, 0x07, 0xcc, 0x01, 0x2e, 0xdd, 0x00, + 0x00, 0xb2, 0x27, 0x2f, 0x05, 0x2e, 0x8a, 0x00, 0x05, 0x52, 0x98, 0x2e, 0xc7, 0xc1, 0x03, 0x2e, 0xe9, 0x00, 0x40, 0xb2, 0xf0, + 0x7f, 0x08, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0xe9, 0x00, 0x98, 0x2e, 0xb4, 0xb1, + 0x01, 0x2e, 0x18, 0x00, 0x00, 0xb2, 0x10, 0x2f, 0x05, 0x50, 0x98, 0x2e, 0x4d, 0xc3, 0x05, 0x50, 0x98, 0x2e, 0x5a, 0xc7, 0x98, + 0x2e, 0xf9, 0xb4, 0x98, 0x2e, 0x54, 0xb2, 0x98, 0x2e, 0x67, 0xb6, 0x98, 0x2e, 0x17, 0xb2, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, + 0x01, 0x2e, 0xef, 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x7a, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0xef, 0x00, 0x01, 0x2e, 0xd4, + 0x00, 0x04, 0xae, 0x0b, 0x2f, 0x01, 0x2e, 0xdd, 0x00, 0x00, 0xb2, 0x07, 0x2f, 0x05, 0x52, 0x98, 0x2e, 0x8e, 0x0e, 0x00, 0xb2, + 0x02, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0x7d, 0x00, 0x01, 0x2e, 0x7d, 0x00, 0x00, 0x90, 0x90, 0x2e, 0xf1, 0x02, 0x01, 0x2e, 0xd7, + 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x2f, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x7b, 0x00, 0x00, 0xb2, + 0x12, 0x2f, 0x01, 0x2e, 0xd4, 0x00, 0x00, 0x90, 0x02, 0x2f, 0x98, 0x2e, 0x1f, 0x0e, 0x09, 0x2d, 0x98, 0x2e, 0x81, 0x0d, 0x01, + 0x2e, 0xd4, 0x00, 0x04, 0x90, 0x02, 0x2f, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x00, 0x30, 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, + 0x7c, 0x00, 0x00, 0xb2, 0x90, 0x2e, 0x09, 0x03, 0x01, 0x2e, 0x7c, 0x00, 0x01, 0x31, 0x01, 0x08, 0x00, 0xb2, 0x04, 0x2f, 0x98, + 0x2e, 0x47, 0xcb, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x81, 0x30, 0x01, 0x2e, 0x7c, 0x00, 0x01, 0x08, 0x00, 0xb2, 0x61, 0x2f, + 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, 0xd4, 0x00, 0x98, 0xbc, 0x98, 0xb8, 0x05, 0xb2, 0x0f, 0x58, 0x23, 0x2f, 0x07, 0x90, 0x09, + 0x54, 0x00, 0x30, 0x37, 0x2f, 0x15, 0x41, 0x04, 0x41, 0xdc, 0xbe, 0x44, 0xbe, 0xdc, 0xba, 0x2c, 0x01, 0x61, 0x00, 0x0f, 0x56, + 0x4a, 0x0f, 0x0c, 0x2f, 0xd1, 0x42, 0x94, 0xb8, 0xc1, 0x42, 0x11, 0x30, 0x05, 0x2e, 0x6a, 0xf7, 0x2c, 0xbd, 0x2f, 0xb9, 0x80, + 0xb2, 0x08, 0x22, 0x98, 0x2e, 0xc3, 0xb7, 0x21, 0x2d, 0x61, 0x30, 0x23, 0x2e, 0xd4, 0x00, 0x98, 0x2e, 0xc3, 0xb7, 0x00, 0x30, + 0x21, 0x2e, 0x5a, 0xf5, 0x18, 0x2d, 0xe1, 0x7f, 0x50, 0x30, 0x98, 0x2e, 0xfa, 0x03, 0x0f, 0x52, 0x07, 0x50, 0x50, 0x42, 0x70, + 0x30, 0x0d, 0x54, 0x42, 0x42, 0x7e, 0x82, 0xe2, 0x6f, 0x80, 0xb2, 0x42, 0x42, 0x05, 0x2f, 0x21, 0x2e, 0xd4, 0x00, 0x10, 0x30, + 0x98, 0x2e, 0xc3, 0xb7, 0x03, 0x2d, 0x60, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x01, 0x2e, 0xd4, 0x00, 0x06, 0x90, 0x18, 0x2f, 0x01, + 0x2e, 0x76, 0x00, 0x0b, 0x54, 0x07, 0x52, 0xe0, 0x7f, 0x98, 0x2e, 0x7a, 0xc1, 0xe1, 0x6f, 0x08, 0x1a, 0x40, 0x30, 0x08, 0x2f, + 0x21, 0x2e, 0xd4, 0x00, 0x20, 0x30, 0x98, 0x2e, 0xaf, 0xb7, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x05, 0x2d, 0x98, 0x2e, 0x38, + 0x0e, 0x00, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x00, 0x30, 0x21, 0x2e, 0x7c, 0x00, 0x18, 0x2d, 0x01, 0x2e, 0xd4, 0x00, 0x03, 0xaa, + 0x01, 0x2f, 0x98, 0x2e, 0x45, 0x0e, 0x01, 0x2e, 0xd4, 0x00, 0x3f, 0x80, 0x03, 0xa2, 0x01, 0x2f, 0x00, 0x2e, 0x02, 0x2d, 0x98, + 0x2e, 0x5b, 0x0e, 0x30, 0x30, 0x98, 0x2e, 0xce, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0x7d, 0x00, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, + 0x01, 0x2e, 0x77, 0x00, 0x00, 0xb2, 0x24, 0x2f, 0x98, 0x2e, 0xf5, 0xcb, 0x03, 0x2e, 0xd5, 0x00, 0x11, 0x54, 0x01, 0x0a, 0xbc, + 0x84, 0x83, 0x86, 0x21, 0x2e, 0xc9, 0x01, 0xe0, 0x40, 0x13, 0x52, 0xc4, 0x40, 0x82, 0x40, 0xa8, 0xb9, 0x52, 0x42, 0x43, 0xbe, + 0x53, 0x42, 0x04, 0x0a, 0x50, 0x42, 0xe1, 0x7f, 0xf0, 0x31, 0x41, 0x40, 0xf2, 0x6f, 0x25, 0xbd, 0x08, 0x08, 0x02, 0x0a, 0xd0, + 0x7f, 0x98, 0x2e, 0xa8, 0xcf, 0x06, 0xbc, 0xd1, 0x6f, 0xe2, 0x6f, 0x08, 0x0a, 0x80, 0x42, 0x98, 0x2e, 0x58, 0xb7, 0x00, 0x30, + 0x21, 0x2e, 0xee, 0x00, 0x21, 0x2e, 0x77, 0x00, 0x21, 0x2e, 0xdd, 0x00, 0x80, 0x2e, 0xf4, 0x01, 0x1a, 0x24, 0x22, 0x00, 0x80, + 0x2e, 0xec, 0x01, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0xf3, 0x03, 0x57, 0x50, 0xfb, 0x6f, 0x01, 0x30, 0x71, 0x54, 0x11, 0x42, + 0x42, 0x0e, 0xfc, 0x2f, 0xc0, 0x2e, 0x01, 0x42, 0xf0, 0x5f, 0x80, 0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x01, 0x34, 0x03, 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, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x06, 0x32, 0x0f, 0x2e, 0x61, 0xf5, 0xfe, 0x09, 0xc0, 0xb3, 0x04, 0x2f, 0x17, 0x30, 0x2f, 0x2e, + 0xef, 0x00, 0x2d, 0x2e, 0x61, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e, 0x20, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x46, + 0x30, 0x0f, 0x2e, 0xa4, 0xf1, 0xbe, 0x09, 0x80, 0xb3, 0x06, 0x2f, 0x0d, 0x2e, 0xd4, 0x00, 0x84, 0xaf, 0x02, 0x2f, 0x16, 0x30, + 0x2d, 0x2e, 0x7b, 0x00, 0x86, 0x30, 0x2d, 0x2e, 0x60, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e, 0x01, 0x2e, 0x77, + 0xf7, 0x09, 0xbc, 0x0f, 0xb8, 0x00, 0xb2, 0x10, 0x50, 0xfb, 0x7f, 0x10, 0x30, 0x0b, 0x2f, 0x03, 0x2e, 0x8a, 0x00, 0x96, 0xbc, + 0x9f, 0xb8, 0x40, 0xb2, 0x05, 0x2f, 0x03, 0x2e, 0x68, 0xf7, 0x9e, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x07, 0x2f, 0x03, 0x2e, 0x7e, + 0x00, 0x41, 0x90, 0x01, 0x2f, 0x98, 0x2e, 0xdc, 0x03, 0x03, 0x2c, 0x00, 0x30, 0x21, 0x2e, 0x7e, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, + 0xb8, 0x2e, 0x20, 0x50, 0xe0, 0x7f, 0xfb, 0x7f, 0x00, 0x2e, 0x27, 0x50, 0x98, 0x2e, 0x3b, 0xc8, 0x29, 0x50, 0x98, 0x2e, 0xa7, + 0xc8, 0x01, 0x50, 0x98, 0x2e, 0x55, 0xcc, 0xe1, 0x6f, 0x2b, 0x50, 0x98, 0x2e, 0xe0, 0xc9, 0xfb, 0x6f, 0x00, 0x30, 0xe0, 0x5f, + 0x21, 0x2e, 0x7e, 0x00, 0xb8, 0x2e, 0x73, 0x50, 0x01, 0x30, 0x57, 0x54, 0x11, 0x42, 0x42, 0x0e, 0xfc, 0x2f, 0xb8, 0x2e, 0x21, + 0x2e, 0x59, 0xf5, 0x10, 0x30, 0xc0, 0x2e, 0x21, 0x2e, 0x4a, 0xf1, 0x90, 0x50, 0xf7, 0x7f, 0xe6, 0x7f, 0xd5, 0x7f, 0xc4, 0x7f, + 0xb3, 0x7f, 0xa1, 0x7f, 0x90, 0x7f, 0x82, 0x7f, 0x7b, 0x7f, 0x98, 0x2e, 0x35, 0xb7, 0x00, 0xb2, 0x90, 0x2e, 0x97, 0xb0, 0x03, + 0x2e, 0x8f, 0x00, 0x07, 0x2e, 0x91, 0x00, 0x05, 0x2e, 0xb1, 0x00, 0x3f, 0xba, 0x9f, 0xb8, 0x01, 0x2e, 0xb1, 0x00, 0xa3, 0xbd, + 0x4c, 0x0a, 0x05, 0x2e, 0xb1, 0x00, 0x04, 0xbe, 0xbf, 0xb9, 0xcb, 0x0a, 0x4f, 0xba, 0x22, 0xbd, 0x01, 0x2e, 0xb3, 0x00, 0xdc, + 0x0a, 0x2f, 0xb9, 0x03, 0x2e, 0xb8, 0x00, 0x0a, 0xbe, 0x9a, 0x0a, 0xcf, 0xb9, 0x9b, 0xbc, 0x01, 0x2e, 0x97, 0x00, 0x9f, 0xb8, + 0x93, 0x0a, 0x0f, 0xbc, 0x91, 0x0a, 0x0f, 0xb8, 0x90, 0x0a, 0x25, 0x2e, 0x18, 0x00, 0x05, 0x2e, 0xc1, 0xf5, 0x2e, 0xbd, 0x2e, + 0xb9, 0x01, 0x2e, 0x19, 0x00, 0x31, 0x30, 0x8a, 0x04, 0x00, 0x90, 0x07, 0x2f, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0xa2, 0x03, 0x2f, + 0x01, 0x2e, 0x18, 0x00, 0x00, 0xb2, 0x0c, 0x2f, 0x19, 0x50, 0x05, 0x52, 0x98, 0x2e, 0x4d, 0xb7, 0x05, 0x2e, 0x78, 0x00, 0x80, + 0x90, 0x10, 0x30, 0x01, 0x2f, 0x21, 0x2e, 0x78, 0x00, 0x25, 0x2e, 0xdd, 0x00, 0x98, 0x2e, 0x3e, 0xb7, 0x00, 0xb2, 0x02, 0x30, + 0x01, 0x30, 0x04, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x00, 0x2f, 0x21, 0x30, 0x01, 0x2e, 0xea, 0x00, 0x08, 0x1a, 0x0e, + 0x2f, 0x23, 0x2e, 0xea, 0x00, 0x33, 0x30, 0x1b, 0x50, 0x0b, 0x09, 0x01, 0x40, 0x17, 0x56, 0x46, 0xbe, 0x4b, 0x08, 0x4c, 0x0a, + 0x01, 0x42, 0x0a, 0x80, 0x15, 0x52, 0x01, 0x42, 0x00, 0x2e, 0x01, 0x2e, 0x18, 0x00, 0x00, 0xb2, 0x1f, 0x2f, 0x03, 0x2e, 0xc0, + 0xf5, 0xf0, 0x30, 0x48, 0x08, 0x47, 0xaa, 0x74, 0x30, 0x07, 0x2e, 0x7a, 0x00, 0x61, 0x22, 0x4b, 0x1a, 0x05, 0x2f, 0x07, 0x2e, + 0x66, 0xf5, 0xbf, 0xbd, 0xbf, 0xb9, 0xc0, 0x90, 0x0b, 0x2f, 0x1d, 0x56, 0x2b, 0x30, 0xd2, 0x42, 0xdb, 0x42, 0x01, 0x04, 0xc2, + 0x42, 0x04, 0xbd, 0xfe, 0x80, 0x81, 0x84, 0x23, 0x2e, 0x7a, 0x00, 0x02, 0x42, 0x02, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x05, 0x2e, + 0xd6, 0x00, 0x81, 0x84, 0x25, 0x2e, 0xd6, 0x00, 0x02, 0x31, 0x25, 0x2e, 0x60, 0xf5, 0x05, 0x2e, 0x8a, 0x00, 0x0b, 0x50, 0x90, + 0x08, 0x80, 0xb2, 0x0b, 0x2f, 0x05, 0x2e, 0xca, 0xf5, 0xf0, 0x3e, 0x90, 0x08, 0x25, 0x2e, 0xca, 0xf5, 0x05, 0x2e, 0x59, 0xf5, + 0xe0, 0x3f, 0x90, 0x08, 0x25, 0x2e, 0x59, 0xf5, 0x90, 0x6f, 0xa1, 0x6f, 0xb3, 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0xe6, 0x6f, 0xf7, + 0x6f, 0x7b, 0x6f, 0x82, 0x6f, 0x70, 0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0x90, 0x7f, 0xe5, 0x7f, 0xd4, 0x7f, 0xc3, 0x7f, 0xb1, 0x7f, + 0xa2, 0x7f, 0x87, 0x7f, 0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x60, 0xf5, 0x60, 0x7f, 0x98, 0x2e, 0x35, 0xb7, 0x02, + 0x30, 0x63, 0x6f, 0x15, 0x52, 0x50, 0x7f, 0x62, 0x7f, 0x5a, 0x2c, 0x02, 0x32, 0x1a, 0x09, 0x00, 0xb3, 0x14, 0x2f, 0x00, 0xb2, + 0x03, 0x2f, 0x09, 0x2e, 0x18, 0x00, 0x00, 0x91, 0x0c, 0x2f, 0x43, 0x7f, 0x98, 0x2e, 0x97, 0xb7, 0x1f, 0x50, 0x02, 0x8a, 0x02, + 0x32, 0x04, 0x30, 0x25, 0x2e, 0x64, 0xf5, 0x15, 0x52, 0x50, 0x6f, 0x43, 0x6f, 0x44, 0x43, 0x25, 0x2e, 0x60, 0xf5, 0xd9, 0x08, + 0xc0, 0xb2, 0x36, 0x2f, 0x98, 0x2e, 0x3e, 0xb7, 0x00, 0xb2, 0x06, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x02, 0x2f, 0x50, + 0x6f, 0x00, 0x90, 0x0a, 0x2f, 0x01, 0x2e, 0x79, 0x00, 0x00, 0x90, 0x19, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0x79, 0x00, 0x00, 0x30, + 0x98, 0x2e, 0xdc, 0x03, 0x13, 0x2d, 0x01, 0x2e, 0xc3, 0xf5, 0x0c, 0xbc, 0x0f, 0xb8, 0x12, 0x30, 0x10, 0x04, 0x03, 0xb0, 0x26, + 0x25, 0x21, 0x50, 0x03, 0x52, 0x98, 0x2e, 0x4d, 0xb7, 0x10, 0x30, 0x21, 0x2e, 0xee, 0x00, 0x02, 0x30, 0x60, 0x7f, 0x25, 0x2e, + 0x79, 0x00, 0x60, 0x6f, 0x00, 0x90, 0x05, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0xea, 0x00, 0x15, 0x50, 0x21, 0x2e, 0x64, 0xf5, 0x15, + 0x52, 0x23, 0x2e, 0x60, 0xf5, 0x02, 0x32, 0x50, 0x6f, 0x00, 0x90, 0x02, 0x2f, 0x03, 0x30, 0x27, 0x2e, 0x78, 0x00, 0x07, 0x2e, + 0x60, 0xf5, 0x1a, 0x09, 0x00, 0x91, 0xa3, 0x2f, 0x19, 0x09, 0x00, 0x91, 0xa0, 0x2f, 0x90, 0x6f, 0xa2, 0x6f, 0xb1, 0x6f, 0xc3, + 0x6f, 0xd4, 0x6f, 0xe5, 0x6f, 0x7b, 0x6f, 0xf6, 0x6f, 0x87, 0x6f, 0x40, 0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, + 0x26, 0x30, 0x0f, 0x2e, 0x61, 0xf5, 0x2f, 0x2e, 0x7c, 0x00, 0x0f, 0x2e, 0x7c, 0x00, 0xbe, 0x09, 0xa2, 0x7f, 0x80, 0x7f, 0x80, + 0xb3, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0x91, 0x7f, 0x7b, 0x7f, 0x0b, 0x2f, 0x23, 0x50, 0x1a, 0x25, 0x12, 0x40, 0x42, 0x7f, + 0x74, 0x82, 0x12, 0x40, 0x52, 0x7f, 0x00, 0x2e, 0x00, 0x40, 0x60, 0x7f, 0x98, 0x2e, 0x6a, 0xd6, 0x81, 0x30, 0x01, 0x2e, 0x7c, + 0x00, 0x01, 0x08, 0x00, 0xb2, 0x42, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, 0x89, 0x00, 0x97, 0xbc, 0x06, 0xbc, 0x9f, 0xb8, + 0x0f, 0xb8, 0x00, 0x90, 0x23, 0x2e, 0xd8, 0x00, 0x10, 0x30, 0x01, 0x30, 0x2a, 0x2f, 0x03, 0x2e, 0xd4, 0x00, 0x44, 0xb2, 0x05, + 0x2f, 0x47, 0xb2, 0x00, 0x30, 0x2d, 0x2f, 0x21, 0x2e, 0x7c, 0x00, 0x2b, 0x2d, 0x03, 0x2e, 0xfd, 0xf5, 0x9e, 0xbc, 0x9f, 0xb8, + 0x40, 0x90, 0x14, 0x2f, 0x03, 0x2e, 0xfc, 0xf5, 0x99, 0xbc, 0x9f, 0xb8, 0x40, 0x90, 0x0e, 0x2f, 0x03, 0x2e, 0x49, 0xf1, 0x25, + 0x54, 0x4a, 0x08, 0x40, 0x90, 0x08, 0x2f, 0x98, 0x2e, 0x35, 0xb7, 0x00, 0xb2, 0x10, 0x30, 0x03, 0x2f, 0x50, 0x30, 0x21, 0x2e, + 0xd4, 0x00, 0x10, 0x2d, 0x98, 0x2e, 0xaf, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0x7c, 0x00, 0x0a, 0x2d, 0x05, 0x2e, 0x69, 0xf7, 0x2d, + 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x01, 0x2f, 0x21, 0x2e, 0x7d, 0x00, 0x23, 0x2e, 0x7c, 0x00, 0xe0, 0x31, 0x21, 0x2e, 0x61, 0xf5, + 0xf6, 0x6f, 0xe7, 0x6f, 0x80, 0x6f, 0xa2, 0x6f, 0xb3, 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0x7b, 0x6f, 0x91, 0x6f, 0x40, 0x5f, 0xc8, + 0x2e, 0x60, 0x51, 0x0a, 0x25, 0x36, 0x88, 0xf4, 0x7f, 0xeb, 0x7f, 0x00, 0x32, 0x31, 0x52, 0x32, 0x30, 0x13, 0x30, 0x98, 0x2e, + 0x15, 0xcb, 0x0a, 0x25, 0x33, 0x84, 0xd2, 0x7f, 0x43, 0x30, 0x05, 0x50, 0x2d, 0x52, 0x98, 0x2e, 0x95, 0xc1, 0xd2, 0x6f, 0x27, + 0x52, 0x98, 0x2e, 0xd7, 0xc7, 0x2a, 0x25, 0xb0, 0x86, 0xc0, 0x7f, 0xd3, 0x7f, 0xaf, 0x84, 0x29, 0x50, 0xf1, 0x6f, 0x98, 0x2e, + 0x4d, 0xc8, 0x2a, 0x25, 0xae, 0x8a, 0xaa, 0x88, 0xf2, 0x6e, 0x2b, 0x50, 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x7f, 0x98, 0x2e, 0xb6, + 0xc8, 0xe0, 0x6e, 0x00, 0xb2, 0x32, 0x2f, 0x33, 0x54, 0x83, 0x86, 0xf1, 0x6f, 0xc3, 0x7f, 0x04, 0x30, 0x30, 0x30, 0xf4, 0x7f, + 0xd0, 0x7f, 0xb2, 0x7f, 0xe3, 0x30, 0xc5, 0x6f, 0x56, 0x40, 0x45, 0x41, 0x28, 0x08, 0x03, 0x14, 0x0e, 0xb4, 0x08, 0xbc, 0x82, + 0x40, 0x10, 0x0a, 0x2f, 0x54, 0x26, 0x05, 0x91, 0x7f, 0x44, 0x28, 0xa3, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0x08, 0xb9, 0x33, 0x30, + 0x53, 0x09, 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x6f, 0x83, 0x17, 0x47, 0x40, 0x6c, 0x15, 0xb2, 0x6f, 0xbe, 0x09, 0x75, 0x0b, 0x90, + 0x42, 0x45, 0x42, 0x51, 0x0e, 0x32, 0xbc, 0x02, 0x89, 0xa1, 0x6f, 0x7e, 0x86, 0xf4, 0x7f, 0xd0, 0x7f, 0xb2, 0x7f, 0x04, 0x30, + 0x91, 0x6f, 0xd6, 0x2f, 0xeb, 0x6f, 0xa0, 0x5e, 0xb8, 0x2e, 0x03, 0x2e, 0x97, 0x00, 0x1b, 0xbc, 0x60, 0x50, 0x9f, 0xbc, 0x0c, + 0xb8, 0xf0, 0x7f, 0x40, 0xb2, 0xeb, 0x7f, 0x2b, 0x2f, 0x03, 0x2e, 0x7f, 0x00, 0x41, 0x40, 0x01, 0x2e, 0xc8, 0x00, 0x01, 0x1a, + 0x11, 0x2f, 0x37, 0x58, 0x23, 0x2e, 0xc8, 0x00, 0x10, 0x41, 0xa0, 0x7f, 0x38, 0x81, 0x01, 0x41, 0xd0, 0x7f, 0xb1, 0x7f, 0x98, + 0x2e, 0x64, 0xcf, 0xd0, 0x6f, 0x07, 0x80, 0xa1, 0x6f, 0x11, 0x42, 0x00, 0x2e, 0xb1, 0x6f, 0x01, 0x42, 0x11, 0x30, 0x01, 0x2e, + 0xfc, 0x00, 0x00, 0xa8, 0x03, 0x30, 0xcb, 0x22, 0x4a, 0x25, 0x01, 0x2e, 0x7f, 0x00, 0x3c, 0x89, 0x35, 0x52, 0x05, 0x54, 0x98, + 0x2e, 0xc4, 0xce, 0xc1, 0x6f, 0xf0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x04, 0x2d, 0x01, 0x30, 0xf0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, + 0xeb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, 0x03, 0x2e, 0xb3, 0x00, 0x02, 0x32, 0xf0, 0x30, 0x03, 0x31, 0x30, 0x50, 0x8a, 0x08, 0x08, + 0x08, 0xcb, 0x08, 0xe0, 0x7f, 0x80, 0xb2, 0xf3, 0x7f, 0xdb, 0x7f, 0x25, 0x2f, 0x03, 0x2e, 0xca, 0x00, 0x41, 0x90, 0x04, 0x2f, + 0x01, 0x30, 0x23, 0x2e, 0xca, 0x00, 0x98, 0x2e, 0x3f, 0x03, 0xc0, 0xb2, 0x05, 0x2f, 0x03, 0x2e, 0xda, 0x00, 0x00, 0x30, 0x41, + 0x04, 0x23, 0x2e, 0xda, 0x00, 0x98, 0x2e, 0x92, 0xb2, 0x10, 0x25, 0xf0, 0x6f, 0x00, 0xb2, 0x05, 0x2f, 0x01, 0x2e, 0xda, 0x00, + 0x02, 0x30, 0x10, 0x04, 0x21, 0x2e, 0xda, 0x00, 0x40, 0xb2, 0x01, 0x2f, 0x23, 0x2e, 0xc8, 0x01, 0xdb, 0x6f, 0xe0, 0x6f, 0xd0, + 0x5f, 0x80, 0x2e, 0x95, 0xcf, 0x01, 0x30, 0xe0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x11, 0x30, 0x23, 0x2e, 0xca, 0x00, 0xdb, 0x6f, + 0xd0, 0x5f, 0xb8, 0x2e, 0xd0, 0x50, 0x0a, 0x25, 0x33, 0x84, 0x55, 0x50, 0xd2, 0x7f, 0xe2, 0x7f, 0x03, 0x8c, 0xc0, 0x7f, 0xbb, + 0x7f, 0x00, 0x30, 0x05, 0x5a, 0x39, 0x54, 0x51, 0x41, 0xa5, 0x7f, 0x96, 0x7f, 0x80, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0x05, 0x30, + 0xf5, 0x7f, 0x20, 0x25, 0x91, 0x6f, 0x3b, 0x58, 0x3d, 0x5c, 0x3b, 0x56, 0x98, 0x2e, 0x67, 0xcc, 0xc1, 0x6f, 0xd5, 0x6f, 0x52, + 0x40, 0x50, 0x43, 0xc1, 0x7f, 0xd5, 0x7f, 0x10, 0x25, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, 0xc0, 0x86, 0x6f, + 0x30, 0x28, 0x92, 0x6f, 0x82, 0x8c, 0xa5, 0x6f, 0x6f, 0x52, 0x69, 0x0e, 0x39, 0x54, 0xdb, 0x2f, 0x19, 0xa0, 0x15, 0x30, 0x03, + 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x81, 0x01, 0x0a, 0x2d, 0x01, 0x2e, 0x81, 0x01, 0x05, 0x28, 0x42, 0x36, 0x21, 0x2e, 0x81, 0x01, + 0x02, 0x0e, 0x01, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0x57, 0x50, 0x12, 0x30, 0x01, 0x40, 0x98, 0x2e, 0xfe, 0xc9, 0x51, 0x6f, 0x0b, + 0x5c, 0x8e, 0x0e, 0x3b, 0x6f, 0x57, 0x58, 0x02, 0x30, 0x21, 0x2e, 0x95, 0x01, 0x45, 0x6f, 0x2a, 0x8d, 0xd2, 0x7f, 0xcb, 0x7f, + 0x13, 0x2f, 0x02, 0x30, 0x3f, 0x50, 0xd2, 0x7f, 0xa8, 0x0e, 0x0e, 0x2f, 0xc0, 0x6f, 0x53, 0x54, 0x02, 0x00, 0x51, 0x54, 0x42, + 0x0e, 0x10, 0x30, 0x59, 0x52, 0x02, 0x30, 0x01, 0x2f, 0x00, 0x2e, 0x03, 0x2d, 0x50, 0x42, 0x42, 0x42, 0x12, 0x30, 0xd2, 0x7f, + 0x80, 0xb2, 0x03, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x80, 0x01, 0x12, 0x2d, 0x01, 0x2e, 0xc9, 0x00, 0x02, 0x80, 0x05, 0x2e, 0x80, + 0x01, 0x11, 0x30, 0x91, 0x28, 0x00, 0x40, 0x25, 0x2e, 0x80, 0x01, 0x10, 0x0e, 0x05, 0x2f, 0x01, 0x2e, 0x7f, 0x01, 0x01, 0x90, + 0x01, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0x00, 0x2e, 0xa0, 0x41, 0x01, 0x90, 0xa6, 0x7f, 0x90, 0x2e, 0xe3, 0xb4, 0x01, 0x2e, 0x95, + 0x01, 0x00, 0xa8, 0x90, 0x2e, 0xe3, 0xb4, 0x5b, 0x54, 0x95, 0x80, 0x82, 0x40, 0x80, 0xb2, 0x02, 0x40, 0x2d, 0x8c, 0x3f, 0x52, + 0x96, 0x7f, 0x90, 0x2e, 0xc2, 0xb3, 0x29, 0x0e, 0x76, 0x2f, 0x01, 0x2e, 0xc9, 0x00, 0x00, 0x40, 0x81, 0x28, 0x45, 0x52, 0xb3, + 0x30, 0x98, 0x2e, 0x0f, 0xca, 0x5d, 0x54, 0x80, 0x7f, 0x00, 0x2e, 0xa1, 0x40, 0x72, 0x7f, 0x82, 0x80, 0x82, 0x40, 0x60, 0x7f, + 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, 0xc0, 0x62, 0x6f, 0x05, 0x30, 0x87, 0x40, 0xc0, 0x91, 0x04, 0x30, 0x05, + 0x2f, 0x05, 0x2e, 0x83, 0x01, 0x80, 0xb2, 0x14, 0x30, 0x00, 0x2f, 0x04, 0x30, 0x05, 0x2e, 0xc9, 0x00, 0x73, 0x6f, 0x81, 0x40, + 0xe2, 0x40, 0x69, 0x04, 0x11, 0x0f, 0xe1, 0x40, 0x16, 0x30, 0xfe, 0x29, 0xcb, 0x40, 0x02, 0x2f, 0x83, 0x6f, 0x83, 0x0f, 0x22, + 0x2f, 0x47, 0x56, 0x13, 0x0f, 0x12, 0x30, 0x77, 0x2f, 0x49, 0x54, 0x42, 0x0e, 0x12, 0x30, 0x73, 0x2f, 0x00, 0x91, 0x0a, 0x2f, + 0x01, 0x2e, 0x8b, 0x01, 0x19, 0xa8, 0x02, 0x30, 0x6c, 0x2f, 0x63, 0x50, 0x00, 0x2e, 0x17, 0x42, 0x05, 0x42, 0x68, 0x2c, 0x12, + 0x30, 0x0b, 0x25, 0x08, 0x0f, 0x50, 0x30, 0x02, 0x2f, 0x21, 0x2e, 0x83, 0x01, 0x03, 0x2d, 0x40, 0x30, 0x21, 0x2e, 0x83, 0x01, + 0x2b, 0x2e, 0x85, 0x01, 0x5a, 0x2c, 0x12, 0x30, 0x00, 0x91, 0x2b, 0x25, 0x04, 0x2f, 0x63, 0x50, 0x02, 0x30, 0x17, 0x42, 0x17, + 0x2c, 0x02, 0x42, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, 0xc0, 0x05, 0x2e, 0xc9, 0x00, 0x81, 0x84, 0x5b, 0x30, + 0x82, 0x40, 0x37, 0x2e, 0x83, 0x01, 0x02, 0x0e, 0x07, 0x2f, 0x5f, 0x52, 0x40, 0x30, 0x62, 0x40, 0x41, 0x40, 0x91, 0x0e, 0x01, + 0x2f, 0x21, 0x2e, 0x83, 0x01, 0x05, 0x30, 0x2b, 0x2e, 0x85, 0x01, 0x12, 0x30, 0x36, 0x2c, 0x16, 0x30, 0x15, 0x25, 0x81, 0x7f, + 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, 0xc0, 0x19, 0xa2, 0x16, 0x30, 0x15, 0x2f, 0x05, 0x2e, 0x97, 0x01, 0x80, + 0x6f, 0x82, 0x0e, 0x05, 0x2f, 0x01, 0x2e, 0x86, 0x01, 0x06, 0x28, 0x21, 0x2e, 0x86, 0x01, 0x0b, 0x2d, 0x03, 0x2e, 0x87, 0x01, + 0x5f, 0x54, 0x4e, 0x28, 0x91, 0x42, 0x00, 0x2e, 0x82, 0x40, 0x90, 0x0e, 0x01, 0x2f, 0x21, 0x2e, 0x88, 0x01, 0x02, 0x30, 0x13, + 0x2c, 0x05, 0x30, 0xc0, 0x6f, 0x08, 0x1c, 0xa8, 0x0f, 0x16, 0x30, 0x05, 0x30, 0x5b, 0x50, 0x09, 0x2f, 0x02, 0x80, 0x2d, 0x2e, + 0x82, 0x01, 0x05, 0x42, 0x05, 0x80, 0x00, 0x2e, 0x02, 0x42, 0x3e, 0x80, 0x00, 0x2e, 0x06, 0x42, 0x02, 0x30, 0x90, 0x6f, 0x3e, + 0x88, 0x01, 0x40, 0x04, 0x41, 0x4c, 0x28, 0x01, 0x42, 0x07, 0x80, 0x10, 0x25, 0x24, 0x40, 0x00, 0x40, 0x00, 0xa8, 0xf5, 0x22, + 0x23, 0x29, 0x44, 0x42, 0x7a, 0x82, 0x7e, 0x88, 0x43, 0x40, 0x04, 0x41, 0x00, 0xab, 0xf5, 0x23, 0xdf, 0x28, 0x43, 0x42, 0xd9, + 0xa0, 0x14, 0x2f, 0x00, 0x90, 0x02, 0x2f, 0xd2, 0x6f, 0x81, 0xb2, 0x05, 0x2f, 0x63, 0x54, 0x06, 0x28, 0x90, 0x42, 0x85, 0x42, + 0x09, 0x2c, 0x02, 0x30, 0x5b, 0x50, 0x03, 0x80, 0x29, 0x2e, 0x7e, 0x01, 0x2b, 0x2e, 0x82, 0x01, 0x05, 0x42, 0x12, 0x30, 0x2b, + 0x2e, 0x83, 0x01, 0x45, 0x82, 0x00, 0x2e, 0x40, 0x40, 0x7a, 0x82, 0x02, 0xa0, 0x08, 0x2f, 0x63, 0x50, 0x3b, 0x30, 0x15, 0x42, + 0x05, 0x42, 0x37, 0x80, 0x37, 0x2e, 0x7e, 0x01, 0x05, 0x42, 0x12, 0x30, 0x01, 0x2e, 0xc9, 0x00, 0x02, 0x8c, 0x40, 0x40, 0x84, + 0x41, 0x7a, 0x8c, 0x04, 0x0f, 0x03, 0x2f, 0x01, 0x2e, 0x8b, 0x01, 0x19, 0xa4, 0x04, 0x2f, 0x2b, 0x2e, 0x82, 0x01, 0x98, 0x2e, + 0xf3, 0x03, 0x12, 0x30, 0x81, 0x90, 0x61, 0x52, 0x08, 0x2f, 0x65, 0x42, 0x65, 0x42, 0x43, 0x80, 0x39, 0x84, 0x82, 0x88, 0x05, + 0x42, 0x45, 0x42, 0x85, 0x42, 0x05, 0x43, 0x00, 0x2e, 0x80, 0x41, 0x00, 0x90, 0x90, 0x2e, 0xe1, 0xb4, 0x65, 0x54, 0xc1, 0x6f, + 0x80, 0x40, 0x00, 0xb2, 0x43, 0x58, 0x69, 0x50, 0x44, 0x2f, 0x55, 0x5c, 0xb7, 0x87, 0x8c, 0x0f, 0x0d, 0x2e, 0x96, 0x01, 0xc4, + 0x40, 0x36, 0x2f, 0x41, 0x56, 0x8b, 0x0e, 0x2a, 0x2f, 0x0b, 0x52, 0xa1, 0x0e, 0x0a, 0x2f, 0x05, 0x2e, 0x8f, 0x01, 0x14, 0x25, + 0x98, 0x2e, 0xfe, 0xc9, 0x4b, 0x54, 0x02, 0x0f, 0x69, 0x50, 0x05, 0x30, 0x65, 0x54, 0x15, 0x2f, 0x03, 0x2e, 0x8e, 0x01, 0x4d, + 0x5c, 0x8e, 0x0f, 0x3a, 0x2f, 0x05, 0x2e, 0x8f, 0x01, 0x98, 0x2e, 0xfe, 0xc9, 0x4f, 0x54, 0x82, 0x0f, 0x05, 0x30, 0x69, 0x50, + 0x65, 0x54, 0x30, 0x2f, 0x6d, 0x52, 0x15, 0x30, 0x42, 0x8c, 0x45, 0x42, 0x04, 0x30, 0x2b, 0x2c, 0x84, 0x43, 0x6b, 0x52, 0x42, + 0x8c, 0x00, 0x2e, 0x85, 0x43, 0x15, 0x30, 0x24, 0x2c, 0x45, 0x42, 0x8e, 0x0f, 0x20, 0x2f, 0x0d, 0x2e, 0x8e, 0x01, 0xb1, 0x0e, + 0x1c, 0x2f, 0x23, 0x2e, 0x8e, 0x01, 0x1a, 0x2d, 0x0e, 0x0e, 0x17, 0x2f, 0xa1, 0x0f, 0x15, 0x2f, 0x23, 0x2e, 0x8d, 0x01, 0x13, + 0x2d, 0x98, 0x2e, 0x74, 0xc0, 0x43, 0x54, 0xc2, 0x0e, 0x0a, 0x2f, 0x65, 0x50, 0x04, 0x80, 0x0b, 0x30, 0x06, 0x82, 0x0b, 0x42, + 0x79, 0x80, 0x41, 0x40, 0x12, 0x30, 0x25, 0x2e, 0x8c, 0x01, 0x01, 0x42, 0x05, 0x30, 0x69, 0x50, 0x65, 0x54, 0x84, 0x82, 0x43, + 0x84, 0xbe, 0x8c, 0x84, 0x40, 0x86, 0x41, 0x26, 0x29, 0x94, 0x42, 0xbe, 0x8e, 0xd5, 0x7f, 0x19, 0xa1, 0x43, 0x40, 0x0b, 0x2e, + 0x8c, 0x01, 0x84, 0x40, 0xc7, 0x41, 0x5d, 0x29, 0x27, 0x29, 0x45, 0x42, 0x84, 0x42, 0xc2, 0x7f, 0x01, 0x2f, 0xc0, 0xb3, 0x1d, + 0x2f, 0x05, 0x2e, 0x94, 0x01, 0x99, 0xa0, 0x01, 0x2f, 0x80, 0xb3, 0x13, 0x2f, 0x80, 0xb3, 0x18, 0x2f, 0xc0, 0xb3, 0x16, 0x2f, + 0x12, 0x40, 0x01, 0x40, 0x92, 0x7f, 0x98, 0x2e, 0x74, 0xc0, 0x92, 0x6f, 0x10, 0x0f, 0x20, 0x30, 0x03, 0x2f, 0x10, 0x30, 0x21, + 0x2e, 0x7e, 0x01, 0x0a, 0x2d, 0x21, 0x2e, 0x7e, 0x01, 0x07, 0x2d, 0x20, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0x03, 0x2d, 0x10, 0x30, + 0x21, 0x2e, 0x7e, 0x01, 0xc2, 0x6f, 0x01, 0x2e, 0xc9, 0x00, 0xbc, 0x84, 0x02, 0x80, 0x82, 0x40, 0x00, 0x40, 0x90, 0x0e, 0xd5, + 0x6f, 0x02, 0x2f, 0x15, 0x30, 0x98, 0x2e, 0xf3, 0x03, 0x41, 0x91, 0x05, 0x30, 0x07, 0x2f, 0x67, 0x50, 0x3d, 0x80, 0x2b, 0x2e, + 0x8f, 0x01, 0x05, 0x42, 0x04, 0x80, 0x00, 0x2e, 0x05, 0x42, 0x02, 0x2c, 0x00, 0x30, 0x00, 0x30, 0xa2, 0x6f, 0x98, 0x8a, 0x86, + 0x40, 0x80, 0xa7, 0x05, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0xc0, 0x30, 0x21, 0x2e, 0x95, 0x01, 0x06, 0x25, 0x1a, 0x25, 0xe2, 0x6f, + 0x76, 0x82, 0x96, 0x40, 0x56, 0x43, 0x51, 0x0e, 0xfb, 0x2f, 0xbb, 0x6f, 0x30, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0xb8, 0x00, 0x01, + 0x31, 0x41, 0x08, 0x40, 0xb2, 0x20, 0x50, 0xf2, 0x30, 0x02, 0x08, 0xfb, 0x7f, 0x01, 0x30, 0x10, 0x2f, 0x05, 0x2e, 0xcc, 0x00, + 0x81, 0x90, 0xe0, 0x7f, 0x03, 0x2f, 0x23, 0x2e, 0xcc, 0x00, 0x98, 0x2e, 0x55, 0xb6, 0x98, 0x2e, 0x1d, 0xb5, 0x10, 0x25, 0xfb, + 0x6f, 0xe0, 0x6f, 0xe0, 0x5f, 0x80, 0x2e, 0x95, 0xcf, 0x98, 0x2e, 0x95, 0xcf, 0x10, 0x30, 0x21, 0x2e, 0xcc, 0x00, 0xfb, 0x6f, + 0xe0, 0x5f, 0xb8, 0x2e, 0x00, 0x51, 0x05, 0x58, 0xeb, 0x7f, 0x2a, 0x25, 0x89, 0x52, 0x6f, 0x5a, 0x89, 0x50, 0x13, 0x41, 0x06, + 0x40, 0xb3, 0x01, 0x16, 0x42, 0xcb, 0x16, 0x06, 0x40, 0xf3, 0x02, 0x13, 0x42, 0x65, 0x0e, 0xf5, 0x2f, 0x05, 0x40, 0x14, 0x30, + 0x2c, 0x29, 0x04, 0x42, 0x08, 0xa1, 0x00, 0x30, 0x90, 0x2e, 0x52, 0xb6, 0xb3, 0x88, 0xb0, 0x8a, 0xb6, 0x84, 0xa4, 0x7f, 0xc4, + 0x7f, 0xb5, 0x7f, 0xd5, 0x7f, 0x92, 0x7f, 0x73, 0x30, 0x04, 0x30, 0x55, 0x40, 0x42, 0x40, 0x8a, 0x17, 0xf3, 0x08, 0x6b, 0x01, + 0x90, 0x02, 0x53, 0xb8, 0x4b, 0x82, 0xad, 0xbe, 0x71, 0x7f, 0x45, 0x0a, 0x09, 0x54, 0x84, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0xa3, + 0x6f, 0x7b, 0x54, 0xd0, 0x42, 0xa3, 0x7f, 0xf2, 0x7f, 0x60, 0x7f, 0x20, 0x25, 0x71, 0x6f, 0x75, 0x5a, 0x77, 0x58, 0x79, 0x5c, + 0x75, 0x56, 0x98, 0x2e, 0x67, 0xcc, 0xb1, 0x6f, 0x62, 0x6f, 0x50, 0x42, 0xb1, 0x7f, 0xb3, 0x30, 0x10, 0x25, 0x98, 0x2e, 0x0f, + 0xca, 0x84, 0x6f, 0x20, 0x29, 0x71, 0x6f, 0x92, 0x6f, 0xa5, 0x6f, 0x76, 0x82, 0x6a, 0x0e, 0x73, 0x30, 0x00, 0x30, 0xd0, 0x2f, + 0xd2, 0x6f, 0xd1, 0x7f, 0xb4, 0x7f, 0x98, 0x2e, 0x2b, 0xb7, 0x15, 0xbd, 0x0b, 0xb8, 0x02, 0x0a, 0xc2, 0x6f, 0xc0, 0x7f, 0x98, + 0x2e, 0x2b, 0xb7, 0x15, 0xbd, 0x0b, 0xb8, 0x42, 0x0a, 0xc0, 0x6f, 0x08, 0x17, 0x41, 0x18, 0x89, 0x16, 0xe1, 0x18, 0xd0, 0x18, + 0xa1, 0x7f, 0x27, 0x25, 0x16, 0x25, 0x98, 0x2e, 0x79, 0xc0, 0x8b, 0x54, 0x90, 0x7f, 0xb3, 0x30, 0x82, 0x40, 0x80, 0x90, 0x0d, + 0x2f, 0x7d, 0x52, 0x92, 0x6f, 0x98, 0x2e, 0x0f, 0xca, 0xb2, 0x6f, 0x90, 0x0e, 0x06, 0x2f, 0x8b, 0x50, 0x14, 0x30, 0x42, 0x6f, + 0x51, 0x6f, 0x14, 0x42, 0x12, 0x42, 0x01, 0x42, 0x00, 0x2e, 0x31, 0x6f, 0x98, 0x2e, 0x74, 0xc0, 0x41, 0x6f, 0x80, 0x7f, 0x98, + 0x2e, 0x74, 0xc0, 0x82, 0x6f, 0x10, 0x04, 0x43, 0x52, 0x01, 0x0f, 0x05, 0x2e, 0xcb, 0x00, 0x00, 0x30, 0x04, 0x30, 0x21, 0x2f, + 0x51, 0x6f, 0x43, 0x58, 0x8c, 0x0e, 0x04, 0x30, 0x1c, 0x2f, 0x85, 0x88, 0x41, 0x6f, 0x04, 0x41, 0x8c, 0x0f, 0x04, 0x30, 0x16, + 0x2f, 0x84, 0x88, 0x00, 0x2e, 0x04, 0x41, 0x04, 0x05, 0x8c, 0x0e, 0x04, 0x30, 0x0f, 0x2f, 0x82, 0x88, 0x31, 0x6f, 0x04, 0x41, + 0x04, 0x05, 0x8c, 0x0e, 0x04, 0x30, 0x08, 0x2f, 0x83, 0x88, 0x00, 0x2e, 0x04, 0x41, 0x8c, 0x0f, 0x04, 0x30, 0x02, 0x2f, 0x21, + 0x2e, 0xad, 0x01, 0x14, 0x30, 0x00, 0x91, 0x14, 0x2f, 0x03, 0x2e, 0xa1, 0x01, 0x41, 0x90, 0x0e, 0x2f, 0x03, 0x2e, 0xad, 0x01, + 0x14, 0x30, 0x4c, 0x28, 0x23, 0x2e, 0xad, 0x01, 0x46, 0xa0, 0x06, 0x2f, 0x81, 0x84, 0x8d, 0x52, 0x48, 0x82, 0x82, 0x40, 0x21, + 0x2e, 0xa1, 0x01, 0x42, 0x42, 0x5c, 0x2c, 0x02, 0x30, 0x05, 0x2e, 0xaa, 0x01, 0x80, 0xb2, 0x02, 0x30, 0x55, 0x2f, 0x03, 0x2e, + 0xa9, 0x01, 0x92, 0x6f, 0xb3, 0x30, 0x98, 0x2e, 0x0f, 0xca, 0xb2, 0x6f, 0x90, 0x0f, 0x00, 0x30, 0x02, 0x30, 0x4a, 0x2f, 0xa2, + 0x6f, 0x87, 0x52, 0x91, 0x00, 0x85, 0x52, 0x51, 0x0e, 0x02, 0x2f, 0x00, 0x2e, 0x43, 0x2c, 0x02, 0x30, 0xc2, 0x6f, 0x7f, 0x52, + 0x91, 0x0e, 0x02, 0x30, 0x3c, 0x2f, 0x51, 0x6f, 0x81, 0x54, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0xb3, 0x30, 0x21, 0x25, 0x98, + 0x2e, 0x0f, 0xca, 0x32, 0x6f, 0xc0, 0x7f, 0xb3, 0x30, 0x12, 0x25, 0x98, 0x2e, 0x0f, 0xca, 0x42, 0x6f, 0xb0, 0x7f, 0xb3, 0x30, + 0x12, 0x25, 0x98, 0x2e, 0x0f, 0xca, 0xb2, 0x6f, 0x90, 0x28, 0x83, 0x52, 0x98, 0x2e, 0xfe, 0xc9, 0xc2, 0x6f, 0x90, 0x0f, 0x00, + 0x30, 0x02, 0x30, 0x1d, 0x2f, 0x05, 0x2e, 0xa1, 0x01, 0x80, 0xb2, 0x12, 0x30, 0x0f, 0x2f, 0x42, 0x6f, 0x03, 0x2e, 0xab, 0x01, + 0x91, 0x0e, 0x02, 0x30, 0x12, 0x2f, 0x52, 0x6f, 0x03, 0x2e, 0xac, 0x01, 0x91, 0x0f, 0x02, 0x30, 0x0c, 0x2f, 0x21, 0x2e, 0xaa, + 0x01, 0x0a, 0x2c, 0x12, 0x30, 0x03, 0x2e, 0xcb, 0x00, 0x8d, 0x58, 0x08, 0x89, 0x41, 0x40, 0x11, 0x43, 0x00, 0x43, 0x25, 0x2e, + 0xa1, 0x01, 0xd4, 0x6f, 0x8f, 0x52, 0x00, 0x43, 0x3a, 0x89, 0x00, 0x2e, 0x10, 0x43, 0x10, 0x43, 0x61, 0x0e, 0xfb, 0x2f, 0x03, + 0x2e, 0xa0, 0x01, 0x11, 0x1a, 0x02, 0x2f, 0x02, 0x25, 0x21, 0x2e, 0xa0, 0x01, 0xeb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x91, 0x52, + 0x10, 0x30, 0x02, 0x30, 0x95, 0x56, 0x52, 0x42, 0x4b, 0x0e, 0xfc, 0x2f, 0x8d, 0x54, 0x88, 0x82, 0x93, 0x56, 0x80, 0x42, 0x53, + 0x42, 0x40, 0x42, 0x42, 0x86, 0x83, 0x54, 0xc0, 0x2e, 0xc2, 0x42, 0x00, 0x2e, 0xa3, 0x52, 0x00, 0x51, 0x52, 0x40, 0x47, 0x40, + 0x1a, 0x25, 0x01, 0x2e, 0x97, 0x00, 0x8f, 0xbe, 0x72, 0x86, 0xfb, 0x7f, 0x0b, 0x30, 0x7c, 0xbf, 0xa5, 0x50, 0x10, 0x08, 0xdf, + 0xba, 0x70, 0x88, 0xf8, 0xbf, 0xcb, 0x42, 0xd3, 0x7f, 0x6c, 0xbb, 0xfc, 0xbb, 0xc5, 0x0a, 0x90, 0x7f, 0x1b, 0x7f, 0x0b, 0x43, + 0xc0, 0xb2, 0xe5, 0x7f, 0xb7, 0x7f, 0xa6, 0x7f, 0xc4, 0x7f, 0x90, 0x2e, 0x1c, 0xb7, 0x07, 0x2e, 0xd2, 0x00, 0xc0, 0xb2, 0x0b, + 0x2f, 0x97, 0x52, 0x01, 0x2e, 0xcd, 0x00, 0x82, 0x7f, 0x98, 0x2e, 0xbb, 0xcc, 0x0b, 0x30, 0x37, 0x2e, 0xd2, 0x00, 0x82, 0x6f, + 0x90, 0x6f, 0x1a, 0x25, 0x00, 0xb2, 0x8b, 0x7f, 0x14, 0x2f, 0xa6, 0xbd, 0x25, 0xbd, 0xb6, 0xb9, 0x2f, 0xb9, 0x80, 0xb2, 0xd4, + 0xb0, 0x0c, 0x2f, 0x99, 0x54, 0x9b, 0x56, 0x0b, 0x30, 0x0b, 0x2e, 0xb1, 0x00, 0xa1, 0x58, 0x9b, 0x42, 0xdb, 0x42, 0x6c, 0x09, + 0x2b, 0x2e, 0xb1, 0x00, 0x8b, 0x42, 0xcb, 0x42, 0x86, 0x7f, 0x73, 0x84, 0xa7, 0x56, 0xc3, 0x08, 0x39, 0x52, 0x05, 0x50, 0x72, + 0x7f, 0x63, 0x7f, 0x98, 0x2e, 0xc2, 0xc0, 0xe1, 0x6f, 0x62, 0x6f, 0xd1, 0x0a, 0x01, 0x2e, 0xcd, 0x00, 0xd5, 0x6f, 0xc4, 0x6f, + 0x72, 0x6f, 0x97, 0x52, 0x9d, 0x5c, 0x98, 0x2e, 0x06, 0xcd, 0x23, 0x6f, 0x90, 0x6f, 0x99, 0x52, 0xc0, 0xb2, 0x04, 0xbd, 0x54, + 0x40, 0xaf, 0xb9, 0x45, 0x40, 0xe1, 0x7f, 0x02, 0x30, 0x06, 0x2f, 0xc0, 0xb2, 0x02, 0x30, 0x03, 0x2f, 0x9b, 0x5c, 0x12, 0x30, + 0x94, 0x43, 0x85, 0x43, 0x03, 0xbf, 0x6f, 0xbb, 0x80, 0xb3, 0x20, 0x2f, 0x06, 0x6f, 0x26, 0x01, 0x16, 0x6f, 0x6e, 0x03, 0x45, + 0x42, 0xc0, 0x90, 0x29, 0x2e, 0xce, 0x00, 0x9b, 0x52, 0x14, 0x2f, 0x9b, 0x5c, 0x00, 0x2e, 0x93, 0x41, 0x86, 0x41, 0xe3, 0x04, + 0xae, 0x07, 0x80, 0xab, 0x04, 0x2f, 0x80, 0x91, 0x0a, 0x2f, 0x86, 0x6f, 0x73, 0x0f, 0x07, 0x2f, 0x83, 0x6f, 0xc0, 0xb2, 0x04, + 0x2f, 0x54, 0x42, 0x45, 0x42, 0x12, 0x30, 0x04, 0x2c, 0x11, 0x30, 0x02, 0x2c, 0x11, 0x30, 0x11, 0x30, 0x02, 0xbc, 0x0f, 0xb8, + 0xd2, 0x7f, 0x00, 0xb2, 0x0a, 0x2f, 0x01, 0x2e, 0xfc, 0x00, 0x05, 0x2e, 0xc7, 0x01, 0x10, 0x1a, 0x02, 0x2f, 0x21, 0x2e, 0xc7, + 0x01, 0x03, 0x2d, 0x02, 0x2c, 0x01, 0x30, 0x01, 0x30, 0xb0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0xd1, 0x6f, 0xa0, 0x6f, 0x98, 0x2e, + 0x95, 0xcf, 0xe2, 0x6f, 0x9f, 0x52, 0x01, 0x2e, 0xce, 0x00, 0x82, 0x40, 0x50, 0x42, 0x0c, 0x2c, 0x42, 0x42, 0x11, 0x30, 0x23, + 0x2e, 0xd2, 0x00, 0x01, 0x30, 0xb0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0xa0, 0x6f, 0x01, 0x30, 0x98, 0x2e, 0x95, 0xcf, 0x00, 0x2e, + 0xfb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x83, 0x86, 0x01, 0x30, 0x00, 0x30, 0x94, 0x40, 0x24, 0x18, 0x06, 0x00, 0x53, 0x0e, 0x4f, + 0x02, 0xf9, 0x2f, 0xb8, 0x2e, 0xa9, 0x52, 0x00, 0x2e, 0x60, 0x40, 0x41, 0x40, 0x0d, 0xbc, 0x98, 0xbc, 0xc0, 0x2e, 0x01, 0x0a, + 0x0f, 0xb8, 0xab, 0x52, 0x53, 0x3c, 0x52, 0x40, 0x40, 0x40, 0x4b, 0x00, 0x82, 0x16, 0x26, 0xb9, 0x01, 0xb8, 0x41, 0x40, 0x10, + 0x08, 0x97, 0xb8, 0x01, 0x08, 0xc0, 0x2e, 0x11, 0x30, 0x01, 0x08, 0x43, 0x86, 0x25, 0x40, 0x04, 0x40, 0xd8, 0xbe, 0x2c, 0x0b, + 0x22, 0x11, 0x54, 0x42, 0x03, 0x80, 0x4b, 0x0e, 0xf6, 0x2f, 0xb8, 0x2e, 0x9f, 0x50, 0x10, 0x50, 0xad, 0x52, 0x05, 0x2e, 0xd3, + 0x00, 0xfb, 0x7f, 0x00, 0x2e, 0x13, 0x40, 0x93, 0x42, 0x41, 0x0e, 0xfb, 0x2f, 0x98, 0x2e, 0xa5, 0xb7, 0x98, 0x2e, 0x87, 0xcf, + 0x01, 0x2e, 0xd9, 0x00, 0x00, 0xb2, 0xfb, 0x6f, 0x0b, 0x2f, 0x01, 0x2e, 0x69, 0xf7, 0xb1, 0x3f, 0x01, 0x08, 0x01, 0x30, 0xf0, + 0x5f, 0x23, 0x2e, 0xd9, 0x00, 0x21, 0x2e, 0x69, 0xf7, 0x80, 0x2e, 0x7a, 0xb7, 0xf0, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0xc0, 0xf8, + 0x03, 0x2e, 0xfc, 0xf5, 0x15, 0x54, 0xaf, 0x56, 0x82, 0x08, 0x0b, 0x2e, 0x69, 0xf7, 0xcb, 0x0a, 0xb1, 0x58, 0x80, 0x90, 0xdd, + 0xbe, 0x4c, 0x08, 0x5f, 0xb9, 0x59, 0x22, 0x80, 0x90, 0x07, 0x2f, 0x03, 0x34, 0xc3, 0x08, 0xf2, 0x3a, 0x0a, 0x08, 0x02, 0x35, + 0xc0, 0x90, 0x4a, 0x0a, 0x48, 0x22, 0xc0, 0x2e, 0x23, 0x2e, 0xfc, 0xf5, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x56, 0xc7, 0x98, + 0x2e, 0x49, 0xc3, 0x10, 0x30, 0xfb, 0x6f, 0xf0, 0x5f, 0x21, 0x2e, 0xcc, 0x00, 0x21, 0x2e, 0xca, 0x00, 0xb8, 0x2e, 0x03, 0x2e, + 0xd3, 0x00, 0x16, 0xb8, 0x02, 0x34, 0x4a, 0x0c, 0x21, 0x2e, 0x2d, 0xf5, 0xc0, 0x2e, 0x23, 0x2e, 0xd3, 0x00, 0x03, 0xbc, 0x21, + 0x2e, 0xd5, 0x00, 0x03, 0x2e, 0xd5, 0x00, 0x40, 0xb2, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x01, 0x30, 0x05, 0x2f, 0x05, 0x2e, + 0xd8, 0x00, 0x80, 0x90, 0x01, 0x2f, 0x23, 0x2e, 0x6f, 0xf5, 0xc0, 0x2e, 0x21, 0x2e, 0xd9, 0x00, 0x11, 0x30, 0x81, 0x08, 0x01, + 0x2e, 0x6a, 0xf7, 0x71, 0x3f, 0x23, 0xbd, 0x01, 0x08, 0x02, 0x0a, 0xc0, 0x2e, 0x21, 0x2e, 0x6a, 0xf7, 0x30, 0x25, 0x00, 0x30, + 0x21, 0x2e, 0x5a, 0xf5, 0x10, 0x50, 0x21, 0x2e, 0x7b, 0x00, 0x21, 0x2e, 0x7c, 0x00, 0xfb, 0x7f, 0x98, 0x2e, 0xc3, 0xb7, 0x40, + 0x30, 0x21, 0x2e, 0xd4, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0x03, 0x25, 0x80, 0x2e, 0xaf, 0xb7, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x01, 0x2e, 0x5d, 0xf7, 0x08, 0xbc, 0x80, 0xac, 0x0e, + 0xbb, 0x02, 0x2f, 0x00, 0x30, 0x41, 0x04, 0x82, 0x06, 0xc0, 0xa4, 0x00, 0x30, 0x11, 0x2f, 0x40, 0xa9, 0x03, 0x2f, 0x40, 0x91, + 0x0d, 0x2f, 0x00, 0xa7, 0x0b, 0x2f, 0x80, 0xb3, 0xb3, 0x58, 0x02, 0x2f, 0x90, 0xa1, 0x26, 0x13, 0x20, 0x23, 0x80, 0x90, 0x10, + 0x30, 0x01, 0x2f, 0xcc, 0x0e, 0x00, 0x2f, 0x00, 0x30, 0xb8, 0x2e, 0xb5, 0x50, 0x18, 0x08, 0x08, 0xbc, 0x88, 0xb6, 0x0d, 0x17, + 0xc6, 0xbd, 0x56, 0xbc, 0xb7, 0x58, 0xda, 0xba, 0x04, 0x01, 0x1d, 0x0a, 0x10, 0x50, 0x05, 0x30, 0x32, 0x25, 0x45, 0x03, 0xfb, + 0x7f, 0xf6, 0x30, 0x21, 0x25, 0x98, 0x2e, 0x37, 0xca, 0x16, 0xb5, 0x9a, 0xbc, 0x06, 0xb8, 0x80, 0xa8, 0x41, 0x0a, 0x0e, 0x2f, + 0x80, 0x90, 0x02, 0x2f, 0x2d, 0x50, 0x48, 0x0f, 0x09, 0x2f, 0xbf, 0xa0, 0x04, 0x2f, 0xbf, 0x90, 0x06, 0x2f, 0xb7, 0x54, 0xca, + 0x0f, 0x03, 0x2f, 0x00, 0x2e, 0x02, 0x2c, 0xb7, 0x52, 0x2d, 0x52, 0xf2, 0x33, 0x98, 0x2e, 0xd9, 0xc0, 0xfb, 0x6f, 0xf1, 0x37, + 0xc0, 0x2e, 0x01, 0x08, 0xf0, 0x5f, 0xbf, 0x56, 0xb9, 0x54, 0xd0, 0x40, 0xc4, 0x40, 0x0b, 0x2e, 0xfd, 0xf3, 0xbf, 0x52, 0x90, + 0x42, 0x94, 0x42, 0x95, 0x42, 0x05, 0x30, 0xc1, 0x50, 0x0f, 0x88, 0x06, 0x40, 0x04, 0x41, 0x96, 0x42, 0xc5, 0x42, 0x48, 0xbe, + 0x73, 0x30, 0x0d, 0x2e, 0xd8, 0x00, 0x4f, 0xba, 0x84, 0x42, 0x03, 0x42, 0x81, 0xb3, 0x02, 0x2f, 0x2b, 0x2e, 0x6f, 0xf5, 0x06, + 0x2d, 0x05, 0x2e, 0x77, 0xf7, 0xbd, 0x56, 0x93, 0x08, 0x25, 0x2e, 0x77, 0xf7, 0xbb, 0x54, 0x25, 0x2e, 0xc2, 0xf5, 0x07, 0x2e, + 0xfd, 0xf3, 0x42, 0x30, 0xb4, 0x33, 0xda, 0x0a, 0x4c, 0x00, 0x27, 0x2e, 0xfd, 0xf3, 0x43, 0x40, 0xd4, 0x3f, 0xdc, 0x08, 0x43, + 0x42, 0x00, 0x2e, 0x00, 0x2e, 0x43, 0x40, 0x24, 0x30, 0xdc, 0x0a, 0x43, 0x42, 0x04, 0x80, 0x03, 0x2e, 0xfd, 0xf3, 0x4a, 0x0a, + 0x23, 0x2e, 0xfd, 0xf3, 0x61, 0x34, 0xc0, 0x2e, 0x01, 0x42, 0x00, 0x2e, 0x60, 0x50, 0x1a, 0x25, 0x7a, 0x86, 0xe0, 0x7f, 0xf3, + 0x7f, 0x03, 0x25, 0xc3, 0x52, 0x41, 0x84, 0xdb, 0x7f, 0x33, 0x30, 0x98, 0x2e, 0x16, 0xc2, 0x1a, 0x25, 0x7d, 0x82, 0xf0, 0x6f, + 0xe2, 0x6f, 0x32, 0x25, 0x16, 0x40, 0x94, 0x40, 0x26, 0x01, 0x85, 0x40, 0x8e, 0x17, 0xc4, 0x42, 0x6e, 0x03, 0x95, 0x42, 0x41, + 0x0e, 0xf4, 0x2f, 0xdb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, 0xb0, 0x51, 0xfb, 0x7f, 0x98, 0x2e, 0xe8, 0x0d, 0x5a, 0x25, 0x98, 0x2e, + 0x0f, 0x0e, 0xcb, 0x58, 0x32, 0x87, 0xc4, 0x7f, 0x65, 0x89, 0x6b, 0x8d, 0xc5, 0x5a, 0x65, 0x7f, 0xe1, 0x7f, 0x83, 0x7f, 0xa6, + 0x7f, 0x74, 0x7f, 0xd0, 0x7f, 0xb6, 0x7f, 0x94, 0x7f, 0x17, 0x30, 0xc7, 0x52, 0xc9, 0x54, 0x51, 0x7f, 0x00, 0x2e, 0x85, 0x6f, + 0x42, 0x7f, 0x00, 0x2e, 0x51, 0x41, 0x45, 0x81, 0x42, 0x41, 0x13, 0x40, 0x3b, 0x8a, 0x00, 0x40, 0x4b, 0x04, 0xd0, 0x06, 0xc0, + 0xac, 0x85, 0x7f, 0x02, 0x2f, 0x02, 0x30, 0x51, 0x04, 0xd3, 0x06, 0x41, 0x84, 0x05, 0x30, 0x5d, 0x02, 0xc9, 0x16, 0xdf, 0x08, + 0xd3, 0x00, 0x8d, 0x02, 0xaf, 0xbc, 0xb1, 0xb9, 0x59, 0x0a, 0x65, 0x6f, 0x11, 0x43, 0xa1, 0xb4, 0x52, 0x41, 0x53, 0x41, 0x01, + 0x43, 0x34, 0x7f, 0x65, 0x7f, 0x26, 0x31, 0xe5, 0x6f, 0xd4, 0x6f, 0x98, 0x2e, 0x37, 0xca, 0x32, 0x6f, 0x75, 0x6f, 0x83, 0x40, + 0x42, 0x41, 0x23, 0x7f, 0x12, 0x7f, 0xf6, 0x30, 0x40, 0x25, 0x51, 0x25, 0x98, 0x2e, 0x37, 0xca, 0x14, 0x6f, 0x20, 0x05, 0x70, + 0x6f, 0x25, 0x6f, 0x69, 0x07, 0xa2, 0x6f, 0x31, 0x6f, 0x0b, 0x30, 0x04, 0x42, 0x9b, 0x42, 0x8b, 0x42, 0x55, 0x42, 0x32, 0x7f, + 0x40, 0xa9, 0xc3, 0x6f, 0x71, 0x7f, 0x02, 0x30, 0xd0, 0x40, 0xc3, 0x7f, 0x03, 0x2f, 0x40, 0x91, 0x15, 0x2f, 0x00, 0xa7, 0x13, + 0x2f, 0x00, 0xa4, 0x11, 0x2f, 0x84, 0xbd, 0x98, 0x2e, 0x79, 0xca, 0x55, 0x6f, 0xb7, 0x54, 0x54, 0x41, 0x82, 0x00, 0xf3, 0x3f, + 0x45, 0x41, 0xcb, 0x02, 0xf6, 0x30, 0x98, 0x2e, 0x37, 0xca, 0x35, 0x6f, 0xa4, 0x6f, 0x41, 0x43, 0x03, 0x2c, 0x00, 0x43, 0xa4, + 0x6f, 0x35, 0x6f, 0x17, 0x30, 0x42, 0x6f, 0x51, 0x6f, 0x93, 0x40, 0x42, 0x82, 0x00, 0x41, 0xc3, 0x00, 0x03, 0x43, 0x51, 0x7f, + 0x00, 0x2e, 0x94, 0x40, 0x41, 0x41, 0x4c, 0x02, 0xc4, 0x6f, 0xd1, 0x56, 0x63, 0x0e, 0x74, 0x6f, 0x51, 0x43, 0xa5, 0x7f, 0x8a, + 0x2f, 0x09, 0x2e, 0xd8, 0x00, 0x01, 0xb3, 0x21, 0x2f, 0xcb, 0x58, 0x90, 0x6f, 0x13, 0x41, 0xb6, 0x6f, 0xe4, 0x7f, 0x00, 0x2e, + 0x91, 0x41, 0x14, 0x40, 0x92, 0x41, 0x15, 0x40, 0x17, 0x2e, 0x6f, 0xf5, 0xb6, 0x7f, 0xd0, 0x7f, 0xcb, 0x7f, 0x98, 0x2e, 0x00, + 0x0c, 0x07, 0x15, 0xc2, 0x6f, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0xc3, 0xa3, 0xc1, 0x8f, 0xe4, 0x6f, 0xd0, 0x6f, 0xe6, 0x2f, + 0x14, 0x30, 0x05, 0x2e, 0x6f, 0xf5, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0x18, 0x2d, 0xcd, 0x56, 0x04, 0x32, 0xb5, 0x6f, 0x1c, + 0x01, 0x51, 0x41, 0x52, 0x41, 0xc3, 0x40, 0xb5, 0x7f, 0xe4, 0x7f, 0x98, 0x2e, 0x1f, 0x0c, 0xe4, 0x6f, 0x21, 0x87, 0x00, 0x43, + 0x04, 0x32, 0xcf, 0x54, 0x5a, 0x0e, 0xef, 0x2f, 0x15, 0x54, 0x09, 0x2e, 0x77, 0xf7, 0x22, 0x0b, 0x29, 0x2e, 0x77, 0xf7, 0xfb, + 0x6f, 0x50, 0x5e, 0xb8, 0x2e, 0x10, 0x50, 0x01, 0x2e, 0xd4, 0x00, 0x00, 0xb2, 0xfb, 0x7f, 0x51, 0x2f, 0x01, 0xb2, 0x48, 0x2f, + 0x02, 0xb2, 0x42, 0x2f, 0x03, 0x90, 0x56, 0x2f, 0xd7, 0x52, 0x79, 0x80, 0x42, 0x40, 0x81, 0x84, 0x00, 0x40, 0x42, 0x42, 0x98, + 0x2e, 0x93, 0x0c, 0xd9, 0x54, 0xd7, 0x50, 0xa1, 0x40, 0x98, 0xbd, 0x82, 0x40, 0x3e, 0x82, 0xda, 0x0a, 0x44, 0x40, 0x8b, 0x16, + 0xe3, 0x00, 0x53, 0x42, 0x00, 0x2e, 0x43, 0x40, 0x9a, 0x02, 0x52, 0x42, 0x00, 0x2e, 0x41, 0x40, 0x15, 0x54, 0x4a, 0x0e, 0x3a, + 0x2f, 0x3a, 0x82, 0x00, 0x30, 0x41, 0x40, 0x21, 0x2e, 0x85, 0x0f, 0x40, 0xb2, 0x0a, 0x2f, 0x98, 0x2e, 0xb1, 0x0c, 0x98, 0x2e, + 0x45, 0x0e, 0x98, 0x2e, 0x5b, 0x0e, 0xfb, 0x6f, 0xf0, 0x5f, 0x00, 0x30, 0x80, 0x2e, 0xce, 0xb7, 0xdd, 0x52, 0xd3, 0x54, 0x42, + 0x42, 0x4f, 0x84, 0x73, 0x30, 0xdb, 0x52, 0x83, 0x42, 0x1b, 0x30, 0x6b, 0x42, 0x23, 0x30, 0x27, 0x2e, 0xd7, 0x00, 0x37, 0x2e, + 0xd4, 0x00, 0x21, 0x2e, 0xd6, 0x00, 0x7a, 0x84, 0x17, 0x2c, 0x42, 0x42, 0x30, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x12, 0x2d, 0x21, + 0x30, 0x00, 0x30, 0x23, 0x2e, 0xd4, 0x00, 0x21, 0x2e, 0x7b, 0xf7, 0x0b, 0x2d, 0x17, 0x30, 0x98, 0x2e, 0x51, 0x0c, 0xd5, 0x50, + 0x0c, 0x82, 0x72, 0x30, 0x2f, 0x2e, 0xd4, 0x00, 0x25, 0x2e, 0x7b, 0xf7, 0x40, 0x42, 0x00, 0x2e, 0xfb, 0x6f, 0xf0, 0x5f, 0xb8, + 0x2e, 0x70, 0x50, 0x0a, 0x25, 0x39, 0x86, 0xfb, 0x7f, 0xe1, 0x32, 0x62, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xb5, 0x56, 0xa5, 0x6f, + 0xab, 0x08, 0x91, 0x6f, 0x4b, 0x08, 0xdf, 0x56, 0xc4, 0x6f, 0x23, 0x09, 0x4d, 0xba, 0x93, 0xbc, 0x8c, 0x0b, 0xd1, 0x6f, 0x0b, + 0x09, 0xcb, 0x52, 0xe1, 0x5e, 0x56, 0x42, 0xaf, 0x09, 0x4d, 0xba, 0x23, 0xbd, 0x94, 0x0a, 0xe5, 0x6f, 0x68, 0xbb, 0xeb, 0x08, + 0xbd, 0xb9, 0x63, 0xbe, 0xfb, 0x6f, 0x52, 0x42, 0xe3, 0x0a, 0xc0, 0x2e, 0x43, 0x42, 0x90, 0x5f, 0xd1, 0x50, 0x03, 0x2e, 0x25, + 0xf3, 0x13, 0x40, 0x00, 0x40, 0x9b, 0xbc, 0x9b, 0xb4, 0x08, 0xbd, 0xb8, 0xb9, 0x98, 0xbc, 0xda, 0x0a, 0x08, 0xb6, 0x89, 0x16, + 0xc0, 0x2e, 0x19, 0x00, 0x62, 0x02, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x81, 0x0d, 0x01, 0x2e, 0xd4, 0x00, 0x31, 0x30, 0x08, + 0x04, 0xfb, 0x6f, 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, 0xd6, 0x00, 0x21, 0x2e, 0xd7, 0x00, 0xb8, 0x2e, 0x01, 0x2e, 0xd7, 0x00, + 0x03, 0x2e, 0xd6, 0x00, 0x48, 0x0e, 0x01, 0x2f, 0x80, 0x2e, 0x1f, 0x0e, 0xb8, 0x2e, 0xe3, 0x50, 0x21, 0x34, 0x01, 0x42, 0x82, + 0x30, 0xc1, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x01, 0x00, 0x22, 0x30, 0x01, 0x40, 0x4a, 0x0a, 0x01, 0x42, 0xb8, 0x2e, 0xe3, 0x54, + 0xf0, 0x3b, 0x83, 0x40, 0xd8, 0x08, 0xe5, 0x52, 0x83, 0x42, 0x00, 0x30, 0x83, 0x30, 0x50, 0x42, 0xc4, 0x32, 0x27, 0x2e, 0x64, + 0xf5, 0x94, 0x00, 0x50, 0x42, 0x40, 0x42, 0xd3, 0x3f, 0x84, 0x40, 0x7d, 0x82, 0xe3, 0x08, 0x40, 0x42, 0x83, 0x42, 0xb8, 0x2e, + 0xdd, 0x52, 0x00, 0x30, 0x40, 0x42, 0x7c, 0x86, 0xb9, 0x52, 0x09, 0x2e, 0x70, 0x0f, 0xbf, 0x54, 0xc4, 0x42, 0xd3, 0x86, 0x54, + 0x40, 0x55, 0x40, 0x94, 0x42, 0x85, 0x42, 0x21, 0x2e, 0xd7, 0x00, 0x42, 0x40, 0x25, 0x2e, 0xfd, 0xf3, 0xc0, 0x42, 0x7e, 0x82, + 0x05, 0x2e, 0x7d, 0x00, 0x80, 0xb2, 0x14, 0x2f, 0x05, 0x2e, 0x89, 0x00, 0x27, 0xbd, 0x2f, 0xb9, 0x80, 0x90, 0x02, 0x2f, 0x21, + 0x2e, 0x6f, 0xf5, 0x0c, 0x2d, 0x07, 0x2e, 0x71, 0x0f, 0x14, 0x30, 0x1c, 0x09, 0x05, 0x2e, 0x77, 0xf7, 0xbd, 0x56, 0x47, 0xbe, + 0x93, 0x08, 0x94, 0x0a, 0x25, 0x2e, 0x77, 0xf7, 0xe7, 0x54, 0x50, 0x42, 0x4a, 0x0e, 0xfc, 0x2f, 0xb8, 0x2e, 0x50, 0x50, 0x02, + 0x30, 0x43, 0x86, 0xe5, 0x50, 0xfb, 0x7f, 0xe3, 0x7f, 0xd2, 0x7f, 0xc0, 0x7f, 0xb1, 0x7f, 0x00, 0x2e, 0x41, 0x40, 0x00, 0x40, + 0x48, 0x04, 0x98, 0x2e, 0x74, 0xc0, 0x1e, 0xaa, 0xd3, 0x6f, 0x14, 0x30, 0xb1, 0x6f, 0xe3, 0x22, 0xc0, 0x6f, 0x52, 0x40, 0xe4, + 0x6f, 0x4c, 0x0e, 0x12, 0x42, 0xd3, 0x7f, 0xeb, 0x2f, 0x03, 0x2e, 0x86, 0x0f, 0x40, 0x90, 0x11, 0x30, 0x03, 0x2f, 0x23, 0x2e, + 0x86, 0x0f, 0x02, 0x2c, 0x00, 0x30, 0xd0, 0x6f, 0xfb, 0x6f, 0xb0, 0x5f, 0xb8, 0x2e, 0x40, 0x50, 0xf1, 0x7f, 0x0a, 0x25, 0x3c, + 0x86, 0xeb, 0x7f, 0x41, 0x33, 0x22, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xd3, 0x6f, 0xf4, 0x30, 0xdc, 0x09, 0x47, 0x58, 0xc2, 0x6f, + 0x94, 0x09, 0xeb, 0x58, 0x6a, 0xbb, 0xdc, 0x08, 0xb4, 0xb9, 0xb1, 0xbd, 0xe9, 0x5a, 0x95, 0x08, 0x21, 0xbd, 0xf6, 0xbf, 0x77, + 0x0b, 0x51, 0xbe, 0xf1, 0x6f, 0xeb, 0x6f, 0x52, 0x42, 0x54, 0x42, 0xc0, 0x2e, 0x43, 0x42, 0xc0, 0x5f, 0x50, 0x50, 0xf5, 0x50, + 0x31, 0x30, 0x11, 0x42, 0xfb, 0x7f, 0x7b, 0x30, 0x0b, 0x42, 0x11, 0x30, 0x02, 0x80, 0x23, 0x33, 0x01, 0x42, 0x03, 0x00, 0x07, + 0x2e, 0x80, 0x03, 0x05, 0x2e, 0xd3, 0x00, 0x23, 0x52, 0xe2, 0x7f, 0xd3, 0x7f, 0xc0, 0x7f, 0x98, 0x2e, 0xb6, 0x0e, 0xd1, 0x6f, + 0x08, 0x0a, 0x1a, 0x25, 0x7b, 0x86, 0xd0, 0x7f, 0x01, 0x33, 0x12, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xd1, 0x6f, 0x08, 0x0a, 0x00, + 0xb2, 0x0d, 0x2f, 0xe3, 0x6f, 0x01, 0x2e, 0x80, 0x03, 0x51, 0x30, 0xc7, 0x86, 0x23, 0x2e, 0x21, 0xf2, 0x08, 0xbc, 0xc0, 0x42, + 0x98, 0x2e, 0xa5, 0xb7, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0xb0, 0x6f, 0x0b, 0xb8, 0x03, 0x2e, 0x1b, 0x00, 0x08, 0x1a, 0xb0, + 0x7f, 0x70, 0x30, 0x04, 0x2f, 0x21, 0x2e, 0x21, 0xf2, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x98, 0x2e, 0x6d, 0xc0, 0x98, 0x2e, + 0x5d, 0xc0, 0xed, 0x50, 0x98, 0x2e, 0x44, 0xcb, 0xef, 0x50, 0x98, 0x2e, 0x46, 0xc3, 0xf1, 0x50, 0x98, 0x2e, 0x53, 0xc7, 0x35, + 0x50, 0x98, 0x2e, 0x64, 0xcf, 0x10, 0x30, 0x98, 0x2e, 0xdc, 0x03, 0x20, 0x26, 0xc0, 0x6f, 0x02, 0x31, 0x12, 0x42, 0xab, 0x33, + 0x0b, 0x42, 0x37, 0x80, 0x01, 0x30, 0x01, 0x42, 0xf3, 0x37, 0xf7, 0x52, 0xfb, 0x50, 0x44, 0x40, 0xa2, 0x0a, 0x42, 0x42, 0x8b, + 0x31, 0x09, 0x2e, 0x5e, 0xf7, 0xf9, 0x54, 0xe3, 0x08, 0x83, 0x42, 0x1b, 0x42, 0x23, 0x33, 0x4b, 0x00, 0xbc, 0x84, 0x0b, 0x40, + 0x33, 0x30, 0x83, 0x42, 0x0b, 0x42, 0xe0, 0x7f, 0xd1, 0x7f, 0x98, 0x2e, 0x58, 0xb7, 0xd1, 0x6f, 0x80, 0x30, 0x40, 0x42, 0x03, + 0x30, 0xe0, 0x6f, 0xf3, 0x54, 0x04, 0x30, 0x00, 0x2e, 0x00, 0x2e, 0x01, 0x89, 0x62, 0x0e, 0xfa, 0x2f, 0x43, 0x42, 0x11, 0x30, + 0xfb, 0x6f, 0xc0, 0x2e, 0x01, 0x42, 0xb0, 0x5f, 0xc1, 0x4a, 0x00, 0x00, 0x6d, 0x57, 0x00, 0x00, 0x77, 0x8e, 0x00, 0x00, 0xe0, + 0xff, 0xff, 0xff, 0xd3, 0xff, 0xff, 0xff, 0xe5, 0xff, 0xff, 0xff, 0xee, 0xe1, 0xff, 0xff, 0x7c, 0x13, 0x00, 0x00, 0x46, 0xe6, + 0xff, 0xff, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1}; + +#define BMI270_CONFIG_FILE_SIZE sizeof(bmi270_config_file) + +BMI270Sensor::BMI270Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) +{ + if (foundDevice.address.port == ScanI2C::I2CPort::WIRE1) { +#ifdef I2C_SDA1 + wire = &Wire1; +#else + wire = &Wire; +#endif + } else { + wire = &Wire; + } +} + +bool BMI270Sensor::writeRegister(uint8_t reg, uint8_t value) +{ + wire->beginTransmission(deviceAddress()); + wire->write(reg); + wire->write(value); + return wire->endTransmission() == 0; +} + +bool BMI270Sensor::writeRegisters(uint8_t reg, const uint8_t *data, size_t len) +{ + wire->beginTransmission(deviceAddress()); + wire->write(reg); + for (size_t i = 0; i < len; i++) { + wire->write(data[i]); + } + return wire->endTransmission() == 0; +} + +uint8_t BMI270Sensor::readRegister(uint8_t reg) +{ + wire->beginTransmission(deviceAddress()); + wire->write(reg); + wire->endTransmission(false); + wire->requestFrom(deviceAddress(), (uint8_t)1); + if (wire->available()) { + return wire->read(); + } + return 0; +} + +bool BMI270Sensor::readRegisters(uint8_t reg, uint8_t *data, size_t len) +{ + wire->beginTransmission(deviceAddress()); + wire->write(reg); + wire->endTransmission(false); + size_t bytesRead = wire->requestFrom(deviceAddress(), (uint8_t)len); + if (bytesRead != len) { + // Read any available bytes to keep the bus state clean, but report failure. + for (size_t i = 0; i < bytesRead && wire->available(); i++) { + data[i] = wire->read(); + } + return false; + } + + for (size_t i = 0; i < len && wire->available(); i++) { + data[i] = wire->read(); + } + return true; +} + +bool BMI270Sensor::uploadConfigFile() +{ + if (!writeRegister(BMI270_REG_INIT_CTRL, 0x00)) + return false; + + const size_t chunkSize = 32; + size_t bytesWritten = 0; + + while (bytesWritten < BMI270_CONFIG_FILE_SIZE) { + size_t remaining = BMI270_CONFIG_FILE_SIZE - bytesWritten; + size_t toWrite = (remaining < chunkSize) ? remaining : chunkSize; + + // Set address in word units before each chunk + uint16_t wordAddr = bytesWritten / 2; + if (!writeRegister(BMI270_REG_INIT_ADDR_0, (uint8_t)(wordAddr & 0x0F))) + return false; + if (!writeRegister(BMI270_REG_INIT_ADDR_1, (uint8_t)(wordAddr >> 4))) + return false; + + wire->beginTransmission(deviceAddress()); + wire->write(BMI270_REG_INIT_DATA); + for (size_t i = 0; i < toWrite; i++) { + wire->write(pgm_read_byte(&bmi270_config_file[bytesWritten + i])); + } + if (wire->endTransmission() != 0) + return false; + + bytesWritten += toWrite; + } + + if (!writeRegister(BMI270_REG_INIT_CTRL, 0x01)) + return false; + + delay(50); + + for (int i = 0; i < 10; i++) { + uint8_t status = readRegister(BMI270_REG_INTERNAL_STATUS); + if ((status & 0x0F) == BMI270_INIT_OK) + return true; + delay(20); + } + + LOG_WARN("BMI270 status=0x%02X", readRegister(BMI270_REG_INTERNAL_STATUS)); + return false; +} + +bool BMI270Sensor::init() +{ + delay(10); + writeRegister(BMI270_REG_CMD, BMI270_CMD_SOFTRESET); + delay(50); + + if (!writeRegister(BMI270_REG_PWR_CONF, BMI270_PWR_CONF_ADV_POWER_SAVE_DISABLED)) + return false; + delay(2); + + if (!uploadConfigFile()) { + LOG_WARN("BMI270 config failed"); + return false; + } + + uint8_t accConf = BMI270_ACC_ODR_50HZ | BMI270_ACC_BWP_NORMAL | BMI270_ACC_FILTER_PERF; + if (!writeRegister(BMI270_REG_ACC_CONF, accConf) || !writeRegister(BMI270_REG_ACC_RANGE, BMI270_ACC_RANGE_2G) || + !writeRegister(BMI270_REG_PWR_CTRL, BMI270_PWR_CTRL_ACC_EN)) + return false; + + delay(50); + initialized = true; + return true; +} + +int32_t BMI270Sensor::runOnce() +{ + if (!initialized) { + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + // Read accelerometer data (6 bytes) + uint8_t data[6]; + if (!readRegisters(BMI270_REG_ACC_X_LSB, data, 6)) { + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + // Convert to 16-bit signed values + int16_t x = (int16_t)((data[1] << 8) | data[0]); + int16_t y = (int16_t)((data[3] << 8) | data[2]); + int16_t z = (int16_t)((data[5] << 8) | data[4]); + + if (!hasBaseline) { + prevX = x; + prevY = y; + prevZ = z; + hasBaseline = true; + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + // Calculate change in acceleration + int16_t deltaX = abs(x - prevX); + int16_t deltaY = abs(y - prevY); + int16_t deltaZ = abs(z - prevZ); + + // Update baseline with low-pass filter + prevX = (prevX * 9 + x) / 10; + prevY = (prevY * 9 + y) / 10; + prevZ = (prevZ * 9 + z) / 10; + + // Check for significant motion (~0.2g at 2g range) + const int16_t threshold = 3200; + if (deltaX > threshold || deltaY > threshold || deltaZ > threshold) { + wakeScreen(); + return 500; + } + + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif diff --git a/src/motion/BMI270Sensor.h b/src/motion/BMI270Sensor.h new file mode 100644 index 000000000..7d6cdeaa9 --- /dev/null +++ b/src/motion/BMI270Sensor.h @@ -0,0 +1,36 @@ +#pragma once +#ifndef _BMI270_SENSOR_H_ +#define _BMI270_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && defined(HAS_BMI270) + +class BMI270Sensor : public MotionSensor +{ + private: + bool initialized = false; + TwoWire *wire = nullptr; + + // Previous readings for motion detection + int16_t prevX = 0, prevY = 0, prevZ = 0; + bool hasBaseline = false; + + // BMI270 register access + bool writeRegister(uint8_t reg, uint8_t value); + bool writeRegisters(uint8_t reg, const uint8_t *data, size_t len); + uint8_t readRegister(uint8_t reg); + bool readRegisters(uint8_t reg, uint8_t *data, size_t len); + + // Config file upload (BMI270 requires 8KB config blob) + bool uploadConfigFile(); + + public: + explicit BMI270Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp old mode 100755 new mode 100644 diff --git a/src/motion/BMX160Sensor.h b/src/motion/BMX160Sensor.h old mode 100755 new mode 100644 diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp old mode 100755 new mode 100644 diff --git a/src/motion/ICM20948Sensor.h b/src/motion/ICM20948Sensor.h old mode 100755 new mode 100644 diff --git a/src/motion/LIS3DHSensor.cpp b/src/motion/LIS3DHSensor.cpp old mode 100755 new mode 100644 diff --git a/src/motion/LIS3DHSensor.h b/src/motion/LIS3DHSensor.h old mode 100755 new mode 100644 diff --git a/src/motion/LSM6DS3Sensor.cpp b/src/motion/LSM6DS3Sensor.cpp old mode 100755 new mode 100644 diff --git a/src/motion/LSM6DS3Sensor.h b/src/motion/LSM6DS3Sensor.h old mode 100755 new mode 100644 diff --git a/src/motion/MPU6050Sensor.cpp b/src/motion/MPU6050Sensor.cpp old mode 100755 new mode 100644 diff --git a/src/motion/MPU6050Sensor.h b/src/motion/MPU6050Sensor.h old mode 100755 new mode 100644 diff --git a/src/motion/MotionSensor.cpp b/src/motion/MotionSensor.cpp old mode 100755 new mode 100644 diff --git a/src/motion/MotionSensor.h b/src/motion/MotionSensor.h old mode 100755 new mode 100644 diff --git a/src/motion/STK8XXXSensor.cpp b/src/motion/STK8XXXSensor.cpp old mode 100755 new mode 100644 diff --git a/src/motion/STK8XXXSensor.h b/src/motion/STK8XXXSensor.h old mode 100755 new mode 100644 diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 2c10c4b2b..32b637e81 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -53,6 +53,9 @@ static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for cha static bool isMqttServerAddressPrivate = false; static bool isConnected = false; +static uint32_t lastPositionUnavailableWarning = 0; +static const uint32_t POSITION_UNAVAILABLE_WARNING_INTERVAL_MS = 15000; // 15 seconds + inline void onReceiveProto(char *topic, byte *payload, size_t length) { const DecodedServiceEnvelope e(payload, length); @@ -453,7 +456,9 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) enabled = true; runASAP = true; reconnectCount = 0; +#if !IS_RUNNING_TESTS publishNodeInfo(); +#endif } // preflightSleepObserver.observe(&preflightSleep); } else { @@ -845,12 +850,14 @@ void MQTT::perhapsReportToMap() map_position_precision = default_map_position_precision; } - if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs)) + if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs) && last_report_to_map != 0) return; if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) { - last_report_to_map = millis(); - LOG_WARN("MQTT Map report enabled, but no position available"); + if (Throttle::isWithinTimespanMs(lastPositionUnavailableWarning, POSITION_UNAVAILABLE_WARNING_INTERVAL_MS) == false) { + LOG_WARN("MQTT Map report enabled, but no position available"); + lastPositionUnavailableWarning = millis(); + } return; } diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 89d74dbd7..3bb4ce817 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -52,6 +52,20 @@ NimBLEServer *bleServer; static bool passkeyShowing; static std::atomic nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection" +static void clearPairingDisplay() +{ + if (!passkeyShowing) { + return; + } + + passkeyShowing = false; +#if HAS_SCREEN + if (screen) { + screen->endAlert(); + } +#endif +} + class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread { /* @@ -630,13 +644,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); bluetoothStatus->updateStatus(&newStatus); - - // Todo: migrate this display code back into Screen class, and observe bluetoothStatus - if (passkeyShowing) { - passkeyShowing = false; - if (screen) - screen->endAlert(); - } + clearPairingDisplay(); // Store the connection handle for future use #ifdef NIMBLE_TWO @@ -693,6 +701,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); + clearPairingDisplay(); if (bluetoothPhoneAPI) { bluetoothPhoneAPI->close(); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 7aee45f81..30398a675 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -202,6 +202,8 @@ #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L #elif defined(HELTEC_WIRELESS_TRACKER_V2) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 +#elif defined(M5STACK_CARDPUTER_ADV) +#define HW_VENDOR meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV #else #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #endif diff --git a/src/platform/nrf52/AsyncUDP.cpp b/src/platform/nrf52/AsyncUDP.cpp index d119ffddb..8c937d71f 100644 --- a/src/platform/nrf52/AsyncUDP.cpp +++ b/src/platform/nrf52/AsyncUDP.cpp @@ -33,7 +33,10 @@ bool AsyncUDP::writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t p if (!udp.beginPacket(ip, port)) return false; udp.write(data, len); - return udp.endPacket(); + isSending = true; + bool ok = udp.endPacket(); + isSending = false; + return ok; } void AsyncUDP::close() @@ -70,7 +73,7 @@ const uint8_t *AsyncUDPPacket::data() int32_t AsyncUDP::runOnce() { - if (_onPacket && udp.parsePacket() > 0) { + if (_onPacket && !isSending && udp.parsePacket() > 0) { AsyncUDPPacket packet(udp); _onPacket(packet); } diff --git a/src/platform/nrf52/AsyncUDP.h b/src/platform/nrf52/AsyncUDP.h index ecbcb12fb..eb6083bc4 100644 --- a/src/platform/nrf52/AsyncUDP.h +++ b/src/platform/nrf52/AsyncUDP.h @@ -31,6 +31,7 @@ class AsyncUDP : public Print, private concurrency::OSThread private: EthernetUDP udp; uint16_t localPort; + volatile bool isSending = false; std::function _onPacket; virtual int32_t runOnce() override; }; diff --git a/src/platform/nrf52/BLEDfuScure.cpp b/src/platform/nrf52/BLEDfuSecure.cpp similarity index 99% rename from src/platform/nrf52/BLEDfuScure.cpp rename to src/platform/nrf52/BLEDfuSecure.cpp index 82cb8905a..040df8bdf 100644 --- a/src/platform/nrf52/BLEDfuScure.cpp +++ b/src/platform/nrf52/BLEDfuSecure.cpp @@ -1,6 +1,6 @@ /**************************************************************************/ /*! - @file BLEDfu.cpp + @file BLEDfuSecure.cpp @author hathach (tinyusb.org) @section LICENSE diff --git a/src/platform/nrf52/BLEDfuSecure.h b/src/platform/nrf52/BLEDfuSecure.h index bd5d910e8..dc52d3940 100644 --- a/src/platform/nrf52/BLEDfuSecure.h +++ b/src/platform/nrf52/BLEDfuSecure.h @@ -1,6 +1,6 @@ /**************************************************************************/ /*! - @file BLEDfu.h + @file BLEDfuSecure.h @author hathach (tinyusb.org) @section LICENSE diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 6a552f236..307e35b0c 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -32,6 +32,7 @@ static uint8_t toRadioBytes[meshtastic_ToRadio_size]; static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; static uint16_t connectionHandle; +static bool passkeyShowing; class BluetoothPhoneAPI : public PhoneAPI { @@ -86,6 +87,16 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason) // Notify UI (or any other interested firmware components) meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); + +#if HAS_SCREEN + // If a pairing prompt is active, make sure we dismiss it on disconnect/cancel/failure paths. + if (passkeyShowing) { + passkeyShowing = false; + if (screen) { + screen->endAlert(); + } + } +#endif } void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { @@ -400,6 +411,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke }); } #endif + passkeyShowing = true; + if (match_request) { uint32_t start_time = millis(); while (millis() < start_time + 30000) { @@ -451,6 +464,7 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu } // Todo: migrate this display code back into Screen class, and observe bluetoothStatus + passkeyShowing = false; if (screen) { screen->endAlert(); } @@ -464,4 +478,4 @@ void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length) logRadio.indicate(logMessage, (uint16_t)length); else logRadio.notify(logMessage, (uint16_t)length); -} \ No newline at end of file +} diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 3f0b2147c..4bbdbee7a 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -277,6 +278,24 @@ void portduinoSetup() std::cout << "autoconf: Found Pi HAT+ " << hat_vendor << " " << autoconf_product << " at /proc/device-tree/hat" << std::endl; + // check for custom data fields + int i = 0; + while (access(("/proc/device-tree/hat/custom_" + std::to_string(i)).c_str(), R_OK) == 0) { + std::ifstream customFieldFile(("/proc/device-tree/hat/custom_" + std::to_string(i)).c_str()); + if (customFieldFile.is_open()) { + std::string customFieldName; + std::string customFieldValue; + getline(customFieldFile, customFieldName, ' '); + getline(customFieldFile, customFieldValue, ' '); + customFieldFile.close(); + + printf("autoconf: Found hat+ custom field %s: %s\n", customFieldName.c_str(), customFieldValue.c_str()); + portduino_config.hat_plus_custom_fields[customFieldName] = customFieldValue; + } + + i++; + } + // potential TODO: Validate that this is a real UUID std::ifstream hatUUID("/proc/device-tree/hat/uuid"); char uuid[38] = {0}; @@ -496,14 +515,19 @@ void portduinoSetup() randomSeed(time(NULL)); std::string defaultGpioChipName = gpioChipName + std::to_string(portduino_config.lora_default_gpiochip); + + std::set used_pins; + for (const auto *i : portduino_config.all_pins) { - if (i->enabled && i->pin > max_GPIO) + if (i->enabled && i->pin > max_GPIO) { max_GPIO = i->pin; + } } for (auto i : portduino_config.extra_pins) { - if (i.enabled && i.pin > max_GPIO) + if (i.enabled && i.pin > max_GPIO) { max_GPIO = i.pin; + } } gpioInit(max_GPIO + 1); // Done here so we can inform Portduino how many GPIOs we need. @@ -517,12 +541,18 @@ void portduinoSetup() 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); + if (used_pins.find(i->pin) != used_pins.end()) { + printf("Pin %d is in use for multiple purposes\n", i->pin); + } else { + 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); + } + used_pins.insert(i->pin); } } } + printf("Initializing extra pins\n"); for (auto i : portduino_config.extra_pins) { // 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. @@ -530,13 +560,58 @@ void portduinoSetup() 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); + if (used_pins.find(i.pin) != used_pins.end()) { + printf("Pin %d is in use for multiple purposes\n", i.pin); + } else { + 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); + } + used_pins.insert(i.pin); } } } + // In one test, this dance seemed necessary to trigger the pin to detect properly. + if (portduino_config.lora_pa_detect_pin.enabled) { + pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT_PULLDOWN); + sleep(1); + if (digitalRead(portduino_config.lora_pa_detect_pin.pin) == LOW) { + std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLDOWN is LOW" << std::endl; + } + pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT_PULLUP); + sleep(1); + if (digitalRead(portduino_config.lora_pa_detect_pin.pin) == HIGH) { + std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLUP is HIGH, dropping PA curve" << std::endl; + portduino_config.num_pa_points = 1; + portduino_config.tx_gain_lora[0] = 0; + } else { + std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLUP is LOW, using PA curve" << std::endl; + } + + // disable bias once finished + pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT); + } else if (portduino_config.hat_plus_custom_fields.find("io_slot1") != portduino_config.hat_plus_custom_fields.end()) { + printf("Hat+ io_slot1 is %s\n", portduino_config.hat_plus_custom_fields["io_slot1"].c_str()); + if (portduino_config.hat_plus_custom_fields["io_slot1"] != "RAK13302") { + std::cout << "Hat+ io_slot1 is not RAK13302, skipping PA curve" << std::endl; + portduino_config.num_pa_points = 1; + portduino_config.tx_gain_lora[0] = 0; + } + } + + for (auto i : portduino_config.extra_pins) { + // 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 && i.default_high) { + pinMode(i.pin, OUTPUT); + digitalWrite(i.pin, HIGH); + } + } + // Only initialize the radio pins when dealing with real, kernel controlled SPI hardware if (portduino_config.lora_spi_dev != "" && portduino_config.lora_spi_dev != "ch341") { SPI.begin(portduino_config.lora_spi_dev.c_str()); @@ -555,7 +630,9 @@ void portduinoSetup() } } else if (portduino_config.JSONFilename != "") { try { - JSONFile.open(portduino_config.JSONFilename, std::ios::out | std::ios::app); + if (portduino_config.JSONFileRotate == 0) { + JSONFile.open(portduino_config.JSONFilename, std::ios::out | std::ios::app); + } } catch (std::ofstream::failure &e) { std::cout << "*** JSONFile Exception " << e.what() << std::endl; exit(EXIT_FAILURE); @@ -612,6 +689,7 @@ bool loadConfig(const char *configPath) } portduino_config.traceFilename = yamlConfig["Logging"]["TraceFile"].as(""); portduino_config.JSONFilename = yamlConfig["Logging"]["JSONFile"].as(""); + portduino_config.JSONFileRotate = yamlConfig["Logging"]["JSONFileRotate"].as(0); portduino_config.JSONFilter = (_meshtastic_PortNum)yamlConfig["Logging"]["JSONFilter"].as(0); if (yamlConfig["Logging"]["JSONFilter"].as("") == "textmessage") portduino_config.JSONFilter = meshtastic_PortNum_TEXT_MESSAGE_APP; @@ -691,6 +769,17 @@ bool loadConfig(const char *configPath) } } + if (yamlConfig["Lora"]["Enable_Pins"]) { + for (auto extra_pin : yamlConfig["Lora"]["Enable_Pins"]) { + portduino_config.extra_pins.push_back(pinMapping()); + portduino_config.extra_pins.back().config_section = "Lora"; + portduino_config.extra_pins.back().config_name = "Enable_Pins"; + portduino_config.extra_pins.back().enabled = true; + portduino_config.extra_pins.back().default_high = true; + readGPIOFromYaml(extra_pin, portduino_config.extra_pins.back()); + } + } + portduino_config.spiSpeed = yamlConfig["Lora"]["spiSpeed"].as(2000000); portduino_config.lora_usb_serial_num = yamlConfig["Lora"]["USB_Serialnum"].as(""); portduino_config.lora_usb_pid = yamlConfig["Lora"]["USB_PID"].as(0x5512); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index aa8847fd7..b38cfca25 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -52,6 +52,7 @@ struct pinMapping { int gpiochip; int line; bool enabled = false; + bool default_high = false; }; extern std::ofstream traceFile; @@ -107,6 +108,7 @@ extern struct portduino_config_struct { pinMapping lora_txen_pin = {"Lora", "TXen"}; pinMapping lora_rxen_pin = {"Lora", "RXen"}; pinMapping lora_sx126x_ant_sw_pin = {"Lora", "SX126X_ANT_SW"}; + pinMapping lora_pa_detect_pin = {"Lora", "GPIO_DETECT_PA"}; std::vector extra_pins = {}; // GPS @@ -163,6 +165,7 @@ extern struct portduino_config_struct { bool ascii_logs_explicit = false; std::string JSONFilename; + int JSONFileRotate = 0; meshtastic_PortNum JSONFilter = (_meshtastic_PortNum)0; // Webserver @@ -194,13 +197,16 @@ extern struct portduino_config_struct { int maxtophone = 100; int MaxNodes = 200; - pinMapping *all_pins[20] = {&lora_cs_pin, + std::unordered_map hat_plus_custom_fields; + + pinMapping *all_pins[21] = {&lora_cs_pin, &lora_irq_pin, &lora_busy_pin, &lora_reset_pin, &lora_txen_pin, &lora_rxen_pin, &lora_sx126x_ant_sw_pin, + &lora_pa_detect_pin, &displayDC, &displayCS, &displayBacklight, @@ -255,18 +261,23 @@ extern struct portduino_config_struct { out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << tx_gain_lora[0]; } - out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; + if (dio2_as_rf_switch) + out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; if (dio3_tcxo_voltage != 0) out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000; if (lora_usb_pid != 0x5512) out << YAML::Key << "USB_PID" << YAML::Value << YAML::Hex << lora_usb_pid; if (lora_usb_vid != 0x1A86) out << YAML::Key << "USB_VID" << YAML::Value << YAML::Hex << lora_usb_vid; - if (lora_spi_dev != "") + if (lora_spi_dev != "" && !(lora_spi_dev == "/dev/spidev0.0" && lora_module == use_autoconf)) { + if (lora_spi_dev.find("/dev/") != std::string::npos) + lora_spi_dev = lora_spi_dev.substr(5); out << YAML::Key << "spidev" << YAML::Value << lora_spi_dev; + } if (lora_usb_serial_num != "") out << YAML::Key << "USB_Serialnum" << YAML::Value << lora_usb_serial_num; - out << YAML::Key << "spiSpeed" << YAML::Value << spiSpeed; + if (spiSpeed != 2000000) + out << YAML::Key << "spiSpeed" << YAML::Value << spiSpeed; if (rfswitch_dio_pins[0] != RADIOLIB_NC) { out << YAML::Key << "rfswitch_table" << YAML::Value << YAML::BeginMap; @@ -462,6 +473,9 @@ extern struct portduino_config_struct { out << YAML::Key << "TraceFile" << YAML::Value << traceFilename; if (JSONFilename != "") { out << YAML::Key << "JSONFile" << YAML::Value << JSONFilename; + if (JSONFileRotate != 0) + out << YAML::Key << "JSONFileRotate" << YAML::Value << JSONFileRotate; + if (JSONFilter == meshtastic_PortNum_TEXT_MESSAGE_APP) out << YAML::Key << "JSONFilter" << YAML::Value << "textmessage"; else if (JSONFilter == meshtastic_PortNum_TELEMETRY_APP) diff --git a/src/sleep.cpp b/src/sleep.cpp index 7c768d573..4fec16571 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -9,6 +9,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerMon.h" +#include "TransmitHistory.h" #include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" @@ -238,6 +239,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN nodeDB->saveToDisk(); } + // Persist broadcast transmit times so throttle survives reboot + if (transmitHistory) + transmitHistory->saveToDisk(); + #ifdef PIN_POWER_EN digitalWrite(PIN_POWER_EN, LOW); pinMode(PIN_POWER_EN, INPUT); // power off peripherals @@ -534,7 +539,8 @@ void enableModemSleep() bool shouldLoraWake(uint32_t msecToWake) { - return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER); + return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE); } void enableLoraInterrupt() @@ -555,10 +561,8 @@ void enableLoraInterrupt() gpio_pullup_en((gpio_num_t)LORA_CS); #endif -#if defined(USE_GC1109_PA) - gpio_pullup_en((gpio_num_t)LORA_PA_POWER); - gpio_pullup_en((gpio_num_t)LORA_PA_EN); - gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN); +#if HAS_LORA_FEM + loraFEMInterface.setRxModeEnableWhenMCUSleep(); #endif LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1); diff --git a/test/test_default/test_main.cpp b/test/test_default/test_main.cpp new file mode 100644 index 000000000..d832fc809 --- /dev/null +++ b/test/test_default/test_main.cpp @@ -0,0 +1,109 @@ +// Unit tests for Default::getConfiguredOrDefaultMsScaled +#include "Default.h" +#include "MeshRadio.h" +#include "TestUtil.h" +#include "meshUtils.h" +#include + +// Helper to compute expected ms using same logic as Default::congestionScalingCoefficient +static uint32_t computeExpectedMs(uint32_t defaultSeconds, uint32_t numOnlineNodes) +{ + uint32_t baseMs = Default::getConfiguredOrDefaultMs(0, defaultSeconds); + + // Routers don't scale + if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) { + return baseMs; + } + + // Sensors and trackers don't scale + if ((config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) || + (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER)) { + return baseMs; + } + + if (numOnlineNodes <= 40) { + return baseMs; + } + + float bwKHz = + config.lora.use_preset ? modemPresetToBwKHz(config.lora.modem_preset, false) : bwCodeToKHz(config.lora.bandwidth); + + uint8_t sf = config.lora.spread_factor; + if (sf < 7) + sf = 7; + else if (sf > 12) + sf = 12; + + float throttlingFactor = static_cast(pow_of_2(sf)) / (bwKHz * 100.0f); +#if USERPREFS_EVENT_MODE + throttlingFactor = static_cast(pow_of_2(sf)) / (bwKHz * 25.0f); +#endif + + int nodesOverForty = (numOnlineNodes - 40); + float coeff = 1.0f + (nodesOverForty * throttlingFactor); + return static_cast(baseMs * coeff + 0.5f); +} + +void test_router_no_scaling() +{ + config.device.role = meshtastic_Config_DeviceConfig_Role_ROUTER; + // set some sane lora config so bootstrap paths are deterministic + config.lora.use_preset = false; + config.lora.spread_factor = 9; + config.lora.bandwidth = 250; + + uint32_t res = Default::getConfiguredOrDefaultMsScaled(0, 60, 100); + uint32_t expected = computeExpectedMs(60, 100); + TEST_ASSERT_EQUAL_UINT32(expected, res); +} + +void test_client_below_threshold() +{ + config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; + config.lora.use_preset = false; + config.lora.spread_factor = 9; + config.lora.bandwidth = 250; + + uint32_t res = Default::getConfiguredOrDefaultMsScaled(0, 60, 40); + uint32_t expected = computeExpectedMs(60, 40); + TEST_ASSERT_EQUAL_UINT32(expected, res); +} + +void test_client_default_preset_scaling() +{ + config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; + config.lora.use_preset = false; + config.lora.spread_factor = 9; // SF9 + config.lora.bandwidth = 250; // 250 kHz + + uint32_t res = Default::getConfiguredOrDefaultMsScaled(0, 60, 50); + uint32_t expected = computeExpectedMs(60, 50); // nodesOverForty = 10 + TEST_ASSERT_EQUAL_UINT32(expected, res); +} + +void test_client_medium_fast_preset_scaling() +{ + config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; + config.lora.use_preset = true; + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + // nodesOverForty = 30 -> test with nodes=70 + uint32_t res = Default::getConfiguredOrDefaultMsScaled(0, 60, 70); + uint32_t expected = computeExpectedMs(60, 70); + // Allow ±1 ms tolerance for floating-point rounding + TEST_ASSERT_INT_WITHIN(1, expected, res); +} + +void setup() +{ + // Small delay to match other test mains + delay(10); + initializeTestEnvironment(); + UNITY_BEGIN(); + RUN_TEST(test_router_no_scaling); + RUN_TEST(test_client_below_threshold); + RUN_TEST(test_client_default_preset_scaling); + RUN_TEST(test_client_medium_fast_preset_scaling); + exit(UNITY_END()); +} + +void loop() {} diff --git a/test/test_http_content_handler/test_main.cpp b/test/test_http_content_handler/test_main.cpp new file mode 100644 index 000000000..af0a41cef --- /dev/null +++ b/test/test_http_content_handler/test_main.cpp @@ -0,0 +1,19 @@ +#include "TestUtil.h" +#include + +static void test_placeholder() +{ + TEST_ASSERT_TRUE(true); +} + +extern "C" { +void setup() +{ + initializeTestEnvironment(); + UNITY_BEGIN(); + RUN_TEST(test_placeholder); + exit(UNITY_END()); +} + +void loop() {} +} diff --git a/test/test_mesh_module/test_main.cpp b/test/test_mesh_module/test_main.cpp new file mode 100644 index 000000000..ec7b1808e --- /dev/null +++ b/test/test_mesh_module/test_main.cpp @@ -0,0 +1,116 @@ +#include "MeshModule.h" +#include "MeshTypes.h" +#include "TestUtil.h" +#include + +// Minimal concrete subclass for testing the base class helper +class TestModule : public MeshModule +{ + public: + TestModule() : MeshModule("TestModule") {} + virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return true; } + using MeshModule::currentRequest; + using MeshModule::isMultiHopBroadcastRequest; +}; + +static TestModule *testModule; +static meshtastic_MeshPacket testPacket; + +void setUp(void) +{ + testModule = new TestModule(); + memset(&testPacket, 0, sizeof(testPacket)); + TestModule::currentRequest = &testPacket; +} + +void tearDown(void) +{ + TestModule::currentRequest = NULL; + delete testModule; +} + +// Zero-hop broadcast (hop_limit == hop_start): should be allowed +static void test_zeroHopBroadcast_isAllowed() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 3; + testPacket.hop_limit = 3; // Not yet relayed + + TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest()); +} + +// Multi-hop broadcast (hop_limit < hop_start): should be blocked +static void test_multiHopBroadcast_isBlocked() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 7; + testPacket.hop_limit = 4; // Already relayed 3 hops + + TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest()); +} + +// Direct message (not broadcast): should always be allowed regardless of hops +static void test_directMessage_isAllowed() +{ + testPacket.to = 0x12345678; // Specific node + testPacket.hop_start = 7; + testPacket.hop_limit = 4; + + TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest()); +} + +// Broadcast with hop_limit == 0 (fully relayed): should be blocked +static void test_fullyRelayedBroadcast_isBlocked() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 3; + testPacket.hop_limit = 0; + + TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest()); +} + +// No current request: should not crash, should return false +static void test_noCurrentRequest_isAllowed() +{ + TestModule::currentRequest = NULL; + + TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest()); +} + +// Broadcast with hop_start == 0 (legacy or local): should be allowed +static void test_legacyPacket_zeroHopStart_isAllowed() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 0; + testPacket.hop_limit = 0; + + // hop_limit == hop_start, so not multi-hop + TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest()); +} + +// Single hop relayed broadcast (hop_limit = hop_start - 1): should be blocked +static void test_singleHopRelayedBroadcast_isBlocked() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 3; + testPacket.hop_limit = 2; + + TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest()); +} + +void setup() +{ + initializeTestEnvironment(); + + UNITY_BEGIN(); + RUN_TEST(test_zeroHopBroadcast_isAllowed); + RUN_TEST(test_multiHopBroadcast_isBlocked); + RUN_TEST(test_directMessage_isAllowed); + RUN_TEST(test_fullyRelayedBroadcast_isBlocked); + RUN_TEST(test_noCurrentRequest_isAllowed); + RUN_TEST(test_legacyPacket_zeroHopStart_isAllowed); + RUN_TEST(test_singleHopRelayedBroadcast_isBlocked); + exit(UNITY_END()); +} + +void loop() {} diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp index a566dabf7..4a2eed87d 100644 --- a/test/test_mqtt/MQTT.cpp +++ b/test/test_mqtt/MQTT.cpp @@ -289,13 +289,23 @@ class MQTTUnitTest : public MQTT mqtt = unitTest = new MQTTUnitTest(); mqtt->start(); + auto clearStartupOutput = []() { + pubsub->published_.clear(); + if (mockMeshService != nullptr) { + mockMeshService->messages_.clear(); + mockMeshService->notifications_.clear(); + } + }; + if (!moduleConfig.mqtt.enabled || moduleConfig.mqtt.proxy_to_client_enabled || *moduleConfig.mqtt.root) { loopUntil([] { return true; }); // Loop once + clearStartupOutput(); return; } // Wait for MQTT to subscribe to all topics. TEST_ASSERT_TRUE(loopUntil( [] { return pubsub->subscriptions_.count("msh/2/e/test/+") && pubsub->subscriptions_.count("msh/2/e/PKI/+"); })); + clearStartupOutput(); } PubSubClient &getPubSub() { return pubSub; } }; @@ -334,7 +344,7 @@ void setUp(void) owner = meshtastic_User{.id = "!12345678"}; 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}; + meshtastic_Position{.has_latitude_i = true, .latitude_i = 700000000, .has_longitude_i = true, .longitude_i = 300000000}; router = mockRouter = new MockRouter(); service = mockMeshService = new MockMeshService(); @@ -930,4 +940,4 @@ void setup() UNITY_END(); } #endif -void loop() {} \ No newline at end of file +void loop() {} diff --git a/test/test_transmit_history/test_main.cpp b/test/test_transmit_history/test_main.cpp new file mode 100644 index 000000000..992668d97 --- /dev/null +++ b/test/test_transmit_history/test_main.cpp @@ -0,0 +1,230 @@ +#include "TestUtil.h" +#include "TransmitHistory.h" +#include +#include + +// Reset the singleton between tests +static void resetTransmitHistory() +{ + if (transmitHistory) { + delete transmitHistory; + transmitHistory = nullptr; + } + transmitHistory = TransmitHistory::getInstance(); +} + +void setUp(void) +{ + resetTransmitHistory(); +} + +void tearDown(void) {} + +static void test_setLastSentToMesh_stores_millis() +{ + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + + uint32_t result = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + TEST_ASSERT_NOT_EQUAL(0, result); + + // The stored millis value should be very close to current millis() + uint32_t diff = millis() - result; + TEST_ASSERT_LESS_OR_EQUAL(100, diff); // Within 100ms +} + +static void test_set_overwrites_previous_value() +{ + transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP); + uint32_t first = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP); + + testDelay(50); + + transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP); + uint32_t second = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP); + + // The second value should be newer (larger millis) + TEST_ASSERT_GREATER_THAN(first, second); +} + +// --- Throttle integration --- + +static void test_throttle_blocks_within_interval() +{ + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + + // Should be within a 10-minute interval (just set it) + bool withinInterval = Throttle::isWithinTimespanMs(lastMs, 10 * 60 * 1000); + TEST_ASSERT_TRUE(withinInterval); +} + +static void test_throttle_allows_after_interval() +{ + // Unknown key returns 0 — throttle should NOT block + uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + TEST_ASSERT_EQUAL_UINT32(0, lastMs); + + // When lastMs == 0, the module check `lastMs == 0 || !isWithinTimespan` allows sending + bool shouldSend = (lastMs == 0) || !Throttle::isWithinTimespanMs(lastMs, 10 * 60 * 1000); + TEST_ASSERT_TRUE(shouldSend); +} + +static void test_throttle_blocks_after_set_then_zero_does_not() +{ + // Set it — now throttle should block + transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP); + uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP); + bool shouldSend = (lastMs == 0) || !Throttle::isWithinTimespanMs(lastMs, 60 * 60 * 1000); + TEST_ASSERT_FALSE(shouldSend); // Should be blocked (within 1hr interval) + + // Different key — should allow + uint32_t otherMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP); + bool otherShouldSend = (otherMs == 0) || !Throttle::isWithinTimespanMs(otherMs, 60 * 60 * 1000); + TEST_ASSERT_TRUE(otherShouldSend); +} + +// --- Multiple keys --- + +static void test_multiple_keys_stored_independently() +{ + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + uint32_t nodeInfoInitial = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + testDelay(20); + transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP); + uint32_t positionInitial = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP); + testDelay(20); + transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP); + + uint32_t nodeInfo = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + uint32_t position = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP); + uint32_t telemetry = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP); + + // All should be non-zero + TEST_ASSERT_NOT_EQUAL(0, nodeInfo); + TEST_ASSERT_NOT_EQUAL(0, position); + TEST_ASSERT_NOT_EQUAL(0, telemetry); + + // Updating other keys should not overwrite earlier key timestamps + TEST_ASSERT_EQUAL_UINT32(nodeInfoInitial, nodeInfo); + TEST_ASSERT_EQUAL_UINT32(positionInitial, position); +} + +// --- Singleton --- + +static void test_getInstance_returns_same_instance() +{ + TransmitHistory *a = TransmitHistory::getInstance(); + TransmitHistory *b = TransmitHistory::getInstance(); + TEST_ASSERT_EQUAL_PTR(a, b); +} + +static void test_getInstance_creates_global() +{ + if (transmitHistory) { + delete transmitHistory; + transmitHistory = nullptr; + } + TEST_ASSERT_NULL(transmitHistory); + + TransmitHistory::getInstance(); + TEST_ASSERT_NOT_NULL(transmitHistory); +} + +// --- Persistence round-trip (loadFromDisk / saveToDisk) --- + +static void test_save_and_load_round_trip() +{ + // Set some values + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + testDelay(10); + transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP); + + uint32_t nodeInfoEpoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP); + uint32_t positionEpoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_POSITION_APP); + + // Force save + transmitHistory->saveToDisk(); + + // Reset and reload + delete transmitHistory; + transmitHistory = nullptr; + transmitHistory = TransmitHistory::getInstance(); + transmitHistory->loadFromDisk(); + + // Epoch values should be restored (if RTC was available when set) + uint32_t restoredNodeInfo = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP); + uint32_t restoredPosition = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_POSITION_APP); + + TEST_ASSERT_EQUAL_UINT32(nodeInfoEpoch, restoredNodeInfo); + TEST_ASSERT_EQUAL_UINT32(positionEpoch, restoredPosition); + + // After loadFromDisk, millis should be seeded (non-zero) for stored entries + uint32_t restoredMillis = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + if (restoredNodeInfo > 0) { + // If epoch was stored, millis should be seeded from load + TEST_ASSERT_NOT_EQUAL(0, restoredMillis); + } +} + +// --- Boot without RTC scenario --- + +static void test_load_seeds_millis_even_without_rtc() +{ + // This tests the critical crash-reboot scenario: + // After loadFromDisk(), even if getTime() returns 0 (no RTC), + // lastMillis should be seeded so throttle blocks immediate re-broadcast. + + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + transmitHistory->saveToDisk(); + + // Simulate reboot: destroy and recreate + delete transmitHistory; + transmitHistory = nullptr; + transmitHistory = TransmitHistory::getInstance(); + transmitHistory->loadFromDisk(); + + // The key insight: after load, getLastSentToMeshMillis should return non-zero + // because loadFromDisk seeds lastMillis[key] = millis() for every loaded entry. + // This ensures throttle works even without RTC. + uint32_t result = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + + uint32_t epoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP); + if (epoch > 0) { + // Data was persisted — millis must be seeded + TEST_ASSERT_NOT_EQUAL(0, result); + + // And it should cause throttle to block (treating as "just sent") + bool withinInterval = Throttle::isWithinTimespanMs(result, 10 * 60 * 1000); + TEST_ASSERT_TRUE(withinInterval); + } + // If epoch == 0, RTC wasn't available — no data was saved, so nothing to restore. + // This is expected on platforms without RTC during the very first boot. +} + +void setup() +{ + initializeTestEnvironment(); + + UNITY_BEGIN(); + + RUN_TEST(test_setLastSentToMesh_stores_millis); + RUN_TEST(test_set_overwrites_previous_value); + + RUN_TEST(test_throttle_blocks_within_interval); + RUN_TEST(test_throttle_allows_after_interval); + RUN_TEST(test_throttle_blocks_after_set_then_zero_does_not); + + RUN_TEST(test_multiple_keys_stored_independently); + + // Singleton + RUN_TEST(test_getInstance_returns_same_instance); + RUN_TEST(test_getInstance_creates_global); + + // Persistence + RUN_TEST(test_save_and_load_round_trip); + RUN_TEST(test_load_seeds_millis_even_without_rtc); + + exit(UNITY_END()); +} + +void loop() {} diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index bbbcd3cbe..f6c00bf88 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -5,7 +5,10 @@ custom_esp32_kind = custom_mtjson_part = platform = # renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32 - platformio/espressif32@6.12.0 + platformio/espressif32@6.13.0 +platform_packages = + # renovate: datasource=custom.pio depName=platformio/tool-mklittlefs packageName=platformio/tool/tool-mklittlefs + platformio/tool-mklittlefs@^1.203.210628 extra_scripts = ${env.extra_scripts} diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini index 92d4bd519..9f8c3a871 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini @@ -31,7 +31,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip # renovate: datasource=custom.pio depName=PCA9557-arduino packageName=maxpromer/library/PCA9557-arduino maxpromer/PCA9557-arduino@1.0.0 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini index 7a0bd31b4..7e37a0eb4 100644 --- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini +++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini @@ -26,7 +26,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:crowpanel-esp32s3-4-epaper] extends = esp32s3_base @@ -56,7 +56,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:crowpanel-esp32s3-2-epaper] extends = esp32s3_base @@ -86,4 +86,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 72c53ded0..9591f2dc1 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -6,6 +6,7 @@ board_build.partitions = default_16MB.csv build_flags = ${esp32s3_base.build_flags} -D HELTEC_V4 + -D HAS_LORA_FEM=1 -I variants/esp32s3/heltec_v4 diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h index 1c1168d94..83443c5f3 100644 --- a/variants/esp32s3/heltec_v4/variant.h +++ b/variants/esp32s3/heltec_v4/variant.h @@ -30,8 +30,8 @@ #define SX126X_DIO3_TCXO_VOLTAGE 1.8 // ---- GC1109 RF FRONT END CONFIGURATION ---- -// The Heltec V4 uses a GC1109 FEM chip with integrated PA and LNA -// RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna +// The Heltec V4.2 uses a GC1109 FEM chip with integrated PA and LNA +// RF path: SX1262 -> Pi attenuator -> GC1109 PA -> Antenna // Measured net TX gain (non-linear due to PA compression): // +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out) // +10dB at 16-17dBm input @@ -47,15 +47,31 @@ // CSD (pin 4) -> GPIO2: Chip enable (HIGH=on, LOW=shutdown) // CPS (pin 5) -> GPIO46: PA mode select (HIGH=full PA, LOW=bypass) // VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7 -#define USE_GC1109_PA -#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable -#define LORA_PA_EN 2 // CSD - GC1109 chip enable (HIGH=on) -#define LORA_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass) - // GC1109 FEM: TX/RX path switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH) -// GPIO46 is CPS (PA mode), not TX control - setTransmitEnable() handles it in SX126xInterface.cpp // Do NOT use SX126X_TXEN/RXEN as that would cause double-control of GPIO46 +#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 and KCT8103L LDO power enable +#define LORA_GC1109_PA_EN 2 // CSD - GC1109 chip enable (HIGH=on) +#define LORA_GC1109_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass) + +// ---- KCT8103L RF FRONT END CONFIGURATION ---- +// The Heltec V4.3 uses a KCT8103L FEM chip with integrated PA and LNA +// RF path: SX1262 -> Pi attenuator -> KCT8103L PA -> Antenna +// Control logic (from KCT8103L datasheet): +// Transmit PA: CSD=1, CTX=1, CPS=1 +// Receive LNA: CSD=1, CTX=0, CPS=X (21dB gain, 1.9dB NF) +// Receive bypass: CSD=1, CTX=1, CPS=0 +// Shutdown: CSD=0, CTX=X, CPS=X +// Pin mapping: +// CPS (pin 5) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH) +// CSD (pin 4) -> GPIO2: Chip enable (HIGH=on, LOW=shutdown) +// CTX (pin 6) -> GPIO5: Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=RX bypass, LOW=RX LNA) +// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7 +// KCT8103L FEM: TX/RX path switching is handled by DIO2 -> CPS pin (via SX126X_DIO2_AS_RF_SWITCH) + +#define LORA_KCT8103L_PA_CSD 2 // CSD - KCT8103L chip enable (HIGH=on) +#define LORA_KCT8103L_PA_CTX 5 // CTX - Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=RX bypass, LOW=RX LNA) + #if HAS_TFT #define USE_TFTDISPLAY 1 #endif diff --git a/variants/esp32s3/heltec_vision_master_e213/platformio.ini b/variants/esp32s3/heltec_vision_master_e213/platformio.ini index 4ace5a45a..1c4c69afe 100644 --- a/variants/esp32s3/heltec_vision_master_e213/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_e213/platformio.ini @@ -29,7 +29,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip upload_speed = 115200 [env:heltec-vision-master-e213-inkhud] diff --git a/variants/esp32s3/heltec_vision_master_e290/platformio.ini b/variants/esp32s3/heltec_vision_master_e290/platformio.ini index e86746b67..5affd24de 100644 --- a/variants/esp32s3/heltec_vision_master_e290/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_e290/platformio.ini @@ -32,7 +32,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip upload_speed = 115200 [env:heltec-vision-master-e290-inkhud] diff --git a/variants/esp32s3/heltec_vision_master_t190/platformio.ini b/variants/esp32s3/heltec_vision_master_t190/platformio.ini index bbc518b39..44e8b2f2d 100644 --- a/variants/esp32s3/heltec_vision_master_t190/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_t190/platformio.ini @@ -20,5 +20,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main - https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip + https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip upload_speed = 921600 diff --git a/variants/esp32s3/heltec_wireless_paper/platformio.ini b/variants/esp32s3/heltec_wireless_paper/platformio.ini index 673c834ea..ce4bed30e 100644 --- a/variants/esp32s3/heltec_wireless_paper/platformio.ini +++ b/variants/esp32s3/heltec_wireless_paper/platformio.ini @@ -29,7 +29,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip upload_speed = 115200 [env:heltec-wireless-paper-inkhud] diff --git a/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini b/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini index 8543e414f..b34adfb17 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini +++ b/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini @@ -26,5 +26,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip upload_speed = 115200 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h index 9fb825002..d30e2fcb7 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h @@ -13,8 +13,8 @@ static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 5; -static const uint8_t SCL = 6; +static const uint8_t SDA = 6; +static const uint8_t SCL = 17; static const uint8_t SS = 8; static const uint8_t MOSI = 10; diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini index a5277ba19..ebf0118bb 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -17,6 +17,7 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_wireless_tracker_v2 -D HELTEC_WIRELESS_TRACKER_V2 + -D HAS_LORA_FEM=1 lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h index 0ca2dfc03..7c797a503 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h @@ -73,29 +73,22 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -// ---- GC1109 RF FRONT END CONFIGURATION ---- -// The Heltec Wireless Tracker V2 uses a GC1109 FEM chip with integrated PA and LNA -// RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna -// Measured net TX gain (non-linear due to PA compression): -// +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out) -// +10dB at 16-17dBm input -// +9dB at 18-19dBm input -// +7dB at 21dBm input (e.g., 21dBm in -> 28dBm out max) -// Control logic (from GC1109 datasheet): +// ---- KCT8103L RF FRONT END CONFIGURATION ---- +// The heltec_wireless_tracker_v2 uses a KCT8103L FEM chip with integrated PA and LNA +// RF path: SX1262 -> Pi attenuator -> KCT8103L PA -> Antenna +// Control logic (from KCT8103L datasheet): +// Transmit PA: CSD=1, CTX=1, CPS=1 +// Receive LNA: CSD=1, CTX=0, CPS=X (21dB gain, 1.9dB NF) +// Receive bypass: CSD=1, CTX=1, CPS=0 // Shutdown: CSD=0, CTX=X, CPS=X -// Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF) -// Transmit bypass: CSD=1, CTX=1, CPS=0 (~1dB loss, no PA) -// Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled) // Pin mapping: -// CTX (pin 6) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH) +// CPS (pin 5) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH) // CSD (pin 4) -> GPIO4: Chip enable (HIGH=on, LOW=shutdown) -// CPS (pin 5) -> GPIO46: PA mode select (HIGH=full PA, LOW=bypass) +// CTX (pin 6) -> GPIO5: Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=RX bypass, LOW=RX LNA) // VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7 -#define USE_GC1109_PA -#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable -#define LORA_PA_EN 4 // CSD - GC1109 chip enable (HIGH=on) -#define LORA_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass) +// KCT8103L FEM: TX/RX path switching is handled by DIO2 -> CPS pin (via SX126X_DIO2_AS_RF_SWITCH) -// GC1109 FEM: TX/RX path switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH) -// GPIO46 is CPS (PA mode), not TX control - setTransmitEnable() handles it in SX126xInterface.cpp -// Do NOT use SX126X_TXEN/RXEN as that would cause double-control of GPIO46 \ No newline at end of file +#define USE_KCT8103L_PA +#define LORA_PA_POWER 7 // VFEM_Ctrl - KCT8103L LDO power enable +#define LORA_KCT8103L_PA_CSD 4 // CSD - KCT8103L chip enable (HIGH=on) +#define LORA_KCT8103L_PA_CTX 5 // CTX - Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=RX bypass, LOW=RX LNA) \ No newline at end of file diff --git a/variants/esp32s3/m5stack_cardputer_adv/pins_arduino.h b/variants/esp32s3/m5stack_cardputer_adv/pins_arduino.h new file mode 100644 index 000000000..12581cde0 --- /dev/null +++ b/variants/esp32s3/m5stack_cardputer_adv/pins_arduino.h @@ -0,0 +1,20 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "soc/soc_caps.h" +#include + +#define USB_VID 0x303a // USB JTAG/serial debug unit ID +#define USB_PID 0x1001 // USB JTAG/serial debug unit ID + +static const uint8_t SS = 5; +static const uint8_t SDA = 8; +static const uint8_t SCL = 9; +static const uint8_t ADC = 10; +static const uint8_t TXD2 = 13; +static const uint8_t MOSI = 14; +static const uint8_t RXD2 = 15; +static const uint8_t MISO = 39; +static const uint8_t SCK = 40; + +#endif diff --git a/variants/esp32s3/m5stack_cardputer_adv/platformio.ini b/variants/esp32s3/m5stack_cardputer_adv/platformio.ini new file mode 100644 index 000000000..7563da082 --- /dev/null +++ b/variants/esp32s3/m5stack_cardputer_adv/platformio.ini @@ -0,0 +1,25 @@ +; M5stack Cardputer Advanced +[env:m5stack-cardputer-adv] +extends = esp32s3_base +board = m5stack-stamps3 +board_check = true +board_build.partitions = default_8MB.csv +upload_protocol = esptool +build_flags = + ${esp32s3_base.build_flags} + -D M5STACK_CARDPUTER_ADV + -D BOARD_HAS_PSRAM + -D ARDUINO_USB_CDC_ON_BOOT=1 + -I variants/esp32s3/m5stack_cardputer_adv +lib_deps = + ${esp32s3_base.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main + https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip +# # renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver +# https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip + # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio + earlephilhower/ESP8266Audio@1.9.9 + # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM + earlephilhower/ESP8266SAM@1.1.0 + # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel + adafruit/Adafruit NeoPixel@1.15.2 diff --git a/variants/esp32s3/m5stack_cardputer_adv/variant.h b/variants/esp32s3/m5stack_cardputer_adv/variant.h new file mode 100644 index 000000000..5fdb1436e --- /dev/null +++ b/variants/esp32s3/m5stack_cardputer_adv/variant.h @@ -0,0 +1,90 @@ +#define USE_ST7789 + +#define ST7789_NSS 37 +#define ST7789_RS 34 // DC +#define ST7789_SDA 35 // MOSI +#define ST7789_SCK 36 +#define ST7789_RESET 33 +#define ST7789_MISO -1 +#define ST7789_BUSY -1 +// #define VTFT_CTRL 38 +#define VTFT_LEDA 38 +// #define ST7789_BL (32+6) +#define ST7789_SPI_HOST SPI2_HOST +// #define TFT_BL (32+6) +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 135 +#define TFT_WIDTH 240 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define HAS_PHYSICAL_KEYBOARD 1 + +// Backlight is controlled to power rail on this board, this also powers the neopixel +// #define PIN_POWER_EN 38 + +#define BUTTON_PIN 0 + +#define I2C_SDA 8 +#define I2C_SCL 9 + +#define I2C_SDA1 2 +#define I2C_SCL1 1 + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +#define LORA_SCK 40 +#define LORA_MISO 39 +#define LORA_MOSI 14 +#define LORA_CS 5 // NSS + +#define USE_SX1262 +#define LORA_DIO0 -1 +#define LORA_RESET 3 +#define LORA_RST 3 +#define LORA_DIO1 4 +#define LORA_DIO2 6 +#define LORA_DIO3 RADIOLIB_NC + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define TCXO_OPTIONAL + +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 15 +#define GPS_TX_PIN 13 +#define HAS_GPS 1 +#define GPS_BAUDRATE 115200 + +// audio codec ES8311 +#define HAS_I2S +#define DAC_I2S_BCK 41 +#define DAC_I2S_WS 43 +#define DAC_I2S_DOUT 42 +#define DAC_I2S_DIN 46 +#define DAC_I2S_MCLK 45 // dummy + +// TCA8418 keyboard +#define I2C_NO_RESCAN +#define KB_INT 11 + +#define HAS_NEOPIXEL +#define NEOPIXEL_COUNT 1 +#define NEOPIXEL_DATA 21 +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) + +#define BATTERY_PIN 10 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO10_CHANNEL +#define ADC_MULTIPLIER 2 * 1.02 // 100k + 100k, and add 2% to kick the voltage over the max voltage to show charging. + +// BMI270 6-axis IMU on internal I2C bus +#define HAS_BMI270 diff --git a/variants/esp32s3/mini-epaper-s3/pins_arduino.h b/variants/esp32s3/mini-epaper-s3/pins_arduino.h new file mode 100644 index 000000000..a4b3c4bf7 --- /dev/null +++ b/variants/esp32s3/mini-epaper-s3/pins_arduino.h @@ -0,0 +1,26 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SDA = 18; +static const uint8_t SCL = 9; + +// Default SPI will be mapped to Radio +static const uint8_t SS = -1; +static const uint8_t MOSI = 17; +static const uint8_t MISO = 6; +static const uint8_t SCK = 8; + +#define SPI_MOSI (39) +#define SPI_SCK (41) +#define SPI_MISO (38) +#define SPI_CS (40) + +#define SDCARD_CS SPI_CS + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/mini-epaper-s3/platformio.ini b/variants/esp32s3/mini-epaper-s3/platformio.ini new file mode 100644 index 000000000..f49be707f --- /dev/null +++ b/variants/esp32s3/mini-epaper-s3/platformio.ini @@ -0,0 +1,31 @@ +[env:mini-epaper-s3] +;custom_meshtastic_hw_model = +custom_meshtastic_hw_model_slug = MINI_EPAPER_S3 +custom_meshtastic_architecture = esp32-s3 +custom_meshtastic_actively_supported = true +custom_meshtastic_support_level = 1 +custom_meshtastic_display_name = LILYGO Mini ePaper S3 E-Ink +custom_meshtastic_images = mini-epaper-s3.svg +custom_meshtastic_tags = LilyGo +custom_meshtastic_requires_dfu = no + +extends = esp32s3_base +board = mini-epaper-s3 +board_check = true +upload_protocol = esptool + +build_flags = + ${esp32s3_base.build_flags} + -I variants/esp32s3/mini-epaper-s3 + -DMINI_EPAPER_S3 + -DUSE_EINK + -DEINK_DISPLAY_MODEL=GxEPD2_102 + -DEINK_WIDTH=128 + -DEINK_HEIGHT=80 + +lib_deps = + ${esp32s3_base.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip + # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib + lewisxhe/SensorLib@0.3.4 diff --git a/variants/esp32s3/mini-epaper-s3/variant.h b/variants/esp32s3/mini-epaper-s3/variant.h new file mode 100644 index 000000000..b464c9b4a --- /dev/null +++ b/variants/esp32s3/mini-epaper-s3/variant.h @@ -0,0 +1,57 @@ +// Display (E-Ink) + +#define PIN_EINK_CS 13 +#define PIN_EINK_BUSY 10 +#define PIN_EINK_RES 11 +#define PIN_EINK_SCLK 14 +#define PIN_EINK_MOSI 15 +#define PIN_EINK_DC 12 +#define PIN_EINK_EN 42 + +#define SPI_INTERFACES_COUNT 2 +#define PIN_SPI1_MISO -1 +#define PIN_SPI1_MOSI PIN_EINK_MOSI +#define PIN_SPI1_SCK PIN_EINK_SCLK +#define DISPLAY_FORCE_SMALL_FONTS + +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define BATTERY_PIN 2 // A battery voltage measurement pin, voltage divider connected here to +// measure battery voltage ratio of voltage divider = 2.0 (assumption) +#define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. +#define ADC_CHANNEL ADC1_GPIO2_CHANNEL + +#define HAS_GPS 0 +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +#define BUTTON_PIN 3 +#define BUTTON_NEED_PULLUP +#define ALT_BUTTON_PIN 4 +#define ALT_BUTTON_ACTIVE_LOW true +#define ALT_BUTTON_ACTIVE_PULLUP true +#define PIN_BUTTON3 0 + +// #define HAS_SDCARD 1 +// #define SDCARD_USE_SOFT_SPI + +// PCF85063 RTC Module +#define PCF85063_RTC 0x51 +#define HAS_RTC 1 + +#define USE_SX1262 +#define LORA_DIO1 5 +#define LORA_SCK 8 +#define LORA_MISO 6 +#define LORA_MOSI 17 +#define LORA_CS 7 // CS not connected; IO7 is free +#define LORA_RESET 21 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 16 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif diff --git a/variants/esp32s3/station-g2/pins_arduino.h b/variants/esp32s3/station-g2/pins_arduino.h old mode 100755 new mode 100644 diff --git a/variants/esp32s3/station-g2/platformio.ini b/variants/esp32s3/station-g2/platformio.ini old mode 100755 new mode 100644 diff --git a/variants/esp32s3/station-g2/variant.h b/variants/esp32s3/station-g2/variant.h old mode 100755 new mode 100644 diff --git a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini index 256cdc0d0..f9d1ea7db 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini +++ b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini @@ -31,7 +31,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:tlora-t3s3-epaper-inkhud] extends = esp32s3_base, inkhud diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini index a4687669b..2a6cea73e 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini @@ -33,7 +33,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip # renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM khoih-prog/nRF52_PWM@1.0.1 ;upload_protocol = fs diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md index 4ffe625cc..5d3d90c72 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md @@ -53,7 +53,7 @@ Making your own node based on this design is straightforward. There are various The E80 from CDEbyte is the most obtainable module at present, and has been selected as the default option. -Naturally, CDEbyte have chosen to ignore the generic Semtech impelementation of the RF switching logic and have supplied confusing and contradictory documentation, which is explained below. +Naturally, CDEbyte have chosen to ignore the generic Semtech implementation of the RF switching logic and have supplied confusing and contradictory documentation, which is explained below. tl;dr: The E80 is chosen as the default. **If you wish to use another module, the table in `rfswitch.h` must be adjusted accordingly.** diff --git a/variants/nrf52840/diy/xiao_ble/README.md b/variants/nrf52840/diy/xiao_ble/README.md index fe6dcba2d..efc1236a8 100644 --- a/variants/nrf52840/diy/xiao_ble/README.md +++ b/variants/nrf52840/diy/xiao_ble/README.md @@ -116,7 +116,7 @@ _(none)_ 1. Double press the XIAO nrf52840's `reset` button to put it in bootloader mode, and a USB volume named `XIAO SENSE` will appear 2. Copy the `firmware.uf2` file to the `XIAO SENSE` volume (refer to the last step of [Build Meshtastic](#2-build-meshtastic)) 3. The XIAO nrf52840's red LED will flash for several seconds as the firmware is copied -4. Once Meshtastic firmware succesfully boots, the: +4. Once Meshtastic firmware successfully boots, the: 1. Green LED will turn on 2. Red LED will flash several times to indicate flash memory writes during initial settings file creation 3. Green LED will blink every second once the firmware is running normally @@ -135,7 +135,7 @@ _(none)_ - If you don't see any specific error message, but the boot process is stuck or not proceeding as expected, this might also mean there is a conflict in `variant.h`. If you have made any changes to the pin mapping, ensure they do not result in a conflict. If all else fails, try reverting your changes and using the known-good configuration included here. - The above might also mean something is wired incorrectly. Try reverting to one of the known-good example wirings in section 4. - If the E22 gets hot to the touch: - - The power amplifier is likely running continually. Disconnect it and the XIAO from power immediately, and double check wiring and pin mapping. In my experimentation this occurred in cases where TXEN was inadvertenly high (usually due to a pin mapping conflict). + - The power amplifier is likely running continually. Disconnect it and the XIAO from power immediately, and double check wiring and pin mapping. In my experimentation this occurred in cases where TXEN was inadvertently high (usually due to a pin mapping conflict). ## 5. Notes diff --git a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini index a39872205..7cbc5f6a9 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini +++ b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini @@ -23,4 +23,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main - https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip + https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip diff --git a/variants/nrf52840/heltec_mesh_pocket/platformio.ini b/variants/nrf52840/heltec_mesh_pocket/platformio.ini index 646304a5a..9fbcc890d 100644 --- a/variants/nrf52840/heltec_mesh_pocket/platformio.ini +++ b/variants/nrf52840/heltec_mesh_pocket/platformio.ini @@ -38,7 +38,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:heltec-mesh-pocket-5000-inkhud] extends = nrf52840_base, inkhud @@ -101,7 +101,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:heltec-mesh-pocket-10000-inkhud] extends = nrf52840_base, inkhud diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini index 69264f0df..6935920cd 100644 --- a/variants/nrf52840/heltec_mesh_solar/platformio.ini +++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini @@ -16,7 +16,7 @@ lib_deps = # renovate: datasource=git-refs depName=NMIoT-meshsolar packageName=https://github.com/NMIoT/meshsolar gitBranch=main https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip # renovate: datasource=custom.pio depName=ArduinoJson packageName=bblanchon/library/ArduinoJson - bblanchon/ArduinoJson@6.21.5 + bblanchon/ArduinoJson@6.21.6 [env:heltec-mesh-solar] custom_meshtastic_hw_model = 108 @@ -68,7 +68,7 @@ build_flags = ${heltec_mesh_solar_base.build_flags} lib_deps = ${heltec_mesh_solar_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:heltec-mesh-solar-inkhud] extends = heltec_mesh_solar_base, inkhud @@ -132,4 +132,4 @@ build_flags = ${heltec_mesh_solar_base.build_flags} lib_deps = ${heltec_mesh_solar_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main - https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip + https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip diff --git a/variants/nrf52840/meshlink/platformio.ini b/variants/nrf52840/meshlink/platformio.ini index e2631affe..28122d9bd 100644 --- a/variants/nrf52840/meshlink/platformio.ini +++ b/variants/nrf52840/meshlink/platformio.ini @@ -47,7 +47,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshlin lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini index 727d2c741..aeda68a2e 100644 --- a/variants/nrf52840/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -2,7 +2,7 @@ ; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files platform = # renovate: datasource=custom.pio depName=platformio/nordicnrf52 packageName=platformio/platform/nordicnrf52 - platformio/nordicnrf52@10.10.0 + platformio/nordicnrf52@10.11.0 extends = arduino_base platform_packages = ; our custom Git version until they merge our PR diff --git a/variants/nrf52840/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini index e06a271aa..0fded96f4 100644 --- a/variants/nrf52840/rak4631_eth_gw/platformio.ini +++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini @@ -14,7 +14,6 @@ build_flags = ${nrf52840_base.build_flags} -DMESHTASTIC_EXCLUDE_WIFI=1 -DMESHTASTIC_EXCLUDE_SCREEN=1 ; -DMESHTASTIC_EXCLUDE_PKI=1 - -DMESHTASTIC_EXCLUDE_POWER_FSM=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 ; -DMESHTASTIC_EXCLUDE_TZ=1 -DMESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1 @@ -36,7 +35,7 @@ lib_deps = # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip # renovate: datasource=custom.pio depName=ArduinoJson packageName=bblanchon/library/ArduinoJson - bblanchon/ArduinoJson@6.21.5 + bblanchon/ArduinoJson@6.21.6 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds ;upload_protocol = jlink diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini index 60d83b95a..d8fbaf8ff 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini @@ -34,7 +34,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_w lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip debug_tool = jlink [env:seeed_wio_tracker_L1_eink-inkhud] diff --git a/variants/nrf52840/t-echo-lite/platformio.ini b/variants/nrf52840/t-echo-lite/platformio.ini index c873dea37..1b725815e 100644 --- a/variants/nrf52840/t-echo-lite/platformio.ini +++ b/variants/nrf52840/t-echo-lite/platformio.ini @@ -30,5 +30,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo- lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip ;upload_protocol = fs diff --git a/variants/nrf52840/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini index 4acd70b02..89ce488ad 100644 --- a/variants/nrf52840/t-echo/platformio.ini +++ b/variants/nrf52840/t-echo/platformio.ini @@ -30,7 +30,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo> lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 ;upload_protocol = fs diff --git a/variants/nrf52840/tracker-t1000-e/variant.cpp b/variants/nrf52840/tracker-t1000-e/variant.cpp index 1d0423b9b..0f7c5adaa 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.cpp +++ b/variants/nrf52840/tracker-t1000-e/variant.cpp @@ -38,6 +38,9 @@ void initVariant() pinMode(PIN_3V3_ACC_EN, OUTPUT); digitalWrite(PIN_3V3_ACC_EN, HIGH); + pinMode(T1000X_SENSOR_EN_PIN, OUTPUT); + digitalWrite(T1000X_SENSOR_EN_PIN, HIGH); + pinMode(BUZZER_EN_PIN, OUTPUT); digitalWrite(BUZZER_EN_PIN, HIGH); diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index e258f63ae..b064dbc9f 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -126,7 +126,7 @@ extern "C" { #define BATTERY_PIN 2 // P0.02/AIN0, BAT_ADC #define BATTERY_IMMUTABLE #define ADC_MULTIPLIER (2.0F) -// P0.04/AIN2 is VCC_ADC, P0.05/AIN3 is CHARGER_DET, P1.03 is CHARGE_STA, P1.04 is CHARGE_DONE +// P0.04 is sensor power enable, P0.05/AIN3 is CHARGER_DET, P1.03 is CHARGE_STA, P1.04 is CHARGE_DONE #define EXT_CHRG_DETECT (32 + 3) // P1.03 #define EXT_CHRG_DETECT_VALUE LOW @@ -148,9 +148,9 @@ extern "C" { #define PIN_BUZZER (0 + 25) // P0.25, pwm output #define T1000X_SENSOR_EN -#define T1000X_VCC_PIN (0 + 4) // P0.4 -#define T1000X_NTC_PIN (0 + 31) // P0.31/AIN7 -#define T1000X_LUX_PIN (0 + 29) // P0.29/AIN5 +#define T1000X_SENSOR_EN_PIN (0 + 4) // P0.4, Power to Sensor (GPIO, not ADC) +#define T1000X_NTC_PIN (0 + 31) // P0.31/AIN7 +#define T1000X_LUX_PIN (0 + 29) // P0.29/AIN5 #define HAS_SCREEN 0 diff --git a/variants/nrf52840/wio-t1000-s/variant.cpp b/variants/nrf52840/wio-t1000-s/variant.cpp index 3c54f6ce5..54d338a19 100644 --- a/variants/nrf52840/wio-t1000-s/variant.cpp +++ b/variants/nrf52840/wio-t1000-s/variant.cpp @@ -38,6 +38,9 @@ void initVariant() pinMode(PIN_3V3_ACC_EN, OUTPUT); digitalWrite(PIN_3V3_ACC_EN, LOW); + pinMode(T1000X_SENSOR_EN_PIN, OUTPUT); + digitalWrite(T1000X_SENSOR_EN_PIN, HIGH); + pinMode(BUZZER_EN_PIN, OUTPUT); digitalWrite(BUZZER_EN_PIN, HIGH); diff --git a/variants/nrf52840/wio-t1000-s/variant.h b/variants/nrf52840/wio-t1000-s/variant.h index a55c42775..c9b98ab87 100644 --- a/variants/nrf52840/wio-t1000-s/variant.h +++ b/variants/nrf52840/wio-t1000-s/variant.h @@ -143,9 +143,9 @@ extern "C" { #define PIN_BUZZER (0 + 25) // P0.25, pwm output #define T1000X_SENSOR_EN -#define T1000X_VCC_PIN (0 + 4) // P0.4 -#define T1000X_NTC_PIN (0 + 31) // P0.31 -#define T1000X_LUX_PIN (0 + 29) // P0.29 +#define T1000X_SENSOR_EN_PIN (0 + 4) // P0.4, Power to Sensor (GPIO, not ADC) +#define T1000X_NTC_PIN (0 + 31) // P0.31 +#define T1000X_LUX_PIN (0 + 29) // P0.29 #ifdef __cplusplus } diff --git a/variants/stm32/stm32.ini b/variants/stm32/stm32.ini index bb0a4d3ce..7a3b37642 100644 --- a/variants/stm32/stm32.ini +++ b/variants/stm32/stm32.ini @@ -2,7 +2,7 @@ extends = arduino_base platform = # renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32 - platformio/ststm32@19.4.0 + platformio/ststm32@19.5.0 platform_packages = # renovate: datasource=github-tags depName=Arduino_Core_STM32 packageName=stm32duino/Arduino_Core_STM32 platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip